cli_util.rs 24 KB

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