darkfid.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. use async_trait::async_trait;
  2. use clap::clap_app;
  3. use log::debug;
  4. use serde_json::{json, Value};
  5. use std::path::PathBuf;
  6. use std::sync::Arc;
  7. use drk::{
  8. cli::{Config, DarkfidConfig},
  9. rpc::{
  10. jsonrpc::{error as jsonerr, request as jsonreq, response as jsonresp, send_request},
  11. jsonrpc::{ErrorCode::*, JsonRequest, JsonResult},
  12. rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig},
  13. },
  14. serial::serialize,
  15. util::{expand_path, join_config_path},
  16. wallet::WalletDb,
  17. Result,
  18. };
  19. #[derive(Clone)]
  20. struct Darkfid {
  21. config: DarkfidConfig,
  22. wallet: Arc<WalletDb>,
  23. tokenlist: Value,
  24. }
  25. #[async_trait]
  26. impl RequestHandler for Darkfid {
  27. // TODO: ServerError codes should be part of the lib.
  28. async fn handle_request(&self, req: JsonRequest) -> JsonResult {
  29. if req.params.as_array().is_none() {
  30. return JsonResult::Err(jsonerr(InvalidParams, None, req.id));
  31. }
  32. debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap());
  33. match req.method.as_str() {
  34. Some("say_hello") => return self.say_hello(req.id, req.params).await,
  35. Some("create_wallet") => return self.create_wallet(req.id, req.params).await,
  36. Some("key_gen") => return self.key_gen(req.id, req.params).await,
  37. Some("get_key") => return self.get_key(req.id, req.params).await,
  38. Some("get_token_id") => return self.get_token_id(req.id, req.params).await,
  39. Some("features") => return self.features(req.id, req.params).await,
  40. Some("deposit") => return self.deposit(req.id, req.params).await,
  41. Some("withdraw") => return self.withdraw(req.id, req.params).await,
  42. Some("transfer") => return self.transfer(req.id, req.params).await,
  43. Some(_) | None => return JsonResult::Err(jsonerr(MethodNotFound, None, req.id)),
  44. };
  45. }
  46. }
  47. impl Darkfid {
  48. fn new(config_path: PathBuf) -> Result<Self> {
  49. let config: DarkfidConfig = Config::<DarkfidConfig>::load(config_path)?;
  50. let wallet = WalletDb::new(
  51. expand_path(&config.wallet_path)?.as_path(),
  52. config.wallet_password.clone(),
  53. )?;
  54. // TODO: FIXME
  55. let file_contents = std::fs::read_to_string("token/solanatokenlist.json")?;
  56. let tokenlist: Value = serde_json::from_str(&file_contents)?;
  57. Ok(Self {
  58. config,
  59. wallet,
  60. tokenlist,
  61. })
  62. }
  63. // --> {"method": "say_hello", "params": []}
  64. // <-- {"result": "hello world"}
  65. async fn say_hello(&self, id: Value, _params: Value) -> JsonResult {
  66. JsonResult::Resp(jsonresp(json!("hello world"), id))
  67. }
  68. // --> {"method": "create_wallet", "params": []}
  69. // <-- {"result": true}
  70. async fn create_wallet(&self, id: Value, _params: Value) -> JsonResult {
  71. match self.wallet.init_db() {
  72. Ok(()) => return JsonResult::Resp(jsonresp(json!(true), id)),
  73. Err(e) => {
  74. return JsonResult::Err(jsonerr(ServerError(-32001), Some(e.to_string()), id))
  75. }
  76. }
  77. }
  78. // --> {"method": "key_gen", "params": []}
  79. // <-- {"result": true}
  80. async fn key_gen(&self, id: Value, _params: Value) -> JsonResult {
  81. match self.wallet.key_gen() {
  82. Ok((_, _)) => return JsonResult::Resp(jsonresp(json!(true), id)),
  83. Err(e) => {
  84. return JsonResult::Err(jsonerr(ServerError(-32002), Some(e.to_string()), id))
  85. }
  86. }
  87. }
  88. // --> {"method": "get_key", "params": []}
  89. // <-- {"result": "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC"}
  90. async fn get_key(&self, id: Value, _params: Value) -> JsonResult {
  91. match self.wallet.get_keypairs() {
  92. Ok(v) => {
  93. let pk = v[0].public;
  94. let b58 = bs58::encode(serialize(&pk)).into_string();
  95. return JsonResult::Resp(jsonresp(json!(b58), id));
  96. }
  97. Err(e) => {
  98. return JsonResult::Err(jsonerr(ServerError(-32003), Some(e.to_string()), id))
  99. }
  100. }
  101. }
  102. // --> {"method": "get_token_id", "params": [token]}
  103. // <-- {"result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL"}
  104. async fn get_token_id(&self, id: Value, params: Value) -> JsonResult {
  105. let args = params.as_array().unwrap();
  106. let symbol = &args[0];
  107. if symbol.as_str().is_none() {
  108. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  109. };
  110. let symbol = symbol.as_str().unwrap();
  111. let token_id = self.search_id(&symbol);
  112. return JsonResult::Resp(jsonresp(json!(token_id), id));
  113. }
  114. // TODO: proper error handling here
  115. fn search_id(&self, symbol: &str) -> Value {
  116. debug!(target: "DARKFID", "SEARCHING FOR {}", symbol);
  117. let tokens = self.tokenlist["tokens"]
  118. .as_array()
  119. .expect("Can't find 'tokens' in file");
  120. for item in tokens {
  121. if item["symbol"] == symbol.to_uppercase() {
  122. let address = item["address"].clone();
  123. return address;
  124. }
  125. }
  126. unreachable!();
  127. }
  128. // --> {""method": "features", "params": []}
  129. // <-- {"result": { "network": ["btc", "sol"] } }
  130. async fn features(&self, id: Value, _params: Value) -> JsonResult {
  131. // TODO: return a dictionary of features
  132. let req = jsonreq(json!("features"), json!([]));
  133. let rep: JsonResult;
  134. match send_request(&self.config.cashier_rpc_url, json!(req)).await {
  135. Ok(v) => rep = v,
  136. Err(e) => {
  137. return JsonResult::Err(jsonerr(ServerError(-32004), Some(e.to_string()), id))
  138. }
  139. }
  140. match rep {
  141. JsonResult::Resp(r) => return JsonResult::Resp(r),
  142. JsonResult::Err(e) => return JsonResult::Err(e),
  143. JsonResult::Notif(_) => return JsonResult::Err(jsonerr(InternalError, None, id)),
  144. }
  145. }
  146. // --> {"method": "deposit", "params": [network, token, publickey]}
  147. // The publickey sent here is used so the cashier can know where to send
  148. // assets once the deposit is received.
  149. // <-- {"result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL"}
  150. async fn deposit(&self, id: Value, params: Value) -> JsonResult {
  151. let args = params.as_array().unwrap();
  152. if args.len() != 2 {
  153. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  154. }
  155. let network = &args[0];
  156. let token = &args[1];
  157. if token.as_str().is_none() {
  158. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  159. }
  160. let _tkn_str = token.as_str().unwrap();
  161. // check if the token input is an ID
  162. // if not, find the associated ID
  163. // TODO
  164. //let _token_id = self.clone().parse_token(tkn_str);
  165. // TODO: Optional sanity checking here, but cashier *must* do so too.
  166. let pubkey: String;
  167. match self.wallet.get_keypairs() {
  168. Ok(v) => {
  169. let pk = v[0].public;
  170. let pk = serialize(&pk);
  171. pubkey = bs58::encode(pk).into_string();
  172. }
  173. Err(e) => {
  174. return JsonResult::Err(jsonerr(ServerError(-32003), Some(e.to_string()), id))
  175. }
  176. }
  177. // Send request to cashier. If the cashier supports the requested network
  178. // (and token), it shall return a valid address where assets can be deposited.
  179. // If not, an error is returned, and forwarded to the method caller.
  180. let req = jsonreq(json!("deposit"), json!([network, token, pubkey]));
  181. let rep: JsonResult;
  182. match send_request(&self.config.cashier_rpc_url, json!(req)).await {
  183. Ok(v) => rep = v,
  184. Err(e) => {
  185. return JsonResult::Err(jsonerr(ServerError(-32004), Some(e.to_string()), id))
  186. }
  187. }
  188. match rep {
  189. JsonResult::Resp(r) => return JsonResult::Resp(r),
  190. JsonResult::Err(e) => return JsonResult::Err(e),
  191. JsonResult::Notif(_n) => return JsonResult::Err(jsonerr(InternalError, None, id)),
  192. }
  193. }
  194. fn parse_token(&self, token: &str) -> Value {
  195. let vec: Vec<char> = token.chars().collect();
  196. let mut counter = 0;
  197. for c in vec {
  198. if c.is_alphabetic() {
  199. counter += 1;
  200. }
  201. }
  202. if counter == token.len() {
  203. self.search_id(token)
  204. } else {
  205. let token_id: Value = serde_json::from_str(token).unwrap();
  206. token_id
  207. }
  208. }
  209. // --> {"method": "withdraw", "params": [network, token, publickey, amount]}
  210. // The publickey sent here is the address where the caller wants to receive
  211. // the tokens they plan to withdraw.
  212. // On request, send request to cashier to get deposit address, and then transfer
  213. // dark assets to the cashier's wallet. Following that, the cashier should return
  214. // a transaction ID of them sending the funds that are requested for withdrawal.
  215. // <-- {"result": "txID"}
  216. async fn withdraw(&self, id: Value, params: Value) -> JsonResult {
  217. let args = params.as_array().unwrap();
  218. if args.len() != 4 {
  219. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  220. }
  221. let _network = &args[0];
  222. let _token = &args[1];
  223. let _address = &args[2];
  224. let _amount = &args[3];
  225. // 1. Send request to cashier.
  226. // 2. Cashier checks if they support the network, and if so,
  227. // return adeposit address.
  228. // 3. We issue a transfer of $amount to the given address.
  229. return JsonResult::Err(jsonerr(
  230. ServerError(-32005),
  231. Some("failed to withdraw".to_string()),
  232. id,
  233. ));
  234. }
  235. // --> {"method": "transfer", [dToken, address, amount]}
  236. // <-- {"result": "txID"}
  237. async fn transfer(&self, id: Value, _params: Value) -> JsonResult {
  238. return JsonResult::Err(jsonerr(
  239. ServerError(-32006),
  240. Some("failed to transfer".to_string()),
  241. id,
  242. ));
  243. }
  244. }
  245. #[async_std::main]
  246. async fn main() -> Result<()> {
  247. let args = clap_app!(darkfid =>
  248. (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
  249. (@arg verbose: -v --verbose "Increase verbosity")
  250. )
  251. .get_matches();
  252. let config_path = if args.is_present("CONFIG") {
  253. PathBuf::from(args.value_of("CONFIG").unwrap())
  254. } else {
  255. join_config_path(&PathBuf::from("darkfid.toml"))?
  256. };
  257. let loglevel = if args.is_present("verbose") {
  258. log::Level::Debug
  259. } else {
  260. log::Level::Info
  261. };
  262. simple_logger::init_with_level(loglevel)?;
  263. let darkfid = Darkfid::new(config_path)?;
  264. let server_config = RpcServerConfig {
  265. socket_addr: darkfid.config.rpc_listen_address.clone(),
  266. use_tls: darkfid.config.serve_tls,
  267. identity_path: expand_path(&darkfid.config.tls_identity_path.clone())?,
  268. identity_pass: darkfid.config.tls_identity_password.clone(),
  269. };
  270. listen_and_serve(server_config, darkfid).await
  271. }
  272. mod tests {
  273. #[test]
  274. fn test_token_parsing() {
  275. let token = "usdc";
  276. let vec: Vec<char> = token.chars().collect();
  277. let mut counter = 0;
  278. for c in vec {
  279. if c.is_alphabetic() {
  280. counter += 1;
  281. println!("Found letter: {}", c)
  282. }
  283. }
  284. if counter == token.len() {
  285. println!("Every character is a letter");
  286. }
  287. }
  288. }