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