data.rs 5.27 KiB
use crate::*;
use indexer::Indexer;
// data derived from command arguments
/// Data of current command
/// can also include fetched information
#[derive(Default)]
pub struct Data {
// command line arguments
pub args: Args,
// config
pub cfg: conf::Config,
// rpc to substrate client
pub client: Option<Client>,
// graphql to duniter-indexer
pub indexer: Option<Indexer>,
// user address
pub address: Option<AccountId>,
// user keypair
pub keypair: Option<Pair>,
// user identity index
pub idty_index: Option<u32>,
// token decimals
pub token_decimals: u32,
// token symbol
pub token_symbol: String,
// genesis hash
pub genesis_hash: Hash,
// indexer genesis hash
pub indexer_genesis_hash: Hash,
}
/// system properties defined in client specs
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SystemProperties {
token_decimals: u32,
token_symbol: String,
}
// implement helper functions for Data
impl Data {
/// --- constructor ---
pub fn new(args: Args) -> Self {
Self {
args,
cfg: conf::load_conf(),
token_decimals: 0,
token_symbol: "tokens".into(),
..Default::default()
}
.overwrite_from_args()
}
// --- getters ---
// the "unwrap" should not fail if data is well prepared
pub fn client(&self) -> &Client {
self.client.as_ref().expect("must build client first")
}
pub fn indexer(&self) -> &Indexer {
self.indexer.as_ref().expect("indexer is not set up")
}
pub fn address(&self) -> AccountId {
self.address.clone().expect("an address is needed")
}
pub fn keypair(&self) -> Pair {
self.keypair.clone().expect("a keypair is needed")
}
pub fn idty_index(&self) -> u32 {
self.idty_index.expect("must fetch idty index first")
}
// --- methods ---
pub fn format_balance(&self, amount: Balance) -> String {
let base: u32 = 10;
format!(
"{} {}",
(amount as f32) / (base.pow(self.token_decimals) as f32),
self.token_symbol
)
}
// --- mutators ---
/// use arguments to overwrite config
pub fn overwrite_from_args(mut self) -> Self {
if let Some(duniter_endpoint) = self.args.url.clone() {
self.cfg.duniter_endpoint = duniter_endpoint;
}
if let Some(indexer_endpoint) = self.args.indexer.clone() {
self.cfg.indexer_endpoint = indexer_endpoint
}
self
}
/// force an address if needed
pub fn build_address(mut self) -> Self {
self.address = Some(
get_keys(
self.args.secret_format,
&self.args.address,
&self.args.secret,
NeededKeys::Public,
)
.expect("needed")
.0
.expect("needed"),
);
self
}
/// force a keypair if needed
pub fn build_keypair(mut self) -> Self {
let (address, keypair) = get_keys(
self.args.secret_format,
&self.args.address,
&self.args.secret,
NeededKeys::Secret,
)
.expect("needed");
self.address = address;
self.keypair = keypair;
self
}
/// build a client from url
// TODO get client from a pre-defined list
pub async fn build_client(mut self) -> Self {
let duniter_endpoint = self.cfg.duniter_endpoint.clone();
self.client = Some(
Client::from_url(&duniter_endpoint)
.await
.unwrap_or_else(|_| {
panic!(
"could not establish connection with the server {}",
duniter_endpoint
)
}),
);
self
}
/// build an indexer if not disabled
// TODO check that indexer matches client
pub fn build_indexer(mut self) -> Result<Self, anyhow::Error> {
self.indexer = if self.args.no_indexer {
None
} else {
Some(Indexer {
gql_client: reqwest::Client::builder()
.user_agent("gcli/0.1.0")
.build()?,
gql_url: self.cfg.indexer_endpoint.clone(),
})
};
Ok(self)
}
/// get issuer index
/// needs address and client first
pub async fn fetch_idty_index(mut self) -> Result<Self, anyhow::Error> {
self.idty_index = Some(
commands::identity::get_idty_index_by_account_id(self.client(), &self.address())
.await?
.ok_or(anyhow::anyhow!("needs to be member to use this command"))?,
);
Ok(self)
}
/// get genesis hash
pub async fn fetch_genesis_hash(mut self) -> Result<Self, anyhow::Error> {
self.genesis_hash = self
.client()
.storage()
.fetch(&runtime::storage().system().block_hash(0), None)
.await?
.unwrap();
Ok(self)
}
/// get indexer genesis hash
pub async fn fetch_indexer_genesis_hash(mut self) -> Result<Self, anyhow::Error> {
self.indexer_genesis_hash = self.indexer().fetch_genesis_hash().await?;
Ok(self)
}
/// get properties
pub async fn fetch_system_properties(mut self) -> Result<Self, anyhow::Error> {
let system_properties = self.client().rpc().system_properties().await?;
let system_properties = serde_json::from_value::<SystemProperties>(
serde_json::Value::Object(system_properties),
)?;
self.token_decimals = system_properties.token_decimals;
self.token_symbol = system_properties.token_symbol;
Ok(self)
}
// TODO prevent awaits in async methods, prefer register requests and execute them all at once with a join
// example below
// example!(
// use futures::join;
// let addr_idty_index = runtime::storage().identity().identity_index_of(&account_id);
// let addr_block_hash = runtime::storage().system().block_hash(0);
// // Multiple fetches can be done in parallel.
// let (idty_index, genesis_hash) = join!(
// api.storage().fetch(&addr_idty_index, None),
// api.storage().fetch(&addr_block_hash, None)
// );
// );
}