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