main.rs 13 KB

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