cli_util.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{io::Cursor, process::exit, str::FromStr};
  19. use rodio::{source::Source, Decoder, OutputStream};
  20. use structopt_toml::clap::{App, Arg, Shell, SubCommand};
  21. use darkfi::{cli_desc, system::sleep, util::parse::decode_base10, Error, Result};
  22. use darkfi_money_contract::model::TokenId;
  23. use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
  24. /// Auxiliary function to parse provided string into a values pair.
  25. pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
  26. let v: Vec<&str> = s.split(':').collect();
  27. if v.len() != 2 {
  28. eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
  29. exit(1);
  30. }
  31. let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
  32. let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
  33. if val0.is_err() || val1.is_err() {
  34. eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
  35. exit(1);
  36. }
  37. Ok((val0.unwrap(), val1.unwrap()))
  38. }
  39. /// Auxiliary function to parse provided string into a tokens pair.
  40. pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
  41. let v: Vec<&str> = s.split(':').collect();
  42. if v.len() != 2 {
  43. eprintln!("Invalid token pair. Use a pair such as:");
  44. eprintln!("WCKD:MLDY");
  45. eprintln!("or");
  46. eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
  47. exit(1);
  48. }
  49. let tok0 = drk.get_token(v[0].to_string()).await;
  50. let tok1 = drk.get_token(v[1].to_string()).await;
  51. if tok0.is_err() || tok1.is_err() {
  52. eprintln!("Invalid token pair. Use a pair such as:");
  53. eprintln!("WCKD:MLDY");
  54. eprintln!("or");
  55. eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
  56. exit(1);
  57. }
  58. Ok((tok0.unwrap(), tok1.unwrap()))
  59. }
  60. /// Fun police go away
  61. pub async fn kaching() {
  62. const WALLET_MP3: &[u8] = include_bytes!("../wallet.mp3");
  63. let cursor = Cursor::new(WALLET_MP3);
  64. let Ok((_stream, stream_handle)) = OutputStream::try_default() else { return };
  65. let Ok(source) = Decoder::new(cursor) else { return };
  66. if stream_handle.play_raw(source.convert_samples()).is_err() {
  67. return
  68. }
  69. sleep(2).await;
  70. }
  71. /// Auxiliary function to generate provided shell completions.
  72. pub fn generate_completions(shell: &str) -> Result<()> {
  73. // Sub-commands
  74. // Kaching
  75. let kaching = SubCommand::with_name("kaching").about("Fun");
  76. // Ping
  77. let ping =
  78. SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
  79. // Completions
  80. let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
  81. let completions = SubCommand::with_name("completions")
  82. .about("Generate a SHELL completion script and print to stdout")
  83. .arg(shell_arg);
  84. // Wallet
  85. let initialize =
  86. Arg::with_name("initialize").long("initialize").help("Initialize wallet database");
  87. let keygen =
  88. Arg::with_name("keygen").long("keygen").help("Generate a new keypair in the wallet");
  89. let balance =
  90. Arg::with_name("balance").long("balance").help("Query the wallet for known balances");
  91. let address =
  92. Arg::with_name("address").long("address").help("Get the default address in the wallet");
  93. let addresses =
  94. Arg::with_name("addresses").long("addresses").help("Print all the addresses in the wallet");
  95. let default_address = Arg::with_name("default-address")
  96. .long("default-address")
  97. .takes_value(true)
  98. .help("Set the default address in the wallet");
  99. let secrets =
  100. Arg::with_name("secrets").long("secrets").help("Print all the secret keys from the wallet");
  101. let import_secrets = Arg::with_name("import-secrets")
  102. .long("import-secrets")
  103. .help("Import secret keys from stdin into the wallet, separated by newlines");
  104. let tree = Arg::with_name("tree").long("tree").help("Print the Merkle tree in the wallet");
  105. let coins = Arg::with_name("coins").long("coins").help("Print all the coins in the wallet");
  106. let wallet = SubCommand::with_name("wallet").about("Wallet operations").args(&vec![
  107. initialize,
  108. keygen,
  109. balance,
  110. address,
  111. addresses,
  112. default_address,
  113. secrets,
  114. import_secrets,
  115. tree,
  116. coins,
  117. ]);
  118. // Unspend
  119. let coin = Arg::with_name("coin").help("base58-encoded coin to mark as unspent");
  120. let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
  121. // Transfer
  122. let amount = Arg::with_name("amount").help("Amount to send");
  123. let token = Arg::with_name("token").help("Token ID to send");
  124. let recipient = Arg::with_name("recipient").help("Recipient address");
  125. let transfer = SubCommand::with_name("transfer")
  126. .about("Create a payment transaction")
  127. .args(&vec![amount, token, recipient]);
  128. // Otc
  129. let value_pair = Arg::with_name("value-pair")
  130. .short("v")
  131. .long("value-pair")
  132. .takes_value(true)
  133. .help("Value pair to send:recv (11.55:99.42)");
  134. let token_pair = Arg::with_name("token-pair")
  135. .short("t")
  136. .long("token-pair")
  137. .takes_value(true)
  138. .help("Token pair to send:recv (f00:b4r)");
  139. let init = SubCommand::with_name("init")
  140. .about("Initialize the first half of the atomic swap")
  141. .args(&vec![value_pair, token_pair]);
  142. let join =
  143. SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
  144. let inspect = SubCommand::with_name("inspect")
  145. .about("Inspect a swap half or the full swap tx from stdin");
  146. let sign = SubCommand::with_name("sign")
  147. .about("Sign a transaction given from stdin as the first-half");
  148. let otc = SubCommand::with_name("otc")
  149. .about("OTC atomic swap")
  150. .subcommands(vec![init, join, inspect, sign]);
  151. // Inspect
  152. let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
  153. // Broadcast
  154. let broadcast =
  155. SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
  156. // Subscribe
  157. let subscribe = SubCommand::with_name("subscribe").about(
  158. "This subscription will listen for incoming blocks from darkfid and look \
  159. through their transactions to see if there's any that interest us. \
  160. With `drk` we look at transactions calling the money contract so we can \
  161. find coins sent to us and fill our wallet with the necessary metadata.",
  162. );
  163. // DAO
  164. let proposer_limit = Arg::with_name("proposer-limit")
  165. .help("The minimum amount of governance tokens needed to open a proposal for this DAO");
  166. let quorum = Arg::with_name("quorum")
  167. .help("Minimal threshold of participating total tokens needed for a proposal to pass");
  168. let approval_ratio = Arg::with_name("approval-ratio")
  169. .help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
  170. let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
  171. let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
  172. proposer_limit,
  173. quorum,
  174. approval_ratio,
  175. gov_token_id,
  176. ]);
  177. let view = SubCommand::with_name("view").about("View DAO data from stdin");
  178. let dao_name = Arg::with_name("dao-name").help("Named identifier for the DAO");
  179. let import =
  180. SubCommand::with_name("import").about("Import DAO data from stdin").args(&vec![dao_name]);
  181. let dao_alias = Arg::with_name("dao-alias").help("Numeric identifier for the DAO (optional)");
  182. let list = SubCommand::with_name("list")
  183. .about("List imported DAOs (or info about a specific one)")
  184. .args(&vec![dao_alias]);
  185. let dao_alias = Arg::with_name("dao-alias").help("Name or numeric identifier for the DAO");
  186. let balance = SubCommand::with_name("balance")
  187. .about("Show the balance of a DAO")
  188. .args(&vec![dao_alias.clone()]);
  189. let mint = SubCommand::with_name("mint")
  190. .about("Mint an imported DAO on-chain")
  191. .args(&vec![dao_alias.clone()]);
  192. let recipient =
  193. Arg::with_name("recipient").help("Pubkey to send tokens to with proposal success");
  194. let amount = Arg::with_name("amount").help("Amount to send from DAO with proposal success");
  195. let token = Arg::with_name("token").help("Token ID to send from DAO with proposal success");
  196. let propose = SubCommand::with_name("propose")
  197. .about("Create a proposal for a DAO")
  198. .args(&vec![dao_alias.clone(), recipient, amount, token]);
  199. let proposals = SubCommand::with_name("proposals")
  200. .about("List DAO proposals")
  201. .args(&vec![dao_alias.clone()]);
  202. let proposal_id = Arg::with_name("proposal-id").help("Numeric identifier for the proposal");
  203. let proposal = SubCommand::with_name("proposal")
  204. .about("View a DAO proposal data")
  205. .args(&vec![dao_alias.clone(), proposal_id.clone()]);
  206. let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
  207. let vote_weight =
  208. Arg::with_name("vote-weight").help("Vote weight (amount of governance tokens)");
  209. let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
  210. dao_alias.clone(),
  211. proposal_id.clone(),
  212. vote,
  213. vote_weight,
  214. ]);
  215. let exec = SubCommand::with_name("exec")
  216. .about("Execute a DAO proposal")
  217. .args(&vec![dao_alias, proposal_id]);
  218. let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
  219. create, view, import, list, balance, mint, propose, proposals, proposal, vote, exec,
  220. ]);
  221. // Scan
  222. let reset = Arg::with_name("reset")
  223. .long("reset")
  224. .help("Reset Merkle tree and start scanning from first block");
  225. let list = Arg::with_name("list").long("list").help("List all available checkpoints");
  226. let checkpoint = Arg::with_name("checkpoint")
  227. .long("checkpoint")
  228. .takes_value(true)
  229. .help("Reset Merkle tree to checkpoint index and start scanning");
  230. let scan = SubCommand::with_name("scan")
  231. .about("Scan the blockchain and parse relevant transactions")
  232. .args(&vec![reset, list, checkpoint]);
  233. // Explorer
  234. let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
  235. let full = Arg::with_name("full").long("full").help("Print the full transaction information");
  236. let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base58");
  237. let fetch_tx = SubCommand::with_name("fetch-tx")
  238. .about("Fetch a blockchain transaction by hash")
  239. .args(&vec![tx_hash, full, encode]);
  240. let simulate_tx =
  241. SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
  242. let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
  243. let encode = Arg::with_name("encode")
  244. .long("encode")
  245. .help("Encode specific history record transaction to base58");
  246. let txs_history = SubCommand::with_name("txs-history")
  247. .about("Fetch broadcasted transactions history")
  248. .args(&vec![tx_hash, encode]);
  249. let explorer = SubCommand::with_name("explorer")
  250. .about("Explorer related subcommands")
  251. .subcommands(vec![fetch_tx, simulate_tx, txs_history]);
  252. // Alias
  253. let alias = Arg::with_name("alias").help("Token alias");
  254. let token = Arg::with_name("token").help("Token to create alias for");
  255. let add = SubCommand::with_name("add").about("Create a Token alias").args(&vec![alias, token]);
  256. let alias = Arg::with_name("alias")
  257. .short("a")
  258. .long("alias")
  259. .takes_value(true)
  260. .help("Token alias to search for");
  261. let token = Arg::with_name("token")
  262. .short("t")
  263. .long("token")
  264. .takes_value(true)
  265. .help("Token to search alias for");
  266. let show = SubCommand::with_name("show")
  267. .about(
  268. "Print alias info of optional arguments. \
  269. If no argument is provided, list all the aliases in the wallet.",
  270. )
  271. .args(&vec![alias, token]);
  272. let alias = Arg::with_name("alias").help("Token alias to remove");
  273. let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
  274. let alias = SubCommand::with_name("alias")
  275. .about("Manage Token aliases")
  276. .subcommands(vec![add, show, remove]);
  277. // Token
  278. let import = SubCommand::with_name("import").about("Import a mint authority secret from stdin");
  279. let generate_mint =
  280. SubCommand::with_name("generate-mint").about("Generate a new mint authority");
  281. let list =
  282. SubCommand::with_name("list").about("List token IDs with available mint authorities");
  283. let token = Arg::with_name("token").help("Token ID to mint");
  284. let amount = Arg::with_name("amount").help("Amount to mint");
  285. let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
  286. let mint =
  287. SubCommand::with_name("mint").about("Mint tokens").args(&vec![token, amount, recipient]);
  288. let token = Arg::with_name("token").help("Token ID to freeze");
  289. let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
  290. let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
  291. import,
  292. generate_mint,
  293. list,
  294. mint,
  295. freeze,
  296. ]);
  297. // Main arguments
  298. let config = Arg::with_name("config")
  299. .short("c")
  300. .long("config")
  301. .takes_value(true)
  302. .help("Configuration file to use");
  303. let wallet_path = Arg::with_name("wallet_path")
  304. .long("wallet-path")
  305. .takes_value(true)
  306. .help("Path to wallet database");
  307. let wallet_pass = Arg::with_name("wallet_pass")
  308. .long("wallet-pass")
  309. .takes_value(true)
  310. .help("Password for the wallet database");
  311. let endpoint = Arg::with_name("endpoint")
  312. .short("e")
  313. .long("endpoint")
  314. .takes_value(true)
  315. .help("darkfid JSON-RPC endpoint");
  316. let command = vec![
  317. kaching,
  318. ping,
  319. completions,
  320. wallet,
  321. unspend,
  322. transfer,
  323. otc,
  324. inspect,
  325. broadcast,
  326. subscribe,
  327. dao,
  328. scan,
  329. explorer,
  330. alias,
  331. token,
  332. ];
  333. let log = Arg::with_name("log")
  334. .short("l")
  335. .long("log")
  336. .takes_value(true)
  337. .help("Set log file to ouput into");
  338. let verbose = Arg::with_name("verbose")
  339. .short("v")
  340. .multiple(true)
  341. .help("Increase verbosity (-vvv supported)");
  342. let mut app = App::new("drk")
  343. .about(cli_desc!())
  344. .args(&vec![config, wallet_path, wallet_pass, endpoint, log, verbose])
  345. .subcommands(command);
  346. let shell = match Shell::from_str(shell) {
  347. Ok(s) => s,
  348. Err(e) => return Err(Error::Custom(e)),
  349. };
  350. app.gen_completions_to("./drk", shell, &mut std::io::stdout());
  351. Ok(())
  352. }