Skip to content
Snippets Groups Projects
main.rs 8.56 KiB
Newer Older
mod cache;
mod commands;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
mod conf;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
mod data;
mod indexer;
use anyhow::anyhow;
Éloïs's avatar
Éloïs committed
use clap::Parser;
use codec::Encode;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
use data::*;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
use keys::*;
use serde::Deserialize;
use sp_core::{sr25519::Pair, Pair as _};
use subxt::blocks::ExtrinsicEvents;
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner, TxStatus};
Éloïs's avatar
Éloïs committed

#[cfg(feature = "gdev")]
#[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;
}
Éloïs's avatar
Éloïs committed

Hugo Trentesaux's avatar
Hugo Trentesaux committed
// declare custom types
Hugo Trentesaux's avatar
Hugo Trentesaux committed
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>;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
pub type Hash = sp_core::H256;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
// declare runtime types
Hugo Trentesaux's avatar
Hugo Trentesaux committed
pub enum Runtime {}
impl subxt::config::Config for Runtime {
	type Index = u32;
	type BlockNumber = u32;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	type Hash = Hash;
	type Hashing = subxt::ext::sp_runtime::traits::BlakeTwo256;
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	type AccountId = AccountId;
	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>;
Éloïs's avatar
Éloïs committed

Hugo Trentesaux's avatar
Hugo Trentesaux committed
// Tip for transaction fee
#[derive(Copy, Clone, Debug, Default, codec::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)
	}
Hugo Trentesaux's avatar
Hugo Trentesaux committed
// define command line arguments
#[derive(Clone, clap::Parser, Debug, Default)]
Éloïs's avatar
Éloïs committed
#[clap(author, version, about, long_about = None)]
pub struct Args {
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	/// Subcommands
	#[clap(subcommand)]
	pub subcommand: Subcommand,
	/// Overwrite indexer endpoint
	#[clap(short, long)]
	indexer: Option<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>,
	/// Secret key format (seed, substrate)
	#[clap(short = 'S', long, default_value = SecretFormat::Substrate)]
	secret_format: SecretFormat,
	/// Address
	#[clap(short, long)]
	address: Option<String>,
	/// Overwrite duniter websocket RPC endpoint
	#[clap(short, long)]
	url: Option<String>,
	/// Chose target network
	#[clap(short, long)]
	network: Option<String>,
Éloïs's avatar
Éloïs committed
}

Hugo Trentesaux's avatar
Hugo Trentesaux committed
/// track progress of transaction on the network
/// until it is in block with success or failure
pub async fn track_progress(
	mut progress: TxProgress,
) -> Result<ExtrinsicEvents<Runtime>, subxt::Error> {
	loop {
		if let Some(status) = progress.next_item().await {
			match status? {
				TxStatus::Ready => {
					println!("transaction submitted to the network, waiting 6 seconds...");
				}
				TxStatus::InBlock(in_block) => break in_block,
				TxStatus::Invalid => {
					println!("Invalid");
				}
				_ => continue,
			}
		}
	}
	.wait_for_success()
	.await
Hugo Trentesaux's avatar
Hugo Trentesaux committed
}

/// custom error type intended to provide more convenient error message to user
#[derive(Debug)]
pub enum GcliError {
	/// error coming from subxt
	Subxt(subxt::Error),
	/// error coming from duniter
	Duniter(String),
	/// error coming from indexer
	Indexer(String),
	/// logic error (illegal operation or security)
	Logic(String),
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	/// error coming from anyhow
	Anyhow(anyhow::Error),
}
impl std::fmt::Display for GcliError {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "{:?}", self)
	}
}
impl std::error::Error for GcliError {}
impl From<subxt::Error> for GcliError {
	fn from(e: subxt::Error) -> GcliError {
		GcliError::Subxt(e)
	}
}
impl From<anyhow::Error> for GcliError {
	fn from(e: anyhow::Error) -> GcliError {
		GcliError::Anyhow(e)
	}
}

