Skip to content
Snippets Groups Projects
data.rs 6.68 KiB
Newer Older
Hugo Trentesaux's avatar
Hugo Trentesaux committed
use crate::*;
use indexer::Indexer;
Hugo Trentesaux's avatar
Hugo Trentesaux committed

// 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",
];

Hugo Trentesaux's avatar
Hugo Trentesaux committed
// 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,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	// config
	pub cfg: conf::Config,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	// 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,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
}

/// 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(),
Hugo Trentesaux's avatar
Hugo Trentesaux committed
			token_decimals: 0,
			token_symbol: "tokens".into(),
			..Default::default()
		}
		.overwrite_from_args()
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	}
	// --- 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(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
		}
		self
	}
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	/// 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
	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)
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	}
	/// build an indexer if not disabled
	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;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
		} else {
			self.indexer = Some(Indexer {
Hugo Trentesaux's avatar
Hugo Trentesaux committed
				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")
			}
Hugo Trentesaux's avatar
Hugo Trentesaux committed
		};
		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)
	// 	);
	// );
}