drk/
interactive.rs

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