main.rs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. use std::{process::exit, str::FromStr, time::Instant};
  2. use clap::{Parser, Subcommand};
  3. use prettytable::{cell, format, row, Table};
  4. use serde_json::json;
  5. use simplelog::{ColorChoice, TermLogger, TerminalMode};
  6. use url::Url;
  7. use darkfi::{
  8. cli_desc,
  9. crypto::{address::Address, token_id},
  10. rpc::{client::RpcClient, jsonrpc::JsonRequest},
  11. util::{
  12. cli::{get_log_config, get_log_level, progress_bar},
  13. encode_base10, NetworkName,
  14. },
  15. Result,
  16. };
  17. #[derive(Parser)]
  18. #[clap(name = "drk", about = cli_desc!(), version)]
  19. #[clap(arg_required_else_help(true))]
  20. struct Args {
  21. #[clap(short, parse(from_occurrences))]
  22. /// Increase verbosity (-vvv supported)
  23. verbose: u8,
  24. #[clap(short, long, default_value = "tcp://127.0.0.1:8340")]
  25. /// darkfid JSON-RPC endpoint
  26. endpoint: Url,
  27. #[clap(subcommand)]
  28. command: DrkSubcommand,
  29. }
  30. #[derive(Subcommand)]
  31. enum DrkSubcommand {
  32. /// Send a ping request to the RPC
  33. Ping,
  34. /// Send an airdrop request to the faucet
  35. Airdrop {
  36. #[clap(long, parse(try_from_str))]
  37. /// Address where the airdrop should be requested
  38. /// (default is darkfid's wallet default)
  39. address: Option<Address>,
  40. #[clap(long)]
  41. /// JSON-RPC endpoint of the faucet
  42. faucet_endpoint: Url,
  43. /// f64 amount requested for airdrop
  44. amount: f64,
  45. /// Token ID to airdrop
  46. #[clap(long)]
  47. token_id: String,
  48. },
  49. /// Wallet operations
  50. Wallet {
  51. #[clap(long)]
  52. /// Generate a new keypair in the wallet
  53. keygen: bool,
  54. #[clap(long)]
  55. /// Query the wallet for known balances
  56. balance: bool,
  57. #[clap(long)]
  58. /// Get the default address in the wallet
  59. address: bool,
  60. #[clap(long)]
  61. /// Get all addresses in the wallet
  62. all_addresses: bool,
  63. },
  64. /// Transfer of value
  65. Transfer {
  66. /// Recipient address
  67. #[clap(parse(try_from_str))]
  68. recipient: Address,
  69. /// Amount to transfer
  70. amount: f64,
  71. /// Coin network
  72. #[clap(short, long, default_value = "darkfi", parse(try_from_str))]
  73. network: NetworkName,
  74. /// Token ID
  75. #[clap(short, long)]
  76. token_id: String,
  77. },
  78. }
  79. struct Drk {
  80. pub rpc_client: RpcClient,
  81. }
  82. impl Drk {
  83. async fn close_connection(&self) -> Result<()> {
  84. self.rpc_client.close().await
  85. }
  86. async fn ping(&self) -> Result<()> {
  87. let start = Instant::now();
  88. let req = JsonRequest::new("ping", json!([]));
  89. let rep = self.rpc_client.request(req).await?;
  90. let latency = Instant::now() - start;
  91. println!("Got reply: {}", rep);
  92. println!("Latency: {:?}", latency);
  93. Ok(())
  94. }
  95. async fn airdrop(
  96. &self,
  97. address: Option<Address>,
  98. endpoint: Url,
  99. amount: f64,
  100. token_id: String,
  101. ) -> Result<()> {
  102. let addr = if address.is_some() {
  103. address.unwrap()
  104. } else {
  105. let req = JsonRequest::new("wallet.get_addrs", json!([0_i64]));
  106. let rep = self.rpc_client.request(req).await?;
  107. Address::from_str(rep.as_array().unwrap()[0].as_str().unwrap())?
  108. };
  109. // Check if token ID is valid base58
  110. if token_id::parse_b58(&token_id).is_err() {
  111. eprintln!("Error: Invalid Token ID passed as argument.");
  112. exit(1);
  113. }
  114. let pb = progress_bar(&format!("Requesting airdrop for {}", addr));
  115. let req = JsonRequest::new("airdrop", json!([json!(addr.to_string()), amount, token_id]));
  116. let rpc_client = RpcClient::new(endpoint).await?;
  117. let rep = match rpc_client.oneshot_request(req).await {
  118. Ok(v) => v,
  119. Err(e) => {
  120. eprintln!("{}", e);
  121. exit(1);
  122. }
  123. };
  124. pb.finish();
  125. println!("Transaction ID: {}", rep);
  126. Ok(())
  127. }
  128. async fn wallet_keygen(&self) -> Result<()> {
  129. let req = JsonRequest::new("wallet.keygen", json!([]));
  130. let rep = self.rpc_client.request(req).await?;
  131. println!("New address: {}", rep);
  132. Ok(())
  133. }
  134. async fn wallet_balance(&self) -> Result<()> {
  135. let req = JsonRequest::new("wallet.get_balances", json!([]));
  136. let rep = self.rpc_client.request(req).await?;
  137. if !rep.is_object() {
  138. eprintln!("Invalid balance data received from darkfid RPC endpoint.");
  139. exit(1);
  140. }
  141. let mut table = Table::new();
  142. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  143. table.set_titles(row!["Token ID", "Balance"]);
  144. for i in rep.as_object().unwrap().keys() {
  145. if let Some(balance) = rep[i].as_u64() {
  146. table.add_row(row![i, encode_base10(balance, 8)]);
  147. continue
  148. }
  149. eprintln!("Found invalid balance data for key \"{}\"", i);
  150. }
  151. if table.is_empty() {
  152. println!("No balances.");
  153. } else {
  154. println!("{}", table);
  155. }
  156. Ok(())
  157. }
  158. async fn wallet_address(&self) -> Result<()> {
  159. let req = JsonRequest::new("wallet.get_addrs", json!([0_i64]));
  160. let rep = self.rpc_client.request(req).await?;
  161. println!("Default wallet address: {}", rep);
  162. Ok(())
  163. }
  164. async fn wallet_all_addresses(&self) -> Result<()> {
  165. let req = JsonRequest::new("wallet.get_addrs", json!([-1]));
  166. let rep = self.rpc_client.request(req).await?;
  167. println!("Wallet addresses:\n{:#?}", rep);
  168. Ok(())
  169. }
  170. async fn tx_transfer(
  171. &self,
  172. network: NetworkName,
  173. token_id: String,
  174. recipient: Address,
  175. amount: f64,
  176. ) -> Result<()> {
  177. println!("Attempting to transfer {} tokens to {}", amount, recipient);
  178. let req = JsonRequest::new(
  179. "tx.transfer",
  180. json!([network.to_string(), token_id, recipient.to_string(), amount]),
  181. );
  182. let rep = self.rpc_client.request(req).await?;
  183. println!("Success! Transaction ID: {}", rep);
  184. Ok(())
  185. }
  186. }
  187. #[async_std::main]
  188. async fn main() -> Result<()> {
  189. let args = Args::parse();
  190. let log_level = get_log_level(args.verbose.into());
  191. let log_config = get_log_config();
  192. TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
  193. let rpc_client = RpcClient::new(args.endpoint).await?;
  194. let drk = Drk { rpc_client };
  195. match args.command {
  196. DrkSubcommand::Ping => drk.ping().await,
  197. DrkSubcommand::Airdrop { address, faucet_endpoint, amount, token_id } => {
  198. drk.airdrop(address, faucet_endpoint, amount, token_id).await
  199. }
  200. DrkSubcommand::Wallet { keygen, balance, address, all_addresses } => {
  201. if keygen {
  202. return drk.wallet_keygen().await
  203. }
  204. if balance {
  205. return drk.wallet_balance().await
  206. }
  207. if address {
  208. return drk.wallet_address().await
  209. }
  210. if all_addresses {
  211. return drk.wallet_all_addresses().await
  212. }
  213. eprintln!("Run 'drk wallet -h' to see the subcommand usage.");
  214. exit(2);
  215. }
  216. DrkSubcommand::Transfer { recipient, amount, network, token_id } => {
  217. drk.tx_transfer(network, token_id, recipient, amount).await
  218. }
  219. }?;
  220. drk.close_connection().await
  221. }