forked from jl777/SuperNET
-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provide commands buy, sell, orderbook, enable, get-enabled etc.
Use common protocol data types
- Loading branch information
rozhkovdmitrii
committed
May 4, 2023
1 parent
e041dbb
commit e788710
Showing
56 changed files
with
2,291 additions
and
606 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
mm2src/adex_cli/src/activation_scheme_db/activation_scheme_impl.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use log::{debug, error, warn}; | ||
use serde_json::{json, Value as Json}; | ||
use std::collections::HashMap; | ||
|
||
use super::init_activation_scheme::get_activation_scheme_path; | ||
use crate::helpers::read_json_file; | ||
|
||
pub(crate) trait ActivationScheme { | ||
type ActivationCommand; | ||
fn get_activation_method(&self, coin: &str) -> Option<Self::ActivationCommand>; | ||
fn get_coins_list(&self) -> Vec<String>; | ||
} | ||
|
||
struct ActivationSchemeJson { | ||
scheme: HashMap<String, Json>, | ||
} | ||
|
||
impl ActivationSchemeJson { | ||
fn new() -> Self { | ||
let mut new = Self { | ||
scheme: HashMap::<String, Json>::new(), | ||
}; | ||
|
||
let Ok(Json::Array(mut results)) = Self::load_json_file() else { | ||
return new; | ||
}; | ||
new.scheme = results.iter_mut().map(Self::get_coin_pair).collect(); | ||
new | ||
} | ||
|
||
fn get_coin_pair(element: &mut Json) -> (String, Json) { | ||
let presence = element.to_string(); | ||
let Ok(result) = Self::get_coin_pair_impl(element) else { | ||
warn!("Failed to process: {presence}"); | ||
return ("".to_string(), Json::Null) | ||
}; | ||
result | ||
} | ||
|
||
fn get_coin_pair_impl(element: &mut Json) -> Result<(String, Json), ()> { | ||
let command = element.get_mut("command").ok_or(())?.take(); | ||
let coin = element.get("coin").ok_or(())?.as_str().ok_or(())?.to_string(); | ||
Ok((coin, command)) | ||
} | ||
|
||
fn load_json_file() -> Result<Json, ()> { | ||
let activation_scheme_path = get_activation_scheme_path()?; | ||
debug!("Start reading activation_scheme from: {activation_scheme_path:?}"); | ||
|
||
let mut activation_scheme: Json = read_json_file(&activation_scheme_path)?; | ||
|
||
let results = activation_scheme | ||
.get_mut("results") | ||
.ok_or_else(|| error!("Failed to get results section"))? | ||
.take(); | ||
|
||
Ok(results) | ||
} | ||
} | ||
|
||
impl ActivationScheme for ActivationSchemeJson { | ||
type ActivationCommand = Json; | ||
fn get_activation_method(&self, coin: &str) -> Option<Self::ActivationCommand> { | ||
let Some(Json::Object(object)) = self.scheme.get(coin) else { return None }; | ||
let mut copy = json!({}); | ||
for (k, v) in object.iter() { | ||
// WORKAROUND: serde_json::Value does not support removing key | ||
if *k == "userpass" { | ||
continue; | ||
} | ||
copy[k] = v.clone(); | ||
} | ||
Some(copy) | ||
} | ||
|
||
fn get_coins_list(&self) -> Vec<String> { vec!["".to_string()] } | ||
} | ||
|
||
pub(crate) fn get_activation_scheme() -> Box<dyn ActivationScheme<ActivationCommand = Json>> { | ||
let activation_scheme: Box<dyn ActivationScheme<ActivationCommand = Json>> = Box::new(ActivationSchemeJson::new()); | ||
activation_scheme | ||
} |
42 changes: 42 additions & 0 deletions
42
mm2src/adex_cli/src/activation_scheme_db/init_activation_scheme.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use common::log::{error, info}; | ||
use mm2_net::transport::slurp_url; | ||
use std::fs::OpenOptions; | ||
use std::io::Write; | ||
use std::path::PathBuf; | ||
|
||
use crate::adex_config::AdexConfigImpl; | ||
|
||
const ACTIVATION_SCHEME_FILE: &str = "activation_scheme.json"; | ||
const COIN_ACTIVATION_SOURCE: &str = "https://stats.kmd.io/api/table/coin_activation/"; | ||
|
||
pub(crate) async fn init_activation_scheme() -> Result<(), ()> { | ||
let config_path = get_activation_scheme_path()?; | ||
info!("Start getting activation_scheme from: {config_path:?}"); | ||
|
||
let mut writer = OpenOptions::new() | ||
.create(true) | ||
.truncate(true) | ||
.write(true) | ||
.open(config_path) | ||
.map_err(|error| error!("Failed to open activation_scheme file to write: {error}"))?; | ||
|
||
let activation_scheme = get_activation_scheme_data().await?; | ||
writer | ||
.write_all(&activation_scheme) | ||
.map_err(|error| error!("Failed to write activation_scheme: {error}")) | ||
} | ||
|
||
pub(crate) fn get_activation_scheme_path() -> Result<PathBuf, ()> { | ||
let mut config_path = AdexConfigImpl::get_config_dir()?; | ||
config_path.push(ACTIVATION_SCHEME_FILE); | ||
Ok(config_path) | ||
} | ||
|
||
async fn get_activation_scheme_data() -> Result<Vec<u8>, ()> { | ||
info!("Download activation_scheme from: {COIN_ACTIVATION_SOURCE}"); | ||
let (_status_code, _headers, data) = slurp_url(COIN_ACTIVATION_SOURCE).await.map_err(|error| { | ||
error!("Failed to get activation_scheme from: {COIN_ACTIVATION_SOURCE}, error: {error}"); | ||
})?; | ||
|
||
Ok(data) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
mod activation_scheme_impl; | ||
mod init_activation_scheme; | ||
|
||
pub(crate) use activation_scheme_impl::get_activation_scheme; | ||
pub(crate) use init_activation_scheme::init_activation_scheme; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use std::env; | ||
use std::io::Write; | ||
|
||
use super::adex_config::AdexConfigImpl; | ||
use super::adex_proc::ResponseHandlerImpl; | ||
use super::cli; | ||
|
||
pub(crate) struct AdexApp { | ||
config: AdexConfigImpl, | ||
} | ||
|
||
impl AdexApp { | ||
pub fn new() -> Result<AdexApp, ()> { | ||
let config = AdexConfigImpl::read_config()?; | ||
Ok(AdexApp { config }) | ||
} | ||
pub async fn execute(&self) { | ||
let mut writer = std::io::stdout(); | ||
let response_handler = ResponseHandlerImpl { | ||
writer: (&mut writer as &mut dyn Write).into(), | ||
}; | ||
let _ = cli::Cli::execute(env::args(), &self.config, &response_handler).await; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
use directories::ProjectDirs; | ||
use inquire::Password; | ||
use log::{error, info, warn}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::fmt::{Display, Formatter}; | ||
use std::fs; | ||
use std::path::{Path, PathBuf}; | ||
|
||
use crate::helpers::rewrite_json_file; | ||
|
||
const PROJECT_QUALIFIER: &str = "com"; | ||
const PROJECT_COMPANY: &str = "komodoplatform"; | ||
const PROJECT_APP: &str = "adex-cli"; | ||
const ADEX_CFG: &str = "adex_cfg.json"; | ||
|
||
const PRICE_PRECISION_MIN: usize = 8; | ||
const PRICE_PRECISION_MAX: usize = 8; | ||
const VOLUME_PRECISION_MIN: usize = 2; | ||
const VOLUME_PRECISION_MAX: usize = 5; | ||
const VOLUME_PRECISION: VolumePrecision = (VOLUME_PRECISION_MIN, VOLUME_PRECISION_MAX); | ||
const PRICE_PRECISION: PricePrecision = (PRICE_PRECISION_MIN, PRICE_PRECISION_MAX); | ||
|
||
pub(crate) type PricePrecision = (usize, usize); | ||
pub(crate) type VolumePrecision = (usize, usize); | ||
|
||
pub fn get_config() { | ||
let Ok(adex_cfg) = AdexConfigImpl::from_config_path() else { return; }; | ||
info!("{}", adex_cfg) | ||
} | ||
|
||
pub fn set_config(set_password: bool, rpc_api_uri: Option<String>) { | ||
let mut adex_cfg = AdexConfigImpl::from_config_path().unwrap_or_else(|()| AdexConfigImpl::default()); | ||
let mut is_changes_happened = false; | ||
if set_password { | ||
let rpc_password = Password::new("Enter RPC API password:") | ||
.prompt() | ||
.map(|value| { | ||
is_changes_happened = true; | ||
value | ||
}) | ||
.map_err(|error| error!("Failed to get rpc_api_password: {error}")) | ||
.ok(); | ||
adex_cfg.set_rpc_password(rpc_password); | ||
} | ||
if rpc_api_uri.is_some() { | ||
adex_cfg.set_rpc_uri(rpc_api_uri); | ||
is_changes_happened = true; | ||
} | ||
|
||
if is_changes_happened && adex_cfg.write_to_config_path().is_ok() { | ||
info!("Configuration has been set"); | ||
} else { | ||
warn!("Nothing changed"); | ||
} | ||
} | ||
|
||
pub(crate) trait AdexConfig { | ||
fn rpc_password(&self) -> String; | ||
fn rpc_uri(&self) -> String; | ||
fn orderbook_price_precision(&self) -> &PricePrecision; | ||
fn orderbook_volume_precision(&self) -> &VolumePrecision; | ||
} | ||
|
||
#[derive(Deserialize, Serialize, Debug, Default)] | ||
pub(crate) struct AdexConfigImpl { | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
rpc_password: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
rpc_uri: Option<String>, | ||
} | ||
|
||
impl AdexConfig for AdexConfigImpl { | ||
fn rpc_password(&self) -> String { self.rpc_password.as_ref().expect("No rpc_password in config").clone() } | ||
fn rpc_uri(&self) -> String { self.rpc_uri.as_ref().expect("No rpc_uri in config").clone() } | ||
fn orderbook_price_precision(&self) -> &PricePrecision { &PRICE_PRECISION } | ||
fn orderbook_volume_precision(&self) -> &VolumePrecision { &VOLUME_PRECISION } | ||
} | ||
|
||
impl Display for AdexConfigImpl { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
if !self.is_set() { | ||
return writeln!(f, "adex configuration is not set"); | ||
} | ||
if let Some(rpc_api_uri) = &self.rpc_uri { | ||
writeln!(f, "kmd wallet RPC URL: {}", rpc_api_uri)? | ||
}; | ||
|
||
if self.rpc_password.is_some() { | ||
writeln!(f, "kmd wallet RPC password: *************")? | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl AdexConfigImpl { | ||
#[cfg(test)] | ||
pub fn new(rpc_password: &str, rpc_uri: &str) -> Self { | ||
Self { | ||
rpc_password: Some(rpc_password.to_string()), | ||
rpc_uri: Some(rpc_uri.to_string()), | ||
} | ||
} | ||
|
||
pub fn read_config() -> Result<AdexConfigImpl, ()> { | ||
let config = AdexConfigImpl::from_config_path().map_err(|_| error!("Failed to get adex_config"))?; | ||
match config { | ||
config @ AdexConfigImpl { | ||
rpc_password: Some(_), | ||
rpc_uri: Some(_), | ||
} => Ok(config), | ||
_ => { | ||
warn!("Failed to process, adex_config is not fully set"); | ||
Err(()) | ||
}, | ||
} | ||
} | ||
|
||
fn is_set(&self) -> bool { self.rpc_uri.is_some() && self.rpc_password.is_some() } | ||
|
||
pub fn get_config_dir() -> Result<PathBuf, ()> { | ||
let project_dirs = ProjectDirs::from(PROJECT_QUALIFIER, PROJECT_COMPANY, PROJECT_APP) | ||
.ok_or_else(|| error!("Failed to get project_dirs"))?; | ||
let config_path: PathBuf = project_dirs.config_dir().into(); | ||
fs::create_dir_all(&config_path) | ||
.map_err(|error| error!("Failed to create config_dir: {config_path:?}, error: {error}"))?; | ||
Ok(config_path) | ||
} | ||
|
||
fn get_config_path() -> Result<PathBuf, ()> { | ||
let mut config_path = Self::get_config_dir()?; | ||
config_path.push(ADEX_CFG); | ||
Ok(config_path) | ||
} | ||
|
||
fn from_config_path() -> Result<AdexConfigImpl, ()> { | ||
let config_path = Self::get_config_path()?; | ||
|
||
if !config_path.exists() { | ||
warn!("Config is not set"); | ||
return Err(()); | ||
} | ||
Self::read_from(&config_path) | ||
} | ||
|
||
fn write_to_config_path(&self) -> Result<(), ()> { | ||
let config_path = Self::get_config_path()?; | ||
self.write_to(&config_path) | ||
} | ||
|
||
fn read_from(cfg_path: &Path) -> Result<AdexConfigImpl, ()> { | ||
let adex_path_str = cfg_path.to_str().unwrap_or("Undefined"); | ||
let adex_cfg_file = fs::File::open(cfg_path).map_err(|error| { | ||
error!("Failed to open: {adex_path_str}, error: {error}"); | ||
})?; | ||
|
||
serde_json::from_reader(adex_cfg_file) | ||
.map_err(|error| error!("Failed to read adex_cfg to read from: {adex_path_str}, error: {error}")) | ||
} | ||
|
||
fn write_to(&self, cfg_path: &Path) -> Result<(), ()> { | ||
let adex_path_str = cfg_path | ||
.to_str() | ||
.ok_or_else(|| error!("Failed to get cfg_path as str"))?; | ||
rewrite_json_file(self, adex_path_str) | ||
} | ||
|
||
fn set_rpc_password(&mut self, rpc_password: Option<String>) { self.rpc_password = rpc_password; } | ||
|
||
fn set_rpc_uri(&mut self, rpc_uri: Option<String>) { self.rpc_uri = rpc_uri; } | ||
} |
Oops, something went wrong.