drk/
interactive.rs

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