main.rs 9.1 KB

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