Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • clients/rust/gcli-v2s
  • d0p1/gcli-v2s
  • flebon/gcli-v2s
  • zicmama/gcli-v2s
  • Nicolas80/gcli-v2s
5 results
Show changes
use crate::*; use crate::*;
use anyhow::Result;
use sp_core::{crypto::AccountId32, sr25519::Pair};
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
#[cfg(any(feature = "dev", feature = "gdev"))] // find how to get runtime calls #[cfg(any(feature = "dev", feature = "gdev"))] // find how to get runtime calls
type Call = runtime::runtime_types::gdev_runtime::RuntimeCall; type Call = runtime::runtime_types::gdev_runtime::RuntimeCall;
type BalancesCall = runtime::runtime_types::pallet_balances::pallet::Call; type BalancesCall = runtime::runtime_types::pallet_balances::pallet::Call;
/// transfer balance to target
pub async fn transfer( pub async fn transfer(
pair: Pair, data: &Data,
client: Client,
balance: u64, balance: u64,
dest: AccountId32, dest: AccountId,
keep_alive: bool, keep_alive: bool,
) -> Result<()> { is_ud: bool,
if keep_alive { ) -> Result<(), subxt::Error> {
client match (keep_alive, is_ud) {
.tx() (true, false) => {
.sign_and_submit_then_watch( submit_call_and_look_event::<
&runtime::tx().balances().transfer(dest.into(), balance), runtime::balances::events::Transfer,
&PairSigner::new(pair), StaticPayload<runtime::balances::calls::types::TransferKeepAlive>,
BaseExtrinsicParamsBuilder::new(), >(
) data,
.await?;
} else {
client
.tx()
.sign_and_submit_then_watch(
&runtime::tx() &runtime::tx()
.balances() .balances()
.transfer_keep_alive(dest.into(), balance), .transfer_keep_alive(dest.into(), balance),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
) )
.await?; .await
}
(false, false) => {
submit_call_and_look_event::<
runtime::balances::events::Transfer,
StaticPayload<runtime::balances::calls::types::TransferAllowDeath>,
>(
data,
&runtime::tx()
.balances()
.transfer_allow_death(dest.into(), balance),
)
.await
}
(true, true) => {
submit_call_and_look_event::<
runtime::balances::events::Transfer,
StaticPayload<runtime::universal_dividend::calls::types::TransferUdKeepAlive>,
>(
data,
&runtime::tx()
.universal_dividend()
.transfer_ud_keep_alive(dest.into(), balance),
)
.await
}
(false, true) => {
submit_call_and_look_event::<
runtime::balances::events::Transfer,
StaticPayload<runtime::universal_dividend::calls::types::TransferUd>,
>(
data,
&runtime::tx()
.universal_dividend()
.transfer_ud(dest.into(), balance),
)
.await
}
} }
Ok(())
} }
/// transfer balance to multiple target
pub async fn transfer_multiple( pub async fn transfer_multiple(
pair: Pair, data: &Data,
client: Client,
amount: u64, amount: u64,
dests: Vec<AccountId32>, dests: Vec<AccountId>,
) -> Result<()> { ) -> Result<(), subxt::Error> {
// build the list of transactions from the destination accounts // build the list of transactions from the destination accounts
let transactions: Vec<Call> = dests let transactions: Vec<Call> = dests
.into_iter() .into_iter()
...@@ -56,16 +80,10 @@ pub async fn transfer_multiple( ...@@ -56,16 +80,10 @@ pub async fn transfer_multiple(
}) })
}) })
.collect(); .collect();
// wrap these calls in a batch call // wrap these calls in a batch call
client submit_call_and_look_event::<
.tx() runtime::utility::events::BatchCompleted,
.sign_and_submit_then_watch( StaticPayload<runtime::utility::calls::types::Batch>,
&runtime::tx().utility().batch(transactions), >(data, &runtime::tx().utility().batch(transactions))
&PairSigner::new(pair.clone()), .await
BaseExtrinsicParamsBuilder::new(),
)
.await?;
Ok(())
} }
use crate::*;
/// define universal dividends subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
#[default]
/// Claim uds
Claim,
}
/// handle ud commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
// build indexer because it is needed for all subcommands
let data = data.build_client().await?.fetch_system_properties().await?;
// match subcommand
match command {
Subcommand::Claim => {
claim_ud(&data).await?;
}
};
Ok(())
}
/// claim universal dividend
pub async fn claim_ud(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::universal_dividend::events::UdsClaimed,
StaticPayload<runtime::universal_dividend::calls::types::ClaimUds>,
>(data, &runtime::tx().universal_dividend().claim_uds())
.await
}
This diff is collapsed.
use crate::commands::cesium;
use crate::entities::vault_account;
use crate::entities::vault_account::AccountTreeNode;
use crate::keys::CryptoScheme;
use crate::utils::GcliError;
use comfy_table::{Cell, Table};
use std::cell::RefCell;
use std::rc::Rc;
use std::str;
#[deprecated(
note = "Should be removed in a future version when db persistence of vault is present for a while"
)]
pub fn compute_vault_key_files_table(vault_key_addresses: &[String]) -> Result<Table, GcliError> {
let mut table = Table::new();
table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
table.set_header(vec!["Key file"]);
vault_key_addresses.iter().for_each(|address| {
table.add_row(vec![Cell::new(address)]);
});
Ok(table)
}
pub fn compute_vault_accounts_table(
account_tree_nodes: &[Rc<RefCell<AccountTreeNode>>],
) -> Result<Table, GcliError> {
let mut table = Table::new();
table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
table.set_header(vec![
"SS58 Address/G1v1 public key",
"Crypto",
"Path",
"Name",
]);
for account_tree_node in account_tree_nodes {
let _ = add_account_tree_node_to_table(&mut table, account_tree_node);
}
Ok(table)
}
fn add_account_tree_node_to_table(
table: &mut Table,
account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) -> Result<(), GcliError> {
let rows = compute_vault_accounts_row(account_tree_node)?;
rows.iter().for_each(|row| {
table.add_row(row.clone());
});
for child in &account_tree_node.borrow().children {
let _ = add_account_tree_node_to_table(table, child);
}
Ok(())
}
/// Computes one or more row of the table for selected account_tree_node
///
/// For ed25519 keys, will display over 2 rows to also show the base 58 G1v1 public key
pub fn compute_vault_accounts_row(
account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) -> Result<Vec<Vec<Cell>>, GcliError> {
let empty_string = "".to_string();
let depth_account_tree_node = vault_account::count_depth_account_tree_node(account_tree_node);
let name = if let Some(name) = account_tree_node.borrow().account.name.clone() {
name
} else if let Some(computed_name) =
vault_account::compute_name_account_tree_node(account_tree_node)
{
format!("<{}>", computed_name)
} else {
empty_string.clone()
};
let account_tree_node = account_tree_node.borrow();
let address = if depth_account_tree_node > 0 {
let ancestors = "│ ".repeat(depth_account_tree_node - 1);
format!("{}├ {}", ancestors, account_tree_node.account.address)
} else {
account_tree_node.account.address.to_string()
};
let mut rows: Vec<Vec<Cell>> = vec![];
let (path, crypto) = if let Some(path) = account_tree_node.account.path.clone() {
(path, empty_string.clone())
} else {
let crypto_scheme = CryptoScheme::from(account_tree_node.account.crypto_scheme.unwrap());
// Adding 2nd row for G1v1 public key
if CryptoScheme::Ed25519 == crypto_scheme {
rows.push(vec![Cell::new(format!(
"└ G1v1: {}",
cesium::compute_g1v1_public_key_from_ed25519_account_id(
&account_tree_node.account.address.0
)
))]);
}
let crypto_scheme_str: &str = crypto_scheme.into();
(
format!("<{}>", account_tree_node.account.account_type()),
crypto_scheme_str.to_string(),
)
};
// Adding 1st row
rows.insert(
0,
vec![
Cell::new(&address),
Cell::new(crypto),
Cell::new(&path),
Cell::new(&name),
],
);
Ok(rows)
}
#[cfg(test)]
mod tests {
mod vault_accounts_table_tests {
use crate::commands::vault::display::compute_vault_accounts_table;
use crate::entities::vault_account::tests::account_tree_node_tests::{
mother_account_tree_node, mother_g1v1_account_tree_node,
};
use indoc::indoc;
#[test]
fn test_compute_vault_accounts_table_empty() {
let table = compute_vault_accounts_table(&[]).unwrap();
let expected_table = indoc! {r#"
┌─────────────────────────────────────────────────────┐
│ SS58 Address/G1v1 public key Crypto Path Name │
╞═════════════════════════════════════════════════════╡
└─────────────────────────────────────────────────────┘"#
};
assert_eq!(table.to_string(), expected_table);
}
#[test]
fn test_compute_vault_accounts_table() {
let account_tree_node = mother_account_tree_node();
let g1v1_account_tree_node = mother_g1v1_account_tree_node();
let table =
compute_vault_accounts_table(&[account_tree_node, g1v1_account_tree_node]).unwrap();
let expected_table = indoc! {r#"
┌──────────────────────────────────────────────────────────────────────────────────────────┐
│ SS58 Address/G1v1 public key Crypto Path Name │
╞══════════════════════════════════════════════════════════════════════════════════════════╡
│ 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV sr25519 <Base> Mother │
│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │
│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │
│ ├ 5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o //1 <Mother//1> │
│ │ ├ 5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT //1 <Mother//1//1> │
│ 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4 ed25519 <Base> MotherG1v1 │
│ └ G1v1: 86pW1doyJPVH3jeDPZNQa1UZFBo5zcdvHERcaeE758W7 │
└──────────────────────────────────────────────────────────────────────────────────────────┘"#
};
assert_eq!(table.to_string(), expected_table);
}
#[test]
fn test_compute_vault_accounts_table_partial() {
let mother = mother_account_tree_node();
let child1 = mother.borrow().children[0].clone();
let table = compute_vault_accounts_table(&[child1]).unwrap();
let expected_table = indoc! {r#"
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ SS58 Address/G1v1 public key Crypto Path Name │
╞═════════════════════════════════════════════════════════════════════════════════════╡
│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │
│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │
└─────────────────────────────────────────────────────────────────────────────────────┘"#
};
assert_eq!(table.to_string(), expected_table);
}
}
}
use crate::entities::vault_account;
use crate::entities::vault_account::DbAccountId;
use crate::*;
use serde::{Deserialize, Serialize};
const APP_NAME: &str = "gcli";
/// defines structure of config file
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
/// duniter endpoint
pub duniter_endpoint: String,
/// indexer endpoint
pub indexer_endpoint: String,
/// user address
/// to perform actions, user must provide secret
pub address: Option<AccountId>,
}
impl std::default::Default for Config {
fn default() -> Self {
Self {
duniter_endpoint: String::from(data::LOCAL_DUNITER_ENDPOINT),
indexer_endpoint: String::from(data::LOCAL_INDEXER_ENDPOINT),
address: None,
}
}
}
impl std::fmt::Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let address = if let Some(address) = &self.address {
format!("{}", address)
} else {
"(no address)".to_string()
};
writeln!(f, "Ğcli config")?;
writeln!(f, "duniter endpoint {}", self.duniter_endpoint)?;
writeln!(f, "indexer endpoint {}", self.indexer_endpoint)?;
write!(f, "address {address}")
}
}
/// load config file and manage error if could not
pub fn load_conf() -> Config {
match confy::load(APP_NAME, None) {
Ok(cfg) => cfg,
Err(e) => {
log::warn!(
"met error while loading config file {}",
confy::get_configuration_file_path(APP_NAME, None)
.unwrap()
.display()
);
log::error!("{:?}", e);
log::info!("using default config instead");
log::info!("call `config save` to overwrite");
Config::default()
}
}
}
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
#[default]
/// Show config path
Where,
/// Show config
Show,
/// Save config as modified by command line arguments
Save,
/// Rest config to default
Default,
}
/// handle conf command
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
// match subcommand
match command {
Subcommand::Where => {
println!(
"{}",
confy::get_configuration_file_path(APP_NAME, None)?.display()
);
}
Subcommand::Show => {
println!("{}", data.cfg);
if let Some(ref account_id) = data.cfg.address {
if let Some(account) = vault_account::find_by_id(
data.connect_db(),
&DbAccountId::from(account_id.clone()),
)
.await?
{
println!("(Vault: {})", account);
}
}
}
Subcommand::Save => {
save(&data.cfg);
}
Subcommand::Default => {
confy::store(APP_NAME, None, Config::default()).expect("unable to write config");
}
};
Ok(())
}
pub fn save(cfg: &Config) {
confy::store(APP_NAME, None, cfg).expect("unable to write config");
println!("Configuration updated!");
}
use crate::commands::vault;
use crate::*;
use indexer::Indexer;
use sea_orm::DatabaseConnection;
// consts
pub const LOCAL_DUNITER_ENDPOINT: &str = "ws://localhost:9944";
pub const LOCAL_INDEXER_ENDPOINT: &str = "http://localhost:4350/graphql";
pub const SQLITE_DB_FILENAME: &str = "gcli.sqlite";
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
#[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://squid.gdev.coinduf.eu/v1/graphql",
"https://squid.gdev.gyroi.de/v1/graphql",
"https://gdev-squid.axiom-team.fr/v1/graphql",
];
// data derived from command arguments
/// Data of current command
/// can also include fetched information
pub struct Data {
/// command line arguments
pub args: Args,
/// config
pub cfg: conf::Config,
/// database connection
connection: Option<DatabaseConnection>,
/// rpc to substrate client
pub client: Option<Client>,
/// graphql to duniter-indexer
pub indexer: Option<Indexer>,
/// user keypair
pub keypair: Option<KeyPair>,
/// user identity index
pub idty_index: Option<IdtyId>,
/// 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,
/// gcli base path
pub project_dir: directories::ProjectDirs,
}
/// system properties defined in client specs
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SystemProperties {
token_decimals: u32,
token_symbol: String,
}
impl Default for Data {
fn default() -> Self {
let project_dir = directories::ProjectDirs::from("org", "duniter", "gcli").unwrap();
if !project_dir.data_dir().exists() {
std::fs::create_dir_all(project_dir.data_dir()).expect("could not create data dir");
};
Self {
project_dir,
args: Default::default(),
cfg: Default::default(),
connection: Default::default(),
client: Default::default(),
indexer: Default::default(),
keypair: Default::default(),
idty_index: Default::default(),
token_decimals: Default::default(),
token_symbol: Default::default(),
genesis_hash: Default::default(),
indexer_genesis_hash: Default::default(),
}
}
}
// implement helper functions for Data
impl Data {
/// --- constructor ---
pub async fn new(args: Args) -> Result<Self, GcliError> {
let mut data = Self {
args,
cfg: conf::load_conf(),
token_decimals: 0,
token_symbol: "tokens".into(),
..Default::default()
};
//Necessary to support checking "vault names" in the base arguments
data = data.build_connection().await?;
data = data.overwrite_from_args().await?;
Ok(data)
}
// --- getters ---
// the "unwrap" should not fail if data is well prepared
/// Returns the DatabaseConnection reference
pub fn connect_db(&self) -> &DatabaseConnection {
self.connection
.as_ref()
.expect("Database connection is not available")
}
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 async fn keypair(&self) -> KeyPair {
match self.keypair.clone() {
Some(keypair) => keypair,
None => loop {
match fetch_or_get_keypair(self, self.cfg.address.clone()).await {
Ok(pair) => return pair,
Err(e) => {
//Adapted code to still be able to go out of the loop when user hit "Esc" key or "ctrl+c" when prompted for a value
//otherwise only way was to kill the process !
if let GcliError::Input(message) = &e {
match message.as_str() {
"Operation was interrupted by the user"
| "Operation was canceled by the user" => {
panic!("{}", e.to_string());
}
_ => {}
}
}
println!("{e:?} → retry")
}
}
},
}
}
pub fn idty_index(&self) -> IdtyId {
self.idty_index.expect("must fetch idty index first")
}
// --- methods ---
pub fn format_balance(&self, amount: Balance) -> String {
let base: u64 = 10;
let integer_part = amount / base.pow(self.token_decimals);
let fractional_part = amount % base.pow(self.token_decimals);
format!(
"{}.{:0left_padding$} {}",
integer_part,
fractional_part,
self.token_symbol,
left_padding = self.token_decimals as usize
)
}
// --- mutators ---
/// use arguments to overwrite config
pub async fn overwrite_from_args(mut self) -> Result<Self, GcliError> {
// network
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}\"");
}
}
}
// duniter endpoint
if let Some(duniter_endpoint) = self.args.url.clone() {
self.cfg.duniter_endpoint = duniter_endpoint;
}
// indexer endpoint
if let Some(indexer_endpoint) = self.args.indexer.clone() {
self.cfg.indexer_endpoint = indexer_endpoint
}
// secret format and value
if let Some(secret_format) = self.args.secret_format {
let keypair = get_keypair(secret_format, self.args.secret.as_deref())?;
self.cfg.address = Some(keypair.address());
self.keypair = Some(keypair);
}
// address
if let Some(address) = self.args.address.clone() {
self.cfg.address = Some(address.clone());
// if giving address, cancel secret
self.keypair = None
}
// (vault)name
if let Some(name) = self.args.name.clone() {
let account = vault::retrieve_vault_account_for_name(self.connect_db(), &name).await?;
self.cfg.address = Some(account.address.0.clone());
// if giving (vault)name, cancel secret
self.keypair = None
}
Ok(self)
}
/// build a client from url
pub async fn build_client(mut self) -> Result<Self, GcliError> {
let duniter_endpoint = &self.cfg.duniter_endpoint;
let client = Client::from_url(duniter_endpoint).await.map_err(|e| {
// to get more details TODO fixme, see issue #18
dbg!(e);
GcliError::Duniter(format!("can not connect to duniter {duniter_endpoint}",))
})?;
self.client = Some(client);
self.genesis_hash = commands::blockchain::fetch_genesis_hash(&self).await?;
Ok(self)
}
/// build an indexer if not disabled
pub async fn build_indexer(mut self) -> Result<Self, GcliError> {
if self.args.no_indexer {
log::info!("called build_indexer while providing no_indexer");
self.indexer = None;
} else {
self.indexer = Some(Indexer {
gql_client: reqwest::Client::builder()
.user_agent(format!("gcli/{PKG_VERSION}"))
.build()
.unwrap(),
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")
}
};
Ok(self)
}
/// build a database connection
async fn build_connection(mut self) -> Result<Self, GcliError> {
let data_dir = self.project_dir.data_dir();
let connection = database::build_sqlite_connection(data_dir, SQLITE_DB_FILENAME).await?;
self.connection = Some(connection);
Ok(self)
}
/// get issuer index<br>
/// needs address and client first
pub async fn fetch_idty_index(mut self) -> Result<Self, GcliError> {
self.idty_index = Some(
commands::identity::get_idty_index_by_account_id(self.client(), &self.address())
.await?
.ok_or(GcliError::Logic(
"you need to be member to use this command".to_string(),
))?,
);
Ok(self)
}
/// get properties
pub async fn fetch_system_properties(mut self) -> Result<Self, GcliError> {
let system_properties = self.legacy_rpc_methods().await.system_properties().await?;
let system_properties = serde_json::from_value::<SystemProperties>(
serde_json::Value::Object(system_properties),
)
.map_err(|e| {
dbg!(e);
GcliError::Duniter("could not read duniter system properties".to_string())
})?;
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)
// );
// );
}
// legacy methods (see subxt changelog)
use subxt::{
backend::{legacy::LegacyRpcMethods, rpc::RpcClient},
config::SubstrateConfig,
};
impl Data {
pub async fn legacy_rpc_methods(&self) -> LegacyRpcMethods<SubstrateConfig> {
let rpc_client = RpcClient::from_url(self.cfg.duniter_endpoint.clone())
.await
.expect("error");
LegacyRpcMethods::<SubstrateConfig>::new(rpc_client)
}
}
use crate::entities::vault_account;
use crate::utils::GcliError;
use sea_orm::sea_query::IndexCreateStatement;
use sea_orm::{ConnectionTrait, Database, DatabaseConnection, Schema};
use std::fs;
use std::path::Path;
pub async fn build_sqlite_connection(
data_dir: &Path,
filename: &str,
) -> Result<DatabaseConnection, GcliError> {
let sqlite_path = data_dir.join(filename);
// Check if the file exists, and create it if it doesn't (otherwise the connection will fail)
if !Path::new(&sqlite_path).exists() {
fs::File::create(sqlite_path.clone())?;
}
let sqlite_path_str = sqlite_path
.into_os_string()
.into_string()
.map_err(|_| GcliError::Input("Invalid SQLite path".to_string()))?;
let sqlite_db_url = format!("sqlite://{}", sqlite_path_str);
let connection = initialize_db(&sqlite_db_url).await?;
Ok(connection)
}
pub async fn initialize_db(db_url: &str) -> Result<DatabaseConnection, GcliError> {
let db = Database::connect(db_url).await?;
let schema = Schema::new(db.get_database_backend());
create_table_if_not_exists(&db, &schema, vault_account::Entity).await?;
Ok(db)
}
async fn create_table_if_not_exists<E: sea_orm::EntityTrait>(
db: &DatabaseConnection,
schema: &Schema,
entity: E,
) -> Result<(), GcliError> {
db.execute(
db.get_database_backend()
.build(schema.create_table_from_entity(entity).if_not_exists()),
)
.await?;
Ok(())
}
/// The only way to add composed unique index...
#[allow(dead_code)]
async fn create_table_if_not_exists_with_index<E: sea_orm::EntityTrait>(
db: &DatabaseConnection,
schema: &Schema,
entity: E,
index: &mut IndexCreateStatement,
) -> Result<(), GcliError> {
db.execute(
db.get_database_backend().build(
schema
.create_table_from_entity(entity)
.index(index)
.if_not_exists(),
),
)
.await?;
Ok(())
}
use crate::*;
use std::str;
// display events in a friendly manner
pub trait DisplayEvent {
fn display(&self, data: &Data) -> String;
}
impl DisplayEvent for runtime::universal_dividend::events::UdsClaimed {
fn display(&self, data: &Data) -> String {
format!(
"claimed {} UD, for a total of {}",
self.count,
data.format_balance(self.total)
)
}
}
impl DisplayEvent for runtime::certification::events::CertAdded {
fn display(&self, _data: &Data) -> String {
format!("new certification {} → {}", self.issuer, self.receiver)
}
}
impl DisplayEvent for runtime::certification::events::CertRenewed {
fn display(&self, _data: &Data) -> String {
format!("renewed cert {:?}", self)
}
}
impl DisplayEvent for runtime::account::events::AccountUnlinked {
fn display(&self, _data: &Data) -> String {
format!("account unlinked: {}", self.0)
}
}
impl DisplayEvent for runtime::technical_committee::events::Voted {
fn display(&self, _data: &Data) -> String {
format!("voted {:?}", self)
}
}
impl DisplayEvent for runtime::identity::events::IdtyCreated {
fn display(&self, _data: &Data) -> String {
format!(
"identity created for {} with index {}",
self.owner_key, self.idty_index
)
}
}
impl DisplayEvent for runtime::identity::events::IdtyConfirmed {
fn display(&self, _data: &Data) -> String {
format!(
"identity confirmed with name \"{}\" (index {}, owner key {})",
str::from_utf8(&self.name.0).unwrap(),
self.idty_index,
self.owner_key
)
}
}
impl DisplayEvent for runtime::identity::events::IdtyChangedOwnerKey {
fn display(&self, _data: &Data) -> String {
format!("identity changed owner key {:?}", self)
}
}
impl DisplayEvent for runtime::distance::events::EvaluationRequested {
fn display(&self, _data: &Data) -> String {
format!("evaluation requested {:?}", self)
}
}
impl DisplayEvent for runtime::smith_members::events::InvitationSent {
fn display(&self, _data: &Data) -> String {
format!("sent smith invitation {:?}", self)
}
}
impl DisplayEvent for runtime::smith_members::events::InvitationAccepted {
fn display(&self, _data: &Data) -> String {
format!("accepted smith invitation {:?}", self)
}
}
impl DisplayEvent for runtime::smith_members::events::SmithCertAdded {
fn display(&self, _data: &Data) -> String {
format!("new smith certification {:?}", self)
}
}
impl DisplayEvent for runtime::smith_members::events::SmithMembershipAdded {
fn display(&self, _data: &Data) -> String {
format!("new smith promoted {:?}", self)
}
}
impl DisplayEvent for runtime::identity::events::IdtyRemoved {
fn display(&self, _data: &Data) -> String {
format!("identity removed {:?}", self)
}
}
impl DisplayEvent for runtime::account::events::AccountLinked {
fn display(&self, _data: &Data) -> String {
format!("account {} linked to identity {}", self.who, self.identity)
}
}
impl DisplayEvent for runtime::oneshot_account::events::OneshotAccountCreated {
fn display(&self, _data: &Data) -> String {
format!("oneshot {:?}", self)
}
}
impl DisplayEvent for runtime::oneshot_account::events::OneshotAccountConsumed {
fn display(&self, _data: &Data) -> String {
format!("oneshot {:?}", self)
}
}
impl DisplayEvent for runtime::authority_members::events::MemberGoOnline {
fn display(&self, _data: &Data) -> String {
format!("smith went online {:?}", self)
}
}
impl DisplayEvent for runtime::authority_members::events::MemberGoOffline {
fn display(&self, _data: &Data) -> String {
format!("smith went offline {:?}", self)
}
}
impl DisplayEvent for runtime::sudo::events::KeyChanged {
fn display(&self, _data: &Data) -> String {
format!("sudo key changed {:?}", self)
}
}
impl DisplayEvent for runtime::balances::events::Transfer {
fn display(&self, data: &Data) -> String {
format!(
"transfered {} ({} → {})",
data.format_balance(self.amount),
self.from,
self.to
)
}
}
impl DisplayEvent for runtime::utility::events::BatchCompleted {
fn display(&self, _data: &Data) -> String {
format!("batch completed {:?}", self)
}
}
impl DisplayEvent for runtime::sudo::events::Sudid {
fn display(&self, _data: &Data) -> String {
format!("SUDO call succeeded {:?}", self)
}
}
impl DisplayEvent for runtime::technical_committee::events::Proposed {
fn display(&self, _data: &Data) -> String {
format!("proposed {:?}", self)
}
}
pub mod vault_account;
This diff is collapsed.
This diff is collapsed.
use graphql_client::GraphQLQuery;
use serde::{Deserialize, Deserializer};
// implementation of byte array
#[derive(Debug, Clone)]
pub struct Bytea {
pub bytes: Vec<u8>,
}
// Hasura uses lowercase type name
#[allow(non_camel_case_types)]
type bytea = Bytea;
// implement deserializing \\x prefixed hexadecimal
impl<'de> Deserialize<'de> for Bytea {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize as a string
let hex_string = String::deserialize(deserializer)?;
// Parse the hexadecimal string into a byte vector
let bytes = hex::decode(&hex_string[2..]).map_err(serde::de::Error::custom)?;
Ok(Bytea { bytes })
}
}
// generate code for given graphql query
macro_rules! graphql_query {
($name:ident) => {
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "res/indexer-schema.json",
query_path = "res/indexer-queries.graphql"
)]
pub struct $name;
};
}
// repeat generation for multiple queries
macro_rules! graphql_query_for {
( $($Name:ident),+ ) => {
$( graphql_query!($Name); )+
};
}
// generate code for all queries in indexer-queries.graphql
graphql_query_for!(
IdentityNameByIndex,
IdentityInfo,
IdentityNameByPubkey,
WasIdentityNameByPubkey,
LatestBlock,
BlockByNumber,
GenesisHash,
NamesByIndexes
);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// #[allow(clippy::enum_variant_names)]
#[cfg(feature = "gdev")]
#[subxt::subxt(
runtime_metadata_path = "res/metadata.scale",
derive_for_all_types = "Debug"
)]
pub mod runtime {}
// declare custom types
pub type Client = subxt::OnlineClient<Runtime>;
pub type AccountId = subxt::utils::AccountId32;
pub type IdtyId = u32;
pub type BlockNumber = u32;
pub type TxProgress = subxt::tx::TxProgress<Runtime, Client>;
pub type Balance = u64;
pub type AccountData =
runtime::runtime_types::pallet_duniter_account::types::AccountData<Balance, IdtyId>;
pub type AccountInfo = runtime::runtime_types::frame_system::AccountInfo<u32, AccountData>;
pub type Hash = sp_core::H256;
// declare runtime types
pub enum Runtime {}
impl subxt::config::Config for Runtime {
type AssetId = ();
type Hash = Hash;
type AccountId = AccountId;
type Address = sp_runtime::MultiAddress<Self::AccountId, u32>;
type Signature = sp_runtime::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<BlockNumber, Self::Hasher>;
type ExtrinsicParams = subxt::config::DefaultExtrinsicParams<Self>;
}
// 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)
}
}
This diff is collapsed.