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