main.rs 13 KB

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