#[derive(Clone, Debug, clap::Subcommand, Default)]
Éloïs's avatar
Éloïs committed
pub enum Subcommand {
	/// Fetch account balance
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	#[default]
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	/// Show address corresponding to given arguments
	ShowAddress,
	#[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,
	},
	Transfer {
		/// Amount to transfer
		amount: u64,
		/// Destination address
Hugo Trentesaux's avatar
Hugo Trentesaux committed
		dest: AccountId,
		/// 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
Hugo Trentesaux's avatar
Hugo Trentesaux committed
		dests: Vec<AccountId>,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	/// Get information about runtime
	RuntimeInfo,
	/// Check current block
	CurrentBlock,
	/// Identity subcommands
	#[clap(subcommand)]
	Identity(commands::identity::Subcommand),
	/// Smith subcommands
	#[clap(subcommand)]
	Smith(commands::smith::Subcommand),
	/// Tech subcommands
	#[clap(subcommand)]
	Tech(commands::collective::Subcommand),
	/// Universal Dividend subcommands
	#[clap(subcommand)]
	Ud(commands::ud::Subcommand),
	/// Oneshot account subcommands
	#[clap(subcommand)]
	Oneshot(commands::oneshot::Subcommand),
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	/// Indexer subcommands
	#[clap(subcommand)]
	Indexer(indexer::Subcommand),
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	/// Config subcommands
	#[clap(subcommand)]
	Config(conf::Subcommand),
Éloïs's avatar
Éloïs committed
}

#[tokio::main(flavor = "current_thread")]
Hugo Trentesaux's avatar
Hugo Trentesaux committed
async fn main() -> Result<(), GcliError> {
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	// init logger
	env_logger::init();
Éloïs's avatar
Éloïs committed

Hugo Trentesaux's avatar
Hugo Trentesaux committed
	// parse argument and initialize data
	let args = Args::parse();
Hugo Trentesaux's avatar
Hugo Trentesaux committed
	let mut data = Data::new(args.clone());
Éloïs's avatar
Éloïs committed

	match args.subcommand {
		Subcommand::Balance => {
			data = data
Hugo Trentesaux's avatar
Hugo Trentesaux committed
				.build_address()
				.build_client()
				.fetch_system_properties()
				.await?;
			commands::account::get_balance(data).await?
		}
Hugo Trentesaux's avatar
Hugo Trentesaux committed
		Subcommand::ShowAddress => {
			data = data.build_address();
			println!("address is: {}", data.address());
		}
		Subcommand::Repart {
			target,
			actual_repart,
		} => {
			data = data.build_client().await?;
			commands::net_test::repart(
				get_keys(
					args.secret_format,
					&args.address,
					&args.secret,
					NeededKeys::Secret,
				)?
				.1
				.unwrap(),
				data.client(),
				target,
				actual_repart,
			)
			.await?
		}
		Subcommand::SpamRoll { actual_repart } => {
			data = data.build_client().await?;
			commands::net_test::spam_roll(
				get_keys(
					args.secret_format,
					&args.address,
					&args.secret,
					NeededKeys::Secret,
				)?
				.1
				.unwrap(),
				data.client(),
				actual_repart,
			)
			.await?
		}
		Subcommand::Transfer {
			amount,
			dest,
			keep_alive,
		} => {
			data = data.build_client().await?;
			commands::transfer::transfer(
				get_keys(
					args.secret_format,
					&args.address,
					&args.secret,
					NeededKeys::Secret,
				)?
				.1
				.unwrap(),
				data.client(),
				amount,
				dest,
				keep_alive,
			)
		}
		Subcommand::TransferMultiple { amount, dests } => {
			data = data.build_client().await?;
			commands::transfer::transfer_multiple(
				get_keys(
					args.secret_format,
					&args.address,
					&args.secret,
					NeededKeys::Secret,
				)?
				.1
				.unwrap(),
				data.client(),
				amount,
				dests,
			)
		Subcommand::RuntimeInfo => {
			data = data.build_client().await?.fetch_system_properties().await?;
			commands::runtime::runtime_info(data).await;
		}
		Subcommand::CurrentBlock => {
			data = data.build_client().await?;
				"current block on {}: {}",
				data.cfg.duniter_endpoint,
				data.client()
					.storage()
					.fetch(&runtime::storage().system().number(), None)
					.await?
					.unwrap()
			);
		}
		Subcommand::Identity(subcommand) => commands::identity::handle_command(data, subcommand).await?,
		Subcommand::Smith(subcommand) => commands::smith::handle_command(data, subcommand).await?,
		Subcommand::Tech(subcommand) => commands::collective::handle_command(data, subcommand).await?,
		Subcommand::Ud(subcommand) => commands::ud::handle_command(data, subcommand).await?,
		Subcommand::Oneshot(subcommand) => {
			commands::oneshot::handle_command(data, subcommand).await?
		}
		Subcommand::Indexer(subcommand) => indexer::handle_command(data, subcommand).await?,
Hugo Trentesaux's avatar
Hugo Trentesaux committed
		Subcommand::Config(subcommand) => conf::handle_command(data, subcommand)?,
Éloïs's avatar
Éloïs committed

	Ok(())
Éloïs's avatar
Éloïs committed
}