Newer
Older
// consts
pub const LOCAL_DUNITER_ENDPOINT: &str = "ws://localhost:9944";
pub const LOCAL_INDEXER_ENDPOINT: &str = "http://localhost:8080/v1/graphql";
#[cfg(feature = "gdev")]
pub const GDEV_DUNITER_ENDPOINTS: [&str; 5] = [
"wss://gdev.p2p.legal:443/ws",
"wss://gdev.coinduf.eu:443/ws",
"wss://vit.fdn.org:443/ws",
"wss://gdev.cgeek.fr:443/ws",
"wss://gdev.pini.fr:443/ws",
];
#[cfg(feature = "gdev")]
pub const GDEV_INDEXER_ENDPOINTS: [&str; 2] = [
"https://gdev-indexer.p2p.legal/v1/graphql",
"https://hasura.gdev.coinduf.eu/v1/graphql",
];
// 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,
// rpc to substrate client
pub client: Option<Client>,
// graphql to duniter-indexer
pub indexer: Option<Indexer>,
// 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,
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.cfg.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 {
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
if let Some(network) = self.args.network.clone() {
// a network was provided as arugment
match &network[..] {
// force local endpoints (always available)
"local" => {
self.cfg.duniter_endpoint = String::from(LOCAL_DUNITER_ENDPOINT);
self.cfg.indexer_endpoint = String::from(LOCAL_INDEXER_ENDPOINT);
}
// if built with gdev feature, use gdev network
#[cfg(feature = "gdev")]
"gdev" => {
// TODO better strategy than first
self.cfg.duniter_endpoint =
String::from(*GDEV_DUNITER_ENDPOINTS.first().unwrap());
self.cfg.indexer_endpoint =
String::from(*GDEV_INDEXER_ENDPOINTS.first().unwrap());
}
// if built with gtest feature, use gtest network
#[cfg(feature = "gtest")]
"gtest" => {
unimplemented!();
}
// if built with g1 feature, use g1 network
#[cfg(feature = "g1")]
"g1" => {
unimplemented!();
}
other => {
panic!("unknown network \"{other}\"");
}
}
}
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
}
// secret
if self.args.secret.is_some() {
self = self.build_keypair();
}
// address
if self.args.address.is_some() {
self = self.build_address();
}
if self.cfg.address.is_none() {
self.cfg.address = Some(
get_keys(
self.args.secret_format,
&self.args.address,
&self.args.secret,
NeededKeys::Public,
)
.expect("needed")
.0
.expect("needed"),
);
}
self
}
/// ask user to input a keypair if needed
pub fn build_keypair(mut self) -> Self {
if self.keypair.is_none() {
let (address, keypair) = get_keys(
self.args.secret_format,
&self.args.address,
&self.args.secret,
.expect("needed");
self.cfg.address = address;
self.keypair = keypair;
}
pub async fn build_client(mut self) -> Result<Self, GcliError> {
let duniter_endpoint = self.cfg.duniter_endpoint.clone();
self.client = Some(Client::from_url(&duniter_endpoint).await.map_err(|e| {
GcliError::Duniter(format!(
"could not establish connection with the server {}, due to error {}",
duniter_endpoint,
dbg!(e)
))
})?);
self.genesis_hash = commands::blockchain::fetch_genesis_hash(&self).await?;
Ok(self)
pub async fn build_indexer(mut self) -> Result<Self, anyhow::Error> {
if self.args.no_indexer {
log::info!("called build_indexer while providing no_indexer");
self.indexer = None;
gql_client: reqwest::Client::builder()
.user_agent("gcli/0.1.0")
.build()?,
gql_url: self.cfg.indexer_endpoint.clone(),
});
self.indexer_genesis_hash = self.indexer().fetch_genesis_hash().await?;
if self.client.is_some() && self.indexer_genesis_hash != self.genesis_hash {
println!("⚠️ indexer does not have the same genesis hash as blockchain")
}
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
};
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 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)
// );
// );
}