drk/
interactive.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::{
20    fs::{File, OpenOptions},
21    io::{stdin, BufRead, BufReader, ErrorKind, Read, Seek, SeekFrom, Write},
22    str::FromStr,
23};
24
25use futures::{select, FutureExt};
26use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
27use linenoise_rs::{
28    linenoise_history_add, linenoise_history_load, linenoise_history_save,
29    linenoise_set_completion_callback, linenoise_set_hints_callback, LinenoiseState,
30};
31use prettytable::{format, row, Table};
32use rand::rngs::OsRng;
33use smol::channel::{Receiver, Sender};
34use url::Url;
35
36use darkfi::{
37    cli_desc,
38    system::{msleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
39    util::{
40        encoding::base64,
41        parse::{decode_base10, encode_base10},
42        path::expand_path,
43    },
44    zk::halo2::Field,
45    Error,
46};
47use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
48use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
49use darkfi_sdk::{
50    crypto::{
51        keypair::{Address, StandardAddress},
52        note::AeadEncryptedNote,
53        BaseBlind, ContractId, FuncId, FuncRef, Keypair, SecretKey, DAO_CONTRACT_ID,
54    },
55    pasta::{group::ff::PrimeField, pallas},
56    tx::TransactionHash,
57};
58use darkfi_serial::{deserialize_async, serialize_async};
59
60use crate::{
61    cli_util::{
62        append_or_print, display_mining_config, generate_completions, kaching,
63        parse_calls_from_input, parse_mining_config_from_input, parse_token_pair, parse_tree,
64        parse_tx_from_input, parse_value_pair, print_output, tx_from_calls_mapped,
65    },
66    common::*,
67    dao::{DaoParams, ProposalRecord},
68    money::BALANCE_BASE10_DECIMALS,
69    rpc::subscribe_blocks,
70    swap::PartialSwapData,
71    DrkPtr,
72};
73
74// TODO:
75//  1. Create a transactions cache in the wallet db, so you can use it to handle them.
76
77/// Auxiliary function to print the help message.
78fn help(output: &mut Vec<String>) {
79    output.push(String::from(cli_desc!()));
80    output.push(String::from("Commands:"));
81    output.push(String::from("\thelp: Prints the help message"));
82    output.push(String::from("\tkaching: Fun"));
83    output.push(String::from("\tping: Send a ping request to the darkfid RPC endpoint"));
84    output.push(String::from(
85        "\tcompletions: Generate a SHELL completion script and print to stdout",
86    ));
87    output.push(String::from("\twallet: Wallet operations"));
88    output.push(String::from(
89        "\tspend: Read a transaction from stdin and mark its input coins as spent",
90    ));
91    output.push(String::from("\tunspend: Unspend a coin"));
92    output.push(String::from("\ttransfer: Create a payment transaction"));
93    output.push(String::from("\totc: OTC atomic swap"));
94    output.push(String::from("\tdao: DAO functionalities"));
95    output
96        .push(String::from("\tattach-fee: Attach the fee call to a transaction given from stdin"));
97    output.push(String::from(
98        "\ttx-from-calls: Create a transaction from newline-separated calls from stdin and attach the fee call",
99    ));
100    output.push(String::from("\tinspect: Inspect a transaction from stdin"));
101    output.push(String::from("\tbroadcast: Read a transaction from stdin and broadcast it"));
102    output.push(String::from(
103        "\tsubscribe: Perform a scan and then subscribe to darkfid to listen for incoming blocks",
104    ));
105    output.push(String::from("\tunsubscribe: Stops the background subscription, if its active"));
106    output.push(String::from("\tsnooze: Disables the background subscription messages printing"));
107    output.push(String::from("\tunsnooze: Enables the background subscription messages printing"));
108    output.push(String::from("\tscan: Scan the blockchain and parse relevant transactions"));
109    output.push(String::from("\texplorer: Explorer related subcommands"));
110    output.push(String::from("\talias: Token alias"));
111    output.push(String::from("\ttoken: Token functionalities"));
112    output.push(String::from("\tcontract: Contract functionalities"));
113}
114
115/// Auxiliary function to define the interactive shell completions.
116fn completion(buffer: &str, lc: &mut Vec<String>) {
117    // Split commands so we always process the last one
118    let commands: Vec<&str> = buffer.split('|').collect();
119    // Grab the prefix
120    let prefix = if commands.len() > 1 {
121        commands[..commands.len() - 1].join("|") + "| "
122    } else {
123        String::from("")
124    };
125    let last = commands.last().unwrap().trim_start();
126
127    // First we define the specific commands prefixes
128    if last.starts_with("h") {
129        lc.push(prefix + "help");
130        return
131    }
132
133    if last.starts_with("k") {
134        lc.push(prefix + "kaching");
135        return
136    }
137
138    if last.starts_with("p") {
139        lc.push(prefix + "ping");
140        return
141    }
142
143    if last.starts_with("com") {
144        lc.push(prefix + "completions");
145        return
146    }
147
148    if last.starts_with("w") {
149        lc.push(prefix.clone() + "wallet");
150        lc.push(prefix.clone() + "wallet initialize");
151        lc.push(prefix.clone() + "wallet keygen");
152        lc.push(prefix.clone() + "wallet balance");
153        lc.push(prefix.clone() + "wallet address");
154        lc.push(prefix.clone() + "wallet addresses");
155        lc.push(prefix.clone() + "wallet default-address");
156        lc.push(prefix.clone() + "wallet secrets");
157        lc.push(prefix.clone() + "wallet import-secrets");
158        lc.push(prefix.clone() + "wallet tree");
159        lc.push(prefix.clone() + "wallet coins");
160        lc.push(prefix + "wallet mining-config");
161        return
162    }
163
164    if last.starts_with("sp") {
165        lc.push(prefix + "spend");
166        return
167    }
168
169    if last.starts_with("unsp") {
170        lc.push(prefix + "unspend");
171        return
172    }
173
174    if last.starts_with("tr") {
175        lc.push(prefix + "transfer");
176        return
177    }
178
179    if last.starts_with("o") {
180        lc.push(prefix.clone() + "otc");
181        lc.push(prefix.clone() + "otc init");
182        lc.push(prefix.clone() + "otc join");
183        lc.push(prefix.clone() + "otc inspect");
184        lc.push(prefix + "otc sign");
185        return
186    }
187
188    if last.starts_with("d") {
189        lc.push(prefix.clone() + "dao");
190        lc.push(prefix.clone() + "dao create");
191        lc.push(prefix.clone() + "dao view");
192        lc.push(prefix.clone() + "dao import");
193        lc.push(prefix.clone() + "dao remove");
194        lc.push(prefix.clone() + "dao list");
195        lc.push(prefix.clone() + "dao balance");
196        lc.push(prefix.clone() + "dao mint");
197        lc.push(prefix.clone() + "dao propose-transfer");
198        lc.push(prefix.clone() + "dao propose-generic");
199        lc.push(prefix.clone() + "dao proposals");
200        lc.push(prefix.clone() + "dao proposal");
201        lc.push(prefix.clone() + "dao proposal-import");
202        lc.push(prefix.clone() + "dao vote");
203        lc.push(prefix.clone() + "dao exec");
204        lc.push(prefix.clone() + "dao spend-hook");
205        lc.push(prefix + "dao mining-config");
206        return
207    }
208
209    if last.starts_with("at") {
210        lc.push(prefix + "attach-fee");
211        return
212    }
213
214    if last.starts_with("tx") {
215        lc.push(prefix + "tx-from-calls");
216        return
217    }
218
219    if last.starts_with("i") {
220        lc.push(prefix + "inspect");
221        return
222    }
223
224    if last.starts_with("b") {
225        lc.push(prefix + "broadcast");
226        return
227    }
228
229    if last.starts_with("su") {
230        lc.push(prefix + "subscribe");
231        return
232    }
233
234    if last.starts_with("unsu") {
235        lc.push(prefix + "unsubscribe");
236        return
237    }
238
239    if last.starts_with("sn") {
240        lc.push(prefix + "snooze");
241        return
242    }
243
244    if last.starts_with("unsn") {
245        lc.push(prefix + "unsnooze");
246        return
247    }
248
249    if last.starts_with("sc") {
250        lc.push(prefix.clone() + "scan");
251        lc.push(prefix + "scan --reset");
252        return
253    }
254
255    if last.starts_with("e") {
256        lc.push(prefix.clone() + "explorer");
257        lc.push(prefix.clone() + "explorer fetch-tx");
258        lc.push(prefix.clone() + "explorer simulate-tx");
259        lc.push(prefix.clone() + "explorer txs-history");
260        lc.push(prefix.clone() + "explorer clear-reverted");
261        lc.push(prefix.clone() + "explorer scanned-blocks");
262        lc.push(prefix + "explorer mining-config");
263        return
264    }
265
266    if last.starts_with("al") {
267        lc.push(prefix.clone() + "alias");
268        lc.push(prefix.clone() + "alias add");
269        lc.push(prefix.clone() + "alias show");
270        lc.push(prefix + "alias remove");
271        return
272    }
273
274    if last.starts_with("to") {
275        lc.push(prefix.clone() + "token");
276        lc.push(prefix.clone() + "token import");
277        lc.push(prefix.clone() + "token generate-mint");
278        lc.push(prefix.clone() + "token list");
279        lc.push(prefix.clone() + "token mint");
280        lc.push(prefix + "token freeze");
281        return
282    }
283
284    if last.starts_with("con") {
285        lc.push(prefix.clone() + "contract");
286        lc.push(prefix.clone() + "contract generate-deploy");
287        lc.push(prefix.clone() + "contract list");
288        lc.push(prefix.clone() + "contract export-data");
289        lc.push(prefix.clone() + "contract deploy");
290        lc.push(prefix + "contract lock");
291        return
292    }
293
294    // Now the catch alls
295    if last.starts_with("a") {
296        lc.push(prefix.clone() + "attach-fee");
297        lc.push(prefix.clone() + "alias");
298        lc.push(prefix.clone() + "alias add");
299        lc.push(prefix.clone() + "alias show");
300        lc.push(prefix + "alias remove");
301        return
302    }
303
304    if last.starts_with("c") {
305        lc.push(prefix.clone() + "completions");
306        lc.push(prefix.clone() + "contract");
307        lc.push(prefix.clone() + "contract generate-deploy");
308        lc.push(prefix.clone() + "contract list");
309        lc.push(prefix.clone() + "contract export-data");
310        lc.push(prefix.clone() + "contract deploy");
311        lc.push(prefix + "contract lock");
312        return
313    }
314
315    if last.starts_with("s") {
316        lc.push(prefix.clone() + "spend");
317        lc.push(prefix.clone() + "subscribe");
318        lc.push(prefix.clone() + "snooze");
319        lc.push(prefix.clone() + "scan");
320        lc.push(prefix + "scan --reset");
321        return
322    }
323
324    if last.starts_with("t") {
325        lc.push(prefix.clone() + "transfer");
326        lc.push(prefix.clone() + "token");
327        lc.push(prefix.clone() + "token import");
328        lc.push(prefix.clone() + "token generate-mint");
329        lc.push(prefix.clone() + "token list");
330        lc.push(prefix.clone() + "token mint");
331        lc.push(prefix + "token freeze");
332        return
333    }
334
335    if last.starts_with("u") {
336        lc.push(prefix.clone() + "unspend");
337        lc.push(prefix.clone() + "unsubscribe");
338        lc.push(prefix + "unsnooze");
339    }
340}
341
342/// Auxiliary function to define the interactive shell hints.
343fn hints(buffer: &str) -> Option<(String, i32, bool)> {
344    // Split commands so we always process the last one
345    let commands: Vec<&str> = buffer.split('|').collect();
346    let last = commands.last().unwrap().trim_start();
347    let color = 35; // magenta
348    let bold = false;
349    match last {
350        "completions " => Some(("<shell>".to_string(), color, bold)),
351        "wallet " => Some(("(initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins|mining-config)".to_string(), color, bold)),
352        "wallet default-address " => Some(("<index>".to_string(), color, bold)),
353        "wallet mining-config " => Some(("<index> [spend_hook] [user_data]".to_string(), color, bold)),
354        "unspend " => Some(("<coin>".to_string(), color, bold)),
355        "transfer " => Some(("[--half-split] <amount> <token> <recipient> [spend_hook] [user_data]".to_string(), color, bold)),
356        "otc " => Some(("(init|join|inspect|sign)".to_string(), color, bold)),
357        "otc init " => Some(("<value_pair> <token_pair>".to_string(), color, bold)),
358        "dao " => Some(("(create|view|import|remove|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook|mining-config)".to_string(), color, bold)),
359        "dao create " => Some(("<proposer-limit> <quorum> <early-exec-quorum> <approval-ratio> <gov-token-id>".to_string(), color, bold)),
360        "dao import " => Some(("<name>".to_string(), color, bold)),
361        "dao remove " => Some(("<name>".to_string(), color, bold)),
362        "dao list " => Some(("[name]".to_string(), color, bold)),
363        "dao balance " => Some(("<name>".to_string(), color, bold)),
364        "dao mint " => Some(("<name>".to_string(), color, bold)),
365        "dao propose-transfer " => Some(("<name> <duration> <amount> <token> <recipient> [spend-hook] [user-data]".to_string(), color, bold)),
366        "dao propose-generic" => Some(("<name> <duration> [user-data]".to_string(), color, bold)),
367        "dao proposals " => Some(("<name>".to_string(), color, bold)),
368        "dao proposal " => Some(("[--(export|mint-proposal)] <bulla>".to_string(), color, bold)),
369        "dao vote " => Some(("<bulla> <vote> [vote-weight]".to_string(), color, bold)),
370        "dao exec " => Some(("[--early] <bulla>".to_string(), color, bold)),
371        "dao mining-config " => Some(("<name>".to_string(), color, bold)),
372        "scan " => Some(("[--reset]".to_string(), color, bold)),
373        "scan --reset " => Some(("<height>".to_string(), color, bold)),
374        "explorer " => Some(("(fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks|mining-config)".to_string(), color, bold)),
375        "explorer fetch-tx " => Some(("[--encode] <tx-hash>".to_string(), color, bold)),
376        "explorer txs-history " => Some(("[--encode] [tx-hash]".to_string(), color, bold)),
377        "explorer scanned-blocks " => Some(("[height]".to_string(), color, bold)),
378        "alias " => Some(("(add|show|remove)".to_string(), color, bold)),
379        "alias add " => Some(("<alias> <token>".to_string(), color, bold)),
380        "alias show " => Some(("[-a, --alias <alias>] [-t, --token <token>]".to_string(), color, bold)),
381        "alias remove " => Some(("<alias>".to_string(), color, bold)),
382        "token " => Some(("(import|generate-mint|list|mint|freeze)".to_string(), color, bold)),
383        "token import " => Some(("<secret-key> <token-blind>".to_string(), color, bold)),
384        "token mint " => Some(("<token> <amount> <recipient> [spend-hook] [user-data]".to_string(), color, bold)),
385        "token freeze " => Some(("<token>".to_string(), color, bold)),
386        "contract " => Some(("(generate-deploy|list|export-data|deploy|lock)".to_string(), color, bold)),
387        "contract list " => Some(("[contract-id]".to_string(), color, bold)),
388        "contract export-data " => Some(("<tx-hash>".to_string(), color, bold)),
389        "contract deploy " => Some(("<deploy-auth> <wasm-path> [deploy-ix]".to_string(), color, bold)),
390        "contract lock " => Some(("<deploy-auth>".to_string(), color, bold)),
391        "tx-from-calls " => Some(("[calls-map]".to_string(), color, bold)),
392        _ => None,
393    }
394}
395
396/// Auxiliary function to start provided Drk as an interactive shell.
397/// Only sane/linenoise terminals are suported.
398pub async fn interactive(
399    drk: &DrkPtr,
400    endpoint: &Url,
401    history_path: &str,
402    shell_sender: &Sender<Vec<String>>,
403    shell_receiver: &Receiver<Vec<String>>,
404    ex: &ExecutorPtr,
405) {
406    // Expand the history file path
407    let history_path = match expand_path(history_path) {
408        Ok(p) => p,
409        Err(e) => {
410            eprintln!("Error while expanding history file path: {e}");
411            return
412        }
413    };
414    let history_path = history_path.into_os_string();
415    let history_file = history_path.to_str().unwrap();
416
417    // Set the completion callback. This will be called every time the
418    // user uses the <tab> key.
419    linenoise_set_completion_callback(completion);
420
421    // Set the shell hints
422    linenoise_set_hints_callback(hints);
423
424    // Load history from file.The history file is just a plain text file
425    // where entries are separated by newlines.
426    let _ = linenoise_history_load(history_file);
427
428    // Create two detached tasks to use for block subscription
429    let mut subscription_active = false;
430    let mut snooze_active = false;
431    let subscription_tasks = [StoppableTask::new(), StoppableTask::new()];
432
433    // Start the interactive shell
434    loop {
435        // Wait for next line to process
436        let line = listen_for_line(&snooze_active, shell_receiver).await;
437
438        // Grab input or end if Ctrl-D or Ctrl-C was pressed
439        let Some(line) = line else { break };
440
441        // Check if line is empty
442        if line.is_empty() {
443            continue
444        }
445
446        // Add line to history
447        linenoise_history_add(&line);
448
449        // Split commands
450        let commands: Vec<&str> = line.split('|').collect();
451
452        // Process each command
453        let mut output = vec![];
454        'commands_loop: for (command_index, command) in commands.iter().enumerate() {
455            let mut input = output;
456            output = vec![];
457
458            // Check if we have to output to a file
459            let (mut command, file, append) = if command.contains('>') {
460                // Check if we write or append to the file
461                let mut split = ">";
462                let mut append = false;
463                if command.contains(">>") {
464                    split = ">>";
465                    append = true;
466                }
467
468                // Parse command parts
469                let parts: Vec<&str> = command.split(split).collect();
470                if parts.len() == 1 || parts[0].contains('>') {
471                    output.push(format!("Malformed command output file definition: {command}"));
472                    continue
473                }
474                let file = String::from(parts[1..].join("").trim());
475                if file.is_empty() || file.contains('>') {
476                    output.push(format!("Malformed command output file definition: {command}"));
477                    continue
478                }
479                (parts[0], Some(file), append)
480            } else {
481                (*command, None, false)
482            };
483
484            // Check if we have to use a file as input
485            if command.contains('<') {
486                // Parse command parts
487                let parts: Vec<&str> = command.split('<').collect();
488                if parts.len() == 1 {
489                    output.push(format!("Malformed command input file definition: {command}"));
490                    continue
491                }
492
493                // Read the input file name
494                let file = String::from(parts[1..].join("").trim());
495                if file.is_empty() {
496                    output.push(format!("Malformed command input file definition: {command}"));
497                    continue
498                }
499
500                // Expand the input file path
501                let file_path = match expand_path(&file) {
502                    Ok(p) => p,
503                    Err(e) => {
504                        output.push(format!("Error while expanding input file path: {e}"));
505                        continue
506                    }
507                };
508
509                // Read the file lines
510                let file = match File::open(file_path) {
511                    Ok(f) => f,
512                    Err(e) => {
513                        output.push(format!("Error while openning input file: {e}"));
514                        continue
515                    }
516                };
517                input = vec![];
518                for (index, line) in BufReader::new(file).lines().enumerate() {
519                    match line {
520                        Ok(l) => input.push(l),
521                        Err(e) => {
522                            output
523                                .push(format!("Error while reading input file line {index}: {e}"));
524                            continue 'commands_loop
525                        }
526                    }
527                }
528                command = parts[0];
529            }
530
531            // Parse command parts
532            let parts: Vec<&str> = command.split_whitespace().collect();
533            if parts.is_empty() {
534                continue
535            }
536
537            // Handle command
538            match parts[0] {
539                "help" => help(&mut output),
540                "kaching" => kaching().await,
541                "ping" => handle_ping(drk, &mut output).await,
542                "completions" => handle_completions(&parts, &mut output),
543                "wallet" => handle_wallet(drk, &parts, &input, &mut output).await,
544                "spend" => handle_spend(drk, &input, &mut output).await,
545                "unspend" => handle_unspend(drk, &parts, &mut output).await,
546                "transfer" => handle_transfer(drk, &parts, &mut output).await,
547                "otc" => handle_otc(drk, &parts, &input, &mut output).await,
548                "dao" => handle_dao(drk, &parts, &input, &mut output).await,
549                "attach-fee" => handle_attach_fee(drk, &input, &mut output).await,
550                "tx-from-calls" => handle_tx_from_calls(drk, &parts, &input, &mut output).await,
551                "inspect" => handle_inspect(&input, &mut output).await,
552                "broadcast" => handle_broadcast(drk, &input, &mut output).await,
553                "subscribe" => {
554                    handle_subscribe(
555                        drk,
556                        endpoint,
557                        &mut subscription_active,
558                        &subscription_tasks,
559                        shell_sender,
560                        ex,
561                    )
562                    .await
563                }
564                "unsubscribe" => {
565                    handle_unsubscribe(&mut subscription_active, &subscription_tasks).await
566                }
567                "snooze" => snooze_active = true,
568                "unsnooze" => snooze_active = false,
569                "scan" => {
570                    handle_scan(
571                        drk,
572                        &subscription_active,
573                        &parts,
574                        &mut output,
575                        &(command_index + 1 == commands.len() && file.is_none()),
576                    )
577                    .await
578                }
579                "explorer" => handle_explorer(drk, &parts, &input, &mut output).await,
580                "alias" => handle_alias(drk, &parts, &mut output).await,
581                "token" => handle_token(drk, &parts, &mut output).await,
582                "contract" => handle_contract(drk, &parts, &mut output).await,
583                _ => output.push(format!("Unrecognized command: {}", parts[0])),
584            }
585
586            // Write output to file, if requested
587            if let Some(file) = file {
588                // Expand the output file path
589                let file_path = match expand_path(&file) {
590                    Ok(p) => p,
591                    Err(e) => {
592                        output.push(format!("Error while expanding output file path: {e}"));
593                        continue
594                    }
595                };
596
597                // Open the file
598                let file = if append {
599                    OpenOptions::new().create(true).append(true).open(&file_path)
600                } else {
601                    File::create(file_path)
602                };
603                let mut file = match file {
604                    Ok(f) => f,
605                    Err(e) => {
606                        output.push(format!("Error while openning output file: {e}"));
607                        continue
608                    }
609                };
610
611                // Seek end if we append to it
612                if append {
613                    if let Err(e) = file.seek(SeekFrom::End(0)) {
614                        output.push(format!("Error while seeking end of output file: {e}"));
615                        continue
616                    }
617                }
618
619                // Write the output
620                if let Err(e) = file.write_all((output.join("\n") + "\n").as_bytes()) {
621                    output.push(format!("Error while writing output file: {e}"));
622                    continue
623                }
624                output = vec![];
625            }
626        }
627
628        // Handle last command output
629        print_output(&output);
630    }
631
632    // Stop the subscription tasks if they are active
633    subscription_tasks[0].stop_nowait();
634    subscription_tasks[1].stop_nowait();
635
636    // Write history file
637    let _ = linenoise_history_save(history_file);
638}
639
640/// Auxiliary function to listen for linenoise input line and handle
641/// background task messages.
642async fn listen_for_line(
643    snooze_active: &bool,
644    shell_receiver: &Receiver<Vec<String>>,
645) -> Option<String> {
646    // Generate the linoise state structure
647    let mut state = match LinenoiseState::edit_start(-1, -1, "drk> ") {
648        Ok(s) => s,
649        Err(e) => {
650            eprintln!("Error while generating linenoise state: {e}");
651            return None
652        }
653    };
654
655    // Set stdin to non-blocking mode
656    let fd = state.get_fd();
657    unsafe {
658        let flags = fcntl(fd, F_GETFL, 0);
659        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
660    }
661
662    // Read until we get a line to process
663    let mut line = None;
664    loop {
665        // Future that polls stdin for input
666        let input_future = async {
667            loop {
668                match state.edit_feed() {
669                    Ok(Some(l)) => {
670                        line = Some(l);
671                        break
672                    }
673                    Ok(None) => break,
674                    Err(e) if e.kind() == ErrorKind::Interrupted => break,
675                    Err(e) if e.kind() == ErrorKind::WouldBlock => {
676                        // No data available, yield and retry
677                        msleep(10).await;
678                        continue
679                    }
680                    Err(e) => {
681                        eprintln!("Error while reading linenoise feed: {e}");
682                        break
683                    }
684                }
685            }
686        };
687
688        // Future that polls the channel
689        let channel_future = async {
690            loop {
691                if !shell_receiver.is_empty() {
692                    break
693                }
694                msleep(1000).await;
695            }
696        };
697
698        // Manage the futures
699        select! {
700            // When input is ready we break out the loop
701            _ = input_future.fuse() => break,
702            // Manage filled channel
703            _ = channel_future.fuse() => {
704                // We only print if snooze is inactive,
705                // but have to consume the message regardless,
706                // so the queue gets empty.
707                if *snooze_active {
708                    while !shell_receiver.is_empty() {
709                        if let Err(e) = shell_receiver.recv().await {
710                            eprintln!("Error while reading shell receiver channel: {e}");
711                            break
712                        }
713                    }
714                } else {
715                    // Hide prompt
716                    if let Err(e) = state.hide() {
717                        eprintln!("Error while hiding linenoise state: {e}");
718                        break
719                    };
720
721                    // Restore blocking mode
722                    unsafe {
723                        let flags = fcntl(fd, F_GETFL, 0);
724                        fcntl(fd, F_SETFL, flags & !O_NONBLOCK);
725                    }
726
727                    // Print output
728                    while !shell_receiver.is_empty() {
729                        match shell_receiver.recv().await {
730                            Ok(msg) => {
731                                for line in msg {
732                                    println!("{}\r", line.replace("\n", "\n\r"));
733                                }
734                            }
735                            Err(e) => {
736                                eprintln!("Error while reading shell receiver channel: {e}");
737                                break
738                            }
739                        }
740                    }
741
742                    // Set stdin to non-blocking mode
743                    unsafe {
744                        let flags = fcntl(fd, F_GETFL, 0);
745                        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
746                    }
747
748                    // Show prompt again
749                    if let Err(e) = state.show() {
750                        eprintln!("Error while showing linenoise state: {e}");
751                        break
752                    };
753                }
754            }
755        }
756    }
757
758    // Restore blocking mode
759    unsafe {
760        let flags = fcntl(fd, F_GETFL, 0);
761        fcntl(fd, F_SETFL, flags & !O_NONBLOCK);
762    }
763
764    if let Err(e) = state.edit_stop() {
765        eprintln!("Error while stopping linenoise state: {e}");
766    };
767    line
768}
769
770/// Auxiliary function to define the ping command handling.
771async fn handle_ping(drk: &DrkPtr, output: &mut Vec<String>) {
772    if let Err(e) = drk.read().await.ping(output).await {
773        output.push(format!("Error while executing ping command: {e}"))
774    }
775}
776
777/// Auxiliary function to define the completions command handling.
778fn handle_completions(parts: &[&str], output: &mut Vec<String>) {
779    // Check correct command structure
780    if parts.len() != 2 {
781        output.push(String::from("Malformed `completions` command"));
782        output.push(String::from("Usage: completions <shell>"));
783        return
784    }
785
786    match generate_completions(parts[1]) {
787        Ok(completions) => output.push(completions),
788        Err(e) => output.push(format!("Error while executing completions command: {e}")),
789    }
790}
791
792/// Auxiliary function to define the wallet command handling.
793async fn handle_wallet(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
794    // Check correct command structure
795    if parts.len() < 2 {
796        output.push(String::from("Malformed `wallet` command"));
797        output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins|mining-config)"));
798        return
799    }
800
801    // Handle subcommand
802    match parts[1] {
803        "initialize" => handle_wallet_initialize(drk, output).await,
804        "keygen" => handle_wallet_keygen(drk, output).await,
805        "balance" => handle_wallet_balance(drk, output).await,
806        "address" => handle_wallet_address(drk, output).await,
807        "addresses" => handle_wallet_addresses(drk, output).await,
808        "default-address" => handle_wallet_default_address(drk, parts, output).await,
809        "secrets" => handle_wallet_secrets(drk, output).await,
810        "import-secrets" => handle_wallet_import_secrets(drk, input, output).await,
811        "tree" => handle_wallet_tree(drk, output).await,
812        "coins" => handle_wallet_coins(drk, output).await,
813        "mining-config" => handle_wallet_mining_config(drk, parts, output).await,
814        _ => {
815            output.push(format!("Unrecognized wallet subcommand: {}", parts[1]));
816            output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins|mining-config)"));
817        }
818    }
819}
820
821/// Auxiliary function to define the wallet initialize subcommand handling.
822async fn handle_wallet_initialize(drk: &DrkPtr, output: &mut Vec<String>) {
823    let lock = drk.read().await;
824    if let Err(e) = lock.initialize_wallet().await {
825        output.push(format!("Error initializing wallet: {e}"));
826        return
827    }
828    if let Err(e) = lock.initialize_money(output).await {
829        output.push(format!("Failed to initialize Money: {e}"));
830        return
831    }
832    if let Err(e) = lock.initialize_dao().await {
833        output.push(format!("Failed to initialize DAO: {e}"));
834        return
835    }
836    if let Err(e) = lock.initialize_deployooor() {
837        output.push(format!("Failed to initialize Deployooor: {e}"));
838    }
839}
840
841/// Auxiliary function to define the wallet keygen subcommand handling.
842async fn handle_wallet_keygen(drk: &DrkPtr, output: &mut Vec<String>) {
843    if let Err(e) = drk.read().await.money_keygen(output).await {
844        output.push(format!("Failed to generate keypair: {e}"));
845    }
846}
847
848/// Auxiliary function to define the wallet balance subcommand handling.
849async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec<String>) {
850    let drk = drk.read().await;
851
852    let balmap = match drk.money_balance().await {
853        Ok(m) => m,
854        Err(e) => {
855            output.push(format!("Failed to fetch balances map: {e}"));
856            return
857        }
858    };
859
860    let alimap = match drk.get_aliases_mapped_by_token().await {
861        Ok(m) => m,
862        Err(e) => {
863            output.push(format!("Failed to fetch aliases map: {e}"));
864            return
865        }
866    };
867
868    let table = prettytable_balance(&balmap, &alimap);
869
870    if table.is_empty() {
871        output.push(String::from("No unspent balances found"));
872    } else {
873        output.push(format!("{table}"));
874    }
875}
876
877/// Auxiliary function to define the wallet address subcommand handling.
878async fn handle_wallet_address(drk: &DrkPtr, output: &mut Vec<String>) {
879    let drk = drk.read().await;
880
881    let public_key = match drk.default_address().await {
882        Ok(v) => v,
883        Err(e) => {
884            output.push(format!("Failed to fetch default address: {e}"));
885            return
886        }
887    };
888
889    let addr: Address = StandardAddress::from_public(drk.network, public_key).into();
890    output.push(format!("{addr}"));
891}
892
893/// Auxiliary function to define the wallet addresses subcommand handling.
894async fn handle_wallet_addresses(drk: &DrkPtr, output: &mut Vec<String>) {
895    let drk = drk.read().await;
896    let network = drk.network;
897    let addresses = match drk.addresses().await {
898        Ok(v) => v,
899        Err(e) => {
900            output.push(format!("Failed to fetch addresses: {e}"));
901            return
902        }
903    };
904
905    let table = prettytable_addrs(network, &addresses);
906
907    if table.is_empty() {
908        output.push(String::from("No addresses found"));
909    } else {
910        output.push(format!("{table}"));
911    }
912}
913
914/// Auxiliary function to define the wallet default address subcommand handling.
915async fn handle_wallet_default_address(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
916    if parts.len() != 3 {
917        output.push(String::from("Malformed `wallet default-address` subcommand"));
918        output.push(String::from("Usage: wallet default-address <index>"));
919        return
920    }
921
922    let index = match usize::from_str(parts[2]) {
923        Ok(i) => i,
924        Err(e) => {
925            output.push(format!("Invalid address id: {e}"));
926            return
927        }
928    };
929
930    if let Err(e) = drk.read().await.set_default_address(index) {
931        output.push(format!("Failed to set default address: {e}"));
932    }
933}
934
935/// Auxiliary function to define the wallet secrets subcommand handling.
936async fn handle_wallet_secrets(drk: &DrkPtr, output: &mut Vec<String>) {
937    match drk.read().await.get_money_secrets().await {
938        Ok(secrets) => {
939            for secret in secrets {
940                output.push(format!("{secret}"));
941            }
942        }
943        Err(e) => output.push(format!("Failed to fetch secrets: {e}")),
944    }
945}
946
947/// Auxiliary function to define the wallet import secrets subcommand handling.
948async fn handle_wallet_import_secrets(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
949    let mut secrets = vec![];
950    // Parse input or read from stdin
951    if input.is_empty() {
952        for (i, line) in stdin().lines().enumerate() {
953            let Ok(line) = line else { continue };
954
955            let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
956                output.push(format!("Warning: Failed to decode secret on line {i}"));
957                continue
958            };
959            let Ok(secret) = deserialize_async(&bytes).await else {
960                output.push(format!("Warning: Failed to deserialize secret on line {i}"));
961                continue
962            };
963            secrets.push(secret);
964        }
965    } else {
966        for (i, line) in input.iter().enumerate() {
967            let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
968                output.push(format!("Warning: Failed to decode secret on line {i}"));
969                continue
970            };
971            let Ok(secret) = deserialize_async(&bytes).await else {
972                output.push(format!("Warning: Failed to deserialize secret on line {i}"));
973                continue
974            };
975            secrets.push(secret);
976        }
977    }
978
979    match drk.read().await.import_money_secrets(secrets, output).await {
980        Ok(pubkeys) => {
981            for key in pubkeys {
982                output.push(format!("{key}"));
983            }
984        }
985        Err(e) => output.push(format!("Failed to import secrets: {e}")),
986    }
987}
988
989/// Auxiliary function to define the wallet tree subcommand handling.
990async fn handle_wallet_tree(drk: &DrkPtr, output: &mut Vec<String>) {
991    match drk.read().await.get_money_tree().await {
992        Ok(tree) => output.push(format!("{tree:#?}")),
993        Err(e) => output.push(format!("Failed to fetch tree: {e}")),
994    }
995}
996
997/// Auxiliary function to define the wallet coins subcommand handling.
998async fn handle_wallet_coins(drk: &DrkPtr, output: &mut Vec<String>) {
999    let lock = drk.read().await;
1000    let coins = match lock.get_coins(true).await {
1001        Ok(c) => c,
1002        Err(e) => {
1003            output.push(format!("Failed to fetch coins: {e}"));
1004            return
1005        }
1006    };
1007
1008    if coins.is_empty() {
1009        return
1010    }
1011
1012    let aliases_map = match lock.get_aliases_mapped_by_token().await {
1013        Ok(m) => m,
1014        Err(e) => {
1015            output.push(format!("Failed to fetch aliases map: {e}"));
1016            return
1017        }
1018    };
1019
1020    let table = prettytable_coins(&coins, &aliases_map);
1021    output.push(format!("{table}"));
1022}
1023
1024/// Auxiliary function to define the wallet mining config subcommand handling.
1025async fn handle_wallet_mining_config(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1026    // Check correct command structure
1027    if parts.len() < 3 || parts.len() > 5 {
1028        output.push(String::from("Malformed `wallet mining-address` subcommand"));
1029        output.push(String::from("Usage: wallet mining-config <index> [spend_hook] [user_data]"));
1030        return
1031    }
1032
1033    // Parse command
1034    let mut index = 2;
1035    let wallet_index = match usize::from_str(parts[index]) {
1036        Ok(i) => i,
1037        Err(e) => {
1038            output.push(format!("Invalid address id: {e}"));
1039            return
1040        }
1041    };
1042    index += 1;
1043
1044    let spend_hook = if index < parts.len() {
1045        match FuncId::from_str(parts[index]) {
1046            Ok(s) => Some(s),
1047            Err(e) => {
1048                output.push(format!("Invalid spend hook: {e}"));
1049                return
1050            }
1051        }
1052    } else {
1053        None
1054    };
1055    index += 1;
1056
1057    let user_data = if index < parts.len() {
1058        let bytes = match bs58::decode(&parts[index]).into_vec() {
1059            Ok(b) => b,
1060            Err(e) => {
1061                output.push(format!("Invalid user data: {e}"));
1062                return
1063            }
1064        };
1065
1066        let bytes: [u8; 32] = match bytes.try_into() {
1067            Ok(b) => b,
1068            Err(e) => {
1069                output.push(format!("Invalid user data: {e:?}"));
1070                return
1071            }
1072        };
1073
1074        let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1075            Some(v) => v,
1076            None => {
1077                output.push(String::from("Invalid user data"));
1078                return
1079            }
1080        };
1081
1082        Some(elem)
1083    } else {
1084        None
1085    };
1086
1087    if let Err(e) =
1088        drk.read().await.mining_config(wallet_index, spend_hook, user_data, output).await
1089    {
1090        output.push(format!("Failed to generate wallet mining configuration: {e}"));
1091    }
1092}
1093
1094/// Auxiliary function to define the spend command handling.
1095async fn handle_spend(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
1096    let tx = match parse_tx_from_input(input).await {
1097        Ok(t) => t,
1098        Err(e) => {
1099            output.push(format!("Error while parsing transaction: {e}"));
1100            return
1101        }
1102    };
1103
1104    if let Err(e) = drk.read().await.mark_tx_spend(&tx, output).await {
1105        output.push(format!("Failed to mark transaction coins as spent: {e}"))
1106    }
1107}
1108
1109/// Auxiliary function to define the unspend command handling.
1110async fn handle_unspend(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1111    // Check correct command structure
1112    if parts.len() != 2 {
1113        output.push(String::from("Malformed `unspend` command"));
1114        output.push(String::from("Usage: unspend <coin>"));
1115        return
1116    }
1117
1118    let bytes = match bs58::decode(&parts[1]).into_vec() {
1119        Ok(b) => b,
1120        Err(e) => {
1121            output.push(format!("Invalid coin: {e}"));
1122            return
1123        }
1124    };
1125
1126    let bytes: [u8; 32] = match bytes.try_into() {
1127        Ok(b) => b,
1128        Err(e) => {
1129            output.push(format!("Invalid coin: {e:?}"));
1130            return
1131        }
1132    };
1133
1134    let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1135        Some(v) => v,
1136        None => {
1137            output.push(String::from("Invalid coin"));
1138            return
1139        }
1140    };
1141
1142    if let Err(e) = drk.read().await.unspend_coin(&Coin::from(elem)).await {
1143        output.push(format!("Failed to mark coin as unspent: {e}"))
1144    }
1145}
1146
1147/// Auxiliary function to define the transfer command handling.
1148async fn handle_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1149    // Check correct command structure
1150    if parts.len() < 4 || parts.len() > 7 {
1151        output.push(String::from("Malformed `transfer` command"));
1152        output.push(String::from(
1153            "Usage: transfer [--half-split] <amount> <token> <recipient> [spend_hook] [user_data]",
1154        ));
1155        return
1156    }
1157
1158    // Parse command
1159    let mut index = 1;
1160    let mut half_split = false;
1161    if parts[index] == "--half-split" {
1162        half_split = true;
1163        index += 1;
1164    }
1165
1166    let amount = String::from(parts[index]);
1167    if let Err(e) = f64::from_str(&amount) {
1168        output.push(format!("Invalid amount: {e}"));
1169        return
1170    }
1171    index += 1;
1172
1173    let lock = drk.read().await;
1174    let token_id = match lock.get_token(String::from(parts[index])).await {
1175        Ok(t) => t,
1176        Err(e) => {
1177            output.push(format!("Invalid token ID: {e}"));
1178            return
1179        }
1180    };
1181    index += 1;
1182
1183    let rcpt = match Address::from_str(parts[index]) {
1184        Ok(r) => r,
1185        Err(e) => {
1186            output.push(format!("Invalid recipient: {e}"));
1187            return
1188        }
1189    };
1190
1191    if rcpt.network() != lock.network {
1192        output.push("Mismatched recipient address prefix".to_string());
1193        return
1194    }
1195
1196    index += 1;
1197
1198    let spend_hook = if index < parts.len() {
1199        match FuncId::from_str(parts[index]) {
1200            Ok(s) => Some(s),
1201            Err(e) => {
1202                output.push(format!("Invalid spend hook: {e}"));
1203                return
1204            }
1205        }
1206    } else {
1207        None
1208    };
1209    index += 1;
1210
1211    let user_data = if index < parts.len() {
1212        let bytes = match bs58::decode(&parts[index]).into_vec() {
1213            Ok(b) => b,
1214            Err(e) => {
1215                output.push(format!("Invalid user data: {e}"));
1216                return
1217            }
1218        };
1219
1220        let bytes: [u8; 32] = match bytes.try_into() {
1221            Ok(b) => b,
1222            Err(e) => {
1223                output.push(format!("Invalid user data: {e:?}"));
1224                return
1225            }
1226        };
1227
1228        let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1229            Some(v) => v,
1230            None => {
1231                output.push(String::from("Invalid user data"));
1232                return
1233            }
1234        };
1235
1236        Some(elem)
1237    } else {
1238        None
1239    };
1240
1241    match lock
1242        .transfer(&amount, token_id, *rcpt.public_key(), spend_hook, user_data, half_split)
1243        .await
1244    {
1245        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
1246        Err(e) => output.push(format!("Failed to create payment transaction: {e}")),
1247    }
1248}
1249
1250/// Auxiliary function to define the otc command handling.
1251async fn handle_otc(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1252    // Check correct command structure
1253    if parts.len() < 2 {
1254        output.push(String::from("Malformed `otc` command"));
1255        output.push(String::from("Usage: otc (init|join|inspect|sign)"));
1256        return
1257    }
1258
1259    // Handle subcommand
1260    match parts[1] {
1261        "init" => handle_otc_init(drk, parts, output).await,
1262        "join" => handle_otc_join(drk, parts, input, output).await,
1263        "inspect" => handle_otc_inspect(drk, parts, input, output).await,
1264        "sign" => handle_otc_sign(drk, parts, input, output).await,
1265        _ => {
1266            output.push(format!("Unrecognized OTC subcommand: {}", parts[1]));
1267            output.push(String::from("Usage: otc (init|join|inspect|sign)"));
1268        }
1269    }
1270}
1271
1272/// Auxiliary function to define the otc init subcommand handling.
1273async fn handle_otc_init(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1274    // Check correct subcommand structure
1275    if parts.len() != 4 {
1276        output.push(String::from("Malformed `otc init` subcommand"));
1277        output.push(String::from("Usage: otc init <value_pair> <token_pair>"));
1278        return
1279    }
1280
1281    let value_pair = match parse_value_pair(parts[2]) {
1282        Ok(v) => v,
1283        Err(e) => {
1284            output.push(format!("Invalid value pair: {e}"));
1285            return
1286        }
1287    };
1288
1289    let lock = drk.read().await;
1290    let token_pair = match parse_token_pair(&lock, parts[3]).await {
1291        Ok(t) => t,
1292        Err(e) => {
1293            output.push(format!("Invalid token pair: {e}"));
1294            return
1295        }
1296    };
1297
1298    match lock.init_swap(value_pair, token_pair, None, None, None).await {
1299        Ok(half) => output.push(base64::encode(&serialize_async(&half).await)),
1300        Err(e) => output.push(format!("Failed to create swap transaction half: {e}")),
1301    }
1302}
1303
1304/// Auxiliary function to define the otc join subcommand handling.
1305async fn handle_otc_join(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1306    // Check correct subcommand structure
1307    if parts.len() != 2 {
1308        output.push(String::from("Malformed `otc join` subcommand"));
1309        output.push(String::from("Usage: otc join"));
1310        return
1311    }
1312
1313    // Parse line from input or fallback to stdin if its empty
1314    let buf = match input.len() {
1315        0 => {
1316            let mut buf = String::new();
1317            if let Err(e) = stdin().read_to_string(&mut buf) {
1318                output.push(format!("Failed to read from stdin: {e}"));
1319                return
1320            };
1321            buf
1322        }
1323        1 => input[0].clone(),
1324        _ => {
1325            output.push(String::from("Multiline input provided"));
1326            return
1327        }
1328    };
1329
1330    let Some(bytes) = base64::decode(buf.trim()) else {
1331        output.push(String::from("Failed to decode partial swap data"));
1332        return
1333    };
1334
1335    let partial: PartialSwapData = match deserialize_async(&bytes).await {
1336        Ok(p) => p,
1337        Err(e) => {
1338            output.push(format!("Failed to deserialize partial swap data: {e}"));
1339            return
1340        }
1341    };
1342
1343    match drk.read().await.join_swap(partial, None, None, None).await {
1344        Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1345        Err(e) => output.push(format!("Failed to create a join swap transaction: {e}")),
1346    }
1347}
1348
1349/// Auxiliary function to define the otc inspect subcommand handling.
1350async fn handle_otc_inspect(
1351    drk: &DrkPtr,
1352    parts: &[&str],
1353    input: &[String],
1354    output: &mut Vec<String>,
1355) {
1356    // Check correct subcommand structure
1357    if parts.len() != 2 {
1358        output.push(String::from("Malformed `otc inspect` subcommand"));
1359        output.push(String::from("Usage: otc inspect"));
1360        return
1361    }
1362
1363    // Parse line from input or fallback to stdin if its empty
1364    let buf = match input.len() {
1365        0 => {
1366            let mut buf = String::new();
1367            if let Err(e) = stdin().read_to_string(&mut buf) {
1368                output.push(format!("Failed to read from stdin: {e}"));
1369                return
1370            };
1371            buf
1372        }
1373        1 => input[0].clone(),
1374        _ => {
1375            output.push(String::from("Multiline input provided"));
1376            return
1377        }
1378    };
1379
1380    let Some(bytes) = base64::decode(buf.trim()) else {
1381        output.push(String::from("Failed to decode swap transaction"));
1382        return
1383    };
1384
1385    if let Err(e) = drk.read().await.inspect_swap(bytes, output).await {
1386        output.push(format!("Failed to inspect swap: {e}"));
1387    }
1388}
1389
1390/// Auxiliary function to define the otc sign subcommand handling.
1391async fn handle_otc_sign(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1392    // Check correct subcommand structure
1393    if parts.len() != 2 {
1394        output.push(String::from("Malformed `otc sign` subcommand"));
1395        output.push(String::from("Usage: otc sign"));
1396        return
1397    }
1398
1399    let mut tx = match parse_tx_from_input(input).await {
1400        Ok(t) => t,
1401        Err(e) => {
1402            output.push(format!("Error while parsing transaction: {e}"));
1403            return
1404        }
1405    };
1406
1407    match drk.read().await.sign_swap(&mut tx).await {
1408        Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
1409        Err(e) => output.push(format!("Failed to sign joined swap transaction: {e}")),
1410    }
1411}
1412
1413/// Auxiliary function to define the dao command handling.
1414async fn handle_dao(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
1415    // Check correct command structure
1416    if parts.len() < 2 {
1417        output.push(String::from("Malformed `dao` command"));
1418        output.push(String::from("Usage: dao (create|view|import|remove|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook|mining-config)"));
1419        return
1420    }
1421
1422    // Handle subcommand
1423    match parts[1] {
1424        "create" => handle_dao_create(drk, parts, output).await,
1425        "view" => handle_dao_view(parts, input, output).await,
1426        "import" => handle_dao_import(drk, parts, input, output).await,
1427        "remove" => handle_dao_remove(drk, parts, output).await,
1428        "list" => handle_dao_list(drk, parts, output).await,
1429        "balance" => handle_dao_balance(drk, parts, output).await,
1430        "mint" => handle_dao_mint(drk, parts, output).await,
1431        "propose-transfer" => handle_dao_propose_transfer(drk, parts, output).await,
1432        "propose-generic" => handle_dao_propose_generic(drk, parts, output).await,
1433        "proposals" => handle_dao_proposals(drk, parts, output).await,
1434        "proposal" => handle_dao_proposal(drk, parts, output).await,
1435        "proposal-import" => handle_dao_proposal_import(drk, parts, input, output).await,
1436        "vote" => handle_dao_vote(drk, parts, output).await,
1437        "exec" => handle_dao_exec(drk, parts, output).await,
1438        "spend-hook" => handle_dao_spend_hook(parts, output).await,
1439        "mining-config" => handle_dao_mining_config(drk, parts, output).await,
1440        _ => {
1441            output.push(format!("Unrecognized DAO subcommand: {}", parts[1]));
1442            output.push(String::from("Usage: dao (create|view|import|remove|list|balance|mint|propose-transfer|propose-generic|proposals|proposal|proposal-import|vote|exec|spend-hook|mining-config)"));
1443        }
1444    }
1445}
1446
1447/// Auxiliary function to define the dao create subcommand handling.
1448async fn handle_dao_create(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1449    // Check correct subcommand structure
1450    if parts.len() != 7 {
1451        output.push(String::from("Malformed `dao create` subcommand"));
1452        output.push(String::from("Usage: dao create <proposer-limit> <quorum> <early-exec-quorum> <approval-ratio> <gov-token-id>"));
1453        return
1454    }
1455
1456    if let Err(e) = f64::from_str(parts[2]) {
1457        output.push(format!("Invalid proposer limit: {e}"));
1458        return
1459    }
1460    let proposer_limit = match decode_base10(parts[2], BALANCE_BASE10_DECIMALS, true) {
1461        Ok(p) => p,
1462        Err(e) => {
1463            output.push(format!("Error while parsing proposer limit: {e}"));
1464            return
1465        }
1466    };
1467
1468    if let Err(e) = f64::from_str(parts[3]) {
1469        output.push(format!("Invalid quorum: {e}"));
1470        return
1471    }
1472    let quorum = match decode_base10(parts[3], BALANCE_BASE10_DECIMALS, true) {
1473        Ok(q) => q,
1474        Err(e) => {
1475            output.push(format!("Error while parsing quorum: {e}"));
1476            return
1477        }
1478    };
1479
1480    if let Err(e) = f64::from_str(parts[4]) {
1481        output.push(format!("Invalid early exec quorum: {e}"));
1482        return
1483    }
1484    let early_exec_quorum = match decode_base10(parts[4], BALANCE_BASE10_DECIMALS, true) {
1485        Ok(e) => e,
1486        Err(e) => {
1487            output.push(format!("Error while parsing early exec quorum: {e}"));
1488            return
1489        }
1490    };
1491
1492    let approval_ratio = match f64::from_str(parts[5]) {
1493        Ok(a) => {
1494            if a > 1.0 {
1495                output.push(String::from("Error: Approval ratio cannot be >1.0"));
1496                return
1497            }
1498            a
1499        }
1500        Err(e) => {
1501            output.push(format!("Invalid approval ratio: {e}"));
1502            return
1503        }
1504    };
1505    let approval_ratio_base = 100_u64;
1506    let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
1507
1508    let gov_token_id = match drk.read().await.get_token(String::from(parts[6])).await {
1509        Ok(g) => g,
1510        Err(e) => {
1511            output.push(format!("Invalid Token ID: {e}"));
1512            return
1513        }
1514    };
1515
1516    let notes_keypair = Keypair::random(&mut OsRng);
1517    let proposer_keypair = Keypair::random(&mut OsRng);
1518    let proposals_keypair = Keypair::random(&mut OsRng);
1519    let votes_keypair = Keypair::random(&mut OsRng);
1520    let exec_keypair = Keypair::random(&mut OsRng);
1521    let early_exec_keypair = Keypair::random(&mut OsRng);
1522    let bulla_blind = BaseBlind::random(&mut OsRng);
1523
1524    let params = DaoParams::new(
1525        proposer_limit,
1526        quorum,
1527        early_exec_quorum,
1528        approval_ratio_base,
1529        approval_ratio_quot,
1530        gov_token_id,
1531        Some(notes_keypair.secret),
1532        notes_keypair.public,
1533        Some(proposer_keypair.secret),
1534        proposer_keypair.public,
1535        Some(proposals_keypair.secret),
1536        proposals_keypair.public,
1537        Some(votes_keypair.secret),
1538        votes_keypair.public,
1539        Some(exec_keypair.secret),
1540        exec_keypair.public,
1541        Some(early_exec_keypair.secret),
1542        early_exec_keypair.public,
1543        bulla_blind,
1544    );
1545
1546    output.push(params.toml_str());
1547}
1548
1549/// Auxiliary function to define the dao view subcommand handling.
1550async fn handle_dao_view(parts: &[&str], input: &[String], output: &mut Vec<String>) {
1551    // Check correct subcommand structure
1552    if parts.len() != 2 {
1553        output.push(String::from("Malformed `dao view` subcommand"));
1554        output.push(String::from("Usage: dao view"));
1555        return
1556    }
1557
1558    // Parse lines from input or fallback to stdin if its empty
1559    let buf = match input.len() {
1560        0 => {
1561            let mut buf = String::new();
1562            if let Err(e) = stdin().read_to_string(&mut buf) {
1563                output.push(format!("Failed to read from stdin: {e}"));
1564                return
1565            };
1566            buf
1567        }
1568        _ => input.join("\n"),
1569    };
1570
1571    let params = match DaoParams::from_toml_str(&buf) {
1572        Ok(p) => p,
1573        Err(e) => {
1574            output.push(format!("Error while parsing DAO params: {e}"));
1575            return
1576        }
1577    };
1578
1579    output.push(format!("{params}"));
1580}
1581
1582/// Auxiliary function to define the dao import subcommand handling.
1583async fn handle_dao_import(
1584    drk: &DrkPtr,
1585    parts: &[&str],
1586    input: &[String],
1587    output: &mut Vec<String>,
1588) {
1589    // Check correct subcommand structure
1590    if parts.len() != 3 {
1591        output.push(String::from("Malformed `dao import` subcommand"));
1592        output.push(String::from("Usage: dao import <name>"));
1593        return
1594    }
1595
1596    // Parse lines from input or fallback to stdin if its empty
1597    let buf = match input.len() {
1598        0 => {
1599            let mut buf = String::new();
1600            if let Err(e) = stdin().read_to_string(&mut buf) {
1601                output.push(format!("Failed to read from stdin: {e}"));
1602                return
1603            };
1604            buf
1605        }
1606        _ => input.join("\n"),
1607    };
1608
1609    let params = match DaoParams::from_toml_str(&buf) {
1610        Ok(p) => p,
1611        Err(e) => {
1612            output.push(format!("Error while parsing DAO params: {e}"));
1613            return
1614        }
1615    };
1616
1617    if let Err(e) = drk.read().await.import_dao(parts[2], &params, output).await {
1618        output.push(format!("Failed to import DAO: {e}"))
1619    }
1620}
1621
1622/// Auxiliary function to define the dao remove subcommand handling.
1623async fn handle_dao_remove(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1624    // Check correct subcommand structure
1625    if parts.len() != 3 {
1626        output.push(String::from("Malformed `dao remove` subcommand"));
1627        output.push(String::from("Usage: dao remove <name>"));
1628        return
1629    }
1630
1631    if let Err(e) = drk.read().await.remove_dao(parts[2], output).await {
1632        output.push(format!("Failed to remove DAO: {e}"))
1633    }
1634}
1635
1636/// Auxiliary function to define the dao list subcommand handling.
1637async fn handle_dao_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1638    // Check correct subcommand structure
1639    if parts.len() != 2 && parts.len() != 3 {
1640        output.push(String::from("Malformed `dao list` subcommand"));
1641        output.push(String::from("Usage: dao list [name]"));
1642        return
1643    }
1644
1645    let name = if parts.len() == 3 { Some(String::from(parts[2])) } else { None };
1646
1647    if let Err(e) = drk.read().await.dao_list(&name, output).await {
1648        output.push(format!("Failed to list DAO: {e}"))
1649    }
1650}
1651
1652/// Auxiliary function to define the dao balance subcommand handling.
1653async fn handle_dao_balance(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1654    // Check correct subcommand structure
1655    if parts.len() != 3 {
1656        output.push(String::from("Malformed `dao balance` subcommand"));
1657        output.push(String::from("Usage: dao balance <name>"));
1658        return
1659    }
1660
1661    let lock = drk.read().await;
1662    let balmap = match lock.dao_balance(parts[2]).await {
1663        Ok(b) => b,
1664        Err(e) => {
1665            output.push(format!("Failed to fetch DAO balance: {e}"));
1666            return
1667        }
1668    };
1669
1670    let alimap = match lock.get_aliases_mapped_by_token().await {
1671        Ok(m) => m,
1672        Err(e) => {
1673            output.push(format!("Failed to fetch aliases map: {e}"));
1674            return
1675        }
1676    };
1677
1678    let table = prettytable_balance(&balmap, &alimap);
1679
1680    if table.is_empty() {
1681        output.push(String::from("No unspent balances found"))
1682    } else {
1683        output.push(format!("{table}"));
1684    }
1685}
1686
1687/// Auxiliary function to define the dao mint subcommand handling.
1688async fn handle_dao_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1689    // Check correct subcommand structure
1690    if parts.len() != 3 {
1691        output.push(String::from("Malformed `dao mint` subcommand"));
1692        output.push(String::from("Usage: dao mint <name>"));
1693        return
1694    }
1695
1696    match drk.read().await.dao_mint(parts[2]).await {
1697        Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1698        Err(e) => output.push(format!("Failed to mint DAO: {e}")),
1699    }
1700}
1701
1702/// Auxiliary function to define the dao propose transfer subcommand handling.
1703async fn handle_dao_propose_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1704    // Check correct subcommand structure
1705    if parts.len() < 7 || parts.len() > 9 {
1706        output.push(String::from("Malformed `dao proposal-transfer` subcommand"));
1707        output.push(String::from("Usage: dao proposal-transfer <name> <duration> <amount> <token> <recipient> [spend-hook] [user-data]"));
1708        return
1709    }
1710
1711    let duration = match u64::from_str(parts[3]) {
1712        Ok(d) => d,
1713        Err(e) => {
1714            output.push(format!("Invalid duration: {e}"));
1715            return
1716        }
1717    };
1718
1719    let amount = String::from(parts[4]);
1720    if let Err(e) = f64::from_str(&amount) {
1721        output.push(format!("Invalid amount: {e}"));
1722        return
1723    }
1724
1725    let lock = drk.read().await;
1726    let token_id = match lock.get_token(String::from(parts[5])).await {
1727        Ok(t) => t,
1728        Err(e) => {
1729            output.push(format!("Invalid token ID: {e}"));
1730            return
1731        }
1732    };
1733
1734    let rcpt = match Address::from_str(parts[6]) {
1735        Ok(r) => r,
1736        Err(e) => {
1737            output.push(format!("Invalid recipient: {e}"));
1738            return
1739        }
1740    };
1741
1742    if rcpt.network() != lock.network {
1743        output.push("Recipient address prefix mismatch".to_string());
1744        return
1745    }
1746
1747    let mut index = 7;
1748    let spend_hook = if index < parts.len() {
1749        match FuncId::from_str(parts[index]) {
1750            Ok(s) => Some(s),
1751            Err(e) => {
1752                output.push(format!("Invalid spend hook: {e}"));
1753                return
1754            }
1755        }
1756    } else {
1757        None
1758    };
1759    index += 1;
1760
1761    let user_data = if index < parts.len() {
1762        let bytes = match bs58::decode(&parts[index]).into_vec() {
1763            Ok(b) => b,
1764            Err(e) => {
1765                output.push(format!("Invalid user data: {e}"));
1766                return
1767            }
1768        };
1769
1770        let bytes: [u8; 32] = match bytes.try_into() {
1771            Ok(b) => b,
1772            Err(e) => {
1773                output.push(format!("Invalid user data: {e:?}"));
1774                return
1775            }
1776        };
1777
1778        let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1779            Some(v) => v,
1780            None => {
1781                output.push(String::from("Invalid user data"));
1782                return
1783            }
1784        };
1785
1786        Some(elem)
1787    } else {
1788        None
1789    };
1790
1791    match drk
1792        .read()
1793        .await
1794        .dao_propose_transfer(
1795            parts[2],
1796            duration,
1797            &amount,
1798            token_id,
1799            *rcpt.public_key(),
1800            spend_hook,
1801            user_data,
1802        )
1803        .await
1804    {
1805        Ok(proposal) => output.push(format!("Generated proposal: {}", proposal.bulla())),
1806        Err(e) => output.push(format!("Failed to create DAO transfer proposal: {e}")),
1807    }
1808}
1809
1810/// Auxiliary function to define the dao propose generic subcommand handling.
1811async fn handle_dao_propose_generic(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1812    // Check correct subcommand structure
1813    if parts.len() != 4 && parts.len() != 5 {
1814        output.push(String::from("Malformed `dao proposal-generic` subcommand"));
1815        output.push(String::from("Usage: dao proposal-generic <name> <duration> [user-data]"));
1816        return
1817    }
1818
1819    let duration = match u64::from_str(parts[3]) {
1820        Ok(d) => d,
1821        Err(e) => {
1822            output.push(format!("Invalid duration: {e}"));
1823            return
1824        }
1825    };
1826
1827    let user_data = if parts.len() == 5 {
1828        let bytes = match bs58::decode(&parts[4]).into_vec() {
1829            Ok(b) => b,
1830            Err(e) => {
1831                output.push(format!("Invalid user data: {e}"));
1832                return
1833            }
1834        };
1835
1836        let bytes: [u8; 32] = match bytes.try_into() {
1837            Ok(b) => b,
1838            Err(e) => {
1839                output.push(format!("Invalid user data: {e:?}"));
1840                return
1841            }
1842        };
1843
1844        let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
1845            Some(v) => v,
1846            None => {
1847                output.push(String::from("Invalid user data"));
1848                return
1849            }
1850        };
1851
1852        Some(elem)
1853    } else {
1854        None
1855    };
1856
1857    match drk.read().await.dao_propose_generic(parts[2], duration, user_data).await {
1858        Ok(proposal) => output.push(format!("Generated proposal: {}", proposal.bulla())),
1859        Err(e) => output.push(format!("Failed to create DAO generic proposal: {e}")),
1860    }
1861}
1862
1863/// Auxiliary function to define the dao proposals subcommand handling.
1864async fn handle_dao_proposals(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1865    // Check correct subcommand structure
1866    if parts.len() != 3 {
1867        output.push(String::from("Malformed `dao proposals` subcommand"));
1868        output.push(String::from("Usage: dao proposals <name>"));
1869        return
1870    }
1871
1872    match drk.read().await.get_dao_proposals(parts[2]).await {
1873        Ok(proposals) => {
1874            for (i, proposal) in proposals.iter().enumerate() {
1875                output.push(format!("{i}. {}", proposal.bulla()));
1876            }
1877        }
1878        Err(e) => output.push(format!("Failed to retrieve DAO proposals: {e}")),
1879    }
1880}
1881
1882/// Auxiliary function to define the dao proposal subcommand handling.
1883async fn handle_dao_proposal(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
1884    // Check correct subcommand structure
1885    if parts.len() != 3 && parts.len() != 4 {
1886        output.push(String::from("Malformed `dao proposal` subcommand"));
1887        output.push(String::from("Usage: dao proposal [--(export|mint-proposal)] <bulla>"));
1888        return
1889    }
1890
1891    let mut index = 2;
1892    let (export, mint_proposal) = if parts.len() == 4 {
1893        let flags = match parts[index] {
1894            "--export" => (true, false),
1895            "--mint-proposal" => (false, true),
1896            _ => {
1897                output.push(String::from("Malformed `dao proposal` subcommand"));
1898                output.push(String::from("Usage: dao proposal [--(export|mint-proposal)] <bulla>"));
1899                return
1900            }
1901        };
1902        index += 1;
1903        flags
1904    } else {
1905        (false, false)
1906    };
1907
1908    let bulla = match DaoProposalBulla::from_str(parts[index]) {
1909        Ok(b) => b,
1910        Err(e) => {
1911            output.push(format!("Invalid proposal bulla: {e}"));
1912            return
1913        }
1914    };
1915
1916    let lock = drk.read().await;
1917    let proposal = match lock.get_dao_proposal_by_bulla(&bulla).await {
1918        Ok(p) => p,
1919        Err(e) => {
1920            output.push(format!("Failed to fetch DAO proposal: {e}"));
1921            return
1922        }
1923    };
1924
1925    if export {
1926        // Retrieve the DAO
1927        let dao = match lock.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
1928            Ok(d) => d,
1929            Err(e) => {
1930                output.push(format!("Failed to fetch DAO: {e}"));
1931                return
1932            }
1933        };
1934
1935        // Encypt the proposal
1936        let enc_note =
1937            AeadEncryptedNote::encrypt(&proposal, &dao.params.dao.proposals_public_key, &mut OsRng)
1938                .unwrap();
1939
1940        // Export it to base64
1941        output.push(base64::encode(&serialize_async(&enc_note).await));
1942        return
1943    }
1944
1945    if mint_proposal {
1946        // Identify proposal type by its auth calls
1947        for call in &proposal.proposal.auth_calls {
1948            // We only support transfer right now
1949            if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
1950                match lock.dao_transfer_proposal_tx(&proposal).await {
1951                    Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1952                    Err(e) => output.push(format!("Failed to create DAO transfer proposal: {e}")),
1953                }
1954                return
1955            }
1956        }
1957
1958        // If proposal has no auth calls, we consider it a generic one
1959        if proposal.proposal.auth_calls.is_empty() {
1960            match lock.dao_generic_proposal_tx(&proposal).await {
1961                Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
1962                Err(e) => output.push(format!("Failed to create DAO generic proposal: {e}")),
1963            }
1964            return
1965        }
1966
1967        output.push(String::from("Unsuported DAO proposal"));
1968        return
1969    }
1970
1971    output.push(format!("{proposal}"));
1972
1973    let mut contract_calls = "\nInvoked contracts:\n".to_string();
1974    for call in proposal.proposal.auth_calls {
1975        contract_calls.push_str(&format!(
1976            "\tContract: {}\n\tFunction: {}\n\tData: ",
1977            call.contract_id, call.function_code
1978        ));
1979
1980        if call.auth_data.is_empty() {
1981            contract_calls.push_str("-\n");
1982            continue;
1983        }
1984
1985        if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
1986            // We know that the plaintext data live in the data plaintext vec
1987            if proposal.data.is_none() {
1988                contract_calls.push_str("-\n");
1989                continue;
1990            }
1991            let coin: CoinAttributes = match deserialize_async(proposal.data.as_ref().unwrap())
1992                .await
1993            {
1994                Ok(c) => c,
1995                Err(e) => {
1996                    output.push(format!("Failed to deserialize transfer proposal coin data: {e}"));
1997                    return
1998                }
1999            };
2000            let recipient: Address =
2001                StandardAddress::from_public(lock.network, coin.public_key).into();
2002            let spend_hook = if coin.spend_hook == FuncId::none() {
2003                "-".to_string()
2004            } else {
2005                format!("{}", coin.spend_hook)
2006            };
2007
2008            let user_data = if coin.user_data == pallas::Base::ZERO {
2009                "-".to_string()
2010            } else {
2011                format!("{:?}", coin.user_data)
2012            };
2013
2014            contract_calls.push_str(&format!(
2015                "\n\t\t{}: {}\n\t\t{}: {} ({})\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\n",
2016                "Recipient",
2017                recipient,
2018                "Amount",
2019                coin.value,
2020                encode_base10(coin.value, BALANCE_BASE10_DECIMALS),
2021                "Token",
2022                coin.token_id,
2023                "Spend hook",
2024                spend_hook,
2025                "User data",
2026                user_data,
2027                "Blind",
2028                coin.blind
2029            ));
2030        }
2031    }
2032
2033    output.push(contract_calls);
2034
2035    let votes = match lock.get_dao_proposal_votes(&bulla).await {
2036        Ok(v) => v,
2037        Err(e) => {
2038            output.push(format!("Failed to fetch DAO proposal votes: {e}"));
2039            return
2040        }
2041    };
2042    let mut total_yes_vote_value = 0;
2043    let mut total_no_vote_value = 0;
2044    let mut total_all_vote_value = 0;
2045    let mut table = Table::new();
2046    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2047    table.set_titles(row!["Transaction", "Tokens", "Vote"]);
2048    for vote in votes {
2049        let vote_option = if vote.vote_option {
2050            total_yes_vote_value += vote.all_vote_value;
2051            "Yes"
2052        } else {
2053            total_no_vote_value += vote.all_vote_value;
2054            "No"
2055        };
2056        total_all_vote_value += vote.all_vote_value;
2057
2058        table.add_row(row![
2059            vote.tx_hash,
2060            encode_base10(vote.all_vote_value, BALANCE_BASE10_DECIMALS),
2061            vote_option
2062        ]);
2063    }
2064
2065    let outcome = if table.is_empty() {
2066        output.push(String::from("Votes: No votes found"));
2067        "Unknown"
2068    } else {
2069        output.push(String::from("Votes:"));
2070        output.push(format!("{table}"));
2071        output.push(format!(
2072            "Total tokens votes: {}",
2073            encode_base10(total_all_vote_value, BALANCE_BASE10_DECIMALS)
2074        ));
2075        let approval_ratio = (total_yes_vote_value as f64 * 100.0) / total_all_vote_value as f64;
2076        output.push(format!(
2077            "Total tokens Yes votes: {} ({approval_ratio:.2}%)",
2078            encode_base10(total_yes_vote_value, BALANCE_BASE10_DECIMALS)
2079        ));
2080        output.push(format!(
2081            "Total tokens No votes: {} ({:.2}%)",
2082            encode_base10(total_no_vote_value, BALANCE_BASE10_DECIMALS),
2083            (total_no_vote_value as f64 * 100.0) / total_all_vote_value as f64
2084        ));
2085
2086        let dao = match lock.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
2087            Ok(d) => d,
2088            Err(e) => {
2089                output.push(format!("Failed to fetch DAO: {e}"));
2090                return
2091            }
2092        };
2093        if total_all_vote_value >= dao.params.dao.quorum &&
2094            approval_ratio >=
2095                (dao.params.dao.approval_ratio_quot / dao.params.dao.approval_ratio_base)
2096                    as f64
2097        {
2098            "Approved"
2099        } else {
2100            "Rejected"
2101        }
2102    };
2103
2104    if let Some(exec_tx_hash) = proposal.exec_tx_hash {
2105        output.push(format!("Proposal was executed on transaction: {exec_tx_hash}"));
2106        return
2107    }
2108
2109    // Retrieve next block height and current block time target,
2110    // to compute their window.
2111    let next_block_height = match lock.get_next_block_height().await {
2112        Ok(n) => n,
2113        Err(e) => {
2114            output.push(format!("Failed to fetch next block height: {e}"));
2115            return
2116        }
2117    };
2118    let block_target = match lock.get_block_target().await {
2119        Ok(b) => b,
2120        Err(e) => {
2121            output.push(format!("Failed to fetch block target: {e}"));
2122            return
2123        }
2124    };
2125    let current_window = blockwindow(next_block_height, block_target);
2126    let end_time = proposal.proposal.creation_blockwindow + proposal.proposal.duration_blockwindows;
2127    let (voting_status, proposal_status_message) = if current_window < end_time {
2128        ("Ongoing", format!("Current proposal outcome: {outcome}"))
2129    } else {
2130        ("Concluded", format!("Proposal outcome: {outcome}"))
2131    };
2132    output.push(format!("Voting status: {voting_status}"));
2133    output.push(proposal_status_message);
2134}
2135
2136/// Auxiliary function to define the dao proposal import subcommand handling.
2137async fn handle_dao_proposal_import(
2138    drk: &DrkPtr,
2139    parts: &[&str],
2140    input: &[String],
2141    output: &mut Vec<String>,
2142) {
2143    // Check correct subcommand structure
2144    if parts.len() != 2 {
2145        output.push(String::from("Malformed `dao proposal-import` subcommand"));
2146        output.push(String::from("Usage: dao proposal-import"));
2147        return
2148    }
2149
2150    // Parse line from input or fallback to stdin if its empty
2151    let buf = match input.len() {
2152        0 => {
2153            let mut buf = String::new();
2154            if let Err(e) = stdin().read_to_string(&mut buf) {
2155                output.push(format!("Failed to read from stdin: {e}"));
2156                return
2157            };
2158            buf
2159        }
2160        1 => input[0].clone(),
2161        _ => {
2162            output.push(String::from("Multiline input provided"));
2163            return
2164        }
2165    };
2166
2167    let Some(bytes) = base64::decode(buf.trim()) else {
2168        output.push(String::from("Failed to decode encrypted proposal data"));
2169        return
2170    };
2171
2172    let encrypted_proposal: AeadEncryptedNote = match deserialize_async(&bytes).await {
2173        Ok(e) => e,
2174        Err(e) => {
2175            output.push(format!("Failed to deserialize encrypted proposal data: {e}"));
2176            return
2177        }
2178    };
2179
2180    let lock = drk.read().await;
2181    let daos = match lock.get_daos().await {
2182        Ok(d) => d,
2183        Err(e) => {
2184            output.push(format!("Failed to retrieve DAOs: {e}"));
2185            return
2186        }
2187    };
2188
2189    for dao in &daos {
2190        // Check if we have the proposals key
2191        let Some(proposals_secret_key) = dao.params.proposals_secret_key else { continue };
2192
2193        // Try to decrypt the proposal
2194        let Ok(proposal) = encrypted_proposal.decrypt::<ProposalRecord>(&proposals_secret_key)
2195        else {
2196            continue
2197        };
2198
2199        let proposal = match lock.get_dao_proposal_by_bulla(&proposal.bulla()).await {
2200            Ok(p) => {
2201                let mut our_proposal = p;
2202                our_proposal.data = proposal.data;
2203                our_proposal
2204            }
2205            Err(_) => proposal,
2206        };
2207
2208        if let Err(e) = lock.put_dao_proposal(&proposal).await {
2209            output.push(format!("Failed to put DAO proposal: {e}"));
2210        }
2211        return
2212    }
2213
2214    output.push(String::from("Couldn't decrypt the proposal with out DAO keys"));
2215}
2216
2217/// Auxiliary function to define the dao vote subcommand handling.
2218async fn handle_dao_vote(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2219    // Check correct subcommand structure
2220    if parts.len() != 4 && parts.len() != 5 {
2221        output.push(String::from("Malformed `dao vote` subcommand"));
2222        output.push(String::from("Usage: dao vote <bulla> <vote> [vote-weight]"));
2223        return
2224    }
2225
2226    let bulla = match DaoProposalBulla::from_str(parts[2]) {
2227        Ok(b) => b,
2228        Err(e) => {
2229            output.push(format!("Invalid proposal bulla: {e}"));
2230            return
2231        }
2232    };
2233
2234    let vote = match u8::from_str(parts[3]) {
2235        Ok(v) => {
2236            if v > 1 {
2237                output.push(String::from("Vote can be either 0 (NO) or 1 (YES)"));
2238                return
2239            }
2240            v != 0
2241        }
2242        Err(e) => {
2243            output.push(format!("Invalid vote: {e}"));
2244            return
2245        }
2246    };
2247
2248    let weight = if parts.len() == 5 {
2249        if let Err(e) = f64::from_str(parts[4]) {
2250            output.push(format!("Invalid vote weight: {e}"));
2251            return
2252        }
2253        match decode_base10(parts[4], BALANCE_BASE10_DECIMALS, true) {
2254            Ok(w) => Some(w),
2255            Err(e) => {
2256                output.push(format!("Error while parsing vote weight: {e}"));
2257                return
2258            }
2259        }
2260    } else {
2261        None
2262    };
2263
2264    match drk.read().await.dao_vote(&bulla, vote, weight).await {
2265        Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
2266        Err(e) => output.push(format!("Failed to create DAO Vote transaction: {e}")),
2267    }
2268}
2269
2270/// Auxiliary function to define the dao exec subcommand handling.
2271async fn handle_dao_exec(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2272    // Check correct subcommand structure
2273    if parts.len() != 3 && parts.len() != 4 {
2274        output.push(String::from("Malformed `dao exec` subcommand"));
2275        output.push(String::from("Usage: dao exec [--early] <bulla>"));
2276        return
2277    }
2278
2279    let mut index = 2;
2280    let mut early = false;
2281    if parts[index] == "--early" {
2282        early = true;
2283        index += 1;
2284    }
2285
2286    let bulla = match DaoProposalBulla::from_str(parts[index]) {
2287        Ok(b) => b,
2288        Err(e) => {
2289            output.push(format!("Invalid proposal bulla: {e}"));
2290            return
2291        }
2292    };
2293
2294    let lock = drk.read().await;
2295    let proposal = match lock.get_dao_proposal_by_bulla(&bulla).await {
2296        Ok(p) => p,
2297        Err(e) => {
2298            output.push(format!("Failed to fetch DAO proposal: {e}"));
2299            return
2300        }
2301    };
2302
2303    // Identify proposal type by its auth calls
2304    for call in &proposal.proposal.auth_calls {
2305        // We only support transfer right now
2306        if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
2307            match lock.dao_exec_transfer(&proposal, early).await {
2308                Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
2309                Err(e) => output.push(format!("Failed to execute DAO transfer proposal: {e}")),
2310            };
2311            return
2312        }
2313    }
2314
2315    // If proposal has no auth calls, we consider it a generic one
2316    if proposal.proposal.auth_calls.is_empty() {
2317        match lock.dao_exec_generic(&proposal, early).await {
2318            Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)),
2319            Err(e) => output.push(format!("Failed to execute DAO generic proposal: {e}")),
2320        };
2321        return
2322    }
2323
2324    output.push(String::from("Unsuported DAO proposal"));
2325}
2326
2327/// Auxiliary function to define the dao spent hook subcommand handling.
2328async fn handle_dao_spend_hook(parts: &[&str], output: &mut Vec<String>) {
2329    // Check correct subcommand structure
2330    if parts.len() != 2 {
2331        output.push(String::from("Malformed `dao spent-hook` subcommand"));
2332        output.push(String::from("Usage: dao spent-hook"));
2333        return
2334    }
2335
2336    let spend_hook =
2337        FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }.to_func_id();
2338    output.push(format!("{spend_hook}"));
2339}
2340
2341/// Auxiliary function to define the dao mining config subcommand handling.
2342async fn handle_dao_mining_config(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2343    // Check correct subcommand structure
2344    if parts.len() != 3 {
2345        output.push(String::from("Malformed `dao mining-config` subcommand"));
2346        output.push(String::from("Usage: dao mining-config <name>"));
2347        return
2348    }
2349
2350    if let Err(e) = drk.read().await.dao_mining_config(parts[2], output).await {
2351        output.push(format!("Failed to generate DAO mining configuration: {e}"));
2352    }
2353}
2354
2355/// Auxiliary function to define the attach fee command handling.
2356async fn handle_attach_fee(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
2357    let mut tx = match parse_tx_from_input(input).await {
2358        Ok(t) => t,
2359        Err(e) => {
2360            output.push(format!("Error while parsing transaction: {e}"));
2361            return
2362        }
2363    };
2364
2365    match drk.read().await.attach_fee(&mut tx).await {
2366        Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
2367        Err(e) => output.push(format!("Failed to attach the fee call to the transaction: {e}")),
2368    }
2369}
2370
2371/// Auxiliary function to define the tx from calls command handling.
2372async fn handle_tx_from_calls(
2373    drk: &DrkPtr,
2374    parts: &[&str],
2375    input: &[String],
2376    output: &mut Vec<String>,
2377) {
2378    // Check correct subcommand structure
2379    if parts.len() != 1 && parts.len() != 2 {
2380        output.push(String::from("Malformed `tx-from-calls` subcommand"));
2381        output.push(String::from("Usage: tx-from-calls [calls-map]"));
2382        return
2383    }
2384
2385    // Parse calls
2386    let calls = match parse_calls_from_input(input).await {
2387        Ok(c) => c,
2388        Err(e) => {
2389            output.push(format!("Error while parsing transaction calls: {e}"));
2390            return
2391        }
2392    };
2393    if calls.is_empty() {
2394        output.push(String::from("No calls were parsed"));
2395        return
2396    }
2397
2398    // If there is a given map, parse it, otherwise construct a
2399    // linear map.
2400    let calls_map = if parts.len() == 2 {
2401        match parse_tree(parts[1]) {
2402            Ok(m) => m,
2403            Err(e) => {
2404                output.push(format!("Failed parsing calls map: {e}"));
2405                return
2406            }
2407        }
2408    } else {
2409        let mut calls_map = Vec::with_capacity(calls.len());
2410        for (i, _) in calls.iter().enumerate() {
2411            calls_map.push((i, vec![]));
2412        }
2413        calls_map
2414    };
2415    if calls_map.len() != calls.len() {
2416        output.push(String::from("Calls map size not equal to parsed calls"));
2417        return
2418    }
2419
2420    // Create a transaction from the mapped calls
2421    let (mut tx_builder, signature_secrets) = match tx_from_calls_mapped(&calls, &calls_map) {
2422        Ok(pair) => pair,
2423        Err(e) => {
2424            output.push(format!("Failed to create a transaction from the mapped calls: {e}"));
2425            return
2426        }
2427    };
2428
2429    // Now build and sign the fee-less tx
2430    let mut tx = match tx_builder.build() {
2431        Ok(tx) => tx,
2432        Err(e) => {
2433            output.push(format!("Failed to build the transaction: {e}"));
2434            return
2435        }
2436    };
2437    let sigs = match tx.create_sigs(&signature_secrets) {
2438        Ok(s) => s,
2439        Err(e) => {
2440            output.push(format!("Failed to create the transaction signatures: {e}"));
2441            return
2442        }
2443    };
2444    tx.signatures.push(sigs);
2445
2446    // Attach its fee and grab its signature
2447    if let Err(e) = drk.read().await.attach_fee(&mut tx).await {
2448        output.push(format!("Failed to attach the fee call to the transaction: {e}"));
2449        return
2450    }
2451    // Its safe to unwrap here since we know the fee signature
2452    // is in the last position.
2453    let fee_signature = tx.signatures.last().unwrap().clone();
2454
2455    // Re-sign the tx using the calls secrets
2456    let sigs = match tx.create_sigs(&signature_secrets) {
2457        Ok(s) => s,
2458        Err(e) => {
2459            output.push(format!("Failed to create the transaction signatures: {e}"));
2460            return
2461        }
2462    };
2463    tx.signatures = vec![sigs, fee_signature];
2464
2465    output.push(base64::encode(&serialize_async(&tx).await));
2466}
2467
2468/// Auxiliary function to define the inspect command handling.
2469async fn handle_inspect(input: &[String], output: &mut Vec<String>) {
2470    match parse_tx_from_input(input).await {
2471        Ok(tx) => output.push(format!("{tx:#?}")),
2472        Err(e) => output.push(format!("Error while parsing transaction: {e}")),
2473    }
2474}
2475
2476/// Auxiliary function to define the broadcast command handling.
2477async fn handle_broadcast(drk: &DrkPtr, input: &[String], output: &mut Vec<String>) {
2478    let tx = match parse_tx_from_input(input).await {
2479        Ok(t) => t,
2480        Err(e) => {
2481            output.push(format!("Error while parsing transaction: {e}"));
2482            return
2483        }
2484    };
2485
2486    let lock = drk.read().await;
2487    if let Err(e) = lock.simulate_tx(&tx).await {
2488        output.push(format!("Failed to simulate tx: {e}"));
2489        return
2490    };
2491
2492    if let Err(e) = lock.mark_tx_spend(&tx, output).await {
2493        output.push(format!("Failed to mark transaction coins as spent: {e}"));
2494        return
2495    };
2496
2497    match lock.broadcast_tx(&tx, output).await {
2498        Ok(txid) => output.push(format!("Transaction ID: {txid}")),
2499        Err(e) => output.push(format!("Failed to broadcast transaction: {e}")),
2500    }
2501}
2502
2503/// Auxiliary function to define the subscribe command handling.
2504async fn handle_subscribe(
2505    drk: &DrkPtr,
2506    endpoint: &Url,
2507    subscription_active: &mut bool,
2508    subscription_tasks: &[StoppableTaskPtr; 2],
2509    shell_sender: &Sender<Vec<String>>,
2510    ex: &ExecutorPtr,
2511) {
2512    // Kill zombie tasks if they failed
2513    subscription_tasks[0].stop_nowait();
2514    subscription_tasks[1].stop_nowait();
2515    *subscription_active = true;
2516
2517    // Start the subscription task
2518    let drk_ = drk.clone();
2519    let rpc_task_ = subscription_tasks[1].clone();
2520    let shell_sender_ = shell_sender.clone();
2521    let endpoint_ = endpoint.clone();
2522    let ex_ = ex.clone();
2523    subscription_tasks[0].clone().start(
2524        async move { subscribe_blocks(&drk_, rpc_task_, shell_sender_, endpoint_, &ex_).await },
2525        |_| async { /* Do nothing */ },
2526        Error::DetachedTaskStopped,
2527        ex.clone(),
2528    );
2529}
2530
2531/// Auxiliary function to define the unsubscribe command handling.
2532async fn handle_unsubscribe(
2533    subscription_active: &mut bool,
2534    subscription_tasks: &[StoppableTaskPtr; 2],
2535) {
2536    subscription_tasks[0].stop_nowait();
2537    subscription_tasks[1].stop_nowait();
2538    *subscription_active = false;
2539}
2540
2541/// Auxiliary function to define the scan command handling.
2542async fn handle_scan(
2543    drk: &DrkPtr,
2544    subscription_active: &bool,
2545    parts: &[&str],
2546    output: &mut Vec<String>,
2547    print: &bool,
2548) {
2549    if *subscription_active {
2550        append_or_print(output, None, print, vec![String::from("Subscription is already active!")])
2551            .await;
2552        return
2553    }
2554
2555    // Check correct command structure
2556    if parts.len() != 1 && parts.len() != 3 {
2557        append_or_print(output, None, print, vec![String::from("Malformed `scan` command")]).await;
2558        return
2559    }
2560
2561    // Check if reset was requested
2562    let lock = drk.read().await;
2563    if parts.len() == 3 {
2564        if parts[1] != "--reset" {
2565            append_or_print(
2566                output,
2567                None,
2568                print,
2569                vec![
2570                    String::from("Malformed `scan` command"),
2571                    String::from("Usage: scan --reset <height>"),
2572                ],
2573            )
2574            .await;
2575            return
2576        }
2577
2578        let height = match u32::from_str(parts[2]) {
2579            Ok(h) => h,
2580            Err(e) => {
2581                append_or_print(output, None, print, vec![format!("Invalid reset height: {e}")])
2582                    .await;
2583                return
2584            }
2585        };
2586
2587        let mut buf = vec![];
2588        if let Err(e) = lock.reset_to_height(height, &mut buf).await {
2589            buf.push(format!("Failed during wallet reset: {e}"));
2590            append_or_print(output, None, print, buf).await;
2591            return
2592        }
2593        append_or_print(output, None, print, buf).await;
2594    }
2595
2596    if let Err(e) = lock.scan_blocks(output, None, print).await {
2597        append_or_print(output, None, print, vec![format!("Failed during scanning: {e}")]).await;
2598        return
2599    }
2600    append_or_print(output, None, print, vec![String::from("Finished scanning blockchain")]).await;
2601}
2602
2603/// Auxiliary function to define the explorer command handling.
2604async fn handle_explorer(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec<String>) {
2605    // Check correct command structure
2606    if parts.len() < 2 {
2607        output.push(String::from("Malformed `explorer` command"));
2608        output.push(String::from(
2609            "Usage: explorer (fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks|mining-config)",
2610        ));
2611        return
2612    }
2613
2614    // Handle subcommand
2615    match parts[1] {
2616        "fetch-tx" => handle_explorer_fetch_tx(drk, parts, output).await,
2617        "simulate-tx" => handle_explorer_simulate_tx(drk, parts, input, output).await,
2618        "txs-history" => handle_explorer_txs_history(drk, parts, output).await,
2619        "clear-reverted" => handle_explorer_clear_reverted(drk, parts, output).await,
2620        "scanned-blocks" => handle_explorer_scanned_blocks(drk, parts, output).await,
2621        "mining-config" => handle_explorer_mining_config(parts, input, output).await,
2622        _ => {
2623            output.push(format!("Unrecognized explorer subcommand: {}", parts[1]));
2624            output.push(String::from(
2625                "Usage: explorer (fetch-tx|simulate-tx|txs-history|clear-reverted|scanned-blocks|mining-config)",
2626            ));
2627        }
2628    }
2629}
2630
2631/// Auxiliary function to define the explorer fetch transaction subcommand handling.
2632async fn handle_explorer_fetch_tx(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2633    // Check correct subcommand structure
2634    if parts.len() != 3 && parts.len() != 4 {
2635        output.push(String::from("Malformed `explorer fetch-tx` subcommand"));
2636        output.push(String::from("Usage: explorer fetch-tx [--encode] <tx-hash>"));
2637        return
2638    }
2639
2640    let mut index = 2;
2641    let mut encode = false;
2642    if parts[index] == "--encode" {
2643        encode = true;
2644        index += 1;
2645    }
2646
2647    let hash = match blake3::Hash::from_hex(parts[index]) {
2648        Ok(h) => h,
2649        Err(e) => {
2650            output.push(format!("Invalid transaction hash: {e}"));
2651            return
2652        }
2653    };
2654    let tx_hash = TransactionHash(*hash.as_bytes());
2655
2656    let tx = match drk.read().await.get_tx(&tx_hash).await {
2657        Ok(tx) => tx,
2658        Err(e) => {
2659            output.push(format!("Failed to fetch transaction: {e}"));
2660            return
2661        }
2662    };
2663
2664    let Some(tx) = tx else {
2665        output.push(String::from("Transaction was not found"));
2666        return
2667    };
2668
2669    // Make sure the tx is correct
2670    if tx.hash() != tx_hash {
2671        output.push(format!("Transaction hash missmatch: {tx_hash} - {}", tx.hash()));
2672        return
2673    }
2674
2675    if encode {
2676        output.push(base64::encode(&serialize_async(&tx).await));
2677        return
2678    }
2679
2680    output.push(format!("Transaction ID: {tx_hash}"));
2681    output.push(format!("{tx:?}"));
2682}
2683
2684/// Auxiliary function to define the explorer simulate transaction subcommand handling.
2685async fn handle_explorer_simulate_tx(
2686    drk: &DrkPtr,
2687    parts: &[&str],
2688    input: &[String],
2689    output: &mut Vec<String>,
2690) {
2691    // Check correct subcommand structure
2692    if parts.len() != 2 {
2693        output.push(String::from("Malformed `explorer simulate-tx` subcommand"));
2694        output.push(String::from("Usage: explorer simulate-tx"));
2695        return
2696    }
2697
2698    let tx = match parse_tx_from_input(input).await {
2699        Ok(t) => t,
2700        Err(e) => {
2701            output.push(format!("Error while parsing transaction: {e}"));
2702            return
2703        }
2704    };
2705
2706    match drk.read().await.simulate_tx(&tx).await {
2707        Ok(is_valid) => {
2708            output.push(format!("Transaction ID: {}", tx.hash()));
2709            output.push(format!("State: {}", if is_valid { "valid" } else { "invalid" }));
2710        }
2711        Err(e) => output.push(format!("Failed to simulate tx: {e}")),
2712    }
2713}
2714
2715/// Auxiliary function to define the explorer transactions history subcommand handling.
2716async fn handle_explorer_txs_history(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2717    // Check correct command structure
2718    if parts.len() < 2 || parts.len() > 4 {
2719        output.push(String::from("Malformed `explorer txs-history` command"));
2720        output.push(String::from("Usage: explorer txs-history [--encode] [tx-hash]"));
2721        return
2722    }
2723
2724    let lock = drk.read().await;
2725    if parts.len() > 2 {
2726        let mut index = 2;
2727        let mut encode = false;
2728        if parts[index] == "--encode" {
2729            encode = true;
2730            index += 1;
2731        }
2732
2733        let (tx_hash, status, block_height, tx) =
2734            match lock.get_tx_history_record(parts[index]).await {
2735                Ok(i) => i,
2736                Err(e) => {
2737                    output.push(format!("Failed to fetch transaction: {e}"));
2738                    return
2739                }
2740            };
2741
2742        if encode {
2743            output.push(base64::encode(&serialize_async(&tx).await));
2744            return
2745        }
2746
2747        output.push(format!("Transaction ID: {tx_hash}"));
2748        output.push(format!("Status: {status}"));
2749        match block_height {
2750            Some(block_height) => output.push(format!("Block height: {block_height}")),
2751            None => output.push(String::from("Block height: -")),
2752        }
2753        output.push(format!("{tx:?}"));
2754        return
2755    }
2756
2757    let map = match lock.get_txs_history() {
2758        Ok(m) => m,
2759        Err(e) => {
2760            output.push(format!("Failed to retrieve transactions history records: {e}"));
2761            return
2762        }
2763    };
2764
2765    // Create a prettytable with the new data:
2766    let mut table = Table::new();
2767    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
2768    table.set_titles(row!["Transaction Hash", "Status", "Block Height"]);
2769    for (txs_hash, status, block_height) in map.iter() {
2770        let block_height = match block_height {
2771            Some(block_height) => block_height.to_string(),
2772            None => String::from("-"),
2773        };
2774        table.add_row(row![txs_hash, status, block_height]);
2775    }
2776
2777    if table.is_empty() {
2778        output.push(String::from("No transactions found"));
2779    } else {
2780        output.push(format!("{table}"));
2781    }
2782}
2783
2784/// Auxiliary function to define the explorer clear reverted subcommand handling.
2785async fn handle_explorer_clear_reverted(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2786    // Check correct subcommand structure
2787    if parts.len() != 2 {
2788        output.push(String::from("Malformed `explorer clear-reverted` subcommand"));
2789        output.push(String::from("Usage: explorer clear-reverted"));
2790        return
2791    }
2792
2793    if let Err(e) = drk.read().await.remove_reverted_txs(output) {
2794        output.push(format!("Failed to remove reverted transactions: {e}"));
2795    }
2796}
2797
2798/// Auxiliary function to define the explorer scanned blocks subcommand handling.
2799async fn handle_explorer_scanned_blocks(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2800    // Check correct subcommand structure
2801    if parts.len() != 2 && parts.len() != 3 {
2802        output.push(String::from("Malformed `explorer scanned-blocks` subcommand"));
2803        output.push(String::from("Usage: explorer scanned-blocks [height]"));
2804        return
2805    }
2806
2807    let lock = drk.read().await;
2808    if parts.len() == 3 {
2809        let height = match u32::from_str(parts[2]) {
2810            Ok(d) => d,
2811            Err(e) => {
2812                output.push(format!("Invalid height: {e}"));
2813                return
2814            }
2815        };
2816
2817        match lock.get_scanned_block(&height) {
2818            Ok((hash, signing_key)) => {
2819                output.push(format!("Height: {height}"));
2820                output.push(format!("Hash: {hash}"));
2821                output.push(format!("Signing key: {signing_key}"));
2822            }
2823            Err(e) => output.push(format!("Failed to retrieve scanned block record: {e}")),
2824        };
2825        return
2826    }
2827
2828    let map = match lock.get_scanned_block_records() {
2829        Ok(m) => m,
2830        Err(e) => {
2831            output.push(format!("Failed to retrieve scanned blocks records: {e}"));
2832            return
2833        }
2834    };
2835
2836    let table = prettytable_scanned_blocks(&map);
2837
2838    if table.is_empty() {
2839        output.push(String::from("No scanned blocks records found"));
2840    } else {
2841        output.push(format!("{table}"));
2842    }
2843}
2844
2845/// Auxiliary function to define the explorer mining config subcommand handling.
2846async fn handle_explorer_mining_config(parts: &[&str], input: &[String], output: &mut Vec<String>) {
2847    // Check correct subcommand structure
2848    if parts.len() != 2 {
2849        output.push(String::from("Malformed `explorer mining-config` subcommand"));
2850        output.push(String::from("Usage: explorer mining-config"));
2851        return
2852    }
2853
2854    let (config, recipient, spend_hook, user_data) =
2855        match parse_mining_config_from_input(input).await {
2856            Ok(c) => c,
2857            Err(e) => {
2858                output.push(format!("Error while parsing mining config: {e}"));
2859                return
2860            }
2861        };
2862
2863    display_mining_config(&config, &recipient, &spend_hook, &user_data, output)
2864}
2865
2866/// Auxiliary function to define the alias command handling.
2867async fn handle_alias(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2868    // Check correct command structure
2869    if parts.len() < 2 {
2870        output.push(String::from("Malformed `alias` command"));
2871        output.push(String::from("Usage: alias (add|show|remove)"));
2872        return
2873    }
2874
2875    // Handle subcommand
2876    match parts[1] {
2877        "add" => handle_alias_add(drk, parts, output).await,
2878        "show" => handle_alias_show(drk, parts, output).await,
2879        "remove" => handle_alias_remove(drk, parts, output).await,
2880        _ => {
2881            output.push(format!("Unrecognized alias subcommand: {}", parts[1]));
2882            output.push(String::from("Usage: alias (add|show|remove)"));
2883        }
2884    }
2885}
2886
2887/// Auxiliary function to define the alias add subcommand handling.
2888async fn handle_alias_add(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2889    // Check correct subcommand structure
2890    if parts.len() != 4 {
2891        output.push(String::from("Malformed `alias add` subcommand"));
2892        output.push(String::from("Usage: alias add <alias> <token>"));
2893        return
2894    }
2895
2896    if parts[2].len() > 5 {
2897        output.push(String::from("Error: Alias exceeds 5 characters"));
2898        return
2899    }
2900
2901    let token_id = match TokenId::from_str(parts[3]) {
2902        Ok(t) => t,
2903        Err(e) => {
2904            output.push(format!("Invalid Token ID: {e}"));
2905            return
2906        }
2907    };
2908
2909    if let Err(e) = drk.read().await.add_alias(String::from(parts[2]), token_id, output).await {
2910        output.push(format!("Failed to add alias: {e}"));
2911    }
2912}
2913
2914/// Auxiliary function to define the alias show subcommand handling.
2915async fn handle_alias_show(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2916    // Check correct command structure
2917    if parts.len() != 2 && parts.len() != 4 && parts.len() != 6 {
2918        output.push(String::from("Malformed `alias show` command"));
2919        output.push(String::from("Usage: alias show [-a, --alias <alias>] [-t, --token <token>]"));
2920        return
2921    }
2922
2923    let mut alias = None;
2924    let mut token_id = None;
2925    if parts.len() > 2 {
2926        let mut index = 2;
2927        if parts[index] == "-a" || parts[index] == "--alias" {
2928            alias = Some(String::from(parts[index + 1]));
2929            index += 2;
2930        }
2931
2932        if index < parts.len() && (parts[index] == "-t" || parts[index] == "--token") {
2933            match TokenId::from_str(parts[index + 1]) {
2934                Ok(t) => token_id = Some(t),
2935                Err(e) => {
2936                    output.push(format!("Invalid Token ID: {e}"));
2937                    return
2938                }
2939            };
2940            index += 2;
2941        }
2942
2943        // Check alias again in case it was after token
2944        if index < parts.len() && (parts[index] == "-a" || parts[index] == "--alias") {
2945            alias = Some(String::from(parts[index + 1]));
2946        }
2947    }
2948
2949    let map = match drk.read().await.get_aliases(alias, token_id).await {
2950        Ok(m) => m,
2951        Err(e) => {
2952            output.push(format!("Failed to fetch aliases map: {e}"));
2953            return
2954        }
2955    };
2956
2957    let table = prettytable_aliases(&map);
2958
2959    if table.is_empty() {
2960        output.push(String::from("No aliases found"));
2961    } else {
2962        output.push(format!("{table}"));
2963    }
2964}
2965
2966/// Auxiliary function to define the alias remove subcommand handling.
2967async fn handle_alias_remove(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2968    // Check correct subcommand structure
2969    if parts.len() != 3 {
2970        output.push(String::from("Malformed `alias remove` subcommand"));
2971        output.push(String::from("Usage: alias remove <alias>"));
2972        return
2973    }
2974
2975    if let Err(e) = drk.read().await.remove_alias(String::from(parts[2]), output).await {
2976        output.push(format!("Failed to remove alias: {e}"));
2977    }
2978}
2979
2980/// Auxiliary function to define the token command handling.
2981async fn handle_token(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
2982    // Check correct command structure
2983    if parts.len() < 2 {
2984        output.push(String::from("Malformed `token` command"));
2985        output.push(String::from("Usage: token (import|generate-mint|list|mint|freeze)"));
2986        return
2987    }
2988
2989    // Handle subcommand
2990    match parts[1] {
2991        "import" => handle_token_import(drk, parts, output).await,
2992        "generate-mint" => handle_token_generate_mint(drk, parts, output).await,
2993        "list" => handle_token_list(drk, parts, output).await,
2994        "mint" => handle_token_mint(drk, parts, output).await,
2995        "freeze" => handle_token_freeze(drk, parts, output).await,
2996        _ => {
2997            output.push(format!("Unrecognized token subcommand: {}", parts[1]));
2998            output.push(String::from("Usage: token (import|generate-mint|list|mint|freeze)"));
2999        }
3000    }
3001}
3002
3003/// Auxiliary function to define the token import subcommand handling.
3004async fn handle_token_import(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3005    // Check correct subcommand structure
3006    if parts.len() != 4 {
3007        output.push(String::from("Malformed `token import` subcommand"));
3008        output.push(String::from("Usage: token import <secret-key> <token-blind>"));
3009        return
3010    }
3011
3012    let mint_authority = match SecretKey::from_str(parts[2]) {
3013        Ok(ma) => ma,
3014        Err(e) => {
3015            output.push(format!("Invalid mint authority: {e}"));
3016            return
3017        }
3018    };
3019
3020    let token_blind = match BaseBlind::from_str(parts[3]) {
3021        Ok(tb) => tb,
3022        Err(e) => {
3023            output.push(format!("Invalid token blind: {e}"));
3024            return
3025        }
3026    };
3027
3028    match drk.read().await.import_mint_authority(mint_authority, token_blind).await {
3029        Ok(token_id) => {
3030            output.push(format!("Successfully imported mint authority for token ID: {token_id}"))
3031        }
3032        Err(e) => output.push(format!("Failed to import mint authority: {e}")),
3033    }
3034}
3035
3036/// Auxiliary function to define the token generate mint subcommand handling.
3037async fn handle_token_generate_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3038    // Check correct subcommand structure
3039    if parts.len() != 2 {
3040        output.push(String::from("Malformed `token generate-mint` subcommand"));
3041        output.push(String::from("Usage: token generate-mint"));
3042        return
3043    }
3044
3045    let mint_authority = SecretKey::random(&mut OsRng);
3046    let token_blind = BaseBlind::random(&mut OsRng);
3047    match drk.read().await.import_mint_authority(mint_authority, token_blind).await {
3048        Ok(token_id) => {
3049            output.push(format!("Successfully imported mint authority for token ID: {token_id}"))
3050        }
3051        Err(e) => output.push(format!("Failed to import mint authority: {e}")),
3052    }
3053}
3054
3055/// Auxiliary function to define the token list subcommand handling.
3056async fn handle_token_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3057    // Check correct subcommand structure
3058    if parts.len() != 2 {
3059        output.push(String::from("Malformed `token list` subcommand"));
3060        output.push(String::from("Usage: token list"));
3061        return
3062    }
3063
3064    let lock = drk.read().await;
3065    let tokens = match lock.get_mint_authorities().await {
3066        Ok(m) => m,
3067        Err(e) => {
3068            output.push(format!("Failed to fetch mint authorities: {e}"));
3069            return
3070        }
3071    };
3072
3073    let aliases_map = match lock.get_aliases_mapped_by_token().await {
3074        Ok(m) => m,
3075        Err(e) => {
3076            output.push(format!("Failed to fetch aliases map: {e}"));
3077            return
3078        }
3079    };
3080
3081    let table = prettytable_tokenlist(&tokens, &aliases_map);
3082
3083    if table.is_empty() {
3084        output.push(String::from("No tokens found"));
3085    } else {
3086        output.push(format!("{table}"));
3087    }
3088}
3089
3090/// Auxiliary function to define the token mint subcommand handling.
3091async fn handle_token_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3092    // Check correct command structure
3093    if parts.len() < 5 || parts.len() > 7 {
3094        output.push(String::from("Malformed `token mint` subcommand"));
3095        output.push(String::from(
3096            "Usage: token mint <token> <amount> <recipient> [spend-hook] [user-data]",
3097        ));
3098        return
3099    }
3100
3101    let amount = String::from(parts[3]);
3102    if let Err(e) = f64::from_str(&amount) {
3103        output.push(format!("Invalid amount: {e}"));
3104        return
3105    }
3106
3107    let rcpt = match Address::from_str(parts[4]) {
3108        Ok(r) => r,
3109        Err(e) => {
3110            output.push(format!("Invalid recipient: {e}"));
3111            return
3112        }
3113    };
3114
3115    let lock = drk.read().await;
3116
3117    if rcpt.network() != lock.network {
3118        output.push("Recipient address prefix mismatch".to_string());
3119        return
3120    }
3121
3122    let token_id = match lock.get_token(String::from(parts[2])).await {
3123        Ok(t) => t,
3124        Err(e) => {
3125            output.push(format!("Invalid token ID: {e}"));
3126            return
3127        }
3128    };
3129
3130    // Parse command
3131    let mut index = 5;
3132    let spend_hook = if index < parts.len() {
3133        match FuncId::from_str(parts[index]) {
3134            Ok(s) => Some(s),
3135            Err(e) => {
3136                output.push(format!("Invalid spend hook: {e}"));
3137                return
3138            }
3139        }
3140    } else {
3141        None
3142    };
3143    index += 1;
3144
3145    let user_data = if index < parts.len() {
3146        let bytes = match bs58::decode(&parts[index]).into_vec() {
3147            Ok(b) => b,
3148            Err(e) => {
3149                output.push(format!("Invalid user data: {e}"));
3150                return
3151            }
3152        };
3153
3154        let bytes: [u8; 32] = match bytes.try_into() {
3155            Ok(b) => b,
3156            Err(e) => {
3157                output.push(format!("Invalid user data: {e:?}"));
3158                return
3159            }
3160        };
3161
3162        let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
3163            Some(v) => v,
3164            None => {
3165                output.push(String::from("Invalid user data"));
3166                return
3167            }
3168        };
3169
3170        Some(elem)
3171    } else {
3172        None
3173    };
3174
3175    match lock.mint_token(&amount, *rcpt.public_key(), token_id, spend_hook, user_data).await {
3176        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3177        Err(e) => output.push(format!("Failed to create token mint transaction: {e}")),
3178    }
3179}
3180
3181/// Auxiliary function to define the token freeze subcommand handling.
3182async fn handle_token_freeze(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3183    // Check correct subcommand structure
3184    if parts.len() != 3 {
3185        output.push(String::from("Malformed `token freeze` subcommand"));
3186        output.push(String::from("Usage: token freeze <token>"));
3187        return
3188    }
3189
3190    let lock = drk.read().await;
3191    let token_id = match lock.get_token(String::from(parts[2])).await {
3192        Ok(t) => t,
3193        Err(e) => {
3194            output.push(format!("Invalid token ID: {e}"));
3195            return
3196        }
3197    };
3198
3199    match lock.freeze_token(token_id).await {
3200        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3201        Err(e) => output.push(format!("Failed to create token freeze transaction: {e}")),
3202    }
3203}
3204
3205/// Auxiliary function to define the contract command handling.
3206async fn handle_contract(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3207    // Check correct command structure
3208    if parts.len() < 2 {
3209        output.push(String::from("Malformed `contract` command"));
3210        output.push(String::from("Usage: contract (generate-deploy|list|export-data|deploy|lock)"));
3211        return
3212    }
3213
3214    // Handle subcommand
3215    match parts[1] {
3216        "generate-deploy" => handle_contract_generate_deploy(drk, parts, output).await,
3217        "list" => handle_contract_list(drk, parts, output).await,
3218        "export-data" => handle_contract_export_data(drk, parts, output).await,
3219        "deploy" => handle_contract_deploy(drk, parts, output).await,
3220        "lock" => handle_contract_lock(drk, parts, output).await,
3221        _ => {
3222            output.push(format!("Unrecognized contract subcommand: {}", parts[1]));
3223            output.push(String::from(
3224                "Usage: contract (generate-deploy|list|export-data|deploy|lock)",
3225            ));
3226        }
3227    }
3228}
3229
3230/// Auxiliary function to define the contract generate deploy subcommand handling.
3231async fn handle_contract_generate_deploy(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3232    // Check correct subcommand structure
3233    if parts.len() != 2 {
3234        output.push(String::from("Malformed `contract generate-deploy` subcommand"));
3235        output.push(String::from("Usage: contract generate-deploy"));
3236        return
3237    }
3238
3239    if let Err(e) = drk.read().await.deploy_auth_keygen(output).await {
3240        output.push(format!("Error creating deploy auth keypair: {e}"));
3241    }
3242}
3243
3244/// Auxiliary function to define the contract list subcommand handling.
3245async fn handle_contract_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3246    // Check correct subcommand structure
3247    if parts.len() != 2 && parts.len() != 3 {
3248        output.push(String::from("Malformed `contract list` subcommand"));
3249        output.push(String::from("Usage: contract list [contract-id]"));
3250        return
3251    }
3252
3253    if parts.len() == 3 {
3254        let deploy_auth = match ContractId::from_str(parts[2]) {
3255            Ok(d) => d,
3256            Err(e) => {
3257                output.push(format!("Invalid deploy authority: {e}"));
3258                return
3259            }
3260        };
3261
3262        let history = match drk.read().await.get_deploy_auth_history(&deploy_auth).await {
3263            Ok(a) => a,
3264            Err(e) => {
3265                output.push(format!("Failed to fetch deploy authority history records: {e}"));
3266                return
3267            }
3268        };
3269
3270        let table = prettytable_contract_history(&history);
3271        if table.is_empty() {
3272            output.push(String::from("No history records found"));
3273        } else {
3274            output.push(format!("{table}"));
3275        }
3276        return
3277    }
3278
3279    let auths = match drk.read().await.list_deploy_auth().await {
3280        Ok(a) => a,
3281        Err(e) => {
3282            output.push(format!("Failed to fetch deploy authorities: {e}"));
3283            return
3284        }
3285    };
3286
3287    let table = prettytable_contract_auth(&auths);
3288
3289    if table.is_empty() {
3290        output.push(String::from("No deploy authorities found"));
3291    } else {
3292        output.push(format!("{table}"));
3293    }
3294}
3295
3296/// Auxiliary function to define the contract export data subcommand handling.
3297async fn handle_contract_export_data(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3298    // Check correct subcommand structure
3299    if parts.len() != 3 {
3300        output.push(String::from("Malformed `contract export-data` subcommand"));
3301        output.push(String::from("Usage: contract export-data <tx-hash>"));
3302        return
3303    }
3304
3305    match drk.read().await.get_deploy_history_record_data(parts[2]).await {
3306        Ok(pair) => output.push(base64::encode(&serialize_async(&pair).await)),
3307        Err(e) => output.push(format!("Failed to retrieve history record: {e}")),
3308    }
3309}
3310
3311/// Auxiliary function to define the contract deploy subcommand handling.
3312async fn handle_contract_deploy(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3313    // Check correct subcommand structure
3314    if parts.len() != 4 && parts.len() != 5 {
3315        output.push(String::from("Malformed `contract deploy` subcommand"));
3316        output.push(String::from("Usage: contract deploy <deploy-auth> <wasm-path> [deploy-ix]"));
3317        return
3318    }
3319
3320    let deploy_auth = match ContractId::from_str(parts[2]) {
3321        Ok(d) => d,
3322        Err(e) => {
3323            output.push(format!("Invalid deploy authority: {e}"));
3324            return
3325        }
3326    };
3327
3328    // Read the wasm bincode and deploy instruction
3329    let file_path = match expand_path(parts[3]) {
3330        Ok(p) => p,
3331        Err(e) => {
3332            output.push(format!("Error while expanding wasm bincode file path: {e}"));
3333            return
3334        }
3335    };
3336    let wasm_bin = match smol::fs::read(file_path).await {
3337        Ok(w) => w,
3338        Err(e) => {
3339            output.push(format!("Error while reading wasm bincode file: {e}"));
3340            return
3341        }
3342    };
3343
3344    let deploy_ix = if parts.len() == 5 {
3345        let file_path = match expand_path(parts[4]) {
3346            Ok(p) => p,
3347            Err(e) => {
3348                output.push(format!("Error while expanding deploy instruction file path: {e}"));
3349                return
3350            }
3351        };
3352        match smol::fs::read(file_path).await {
3353            Ok(d) => d,
3354            Err(e) => {
3355                output.push(format!("Error while reading deploy instruction file: {e}"));
3356                return
3357            }
3358        }
3359    } else {
3360        vec![]
3361    };
3362
3363    match drk.read().await.deploy_contract(&deploy_auth, wasm_bin, deploy_ix).await {
3364        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3365        Err(e) => output.push(format!("Failed to create contract deployment transaction: {e}")),
3366    }
3367}
3368
3369/// Auxiliary function to define the contract lock subcommand handling.
3370async fn handle_contract_lock(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>) {
3371    // Check correct subcommand structure
3372    if parts.len() != 3 {
3373        output.push(String::from("Malformed `contract lock` subcommand"));
3374        output.push(String::from("Usage: contract lock <deploy-auth>"));
3375        return
3376    }
3377
3378    let deploy_auth = match ContractId::from_str(parts[2]) {
3379        Ok(d) => d,
3380        Err(e) => {
3381            output.push(format!("Invalid deploy authority: {e}"));
3382            return
3383        }
3384    };
3385
3386    match drk.read().await.lock_contract(&deploy_auth).await {
3387        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
3388        Err(e) => output.push(format!("Failed to create contract lock transaction: {e}")),
3389    }
3390}