drk/
interactive.rs

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