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