1use std::{
19 io::{stdin, Cursor, Read},
20 process::exit,
21 str::FromStr,
22};
23
24use rodio::{source::Source, Decoder, OutputStream};
25use structopt_toml::clap::{App, Arg, Shell, SubCommand};
26
27use darkfi::{
28 cli_desc,
29 system::sleep,
30 tx::Transaction,
31 util::{encoding::base64, parse::decode_base10},
32 Error, Result,
33};
34use darkfi_money_contract::model::TokenId;
35use darkfi_serial::deserialize_async;
36
37use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
38
39pub async fn parse_tx_from_stdin() -> Result<Transaction> {
41 let mut buf = String::new();
42 stdin().read_to_string(&mut buf)?;
43 let Some(bytes) = base64::decode(buf.trim()) else {
44 eprintln!("Failed to decode transaction");
45 exit(2);
46 };
47
48 Ok(deserialize_async(&bytes).await?)
49}
50
51pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
53 let v: Vec<&str> = s.split(':').collect();
54 if v.len() != 2 {
55 eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
56 exit(2);
57 }
58
59 let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
60 let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
61
62 if val0.is_err() || val1.is_err() {
63 eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
64 exit(2);
65 }
66
67 Ok((val0.unwrap(), val1.unwrap()))
68}
69
70pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
72 let v: Vec<&str> = s.split(':').collect();
73 if v.len() != 2 {
74 eprintln!("Invalid token pair. Use a pair such as:");
75 eprintln!("WCKD:MLDY");
76 eprintln!("or");
77 eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
78 exit(2);
79 }
80
81 let tok0 = drk.get_token(v[0].to_string()).await;
82 let tok1 = drk.get_token(v[1].to_string()).await;
83
84 if tok0.is_err() || tok1.is_err() {
85 eprintln!("Invalid token pair. Use a pair such as:");
86 eprintln!("WCKD:MLDY");
87 eprintln!("or");
88 eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
89 exit(2);
90 }
91
92 Ok((tok0.unwrap(), tok1.unwrap()))
93}
94
95pub async fn kaching() {
97 const WALLET_MP3: &[u8] = include_bytes!("../wallet.mp3");
98
99 let cursor = Cursor::new(WALLET_MP3);
100
101 let Ok((_stream, stream_handle)) = OutputStream::try_default() else { return };
102
103 let Ok(source) = Decoder::new(cursor) else { return };
104
105 if stream_handle.play_raw(source.convert_samples()).is_err() {
106 return
107 }
108
109 sleep(2).await;
110}
111
112pub fn generate_completions(shell: &str) -> Result<()> {
114 let kaching = SubCommand::with_name("kaching").about("Fun");
118
119 let ping =
121 SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
122
123 let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
125
126 let completions = SubCommand::with_name("completions")
127 .about("Generate a SHELL completion script and print to stdout")
128 .arg(shell_arg);
129
130 let initialize =
132 Arg::with_name("initialize").long("initialize").help("Initialize wallet database");
133
134 let keygen =
135 Arg::with_name("keygen").long("keygen").help("Generate a new keypair in the wallet");
136
137 let balance =
138 Arg::with_name("balance").long("balance").help("Query the wallet for known balances");
139
140 let address =
141 Arg::with_name("address").long("address").help("Get the default address in the wallet");
142
143 let addresses =
144 Arg::with_name("addresses").long("addresses").help("Print all the addresses in the wallet");
145
146 let default_address = Arg::with_name("default-address")
147 .long("default-address")
148 .takes_value(true)
149 .help("Set the default address in the wallet");
150
151 let secrets =
152 Arg::with_name("secrets").long("secrets").help("Print all the secret keys from the wallet");
153
154 let import_secrets = Arg::with_name("import-secrets")
155 .long("import-secrets")
156 .help("Import secret keys from stdin into the wallet, separated by newlines");
157
158 let tree = Arg::with_name("tree").long("tree").help("Print the Merkle tree in the wallet");
159
160 let coins = Arg::with_name("coins").long("coins").help("Print all the coins in the wallet");
161
162 let wallet = SubCommand::with_name("wallet").about("Wallet operations").args(&vec![
163 initialize,
164 keygen,
165 balance,
166 address,
167 addresses,
168 default_address,
169 secrets,
170 import_secrets,
171 tree,
172 coins,
173 ]);
174
175 let spend = SubCommand::with_name("spend")
177 .about("Read a transaction from stdin and mark its input coins as spent");
178
179 let coin = Arg::with_name("coin").help("base58-encoded coin to mark as unspent");
181
182 let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
183
184 let amount = Arg::with_name("amount").help("Amount to send");
186
187 let token = Arg::with_name("token").help("Token ID to send");
188
189 let recipient = Arg::with_name("recipient").help("Recipient address");
190
191 let spend_hook = Arg::with_name("spend-hook").help("Optional contract spend hook to use");
192
193 let user_data = Arg::with_name("user-data").help("Optional user data to use");
194
195 let half_split = Arg::with_name("half-split")
196 .long("half-split")
197 .help("Split the output coin into two equal halves");
198
199 let transfer =
200 SubCommand::with_name("transfer").about("Create a payment transaction").args(&vec![
201 amount.clone(),
202 token.clone(),
203 recipient.clone(),
204 spend_hook.clone(),
205 user_data.clone(),
206 half_split,
207 ]);
208
209 let value_pair = Arg::with_name("value-pair")
211 .short("v")
212 .long("value-pair")
213 .takes_value(true)
214 .help("Value pair to send:recv (11.55:99.42)");
215
216 let token_pair = Arg::with_name("token-pair")
217 .short("t")
218 .long("token-pair")
219 .takes_value(true)
220 .help("Token pair to send:recv (f00:b4r)");
221
222 let init = SubCommand::with_name("init")
223 .about("Initialize the first half of the atomic swap")
224 .args(&vec![value_pair, token_pair]);
225
226 let join =
227 SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
228
229 let inspect = SubCommand::with_name("inspect")
230 .about("Inspect a swap half or the full swap tx from stdin");
231
232 let sign = SubCommand::with_name("sign").about("Sign a swap transaction given from stdin");
233
234 let otc = SubCommand::with_name("otc")
235 .about("OTC atomic swap")
236 .subcommands(vec![init, join, inspect, sign]);
237
238 let attach_fee = SubCommand::with_name("attach-fee")
240 .about("Attach the fee call to a transaction given from stdin");
241
242 let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
244
245 let broadcast =
247 SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
248
249 let subscribe = SubCommand::with_name("subscribe").about(
251 "This subscription will listen for incoming blocks from darkfid and look \
252 through their transactions to see if there's any that interest us. \
253 With `drk` we look at transactions calling the money contract so we can \
254 find coins sent to us and fill our wallet with the necessary metadata.",
255 );
256
257 let proposer_limit = Arg::with_name("proposer-limit")
259 .help("The minimum amount of governance tokens needed to open a proposal for this DAO");
260
261 let quorum = Arg::with_name("quorum")
262 .help("Minimal threshold of participating total tokens needed for a proposal to pass");
263
264 let early_exec_quorum = Arg::with_name("early-exec-quorum")
265 .help("Minimal threshold of participating total tokens needed for a proposal to be considered as strongly supported, enabling early execution. Must be greater or equal to normal quorum.");
266
267 let approval_ratio = Arg::with_name("approval-ratio")
268 .help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
269
270 let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
271
272 let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
273 proposer_limit,
274 quorum,
275 early_exec_quorum,
276 approval_ratio,
277 gov_token_id,
278 ]);
279
280 let view = SubCommand::with_name("view").about("View DAO data from stdin");
281
282 let name = Arg::with_name("name").help("Name identifier for the DAO");
283
284 let import = SubCommand::with_name("import")
285 .about("Import DAO data from stdin")
286 .args(&vec![name.clone()]);
287
288 let update_keys = SubCommand::with_name("update-keys").about("Update DAO keys from stdin");
289
290 let opt_name = Arg::with_name("dao-alias").help("Name identifier for the DAO (optional)");
291
292 let list = SubCommand::with_name("list")
293 .about("List imported DAOs (or info about a specific one)")
294 .args(&vec![opt_name]);
295
296 let balance = SubCommand::with_name("balance")
297 .about("Show the balance of a DAO")
298 .args(&vec![name.clone()]);
299
300 let mint = SubCommand::with_name("mint")
301 .about("Mint an imported DAO on-chain")
302 .args(&vec![name.clone()]);
303
304 let duration = Arg::with_name("duration").help("Duration of the proposal, in block windows");
305
306 let propose_transfer = SubCommand::with_name("propose-transfer")
307 .about("Create a transfer proposal for a DAO")
308 .args(&vec![
309 name.clone(),
310 duration.clone(),
311 amount,
312 token,
313 recipient,
314 spend_hook.clone(),
315 user_data.clone(),
316 ]);
317
318 let propose_generic = SubCommand::with_name("propose-generic")
319 .about("Create a generic proposal for a DAO")
320 .args(&vec![name.clone(), duration, user_data.clone()]);
321
322 let proposals =
323 SubCommand::with_name("proposals").about("List DAO proposals").args(&vec![name]);
324
325 let bulla = Arg::with_name("bulla").help("Bulla identifier for the proposal");
326
327 let export = Arg::with_name("export").help("Encrypt the proposal and encode it to base64");
328
329 let mint_proposal = Arg::with_name("mint-proposal").help("Create the proposal transaction");
330
331 let proposal = SubCommand::with_name("proposal").about("View a DAO proposal data").args(&vec![
332 bulla.clone(),
333 export,
334 mint_proposal,
335 ]);
336
337 let proposal_import = SubCommand::with_name("proposal-import")
338 .about("Import a base64 encoded and encrypted proposal from stdin");
339
340 let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
341
342 let vote_weight =
343 Arg::with_name("vote-weight").help("Optional vote weight (amount of governance tokens)");
344
345 let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
346 bulla.clone(),
347 vote,
348 vote_weight,
349 ]);
350
351 let early = Arg::with_name("early").long("early").help("Execute the proposal early");
352
353 let exec =
354 SubCommand::with_name("exec").about("Execute a DAO proposal").args(&vec![bulla, early]);
355
356 let spend_hook_cmd = SubCommand::with_name("spend-hook")
357 .about("Print the DAO contract base58-encoded spend hook");
358
359 let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
360 create,
361 view,
362 import,
363 update_keys,
364 list,
365 balance,
366 mint,
367 propose_transfer,
368 propose_generic,
369 proposals,
370 proposal,
371 proposal_import,
372 vote,
373 exec,
374 spend_hook_cmd,
375 ]);
376
377 let reset = Arg::with_name("reset")
379 .long("reset")
380 .help("Reset wallet state to provided block height and start scanning");
381
382 let scan = SubCommand::with_name("scan")
383 .about("Scan the blockchain and parse relevant transactions")
384 .args(&vec![reset]);
385
386 let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
388
389 let full = Arg::with_name("full").long("full").help("Print the full transaction information");
390
391 let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base58");
392
393 let fetch_tx = SubCommand::with_name("fetch-tx")
394 .about("Fetch a blockchain transaction by hash")
395 .args(&vec![tx_hash, full, encode]);
396
397 let simulate_tx =
398 SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
399
400 let tx_hash = Arg::with_name("tx-hash").help("Fetch specific history record (optional)");
401
402 let encode = Arg::with_name("encode")
403 .long("encode")
404 .help("Encode specific history record transaction to base58");
405
406 let txs_history = SubCommand::with_name("txs-history")
407 .about("Fetch broadcasted transactions history")
408 .args(&vec![tx_hash, encode]);
409
410 let clear_reverted =
411 SubCommand::with_name("clear-reverted").about("Remove reverted transactions from history");
412
413 let height = Arg::with_name("height").help("Fetch specific height record (optional)");
414
415 let scanned_blocks = SubCommand::with_name("scanned-blocks")
416 .about("Fetch scanned blocks records")
417 .args(&vec![height]);
418
419 let explorer = SubCommand::with_name("explorer")
420 .about("Explorer related subcommands")
421 .subcommands(vec![fetch_tx, simulate_tx, txs_history, clear_reverted, scanned_blocks]);
422
423 let alias = Arg::with_name("alias").help("Token alias");
425
426 let token = Arg::with_name("token").help("Token to create alias for");
427
428 let add = SubCommand::with_name("add").about("Create a Token alias").args(&vec![alias, token]);
429
430 let alias = Arg::with_name("alias")
431 .short("a")
432 .long("alias")
433 .takes_value(true)
434 .help("Token alias to search for");
435
436 let token = Arg::with_name("token")
437 .short("t")
438 .long("token")
439 .takes_value(true)
440 .help("Token to search alias for");
441
442 let show = SubCommand::with_name("show")
443 .about(
444 "Print alias info of optional arguments. \
445 If no argument is provided, list all the aliases in the wallet.",
446 )
447 .args(&vec![alias, token]);
448
449 let alias = Arg::with_name("alias").help("Token alias to remove");
450
451 let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
452
453 let alias = SubCommand::with_name("alias")
454 .about("Manage Token aliases")
455 .subcommands(vec![add, show, remove]);
456
457 let secret_key = Arg::with_name("secret-key").help("Mint authority secret key");
459
460 let token_blind = Arg::with_name("token-blind").help("Mint authority token blind");
461
462 let import = SubCommand::with_name("import")
463 .about("Import a mint authority")
464 .args(&vec![secret_key, token_blind]);
465
466 let generate_mint =
467 SubCommand::with_name("generate-mint").about("Generate a new mint authority");
468
469 let list =
470 SubCommand::with_name("list").about("List token IDs with available mint authorities");
471
472 let token = Arg::with_name("token").help("Token ID to mint");
473
474 let amount = Arg::with_name("amount").help("Amount to mint");
475
476 let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
477
478 let mint = SubCommand::with_name("mint")
479 .about("Mint tokens")
480 .args(&vec![token, amount, recipient, spend_hook, user_data]);
481
482 let token = Arg::with_name("token").help("Token ID to freeze");
483
484 let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
485
486 let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
487 import,
488 generate_mint,
489 list,
490 mint,
491 freeze,
492 ]);
493
494 let config = Arg::with_name("config")
496 .short("c")
497 .long("config")
498 .takes_value(true)
499 .help("Configuration file to use");
500
501 let network = Arg::with_name("network")
502 .long("network")
503 .takes_value(true)
504 .help("Blockchain network to use");
505
506 let command = vec![
507 kaching,
508 ping,
509 completions,
510 wallet,
511 spend,
512 unspend,
513 transfer,
514 otc,
515 attach_fee,
516 inspect,
517 broadcast,
518 subscribe,
519 dao,
520 scan,
521 explorer,
522 alias,
523 token,
524 ];
525
526 let fun = Arg::with_name("fun")
527 .short("f")
528 .long("fun")
529 .help("Flag indicating whether you want some fun in your life");
530
531 let log = Arg::with_name("log")
532 .short("l")
533 .long("log")
534 .takes_value(true)
535 .help("Set log file to ouput into");
536
537 let verbose = Arg::with_name("verbose")
538 .short("v")
539 .multiple(true)
540 .help("Increase verbosity (-vvv supported)");
541
542 let mut app = App::new("drk")
543 .about(cli_desc!())
544 .args(&vec![config, network, fun, log, verbose])
545 .subcommands(command);
546
547 let shell = match Shell::from_str(shell) {
548 Ok(s) => s,
549 Err(e) => return Err(Error::Custom(e)),
550 };
551
552 app.gen_completions_to("./drk", shell, &mut std::io::stdout());
553
554 Ok(())
555}