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