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