mod cache; mod commands; mod indexer; use anyhow::{anyhow, Result}; use clap::Parser; use codec::Encode; use sp_core::{ crypto::{Pair as _, Ss58Codec}, sr25519::Pair, H256, }; use std::str::FromStr; #[subxt::subxt(runtime_metadata_path = "res/metadata.scale")] pub mod gdev {} pub type Client = subxt::OnlineClient<GdevConfig>; pub enum GdevConfig {} impl subxt::config::Config for GdevConfig { type Index = u32; type BlockNumber = u32; type Hash = sp_core::H256; type Hashing = subxt::ext::sp_runtime::traits::BlakeTwo256; type AccountId = subxt::ext::sp_runtime::AccountId32; type Address = subxt::ext::sp_runtime::MultiAddress<Self::AccountId, u32>; type Header = subxt::ext::sp_runtime::generic::Header< Self::BlockNumber, subxt::ext::sp_runtime::traits::BlakeTwo256, >; type Signature = subxt::ext::sp_runtime::MultiSignature; type ExtrinsicParams = subxt::tx::BaseExtrinsicParams<Self, Tip>; } #[derive(Copy, Clone, Debug, Default, Encode)] pub struct Tip { #[codec(compact)] tip: u64, } impl Tip { pub fn new(amount: u64) -> Self { Tip { tip: amount } } } impl From<u64> for Tip { fn from(n: u64) -> Self { Self::new(n) } } #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] pub struct Args { #[clap(subcommand)] pub subcommand: Subcommand, /// Indexer URL #[clap( short, long, default_value = "https://gdev-indexer.p2p.legal/v1/graphql" )] indexer: String, /// Do not use indexer #[clap(long)] no_indexer: bool, /// Secret key or BIP39 mnemonic /// (eventually followed by derivation path) #[clap(short, long)] secret: Option<String>, /// Address #[clap(short, long)] address: Option<String>, /// Websocket RPC endpoint #[clap(short, long, default_value = "ws://localhost:9944")] url: String, } #[derive(Debug, clap::Subcommand)] pub enum Subcommand { CreateOneshot { balance: u64, dest: sp_core::crypto::AccountId32, }, ConsumeOneshot { dest: sp_core::crypto::AccountId32, #[clap(long = "oneshot")] dest_oneshot: bool, }, ConsumeOneshotWithRemaining { balance: u64, dest: sp_core::crypto::AccountId32, #[clap(long = "one")] dest_oneshot: bool, remaining_to: sp_core::crypto::AccountId32, #[clap(long = "rem-one")] remaining_to_oneshot: bool, }, /// List upcoming expirations that require an action Expire { /// Show certs that expire within less than this number of blocks #[clap(short, long, default_value_t = 100800)] blocks: u32, /// Show authorities that should rotate keys within less than this number of sessions #[clap(short, long, default_value_t = 100)] sessions: u32, }, /// Fetch identity Identity { #[clap(short = 'p', long = "pubkey")] account_id: Option<sp_core::crypto::AccountId32>, #[clap(short = 'i', long = "identity")] identity_id: Option<u32>, #[clap(short = 'u', long = "username")] username: Option<String>, }, /// Generate a revocation document for the provided account GenRevocDoc, GoOffline, GoOnline, OneshotBalance { account: sp_core::crypto::AccountId32, }, /// List online authorities Online, #[clap(hide = true)] Repart { // Number of transactions per block to target target: u32, #[clap(short = 'o', long = "old-repart")] // Old/actual repartition actual_repart: Option<u32>, }, #[clap(hide = true)] SpamRoll { actual_repart: usize, }, SudoSetKey { new_key: sp_core::crypto::AccountId32, }, /// List members of the technical committee TechMembers, /// List proposals to the technical committee TechProposals, /// Vote a proposal to the technical committee TechVote { /// Proposal hash hash: H256, /// Proposal index index: u32, /// Vote (0=against, 1=for) vote: u8, }, Transfer { /// Amount to transfer amount: u64, /// Destination address dest: sp_core::crypto::AccountId32, /// Prevent from going below account existential deposit #[clap(short = 'k', long = "keep-alive")] keep_alive: bool, }, /// Transfer the same amount for each space-separated address. /// If an address appears mutiple times, it will get multiple times the same amount TransferMultiple { /// Amount given to each destination address amount: u64, /// List of target addresses dests: Vec<sp_core::crypto::AccountId32>, }, /// Rotate and set session keys UpdateKeys, } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { env_logger::init(); let args = Args::parse(); let (account_id, pair) = match (&args.address, &args.secret) { (Some(address), Some(secret)) => { let pair = Pair::from_string(secret, None) .map_err(|_| anyhow!("Invalid secret {}", secret))?; let address = sp_core::crypto::AccountId32::from_string(address) .map_err(|_| anyhow!("Invalid address {}", address))?; assert_eq!( address, pair.public().into(), "Secret and address do not match." ); (Some(pair.public().into()), Some(pair)) } (None, Some(secret)) => { let pair = Pair::from_string(secret, None) .map_err(|_| anyhow!("Invalid secret {}", secret))?; (Some(pair.public().into()), Some(pair)) } (Some(address), None) => ( Some( sp_core::crypto::AccountId32::from_str(address) .map_err(|_| anyhow!("Invalid address {}", address))?, ), None, ), (None, None) => (None, None), }; if let Some(account_id) = &account_id { println!("Account address: {account_id}"); } let client = Client::from_url(&args.url).await.unwrap(); if let Some(account_id) = &account_id { let account = client .storage() .fetch(&gdev::storage().system().account(account_id), None) .await? .expect("Cannot fetch account"); logs::info!("Account free balance: {}", account.data.free); } match args.subcommand { Subcommand::CreateOneshot { balance, dest } => { commands::oneshot::create_oneshot_account( pair.expect("This subcommand needs a secret."), client, balance, dest, ) .await? } Subcommand::ConsumeOneshot { dest, dest_oneshot } => { commands::oneshot::consume_oneshot_account( pair.expect("This subcommand needs a secret."), client, dest, dest_oneshot, ) .await? } Subcommand::ConsumeOneshotWithRemaining { balance, dest, dest_oneshot, remaining_to, remaining_to_oneshot, } => { commands::oneshot::consume_oneshot_account_with_remaining( pair.expect("This subcommand needs a secret."), client, balance, dest, dest_oneshot, remaining_to, remaining_to_oneshot, ) .await? } Subcommand::Expire { blocks, sessions } => { commands::expire::monitor_expirations(client, blocks, sessions, &args).await? } Subcommand::Identity { ref account_id, identity_id, ref username, } => { commands::identity::get_identity( client, account_id.clone(), identity_id, username.clone(), &args, ) .await? } Subcommand::GenRevocDoc => { commands::revocation::gen_revoc_doc( &client, &pair.expect("This subcommand needs a secret."), ) .await? } Subcommand::GoOffline => { commands::smith::go_offline(pair.expect("This subcommand needs a secret."), client) .await? } Subcommand::GoOnline => { commands::smith::go_online(pair.expect("This subcommand needs a secret."), client) .await? } Subcommand::OneshotBalance { account } => { commands::oneshot::oneshot_account_balance(client, account).await? } Subcommand::Online => commands::smith::online(client, &args).await?, Subcommand::Repart { target, actual_repart, } => { commands::net_test::repart( pair.expect("This subcommand needs a secret."), client, target, actual_repart, ) .await? } Subcommand::SpamRoll { actual_repart } => { commands::net_test::spam_roll( pair.expect("This subcommand needs a secret."), client, actual_repart, ) .await? } Subcommand::SudoSetKey { new_key } => { commands::sudo::set_key( pair.expect("This subcommand needs a secret."), client, new_key, ) .await? } Subcommand::TechMembers => { commands::collective::technical_committee_members(client, &args).await? } Subcommand::TechProposals => { commands::collective::technical_committee_proposals(client).await? } Subcommand::TechVote { hash, index, vote } => { let vote = match vote { 0 => false, 1 => true, _ => panic!("Vote must be written 0 if you disagree, or 1 if you agree."), }; commands::collective::technical_committee_vote( pair.expect("This subcommand needs a secret."), client, hash, //H256::from_str(&hash).expect("Invalid hash formatting"), index, vote, ) .await? } Subcommand::Transfer { amount, dest, keep_alive, } => { commands::transfer::transfer( pair.expect("This subcommand needs a secret."), client, amount, dest, keep_alive, ) .await? } Subcommand::TransferMultiple { amount, dests } => { commands::transfer::transfer_multiple( pair.expect("This subcommand needs a secret."), client, amount, dests, ) .await? } Subcommand::UpdateKeys => commands::smith::update_session_keys( pair.expect("This subcommand needs a secret."), client, ) .await .unwrap(), } Ok(()) }