|
|
@@ -1,15 +1,16 @@
|
|
|
+use log::debug;
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
-use serde_json::json;
|
|
|
+use clap::{clap_app, ArgMatches};
|
|
|
+use serde_json::{json, Value};
|
|
|
+use simplelog::{
|
|
|
+ CombinedLogger, Config as SimplelogConfig, ConfigBuilder, LevelFilter, TermLogger,
|
|
|
+ TerminalMode, WriteLogger,
|
|
|
+};
|
|
|
|
|
|
-use drk::cli::{Config, DrkCli, DrkConfig};
|
|
|
-use drk::rpc::jsonrpc;
|
|
|
-use drk::rpc::jsonrpc::JsonResult;
|
|
|
-use drk::serial::serialize;
|
|
|
+use drk::cli::{Config, DrkConfig};
|
|
|
use drk::util::join_config_path;
|
|
|
-use drk::{Error, Result};
|
|
|
-
|
|
|
-use log::debug;
|
|
|
+use drk::{rpc::jsonrpc, rpc::jsonrpc::JsonResult, Error, Result};
|
|
|
|
|
|
struct Drk {
|
|
|
url: String,
|
|
|
@@ -20,169 +21,273 @@ impl Drk {
|
|
|
Self { url }
|
|
|
}
|
|
|
|
|
|
- async fn request(&self, method_name: &str, r: jsonrpc::JsonRequest) -> Result<()> {
|
|
|
- // TODO: Return actual JSON result
|
|
|
- let data = surf::Body::from_json(&r)?;
|
|
|
- debug!(target: "DRK", "--> {:?}", r);
|
|
|
- let mut req = surf::post(&self.url).body(data).await?;
|
|
|
-
|
|
|
- let resp = req.take_body();
|
|
|
- let json = resp.into_string().await?;
|
|
|
+ async fn request(&self, r: jsonrpc::JsonRequest) -> Result<Value> {
|
|
|
+ let reply: JsonResult;
|
|
|
+ debug!(target: "DRK", "--> {:#?}", serde_json::to_string(&r)?);
|
|
|
+ match jsonrpc::send_request(self.url.clone(), json!(r)).await {
|
|
|
+ Ok(v) => reply = v,
|
|
|
+ Err(e) => return Err(e),
|
|
|
+ }
|
|
|
|
|
|
- let v: JsonResult = serde_json::from_str(&json)?;
|
|
|
- match v {
|
|
|
+ match reply {
|
|
|
JsonResult::Resp(r) => {
|
|
|
- debug!(target: "DRK", "<-- {:?}", r);
|
|
|
- println!("{}: {}", method_name, r.result);
|
|
|
- return Ok(());
|
|
|
+ debug!(target: "DRK", "<-- {:#?}", serde_json::to_string(&r)?);
|
|
|
+ return Ok(r.result);
|
|
|
}
|
|
|
|
|
|
JsonResult::Err(e) => {
|
|
|
- debug!(target: "DRK", "<-- {:?}", e);
|
|
|
+ debug!(target: "DRK", "<-- {:#?}", serde_json::to_string(&e)?);
|
|
|
return Err(Error::JsonRpcError(e.error.message.to_string()));
|
|
|
}
|
|
|
|
|
|
JsonResult::Notif(n) => {
|
|
|
- debug!(target: "DRK", "<-- {:?}", n);
|
|
|
- return Err(Error::JsonRpcError(
|
|
|
- "Unexpected reply from server".to_string(),
|
|
|
- ));
|
|
|
+ debug!(target: "DRK", "<-- {:#?}", serde_json::to_string(&n)?);
|
|
|
+ return Err(Error::JsonRpcError("Unexpected reply".to_string()));
|
|
|
}
|
|
|
- };
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- pub async fn say_hello(&self) -> Result<()> {
|
|
|
- let r = jsonrpc::request(json!("say_hello"), json!([]));
|
|
|
- Ok(self.request("say hello", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "say_hello", "params": [], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": "hello world", "id": 42}
|
|
|
+ async fn say_hello(&self) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("say_hello"), json!([]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn create_wallet(&self) -> Result<()> {
|
|
|
- let r = jsonrpc::request(json!("create_wallet"), json!([]));
|
|
|
- Ok(self.request("create wallet", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "create_wallet", "params": [], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
|
|
|
+ async fn create_wallet(&self) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("create_wallet"), json!([]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn key_gen(&self) -> Result<()> {
|
|
|
- let r = jsonrpc::request(json!("key_gen"), json!([]));
|
|
|
- Ok(self.request("key gen", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "key_gen", "params": [], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
|
|
|
+ async fn key_gen(&self) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("key_gen"), json!([]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn get_key(&self) -> Result<()> {
|
|
|
- let r = jsonrpc::request(json!("get_key"), json!([]));
|
|
|
- Ok(self.request("get key", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "get_key", "params": [], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC", "id": 42}
|
|
|
+ async fn get_key(&self) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("get_key"), json!([]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn get_info(&self) -> Result<()> {
|
|
|
- let r = jsonrpc::request(json!("get_info"), json!([]));
|
|
|
- Ok(self.request("get info", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "get_key", "params": ["usdc"], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC", "id": 42}
|
|
|
+ async fn get_token_id(&self, token: &str) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("get_token_id"), json!([token]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn stop(&self) -> Result<()> {
|
|
|
- let r = jsonrpc::request(json!("stop"), json!([]));
|
|
|
- Ok(self.request("stop", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "features", "params": [], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": ["network": "btc", "sol"], "id": 42}
|
|
|
+ async fn features(&self) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("features"), json!([]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn deposit(&self, asset: Vec<u8>) -> Result<()> {
|
|
|
- let r = jsonrpc::request(json!("deposit"), json!([asset]));
|
|
|
- Ok(self.request("deposit coins to this address:", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "deposit", "params": ["solana", "usdc"], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL", "id": 42}
|
|
|
+ async fn deposit(&self, network: &str, asset: &str) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("deposit"), json!([network, asset]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn transfer(&self, asset: Vec<u8>, address: String, amount: f64) -> Result<()> {
|
|
|
- let address = serialize(&address);
|
|
|
- let r = jsonrpc::request(json!("transfer"), json!([asset, address, amount]));
|
|
|
- Ok(self.request("transfer", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "withdraw",
|
|
|
+ // "params": ["solana", "usdc", "Ht5G1RhkcKnpLVLMhqJc5aqZ4wYUEbxbtZwGCVbgU7DL", 13.37"], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": "txID", "id": 42}
|
|
|
+ async fn withdraw(
|
|
|
+ &self,
|
|
|
+ network: &str,
|
|
|
+ asset: &str,
|
|
|
+ address: &str,
|
|
|
+ amount: f64,
|
|
|
+ ) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("withdraw"), json!([network, asset, address, amount]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
|
|
|
- pub async fn withdraw(&self, asset: Vec<u8>, address: String, amount: f64) -> Result<()> {
|
|
|
- let address = serialize(&address);
|
|
|
- let r = jsonrpc::request(json!("withdraw"), json!([asset, address, amount]));
|
|
|
- Ok(self.request("withdraw", r).await?)
|
|
|
+ // --> {"jsonrpc": "2.0", "method": "transfer",
|
|
|
+ // "params": ["dusdc", "vdNS7oBj7KvsMWWmo9r96SV4SqATLrGsH2a3PGpCfJC", 13.37], "id": 42}
|
|
|
+ // <-- {"jsonrpc": "2.0", "result": "txID", "id": 42}
|
|
|
+ async fn transfer(&self, asset: &str, address: &str, amount: f64) -> Result<Value> {
|
|
|
+ let req = jsonrpc::request(json!("transfer"), json!([asset, address, amount]));
|
|
|
+ Ok(self.request(req).await?)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async fn start(config: &DrkConfig, options: DrkCli) -> Result<()> {
|
|
|
- let url = config.rpc_url.clone();
|
|
|
- let client = Drk::new(url);
|
|
|
+async fn start(config: &DrkConfig, options: ArgMatches<'_>) -> Result<()> {
|
|
|
+ let client = Drk::new(config.rpc_url.clone());
|
|
|
|
|
|
- if options.wallet {
|
|
|
- client.create_wallet().await?;
|
|
|
+ if options.is_present("hello") {
|
|
|
+ let reply = client.say_hello().await?;
|
|
|
+ println!("Server replied: {}", &reply.to_string());
|
|
|
+ return Ok(());
|
|
|
}
|
|
|
|
|
|
- if options.key {
|
|
|
- client.key_gen().await?;
|
|
|
- }
|
|
|
+ if let Some(matches) = options.subcommand_matches("wallet") {
|
|
|
+ if matches.is_present("create") {
|
|
|
+ let reply = client.create_wallet().await?;
|
|
|
+ println!("Server replied: {}", &reply.to_string());
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
|
|
|
- if options.get_key {
|
|
|
- client.get_key().await?;
|
|
|
- }
|
|
|
+ if matches.is_present("keygen") {
|
|
|
+ let reply = client.key_gen().await?;
|
|
|
+ println!("Server replied: {}", &reply.to_string());
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
|
|
|
- if options.info {
|
|
|
- client.get_info().await?;
|
|
|
+ if matches.is_present("address") {
|
|
|
+ let reply = client.get_key().await?;
|
|
|
+ println!("Server replied: {}", &reply.to_string());
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if options.hello {
|
|
|
- client.say_hello().await?;
|
|
|
+ if let Some(matches) = options.subcommand_matches("id") {
|
|
|
+ let token = matches.value_of("TOKEN").unwrap();
|
|
|
+
|
|
|
+ let reply = client.get_token_id(&token).await?;
|
|
|
+
|
|
|
+ println!("Server replied: {}", &reply.to_string());
|
|
|
+ return Ok(());
|
|
|
}
|
|
|
|
|
|
- if let Some(transfer) = options.transfer {
|
|
|
- client
|
|
|
- .transfer(transfer.asset_id, transfer.pub_key, transfer.amount)
|
|
|
- .await?;
|
|
|
+ if options.is_present("features") {
|
|
|
+ let reply = client.features().await?;
|
|
|
+ println!("Server replied: {}", &reply.to_string());
|
|
|
+ return Ok(());
|
|
|
}
|
|
|
|
|
|
- if let Some(deposit) = options.deposit {
|
|
|
- client.deposit(deposit.asset_id).await?;
|
|
|
+ if let Some(matches) = options.subcommand_matches("deposit") {
|
|
|
+ let network = matches.value_of("network").unwrap().to_lowercase();
|
|
|
+ // TODO: check that it's a tokenID and not a symbol
|
|
|
+ let token = matches.value_of("TOKENID").unwrap();
|
|
|
+
|
|
|
+ // TODO: Retrieve cashier features and error if they
|
|
|
+ // don't support the network.
|
|
|
+
|
|
|
+ let reply = client.deposit(&network, &token).await?;
|
|
|
+
|
|
|
+ println!(
|
|
|
+ "Deposit your coins to the following address: {}",
|
|
|
+ &reply.to_string()
|
|
|
+ );
|
|
|
+
|
|
|
+ return Ok(());
|
|
|
}
|
|
|
|
|
|
- if let Some(withdraw) = options.withdraw {
|
|
|
- client
|
|
|
- .withdraw(withdraw.asset_id, withdraw.pub_key, withdraw.amount)
|
|
|
- .await?;
|
|
|
+ if let Some(matches) = options.subcommand_matches("withdraw") {
|
|
|
+ let network = matches.value_of("network").unwrap().to_lowercase();
|
|
|
+ // TODO: check that it's a tokenID and not a symbol
|
|
|
+ let token = matches.value_of("TOKENID").unwrap();
|
|
|
+ let address = matches.value_of("ADDRESS").unwrap();
|
|
|
+ let amount = matches.value_of("AMOUNT").unwrap().parse::<f64>()?;
|
|
|
+
|
|
|
+ // TODO: Retrieve cashier features and error if they
|
|
|
+ // don't support the network.
|
|
|
+
|
|
|
+ let reply = client.withdraw(&network, &token, &address, amount).await?;
|
|
|
+
|
|
|
+ println!("Transaction ID: {}", &reply.to_string());
|
|
|
+
|
|
|
+ return Ok(());
|
|
|
}
|
|
|
|
|
|
- if options.stop {
|
|
|
- client.stop().await?;
|
|
|
+ if let Some(matches) = options.subcommand_matches("transfer") {
|
|
|
+ let asset_type = matches.value_of("ASSET_TYPE").unwrap();
|
|
|
+ let address = matches.value_of("ADDRESS").unwrap();
|
|
|
+ let amount = matches.value_of("AMOUNT").unwrap().parse::<f64>()?;
|
|
|
+
|
|
|
+ let reply = client.transfer(&asset_type, &address, amount).await?;
|
|
|
+
|
|
|
+ println!("Transaction ID: {}", &reply.to_string());
|
|
|
+
|
|
|
+ return Ok(());
|
|
|
}
|
|
|
|
|
|
- Ok(())
|
|
|
+ println!("Please run 'drk help' to see usage.");
|
|
|
+ Err(Error::MissingParams)
|
|
|
}
|
|
|
|
|
|
-fn main() -> Result<()> {
|
|
|
- let options = DrkCli::load()?;
|
|
|
+#[tokio::main]
|
|
|
+async fn main() -> Result<()> {
|
|
|
+ let args = clap_app!(drk =>
|
|
|
+ (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
|
|
|
+ (@arg verbose: -v --verbose "Increase verbosity")
|
|
|
+ (@subcommand hello =>
|
|
|
+ (about: "Say hello to the RPC")
|
|
|
+ )
|
|
|
+ (@subcommand wallet =>
|
|
|
+ (about: "Wallet operations")
|
|
|
+ (@arg create: --create "Initialize a new wallet")
|
|
|
+ (@arg keygen: --keygen "Generate wallet keypair")
|
|
|
+ (@arg address: --address "Get wallet address")
|
|
|
+ )
|
|
|
+ (@subcommand id =>
|
|
|
+ (about: "Get hexidecimal ID for token symbol")
|
|
|
+ (@arg TOKEN: +required
|
|
|
+ "Which token to query (BTC/SOL/USDC/...)")
|
|
|
+ )
|
|
|
+ (@subcommand features =>
|
|
|
+ (about: "Show what features the cashier supports")
|
|
|
+ )
|
|
|
+ (@subcommand deposit =>
|
|
|
+ (about: "Deposit clear assets for Dark assets")
|
|
|
+ (@arg network: +required +takes_value --network
|
|
|
+ "Which network to use (bitcoin/solana/...)")
|
|
|
+ (@arg TOKENID: +required
|
|
|
+ "Which tokenID to deposit (alphanumeric string)")
|
|
|
+ )
|
|
|
+ (@subcommand transfer =>
|
|
|
+ (about: "Transfer Dark assets to address")
|
|
|
+ (@arg ASSET_TYPE: +required "Desired asset")
|
|
|
+ (@arg ADDRESS: +required "Recipient address")
|
|
|
+ (@arg AMOUNT: +required "Amount to send")
|
|
|
+ )
|
|
|
+ (@subcommand withdraw =>
|
|
|
+ (about: "Withdraw Dark assets for clear assets")
|
|
|
+ (@arg network: +required +takes_value --network
|
|
|
+ "Which network to use (bitcoin/solana/...)")
|
|
|
+ (@arg TOKENID: +required "Which tokenID to receive (alphanumeric string)")
|
|
|
+ (@arg ADDRESS: +required "Recipient address")
|
|
|
+ (@arg AMOUNT: +required "Amount to send")
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .get_matches();
|
|
|
|
|
|
let config_path: PathBuf;
|
|
|
-
|
|
|
- match options.config.as_ref() {
|
|
|
- Some(path) => {
|
|
|
- config_path = path.to_owned();
|
|
|
- }
|
|
|
- None => {
|
|
|
- config_path = join_config_path(&PathBuf::from("drk.toml"))?;
|
|
|
- }
|
|
|
+ if args.is_present("CONFIG") {
|
|
|
+ config_path = PathBuf::from(args.value_of("CONFIG").unwrap());
|
|
|
+ } else {
|
|
|
+ config_path = join_config_path(&PathBuf::from("drk.toml"))?;
|
|
|
}
|
|
|
|
|
|
- let config: DrkConfig = Config::<DrkConfig>::load(config_path)?;
|
|
|
+ let config = Config::<DrkConfig>::load(config_path)?;
|
|
|
|
|
|
- {
|
|
|
- use simplelog::*;
|
|
|
- let logger_config = ConfigBuilder::new().set_time_format_str("%T%.6f").build();
|
|
|
+ let logger_config = ConfigBuilder::new().set_time_format_str("%T%.6f").build();
|
|
|
|
|
|
- let debug_level = if options.verbose {
|
|
|
- LevelFilter::Debug
|
|
|
- } else {
|
|
|
- LevelFilter::Off
|
|
|
- };
|
|
|
+ let debug_level = if args.is_present("verbose") {
|
|
|
+ LevelFilter::Debug
|
|
|
+ } else {
|
|
|
+ LevelFilter::Off
|
|
|
+ };
|
|
|
|
|
|
- let log_path = config.log_path.clone();
|
|
|
- CombinedLogger::init(vec![
|
|
|
- TermLogger::new(debug_level, logger_config, TerminalMode::Mixed).unwrap(),
|
|
|
- WriteLogger::new(
|
|
|
- LevelFilter::Debug,
|
|
|
- Config::default(),
|
|
|
- std::fs::File::create(log_path).unwrap(),
|
|
|
- ),
|
|
|
- ])
|
|
|
- .unwrap();
|
|
|
- }
|
|
|
+ let log_path = config.log_path.clone();
|
|
|
+
|
|
|
+ CombinedLogger::init(vec![
|
|
|
+ TermLogger::new(debug_level, logger_config, TerminalMode::Mixed).unwrap(),
|
|
|
+ WriteLogger::new(
|
|
|
+ LevelFilter::Debug,
|
|
|
+ SimplelogConfig::default(),
|
|
|
+ std::fs::File::create(log_path).unwrap(),
|
|
|
+ ),
|
|
|
+ ])
|
|
|
+ .unwrap();
|
|
|
|
|
|
- futures::executor::block_on(start(&config, options))
|
|
|
+ start(&config, args).await
|
|
|
}
|