main.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. use std::{path::PathBuf, str::FromStr};
  2. #[macro_use]
  3. extern crate prettytable;
  4. use clap::{IntoApp, Parser};
  5. use log::debug;
  6. use prettytable::{format, Table};
  7. use serde_json::{json, Value};
  8. use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode};
  9. use darkfi::{
  10. cli::{CliDrk, CliDrkSubCommands, Config, DrkConfig},
  11. rpc::{jsonrpc, jsonrpc::JsonResult},
  12. util::{join_config_path, path::expand_path, NetworkName},
  13. Error, Result,
  14. };
  15. struct Drk {
  16. url: String,
  17. }
  18. impl Drk {
  19. pub fn new(url: String) -> Self {
  20. Self { url }
  21. }
  22. // Retrieve cashier features and error if they
  23. // don't support the network
  24. async fn check_network(&self, network: &NetworkName) -> Result<()> {
  25. let features = self.features().await?;
  26. if features.as_object().is_none() &&
  27. features.as_object().unwrap()["networks"].as_array().is_none() &&
  28. features.as_object().unwrap()["networks"].as_array().unwrap().is_empty()
  29. {
  30. return Err(Error::NotSupportedNetwork)
  31. }
  32. for nets in features.as_object().unwrap()["networks"].as_array().unwrap() {
  33. for (net, _) in nets.as_object().unwrap() {
  34. if network == &NetworkName::from_str(net.as_str())? {
  35. return Ok(())
  36. }
  37. }
  38. }
  39. Err(Error::NotSupportedNetwork)
  40. }
  41. async fn request(&self, r: jsonrpc::JsonRequest) -> Result<Value> {
  42. let reply: JsonResult = match jsonrpc::send_raw_request(&self.url, json!(r)).await {
  43. Ok(v) => v,
  44. Err(e) => return Err(e),
  45. };
  46. match reply {
  47. JsonResult::Resp(r) => {
  48. debug!(target: "RPC", "<-- {}", serde_json::to_string(&r)?);
  49. Ok(r.result)
  50. }
  51. JsonResult::Err(e) => {
  52. debug!(target: "RPC", "<-- {}", serde_json::to_string(&e)?);
  53. Err(Error::JsonRpcError(e.error.message.to_string()))
  54. }
  55. JsonResult::Notif(n) => {
  56. debug!(target: "RPC", "<-- {}", serde_json::to_string(&n)?);
  57. Err(Error::JsonRpcError("Unexpected reply".to_string()))
  58. }
  59. }
  60. }
  61. // --> {"jsonrpc": "2.0", "method": "say_hello", "params": [], "id": 42}
  62. // <-- {"jsonrpc": "2.0", "result": "hello world", "id": 42}
  63. async fn say_hello(&self) -> Result<Value> {
  64. let req = jsonrpc::request(json!("say_hello"), json!([]));
  65. Ok(self.request(req).await?)
  66. }
  67. // --> {"jsonrpc": "2.0", "method": "create_wallet", "params": [], "id": 42}
  68. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  69. async fn create_wallet(&self) -> Result<Value> {
  70. let req = jsonrpc::request(json!("create_wallet"), json!([]));
  71. Ok(self.request(req).await?)
  72. }
  73. // --> {"jsonrpc": "2.0", "method": "key_gen", "params": [], "id": 42}
  74. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  75. async fn key_gen(&self) -> Result<Value> {
  76. let req = jsonrpc::request(json!("key_gen"), json!([]));
  77. Ok(self.request(req).await?)
  78. }
  79. // --> {"jsonrpc": "2.0", "method": "get_key", "params": [], "id": 42}
  80. // <-- {"jsonrpc": "2.0", "result": "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC", "id": 42}
  81. async fn get_key(&self) -> Result<Value> {
  82. let req = jsonrpc::request(json!("get_key"), json!([]));
  83. Ok(self.request(req).await?)
  84. }
  85. // --> {"jsonrpc": "2.0", "method": "get_keys", "params": [], "id": 42}
  86. // <-- {"jsonrpc": "2.0", "result": "[vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC, ...]", "id":
  87. // 42}
  88. async fn get_keys(&self) -> Result<Value> {
  89. let req = jsonrpc::request(json!("get_keys"), json!([]));
  90. Ok(self.request(req).await?)
  91. }
  92. // --> {"jsonrpc": "2.0", "method": "set_default_address", "params":
  93. // "[vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC]", "id": 42}
  94. // <-- {"jsonrpc": "2.0", "result":
  95. // true, "id": 42}
  96. async fn set_default_address(&self, address: &str) -> Result<Value> {
  97. let req = jsonrpc::request(json!("set_default_address"), json!([address]));
  98. Ok(self.request(req).await?)
  99. }
  100. // --> {"jsonrpc": "2.0", "method": "export_keypair", "params": "[path/]", "id": 42}
  101. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  102. async fn export_keypair(&self, path: &str) -> Result<Value> {
  103. let req = jsonrpc::request(json!("export_keypair"), json!([path]));
  104. Ok(self.request(req).await?)
  105. }
  106. // --> {"jsonrpc": "2.0", "method": "import_keypair", "params": "[path/]", "id": 42}
  107. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  108. async fn import_keypair(&self, path: &str) -> Result<Value> {
  109. let req = jsonrpc::request(json!("import_keypair"), json!([path]));
  110. Ok(self.request(req).await?)
  111. }
  112. // --> {"jsonrpc": "2.0", "method": "get_key", "params": ["solana", "usdc"], "id": 42}
  113. // <-- {"jsonrpc": "2.0", "result": "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC", "id": 42}
  114. async fn get_token_id(&self, network: &str, token: &str) -> Result<Value> {
  115. let req = jsonrpc::request(json!("get_token_id"), json!([network, token]));
  116. Ok(self.request(req).await?)
  117. }
  118. // --> {"method": "get_balances", "params": []}
  119. // <-- {"result": "get_balances": "[ {"btc": (value, network)}, .. ]"}
  120. async fn get_balances(&self) -> Result<Value> {
  121. let req = jsonrpc::request(json!("get_balances"), json!([]));
  122. Ok(self.request(req).await?)
  123. }
  124. // --> {"jsonrpc": "2.0", "method": "features", "params": [], "id": 42}
  125. // <-- {"jsonrpc": "2.0", "result": ["network": "btc", "sol"], "id": 42}
  126. async fn features(&self) -> Result<Value> {
  127. let req = jsonrpc::request(json!("features"), json!([]));
  128. Ok(self.request(req).await?)
  129. }
  130. // --> {"jsonrpc": "2.0", "method": "deposit", "params": ["solana", "usdc"], "id": 42}
  131. // <-- {"jsonrpc": "2.0", "result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL", "id": 42}
  132. async fn deposit(&self, network: &str, token: &str) -> Result<Value> {
  133. let req = jsonrpc::request(json!("deposit"), json!([network, token]));
  134. Ok(self.request(req).await?)
  135. }
  136. // --> {"jsonrpc": "2.0", "method": "withdraw",
  137. // "params": ["solana", "usdc", "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL", 13.37"],
  138. // "id": 42} <-- {"jsonrpc": "2.0", "result": "txID", "id": 42}
  139. async fn withdraw(
  140. &self,
  141. network: &str,
  142. token: &str,
  143. address: &str,
  144. amount: &str,
  145. ) -> Result<Value> {
  146. let req = jsonrpc::request(json!("withdraw"), json!([network, token, address, amount]));
  147. Ok(self.request(req).await?)
  148. }
  149. // --> {"jsonrpc": "2.0", "method": "transfer",
  150. // "params": ["dusdc", "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC", 13.37], "id": 42}
  151. // <-- {"jsonrpc": "2.0", "result": "txID", "id": 42}
  152. async fn transfer(
  153. &self,
  154. network: &str,
  155. token: &str,
  156. address: &str,
  157. amount: &str,
  158. ) -> Result<Value> {
  159. let req = jsonrpc::request(json!("transfer"), json!([network, token, address, amount]));
  160. Ok(self.request(req).await?)
  161. }
  162. }
  163. async fn start(config: &DrkConfig, options: CliDrk) -> Result<()> {
  164. let client = Drk::new(config.darkfid_rpc_url.clone());
  165. match options.command {
  166. Some(CliDrkSubCommands::Hello {}) => {
  167. let reply = client.say_hello().await?;
  168. println!("Server replied: {}", &reply.to_string());
  169. return Ok(())
  170. }
  171. Some(CliDrkSubCommands::Features {}) => {
  172. let reply = client.features().await?;
  173. println!("Features: {}", &reply.to_string());
  174. return Ok(())
  175. }
  176. Some(CliDrkSubCommands::Wallet {
  177. create,
  178. keygen,
  179. address,
  180. balances,
  181. addresses,
  182. export_keypair,
  183. import_keypair,
  184. set_default_address,
  185. }) => {
  186. if create {
  187. let reply = client.create_wallet().await?;
  188. if reply.as_bool().unwrap() {
  189. println!("Wallet created successfully.")
  190. } else {
  191. println!("Server replied: {}", &reply.to_string());
  192. }
  193. return Ok(())
  194. }
  195. if keygen {
  196. let reply = client.key_gen().await?;
  197. if reply.as_bool().unwrap() {
  198. println!("Key generation successful.")
  199. } else {
  200. println!("Server replied: {}", &reply.to_string());
  201. }
  202. return Ok(())
  203. }
  204. if address {
  205. let reply = client.get_key().await?;
  206. println!("Wallet address: {}", &reply.to_string());
  207. return Ok(())
  208. }
  209. if addresses {
  210. let reply = client.get_keys().await?;
  211. println!("Wallet addresses: ");
  212. if reply.as_array().is_some() {
  213. for (i, address) in reply.as_array().unwrap().iter().enumerate() {
  214. if i == 0 {
  215. println!("- [X] {}", address);
  216. } else {
  217. println!("- [ ] {}", address);
  218. }
  219. }
  220. } else {
  221. println!("Empty!!",);
  222. }
  223. return Ok(())
  224. }
  225. if balances {
  226. let reply = client.get_balances().await?;
  227. if reply.as_object().is_some() && !reply.as_object().unwrap().is_empty() {
  228. let mut table = Table::new();
  229. table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
  230. table.set_titles(row!["token", "amount", "network"]);
  231. for (tkn, data) in reply.as_object().unwrap() {
  232. table.add_row(row![
  233. tkn,
  234. data[0].as_str().unwrap(),
  235. data[1].as_str().unwrap()
  236. ]);
  237. }
  238. table.printstd();
  239. } else {
  240. println!("Balances: {}", 0);
  241. }
  242. return Ok(())
  243. }
  244. if set_default_address.is_some() {
  245. let default_address = set_default_address.unwrap();
  246. client.set_default_address(&default_address).await?;
  247. return Ok(())
  248. }
  249. if export_keypair.is_some() {
  250. let path = export_keypair.unwrap();
  251. client.export_keypair(&path).await?;
  252. return Ok(())
  253. }
  254. if import_keypair.is_some() {
  255. let path = import_keypair.unwrap();
  256. client.import_keypair(&path).await?;
  257. return Ok(())
  258. }
  259. }
  260. Some(CliDrkSubCommands::Id { network, token }) => {
  261. let network = network.to_lowercase();
  262. client.check_network(&NetworkName::from_str(&network)?).await?;
  263. let reply = client.get_token_id(&network, &token).await?;
  264. println!("Token ID: {}", &reply.to_string());
  265. return Ok(())
  266. }
  267. Some(CliDrkSubCommands::Deposit { network, token_sym }) => {
  268. let network = network.to_lowercase();
  269. client.check_network(&NetworkName::from_str(&network)?).await?;
  270. let reply = client.deposit(&network, &token_sym).await?;
  271. println!("Deposit your coins to the following address: {}", &reply.to_string());
  272. return Ok(())
  273. }
  274. Some(CliDrkSubCommands::Transfer { network, token_sym, address, amount }) => {
  275. let network = network.to_lowercase();
  276. client.check_network(&NetworkName::from_str(&network)?).await?;
  277. client.transfer(&network, &token_sym, &address, &amount.to_string()).await?;
  278. println!("{} {} Transfered successfully", amount, token_sym.to_uppercase(),);
  279. return Ok(())
  280. }
  281. Some(CliDrkSubCommands::Withdraw { network, token_sym, address, amount }) => {
  282. let network = network.to_lowercase();
  283. client.check_network(&NetworkName::from_str(&network)?).await?;
  284. let reply =
  285. client.withdraw(&network, &token_sym, &address, &amount.to_string()).await?;
  286. println!("{}", &reply.to_string());
  287. return Ok(())
  288. }
  289. None => {}
  290. }
  291. println!("Please run 'drk help' to see usage.");
  292. Err(Error::MissingParams)
  293. }
  294. #[async_std::main]
  295. async fn main() -> Result<()> {
  296. let args = CliDrk::parse();
  297. let matches = CliDrk::into_app().get_matches();
  298. let config_path = if args.config.is_some() {
  299. expand_path(&args.config.clone().unwrap())?
  300. } else {
  301. join_config_path(&PathBuf::from("drk.toml"))?
  302. };
  303. let mut verbosity_level = 0;
  304. verbosity_level += matches.occurrences_of("verbose");
  305. let loglevel = match verbosity_level {
  306. 0 => LevelFilter::Info,
  307. 1 => LevelFilter::Debug,
  308. _ => LevelFilter::Trace,
  309. };
  310. TermLogger::init(
  311. loglevel,
  312. simplelog::Config::default(),
  313. TerminalMode::Mixed,
  314. ColorChoice::Auto,
  315. )?;
  316. let config = Config::<DrkConfig>::load(config_path)?;
  317. start(&config, args).await
  318. }