cli: Implement drk2 for JSON-RPC API reference.

This commit is contained in:
parazyd
2021-09-14 18:04:05 +02:00
parent 586793c3dc
commit 47b0c211ff
3 changed files with 265 additions and 1 deletions

256
src/bin/drk2.rs Normal file
View File

@@ -0,0 +1,256 @@
#[macro_use]
extern crate clap;
use clap::ArgMatches;
use drk::cli::{Config, DrkConfig};
use drk::util::join_config_path;
use drk::{rpc::jsonrpc, rpc::jsonrpc::JsonResult, Error, Result};
use log::debug;
use serde_json::{json, Value};
use simplelog::{
CombinedLogger, Config as SimplelogConfig, ConfigBuilder, LevelFilter, TermLogger,
TerminalMode, WriteLogger,
};
use std::path::PathBuf;
struct Drk {
url: String,
}
impl Drk {
pub fn new(url: String) -> Self {
Self { url }
}
async fn request(&self, r: jsonrpc::JsonRequest) -> Result<Value> {
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?;
let v: JsonResult = serde_json::from_str(&json)?;
match v {
JsonResult::Resp(r) => {
debug!(target: "DRK", "<-- {:?}", r);
return Ok(r.result);
}
JsonResult::Err(e) => {
debug!(target: "DRK", "<-- {:?}", 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(),
));
}
};
}
// --> {"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?)
}
// --> {"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?)
}
// --> {"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?)
}
// --> {"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?)
}
// --> {"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?)
}
// --> {"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?)
}
// --> {"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: ArgMatches<'_>) -> Result<()> {
let client = Drk::new(config.rpc_url.clone());
if options.is_present("hello") {
let reply = client.say_hello().await?;
println!("Server replied: {}", &reply.to_string());
return Ok(());
}
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 matches.is_present("keygen") {
let reply = client.key_gen().await?;
println!("Server replied: {}", &reply.to_string());
return Ok(());
}
if matches.is_present("address") {
let reply = client.get_key().await?;
println!("Server replied: {}", &reply.to_string());
return Ok(());
}
}
if let Some(matches) = options.subcommand_matches("deposit") {
let network = matches.value_of("network").unwrap().to_lowercase();
let token = matches.value_of("TOKEN").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(matches) = options.subcommand_matches("withdraw") {
let network = matches.value_of("network").unwrap().to_lowercase();
let token = matches.value_of("TOKEN").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 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(());
}
println!("Please run 'drk help' to see usage.");
Err(Error::MissingParams)
}
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 deposit =>
(about: "Deposit clear assets for Dark assets")
(@arg network: +required +takes_value --network
"Which network to use (bitcoin/solana/...)")
(@arg TOKEN: +required
"Which token to deposit (BTC/SOL/USDC/...)")
)
(@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 TOKEN: +required "Desired asset")
(@arg ADDRESS: +required "Recipient address")
(@arg AMOUNT: +required "Amount to send")
)
)
.get_matches();
let config_path: PathBuf;
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 = Config::<DrkConfig>::load(config_path)?;
let logger_config = ConfigBuilder::new().set_time_format_str("%T%.6f").build();
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,
SimplelogConfig::default(),
std::fs::File::create(log_path).unwrap(),
),
])
.unwrap();
futures::executor::block_on(start(&config, args))
}

View File

@@ -9,6 +9,7 @@ use std::{
str,
};
#[derive(Default)]
pub struct Config<T> {
config: PhantomData<T>,
}

View File

@@ -16,6 +16,7 @@ pub enum Error {
/// Parsing error
ParseFailed(&'static str),
ParseIntError,
ParseFloatError,
AsyncChannelSenderError,
AsyncChannelReceiverError,
MalformedPacket,
@@ -71,6 +72,7 @@ impl fmt::Display for Error {
Error::NonMinimalVarInt => f.write_str("non-minimal varint"),
Error::ParseFailed(ref err) => write!(f, "parse failed: {}", err),
Error::ParseIntError => f.write_str("Parse int error"),
Error::ParseFloatError => f.write_str("Parse float error"),
Error::AsyncChannelSenderError => f.write_str("Async_channel sender error"),
Error::AsyncChannelReceiverError => f.write_str("Async_channel receiver error"),
Error::MalformedPacket => f.write_str("Malformed packet"),
@@ -199,6 +201,12 @@ impl From<std::num::ParseIntError> for Error {
}
}
impl From<std::num::ParseFloatError> for Error {
fn from(_err: std::num::ParseFloatError) -> Error {
Error::ParseFloatError
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(_err: std::string::FromUtf8Error) -> Error {
Error::Utf8Error
@@ -266,4 +274,3 @@ impl From<bs58::decode::Error> for Error {
Error::Base58DecodeError(err.to_string())
}
}