diff --git a/src/commands.rs b/src/commands.rs index f419bf31866d46b3a4c0c9d4a919847b8257b869..dc2ab80a11f89c838bf0c569de0333179223443d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,3 +1,4 @@ +pub mod account; pub mod collective; pub mod expire; pub mod identity; diff --git a/src/commands/account.rs b/src/commands/account.rs new file mode 100644 index 0000000000000000000000000000000000000000..a04649c32764c02980a3e5616a73fb4f150ec1bb --- /dev/null +++ b/src/commands/account.rs @@ -0,0 +1,28 @@ +use crate::*; + +use anyhow::Result; +use sp_core::crypto::AccountId32; + +pub async fn get_balance(data: Data) -> Result<()> { + let account_id = data.address(); + let account_info = get_account_info(data.client(), &account_id).await?; + if let Some(account_info) = account_info { + println!( + "{account_id} has {}", + data.format_balance(account_info.data.free) + ); + } else { + println!("account {account_id} does not exist") + } + Ok(()) +} + +pub async fn get_account_info( + client: Client, + account_id: &AccountId32, +) -> Result<Option<AccountInfo>> { + Ok(client + .storage() + .fetch(&runtime::storage().system().account(account_id), None) + .await?) +} diff --git a/src/commands/identity.rs b/src/commands/identity.rs index 71562d9e3d44f0a6232ebe9dcd562b39883881f7..5f10aa622f47c497bd4efc4926968d3f993a7789 100644 --- a/src/commands/identity.rs +++ b/src/commands/identity.rs @@ -1,8 +1,11 @@ use crate::indexer::*; use crate::*; +use crate::commands::revocation::get_revoc_doc; use crate::runtime::runtime_types::common_runtime::entities::IdtyData; use crate::runtime::runtime_types::pallet_identity::types::*; +use crate::runtime::runtime_types::sp_core::sr25519::Signature; +use crate::runtime::runtime_types::sp_runtime::MultiSignature; use anyhow::{anyhow, Result}; use sp_core::{crypto::AccountId32, sr25519::Pair}; use std::str::FromStr; @@ -106,14 +109,14 @@ pub async fn create_identity( client: Client, target: AccountId32, ) -> Result<TxProgress, subxt::Error> { - Ok(client + client .tx() .sign_and_submit_then_watch( &runtime::tx().identity().create_identity(target), &PairSigner::new(pair), BaseExtrinsicParamsBuilder::new(), ) - .await?) + .await } pub async fn confirm_identity( @@ -121,12 +124,39 @@ pub async fn confirm_identity( client: Client, name: String, ) -> Result<TxProgress, subxt::Error> { - Ok(client + client .tx() .sign_and_submit_then_watch( &runtime::tx().identity().confirm_identity(name), &PairSigner::new(pair), BaseExtrinsicParamsBuilder::new(), ) - .await?) + .await +} + +// TODO : use Result<TxProgress, subxt::Error> instead of Result<()> +pub async fn revoke_identity(pair: Pair, client: Client, account_id: AccountId32) -> Result<()> { + let (idty_index, signature) = get_revoc_doc(&client, &pair).await?; + + // Transform signature to MultiSignature + // TODO: this is a hack, we should be able to use the signature directly + let signature = Signature(signature.0); + let multisign = MultiSignature::Sr25519(signature); + + client + .tx() + .sign_and_submit_then_watch( + &runtime::tx() + .identity() + .revoke_identity(idty_index, account_id, multisign), + &PairSigner::new(pair), + BaseExtrinsicParamsBuilder::new(), + ) + .await? + .wait_for_in_block() + .await? + .wait_for_success() + .await?; + + Ok(()) } diff --git a/src/commands/revocation.rs b/src/commands/revocation.rs index a03dd9c33153379a033ef8ce378b6fe52a01c577..69a23041b00143a53d15233f22f78ff222712844 100644 --- a/src/commands/revocation.rs +++ b/src/commands/revocation.rs @@ -2,12 +2,22 @@ use crate::*; use anyhow::Result; use futures::join; +use sp_core::sr25519::Signature; use sp_core::{sr25519::Pair, Encode, Pair as _}; -pub async fn gen_revoc_doc(api: &Client, pair: &Pair) -> Result<()> { +pub async fn generate_revoc_doc(api: &Client, pair: &Pair) -> Result<()> { + let signature = get_revoc_doc(api, pair).await?.1; + + println!("0x{}", hex::encode(signature)); + + Ok(()) +} + +pub async fn get_revoc_doc(api: &Client, pair: &Pair) -> Result<(u32, Signature)> { let account_id: sp_core::crypto::AccountId32 = pair.public().into(); 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 a single request. let (idty_index, genesis_hash) = join!( api.storage().fetch(&addr_idty_index, None,), api.storage().fetch(&addr_block_hash, None) @@ -17,7 +27,5 @@ pub async fn gen_revoc_doc(api: &Client, pair: &Pair) -> Result<()> { let payload = (b"revo", genesis_hash, idty_index).encode(); let signature = pair.sign(&payload); - println!("0x{}", hex::encode(signature)); - - Ok(()) + Ok((idty_index, signature)) } diff --git a/src/main.rs b/src/main.rs index 00dfb471286846072dccb0cdc3170ca3e710fdd4..3f8ec7a72885e295886b96b4e7793e4f2de4ba5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,17 +6,28 @@ mod keys; use clap::Parser; use codec::Encode; use keys::*; +use serde::Deserialize; use sp_core::sr25519::Pair; use sp_core::H256; #[cfg(feature = "dev")] -#[subxt::subxt(runtime_metadata_path = "res/metadata.scale")] -pub mod runtime {} +#[subxt::subxt( + runtime_metadata_path = "res/metadata.scale", + derive_for_all_types = "Debug" +)] +pub mod runtime { + // IF NEEDED + // #[subxt(substitute_type = "spcore::sr25519::Signature")] + // use crate::gdev::runtime_types::sp_core::sr25519::Signature; +} pub type Client = subxt::OnlineClient<Runtime>; pub type AccountId = subxt::ext::sp_runtime::AccountId32; pub type TxInBlock = subxt::tx::TxInBlock<Runtime, Client>; pub type TxProgress = subxt::tx::TxProgress<Runtime, Client>; +pub type Balance = u64; +pub type AccountData = runtime::runtime_types::pallet_duniter_account::types::AccountData<Balance>; +pub type AccountInfo = runtime::runtime_types::frame_system::AccountInfo<u32, AccountData>; pub enum Runtime {} impl subxt::config::Config for Runtime { @@ -88,14 +99,25 @@ pub struct Data { pub address: Option<AccountId>, pub keypair: Option<Pair>, pub idty_index: Option<u32>, + pub token_decimals: u32, + pub token_symbol: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SystemProperties { + token_decimals: u32, + token_symbol: String, } // implement helper functions for Data impl Data { - /// constructor + /// --- constructor --- pub fn new(args: Args) -> Self { Self { args, + token_decimals: 0, + token_symbol: "tokens".into(), ..Default::default() } } @@ -111,7 +133,16 @@ impl Data { self.keypair.clone().unwrap() } pub fn idty_index(&self) -> u32 { - self.idty_index.clone().unwrap() + self.idty_index.unwrap() + } + // --- 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 @@ -161,6 +192,16 @@ impl Data { ); Ok(self) } + /// get properties + pub async fn fetch_system_properties(mut self) -> Result<Self, anyhow::Error> { + let system_properties = self.client().clone().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) + } } /// track progress of transaction on the network @@ -202,7 +243,7 @@ impl From<anyhow::Error> for GcliError { #[derive(Clone, Debug, clap::Subcommand, Default)] pub enum Subcommand { - // TODO flodef + /// Fetch account balance #[default] GetBalance, /// Create and certify an identity @@ -313,6 +354,8 @@ pub enum Subcommand { }, /// Rotate and set session keys UpdateKeys, + /// Revoke an identity + RevokeIdentity, } #[tokio::main(flavor = "current_thread")] @@ -323,7 +366,15 @@ async fn main() -> Result<(), GcliError> { let mut data = Data::new(args.clone()); match args.subcommand { - Subcommand::GetBalance => {} + Subcommand::GetBalance => { + data = data + .build_keypair() + .build_client() + .await + .fetch_system_properties() + .await?; + commands::account::get_balance(data).await? + } Subcommand::CreateIdentity { target } => { data = data.build_client().await.build_keypair(); let progress = @@ -417,7 +468,7 @@ async fn main() -> Result<(), GcliError> { .await? } Subcommand::GenRevocDoc => { - commands::revocation::gen_revoc_doc( + commands::revocation::generate_revoc_doc( &Client::from_url(&args.url).await.unwrap(), &get_keys( args.secret_format, @@ -611,6 +662,21 @@ async fn main() -> Result<(), GcliError> { ) .await .unwrap(), + Subcommand::RevokeIdentity => { + let (address, pair) = get_keys( + args.secret_format, + &args.address, + &args.secret, + NeededKeys::Secret, + )?; + + commands::identity::revoke_identity( + pair.unwrap(), + Client::from_url(&args.url).await.unwrap(), + address.unwrap(), + ) + .await? + } } Ok(())