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