main.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. use std::{process::exit, str::FromStr, time::Instant};
  2. use clap::{Parser, Subcommand};
  3. use log::{debug, error};
  4. use serde_json::{json, Value};
  5. use simplelog::{ColorChoice, TermLogger, TerminalMode};
  6. use url::Url;
  7. use darkfi::{
  8. cli_desc,
  9. crypto::address::Address,
  10. rpc::{
  11. jsonrpc,
  12. jsonrpc::{JsonRequest, JsonResult},
  13. },
  14. util::{cli::log_config, NetworkName},
  15. Error::JsonRpcError,
  16. Result,
  17. };
  18. #[derive(Parser)]
  19. #[clap(name = "drk", about = cli_desc!(), version)]
  20. #[clap(arg_required_else_help(true))]
  21. struct Args {
  22. #[clap(short, parse(from_occurrences))]
  23. /// Increase verbosity (-vvv supported)
  24. verbose: u8,
  25. #[clap(short, long, default_value = "tcp://127.0.0.1:8340")]
  26. /// darkfid JSON-RPC endpoint
  27. endpoint: Url,
  28. #[clap(subcommand)]
  29. command: DrkSubcommand,
  30. }
  31. #[derive(Subcommand)]
  32. enum DrkSubcommand {
  33. /// Send a ping request to the RPC
  34. Ping,
  35. /// Send an airdrop request to the faucet
  36. Airdrop {
  37. #[clap(long, parse(try_from_str))]
  38. /// Address where the airdrop should be requested
  39. /// (default is darkfid's wallet default)
  40. address: Option<Address>,
  41. #[clap(long)]
  42. /// JSON-RPC endpoint of the faucet
  43. faucet_endpoint: Url,
  44. /// f64 amount requested for airdrop
  45. amount: f64,
  46. },
  47. /// Wallet operations
  48. Wallet {
  49. #[clap(long)]
  50. /// Generate a new keypair in the wallet
  51. keygen: bool,
  52. #[clap(long)]
  53. /// Query the wallet for known balances
  54. balance: bool,
  55. #[clap(long)]
  56. /// Get the default address in the wallet
  57. address: bool,
  58. #[clap(long)]
  59. /// Get all addresses in the wallet
  60. all_addresses: bool,
  61. },
  62. /// Transfer of value
  63. Transfer {
  64. /// Recipient address
  65. #[clap(parse(try_from_str))]
  66. recipient: Address,
  67. /// Amount to transfer
  68. amount: f64,
  69. /// Coin network
  70. #[clap(short, long, default_value = "darkfi", parse(try_from_str))]
  71. network: NetworkName,
  72. /// Token ID
  73. #[clap(short, long)]
  74. token_id: String,
  75. },
  76. }
  77. struct Drk {
  78. pub rpc_endpoint: Url,
  79. }
  80. impl Drk {
  81. async fn request(&self, r: JsonRequest, endpoint: Option<Url>) -> Result<Value> {
  82. debug!(target: "rpc", "--> {}", serde_json::to_string(&r)?);
  83. let ep =
  84. if endpoint.is_some() { endpoint.unwrap().clone() } else { self.rpc_endpoint.clone() };
  85. let reply = match jsonrpc::send_request(&ep, json!(r), None).await {
  86. Ok(v) => v,
  87. Err(e) => return Err(e),
  88. };
  89. match reply {
  90. JsonResult::Resp(r) => {
  91. debug!(target: "rpc", "<-- {}", serde_json::to_string(&r)?);
  92. Ok(r.result)
  93. }
  94. JsonResult::Err(e) => {
  95. debug!(target: "rpc", "<-- {}", serde_json::to_string(&e)?);
  96. Err(JsonRpcError(e.error.message.to_string()))
  97. }
  98. JsonResult::Notif(n) => {
  99. debug!(target: "rpc", "<-- {}", serde_json::to_string(&n)?);
  100. Err(JsonRpcError("Unexpected reply".to_string()))
  101. }
  102. }
  103. }
  104. async fn ping(&self) -> Result<()> {
  105. let start = Instant::now();
  106. let req = jsonrpc::request(json!("ping"), json!([]));
  107. let rep = match self.request(req, None).await {
  108. Ok(v) => v,
  109. Err(e) => {
  110. error!("Got an error: {}", e);
  111. return Err(e)
  112. }
  113. };
  114. let latency = Instant::now() - start;
  115. println!("Got reply: {}", rep);
  116. println!("Latency: {:?}", latency);
  117. Ok(())
  118. }
  119. async fn airdrop(&self, address: Option<Address>, endpoint: Url, amount: f64) -> Result<()> {
  120. let addr = if address.is_some() {
  121. address.unwrap()
  122. } else {
  123. let req = jsonrpc::request(json!("wallet.get_key"), json!([0_i64]));
  124. let rep = match self.request(req, None).await {
  125. Ok(v) => v,
  126. Err(e) => {
  127. error!("Error while fetching default key from wallet: {}", e);
  128. return Err(e)
  129. }
  130. };
  131. Address::from_str(rep.as_array().unwrap()[0].as_str().unwrap())?
  132. };
  133. println!("Requesting airdrop for {}", addr);
  134. let req = jsonrpc::request(json!("airdrop"), json!([json!(addr.to_string()), amount]));
  135. let rep = match self.request(req, Some(endpoint)).await {
  136. Ok(v) => v,
  137. Err(e) => {
  138. error!("Failed requesting airdrop: {}", e);
  139. return Err(e)
  140. }
  141. };
  142. println!("Success! Transaction ID: {}", rep);
  143. Ok(())
  144. }
  145. async fn wallet_keygen(&self) -> Result<()> {
  146. let req = jsonrpc::request(json!("wallet.keygen"), json!([]));
  147. let rep = match self.request(req, None).await {
  148. Ok(v) => v,
  149. Err(e) => {
  150. error!("Error while generating new key in wallet: {}", e);
  151. return Err(e)
  152. }
  153. };
  154. println!("New address: {}", rep);
  155. Ok(())
  156. }
  157. async fn wallet_balance(&self) -> Result<()> {
  158. let req = jsonrpc::request(json!("wallet.get_balances"), json!([]));
  159. let rep = match self.request(req, None).await {
  160. Ok(v) => v,
  161. Err(e) => {
  162. error!("Error fetching balances from wallet: {}", e);
  163. return Err(e)
  164. }
  165. };
  166. // TODO: Better representation
  167. println!("Balances:\n{:#?}", rep);
  168. Ok(())
  169. }
  170. async fn wallet_address(&self) -> Result<()> {
  171. let req = jsonrpc::request(json!("wallet.get_key"), json!([0_i64]));
  172. let rep = match self.request(req, None).await {
  173. Ok(v) => v,
  174. Err(e) => {
  175. error!("Error fetching default keypair from wallet: {}", e);
  176. return Err(e)
  177. }
  178. };
  179. println!("Default wallet address: {}", rep);
  180. Ok(())
  181. }
  182. async fn wallet_all_addresses(&self) -> Result<()> {
  183. let req = jsonrpc::request(json!("wallet.get_key"), json!([-1]));
  184. let rep = match self.request(req, None).await {
  185. Ok(v) => v,
  186. Err(e) => {
  187. error!("Error fetching keypairs from wallet: {}", e);
  188. return Err(e)
  189. }
  190. };
  191. println!("Wallet addresses:\n{:#?}", rep);
  192. Ok(())
  193. }
  194. async fn tx_transfer(
  195. &self,
  196. network: NetworkName,
  197. token_id: String,
  198. recipient: Address,
  199. amount: f64,
  200. ) -> Result<()> {
  201. println!("Attempting to transfer {} tokens to {}", amount, recipient);
  202. let req = jsonrpc::request(
  203. json!("tx.transfer"),
  204. json!([network.to_string(), token_id, recipient.to_string(), amount]),
  205. );
  206. let rep = match self.request(req, None).await {
  207. Ok(v) => v,
  208. Err(e) => {
  209. error!("Error building and sending transaction: {}", e);
  210. return Err(e)
  211. }
  212. };
  213. println!("Success! Transaction ID: {}", rep);
  214. Ok(())
  215. }
  216. }
  217. #[async_std::main]
  218. async fn main() -> Result<()> {
  219. let args = Args::parse();
  220. let (lvl, conf) = log_config(args.verbose.into())?;
  221. TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
  222. let drk = Drk { rpc_endpoint: args.endpoint };
  223. match args.command {
  224. DrkSubcommand::Ping => drk.ping().await,
  225. DrkSubcommand::Airdrop { address, faucet_endpoint, amount } => {
  226. drk.airdrop(address, faucet_endpoint, amount).await
  227. }
  228. DrkSubcommand::Wallet { keygen, balance, address, all_addresses } => {
  229. if keygen {
  230. return drk.wallet_keygen().await
  231. }
  232. if balance {
  233. return drk.wallet_balance().await
  234. }
  235. if address {
  236. return drk.wallet_address().await
  237. }
  238. if all_addresses {
  239. return drk.wallet_all_addresses().await
  240. }
  241. eprintln!("Run 'drk wallet -h' to see the subcommand usage.");
  242. exit(2);
  243. }
  244. DrkSubcommand::Transfer { recipient, amount, network, token_id } => {
  245. drk.tx_transfer(network, token_id, recipient, amount).await
  246. }
  247. }
  248. }