drk.rs 12 KB

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