darkfid.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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. Error, 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().await {
  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();
  106. if args.is_none() {
  107. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  108. }
  109. let args = args.unwrap();
  110. let symbol = args[0].as_str();
  111. if symbol.is_none() {
  112. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  113. }
  114. let symbol = symbol.unwrap();
  115. let result: Result<Value> = async {
  116. let token_id = self.search_id(symbol)?;
  117. Ok(token_id)
  118. }
  119. .await;
  120. match result {
  121. Ok(res) => JsonResult::Resp(jsonresp(json!(res), json!(res))),
  122. Err(err) => JsonResult::Err(jsonerr(InternalError, Some(err.to_string()), json!(id))),
  123. }
  124. }
  125. fn search_id(&self, symbol: &str) -> Result<Value> {
  126. debug!(target: "DARKFID", "SEARCHING FOR {}", symbol);
  127. let tokens = self.tokenlist["tokens"]
  128. .as_array()
  129. .ok_or_else(|| Error::TokenParseError)?;
  130. for item in tokens {
  131. if item["symbol"] == symbol.to_uppercase() {
  132. let address = item["address"].clone();
  133. return Ok(address);
  134. }
  135. }
  136. unreachable!();
  137. }
  138. // --> {""method": "features", "params": []}
  139. // <-- {"result": { "network": ["btc", "sol"] } }
  140. async fn features(&self, id: Value, _params: Value) -> JsonResult {
  141. // TODO: return a dictionary of features
  142. let req = jsonreq(json!("features"), json!([]));
  143. let rep: JsonResult;
  144. match send_request(&self.config.cashier_rpc_url, json!(req)).await {
  145. Ok(v) => rep = v,
  146. Err(e) => {
  147. return JsonResult::Err(jsonerr(ServerError(-32004), Some(e.to_string()), id))
  148. }
  149. }
  150. match rep {
  151. JsonResult::Resp(r) => return JsonResult::Resp(r),
  152. JsonResult::Err(e) => return JsonResult::Err(e),
  153. JsonResult::Notif(_) => return JsonResult::Err(jsonerr(InternalError, None, id)),
  154. }
  155. }
  156. // --> {"method": "deposit", "params": [network, token, publickey]}
  157. // The publickey sent here is used so the cashier can know where to send
  158. // assets once the deposit is received.
  159. // <-- {"result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL"}
  160. async fn deposit(&self, id: Value, params: Value) -> JsonResult {
  161. let args = params.as_array();
  162. if args.is_none() {
  163. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  164. }
  165. let args = args.unwrap();
  166. if args.len() != 2 {
  167. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  168. }
  169. let network = &args[0];
  170. let token = &args[1];
  171. if token.as_str().is_none() {
  172. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  173. }
  174. let _tkn_str = token.as_str().unwrap();
  175. // check if the token input is an ID
  176. // if not, find the associated ID
  177. // TODO
  178. //let _token_id = self.clone().parse_token(tkn_str);
  179. // TODO: Optional sanity checking here, but cashier *must* do so too.
  180. let pubkey: String;
  181. match self.wallet.get_keypairs() {
  182. Ok(v) => {
  183. let pk = v[0].public;
  184. let pk = serialize(&pk);
  185. pubkey = bs58::encode(pk).into_string();
  186. }
  187. Err(e) => {
  188. return JsonResult::Err(jsonerr(ServerError(-32003), Some(e.to_string()), id))
  189. }
  190. }
  191. // Send request to cashier. If the cashier supports the requested network
  192. // (and token), it shall return a valid address where assets can be deposited.
  193. // If not, an error is returned, and forwarded to the method caller.
  194. let req = jsonreq(json!("deposit"), json!([network, token, pubkey]));
  195. let rep: JsonResult;
  196. match send_request(&self.config.cashier_rpc_url, json!(req)).await {
  197. Ok(v) => rep = v,
  198. Err(e) => {
  199. return JsonResult::Err(jsonerr(ServerError(-32004), Some(e.to_string()), id))
  200. }
  201. }
  202. match rep {
  203. JsonResult::Resp(r) => return JsonResult::Resp(r),
  204. JsonResult::Err(e) => return JsonResult::Err(e),
  205. JsonResult::Notif(_n) => return JsonResult::Err(jsonerr(InternalError, None, id)),
  206. }
  207. }
  208. fn parse_token(&self, token: &str) -> Result<Value> {
  209. let vec: Vec<char> = token.chars().collect();
  210. let mut counter = 0;
  211. for c in vec {
  212. if c.is_alphabetic() {
  213. counter += 1;
  214. }
  215. }
  216. if counter == token.len() {
  217. self.search_id(token)
  218. } else {
  219. let token_id: Value = serde_json::from_str(token)?;
  220. Ok(token_id)
  221. }
  222. }
  223. // --> {"method": "withdraw", "params": [network, token, publickey, amount]}
  224. // The publickey sent here is the address where the caller wants to receive
  225. // the tokens they plan to withdraw.
  226. // On request, send request to cashier to get deposit address, and then transfer
  227. // dark assets to the cashier's wallet. Following that, the cashier should return
  228. // a transaction ID of them sending the funds that are requested for withdrawal.
  229. // <-- {"result": "txID"}
  230. async fn withdraw(&self, id: Value, params: Value) -> JsonResult {
  231. let args = params.as_array();
  232. if args.is_none() {
  233. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  234. }
  235. let args = args.unwrap();
  236. if args.len() != 4 {
  237. return JsonResult::Err(jsonerr(InvalidParams, None, id));
  238. }
  239. let _network = &args[0];
  240. let _token = &args[1];
  241. let _address = &args[2];
  242. let _amount = &args[3];
  243. // 1. Send request to cashier.
  244. // 2. Cashier checks if they support the network, and if so,
  245. // return adeposit address.
  246. // 3. We issue a transfer of $amount to the given address.
  247. return JsonResult::Err(jsonerr(
  248. ServerError(-32005),
  249. Some("failed to withdraw".to_string()),
  250. id,
  251. ));
  252. }
  253. // --> {"method": "transfer", [dToken, address, amount]}
  254. // <-- {"result": "txID"}
  255. async fn transfer(&self, id: Value, _params: Value) -> JsonResult {
  256. return JsonResult::Err(jsonerr(
  257. ServerError(-32006),
  258. Some("failed to transfer".to_string()),
  259. id,
  260. ));
  261. }
  262. }
  263. #[async_std::main]
  264. async fn main() -> Result<()> {
  265. let args = clap_app!(darkfid =>
  266. (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
  267. (@arg verbose: -v --verbose "Increase verbosity")
  268. )
  269. .get_matches();
  270. let config_path = if args.is_present("CONFIG") {
  271. PathBuf::from(args.value_of("CONFIG").unwrap())
  272. } else {
  273. join_config_path(&PathBuf::from("darkfid.toml"))?
  274. };
  275. let loglevel = if args.is_present("verbose") {
  276. log::Level::Debug
  277. } else {
  278. log::Level::Info
  279. };
  280. simple_logger::init_with_level(loglevel)?;
  281. let darkfid = Darkfid::new(config_path)?;
  282. let server_config = RpcServerConfig {
  283. socket_addr: darkfid.config.rpc_listen_address.clone(),
  284. use_tls: darkfid.config.serve_tls,
  285. identity_path: expand_path(&darkfid.config.tls_identity_path.clone())?,
  286. identity_pass: darkfid.config.tls_identity_password.clone(),
  287. };
  288. listen_and_serve(server_config, darkfid).await
  289. }
  290. mod tests {
  291. #[test]
  292. fn test_token_parsing() {
  293. let token = "usdc";
  294. let vec: Vec<char> = token.chars().collect();
  295. let mut counter = 0;
  296. for c in vec {
  297. if c.is_alphabetic() {
  298. counter += 1;
  299. println!("Found letter: {}", c)
  300. }
  301. }
  302. if counter == token.len() {
  303. println!("Every character is a letter");
  304. }
  305. }
  306. }