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: confy::load("gcli", None).expect("must be able to build config"), token_decimals: 0, token_symbol: "tokens".into(), ..Default::default() } } // --- 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 --- /// 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 { self.client = Some(Client::from_url(&self.args.url).await.unwrap_or_else(|_| { panic!( "could not establish connection with the server {}", self.args.url ) })); 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.args.indexer.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) // ); // ); }