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::*;
#[cfg(any(feature = "dev", feature = "gdev"))] // find how to get runtime calls #[cfg(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;
...@@ -16,7 +16,7 @@ pub async fn transfer( ...@@ -16,7 +16,7 @@ pub async fn transfer(
(true, false) => { (true, false) => {
submit_call_and_look_event::< submit_call_and_look_event::<
runtime::balances::events::Transfer, runtime::balances::events::Transfer,
Payload<runtime::balances::calls::types::TransferKeepAlive>, StaticPayload<runtime::balances::calls::types::TransferKeepAlive>,
>( >(
data, data,
&runtime::tx() &runtime::tx()
...@@ -28,7 +28,7 @@ pub async fn transfer( ...@@ -28,7 +28,7 @@ pub async fn transfer(
(false, false) => { (false, false) => {
submit_call_and_look_event::< submit_call_and_look_event::<
runtime::balances::events::Transfer, runtime::balances::events::Transfer,
Payload<runtime::balances::calls::types::TransferAllowDeath>, StaticPayload<runtime::balances::calls::types::TransferAllowDeath>,
>( >(
data, data,
&runtime::tx() &runtime::tx()
...@@ -40,7 +40,7 @@ pub async fn transfer( ...@@ -40,7 +40,7 @@ pub async fn transfer(
(true, true) => { (true, true) => {
submit_call_and_look_event::< submit_call_and_look_event::<
runtime::balances::events::Transfer, runtime::balances::events::Transfer,
Payload<runtime::universal_dividend::calls::types::TransferUdKeepAlive>, StaticPayload<runtime::universal_dividend::calls::types::TransferUdKeepAlive>,
>( >(
data, data,
&runtime::tx() &runtime::tx()
...@@ -52,7 +52,7 @@ pub async fn transfer( ...@@ -52,7 +52,7 @@ pub async fn transfer(
(false, true) => { (false, true) => {
submit_call_and_look_event::< submit_call_and_look_event::<
runtime::balances::events::Transfer, runtime::balances::events::Transfer,
Payload<runtime::universal_dividend::calls::types::TransferUd>, StaticPayload<runtime::universal_dividend::calls::types::TransferUd>,
>( >(
data, data,
&runtime::tx() &runtime::tx()
...@@ -83,7 +83,7 @@ pub async fn transfer_multiple( ...@@ -83,7 +83,7 @@ pub async fn transfer_multiple(
// wrap these calls in a batch call // wrap these calls in a batch call
submit_call_and_look_event::< submit_call_and_look_event::<
runtime::utility::events::BatchCompleted, runtime::utility::events::BatchCompleted,
Payload<runtime::utility::calls::types::Batch>, StaticPayload<runtime::utility::calls::types::Batch>,
>(data, &runtime::tx().utility().batch(transactions)) >(data, &runtime::tx().utility().batch(transactions))
.await .await
} }
...@@ -26,7 +26,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE ...@@ -26,7 +26,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
pub async fn claim_ud(data: &Data) -> Result<(), subxt::Error> { pub async fn claim_ud(data: &Data) -> Result<(), subxt::Error> {
submit_call_and_look_event::< submit_call_and_look_event::<
runtime::universal_dividend::events::UdsClaimed, runtime::universal_dividend::events::UdsClaimed,
Payload<runtime::universal_dividend::calls::types::ClaimUds>, StaticPayload<runtime::universal_dividend::calls::types::ClaimUds>,
>(data, &runtime::tx().universal_dividend().claim_uds()) >(data, &runtime::tx().universal_dividend().claim_uds())
.await .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> {
// Calling the new function with show_g1v1 = true to maintain compatibility
compute_vault_accounts_table_with_g1v1(account_tree_nodes, true)
}
pub fn compute_vault_accounts_table_with_g1v1(
account_tree_nodes: &[Rc<RefCell<AccountTreeNode>>],
show_g1v1: bool,
) -> Result<Table, GcliError> {
let mut table = Table::new();
table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
// Prepare header based on options
table.set_header(vec![
if show_g1v1 {
"SS58 Address/G1v1 public key"
} else {
"SS58 Address"
},
"Crypto",
"Path",
"Name",
]);
for account_tree_node in account_tree_nodes {
let _ = add_account_tree_node_to_table_with_g1v1(&mut table, account_tree_node, show_g1v1);
}
Ok(table)
}
fn add_account_tree_node_to_table_with_g1v1(
table: &mut Table,
account_tree_node: &Rc<RefCell<AccountTreeNode>>,
show_g1v1: bool,
) -> Result<(), GcliError> {
let rows = compute_vault_accounts_row_with_g1v1(account_tree_node, show_g1v1)?;
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_with_g1v1(table, child, show_g1v1);
}
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 if show_g1v1 is true
pub fn compute_vault_accounts_row_with_g1v1(
account_tree_node: &Rc<RefCell<AccountTreeNode>>,
show_g1v1: bool,
) -> 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());
let crypto_scheme_str: &str = crypto_scheme.into();
// Add a second line for the G1v1 public key only if show_g1v1 is true and it's an Ed25519 key
let is_ed25519 = crypto_scheme == CryptoScheme::Ed25519;
if show_g1v1 && is_ed25519 {
rows.push(vec![Cell::new(format!(
"└ G1v1: {}",
cesium::compute_g1v1_public_key_from_ed25519_account_id(
&account_tree_node.account.address.0
)
))]);
}
(
format!("<{}>", account_tree_node.account.account_type()),
crypto_scheme_str.to_string(),
)
};
// Add the first line
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, compute_vault_accounts_table_with_g1v1,
};
use crate::entities::vault_account::tests::account_tree_node_tests::{
mother_account_tree_node, mother_g1v1_account_tree_node,
};
use indoc::indoc;
// Tests for compute_vault_accounts_table (old function)
#[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);
}
// Tests for compute_vault_accounts_table_with_g1v1
#[test]
fn test_compute_vault_accounts_table_with_g1v1_empty() {
// Test with show_g1v1 = true (default behavior)
let table = compute_vault_accounts_table_with_g1v1(&[], true).unwrap();
let expected_table_with_g1v1 = indoc! {r#"
┌─────────────────────────────────────────────────────┐
│ SS58 Address/G1v1 public key Crypto Path Name │
╞═════════════════════════════════════════════════════╡
└─────────────────────────────────────────────────────┘"#
};
assert_eq!(table.to_string(), expected_table_with_g1v1);
// Test with show_g1v1 = false
let table = compute_vault_accounts_table_with_g1v1(&[], false).unwrap();
let expected_table_without_g1v1 = indoc! {r#"
┌─────────────────────────────────────┐
│ SS58 Address Crypto Path Name │
╞═════════════════════════════════════╡
└─────────────────────────────────────┘"#
};
assert_eq!(table.to_string(), expected_table_without_g1v1);
}
#[test]
fn test_compute_vault_accounts_table_with_g1v1() {
let account_tree_node = mother_account_tree_node();
let g1v1_account_tree_node = mother_g1v1_account_tree_node();
let account_tree_nodes = vec![account_tree_node, g1v1_account_tree_node];
// Test with show_g1v1 = true (default behavior)
let table_with_g1v1 =
compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
let expected_table_with_g1v1 = 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_with_g1v1.to_string(), expected_table_with_g1v1);
// Test with show_g1v1 = false
let table_without_g1v1 =
compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
let expected_table_without_g1v1 = indoc! {r#"
┌──────────────────────────────────────────────────────────────────────────────────────────┐
│ SS58 Address 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 │
└──────────────────────────────────────────────────────────────────────────────────────────┘"#
};
assert_eq!(table_without_g1v1.to_string(), expected_table_without_g1v1);
}
#[test]
fn test_compute_vault_accounts_table_with_g1v1_partial() {
let mother = mother_account_tree_node();
let child1 = mother.borrow().children[0].clone();
let account_tree_nodes = vec![child1];
// Test with show_g1v1 = true (default behavior)
let table_with_g1v1 =
compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
let expected_table_with_g1v1 = indoc! {r#"
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ SS58 Address/G1v1 public key Crypto Path Name │
╞═════════════════════════════════════════════════════════════════════════════════════╡
│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │
│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │
└─────────────────────────────────────────────────────────────────────────────────────┘"#
};
assert_eq!(table_with_g1v1.to_string(), expected_table_with_g1v1);
// Test with show_g1v1 = false
let table_without_g1v1 =
compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
let expected_table_without_g1v1 = indoc! {r#"
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ SS58 Address Crypto Path Name │
╞═════════════════════════════════════════════════════════════════════════════════════╡
│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH //0 Child 1 │
│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d //0 Grandchild 1 │
└─────────────────────────────────────────────────────────────────────────────────────┘"#
};
assert_eq!(table_without_g1v1.to_string(), expected_table_without_g1v1);
}
}
}
use crate::entities::vault_account;
use crate::entities::vault_account::DbAccountId;
use crate::*; use crate::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
...@@ -6,14 +8,13 @@ const APP_NAME: &str = "gcli"; ...@@ -6,14 +8,13 @@ const APP_NAME: &str = "gcli";
/// defines structure of config file /// defines structure of config file
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Config { pub struct Config {
// duniter endpoint /// duniter endpoint
pub duniter_endpoint: String, pub duniter_endpoint: String,
// indexer endpoint /// indexer endpoint
pub indexer_endpoint: String, pub indexer_endpoint: String,
// user address /// user address
/// to perform actions, user must provide secret
pub address: Option<AccountId>, pub address: Option<AccountId>,
// user secret (substrate format)
pub secret: Option<String>,
} }
impl std::default::Default for Config { impl std::default::Default for Config {
...@@ -22,18 +23,12 @@ impl std::default::Default for Config { ...@@ -22,18 +23,12 @@ impl std::default::Default for Config {
duniter_endpoint: String::from(data::LOCAL_DUNITER_ENDPOINT), duniter_endpoint: String::from(data::LOCAL_DUNITER_ENDPOINT),
indexer_endpoint: String::from(data::LOCAL_INDEXER_ENDPOINT), indexer_endpoint: String::from(data::LOCAL_INDEXER_ENDPOINT),
address: None, address: None,
secret: None,
} }
} }
} }
impl std::fmt::Display for Config { impl std::fmt::Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let secret = if self.secret.is_some() {
"(secret defined)"
} else {
"(no secret)"
};
let address = if let Some(address) = &self.address { let address = if let Some(address) = &self.address {
format!("{}", address) format!("{}", address)
} else { } else {
...@@ -42,7 +37,7 @@ impl std::fmt::Display for Config { ...@@ -42,7 +37,7 @@ impl std::fmt::Display for Config {
writeln!(f, "Ğcli config")?; writeln!(f, "Ğcli config")?;
writeln!(f, "duniter endpoint {}", self.duniter_endpoint)?; writeln!(f, "duniter endpoint {}", self.duniter_endpoint)?;
writeln!(f, "indexer endpoint {}", self.indexer_endpoint)?; writeln!(f, "indexer endpoint {}", self.indexer_endpoint)?;
write!(f, "address {address} {secret}") write!(f, "address {address}")
} }
} }
...@@ -79,7 +74,7 @@ pub enum Subcommand { ...@@ -79,7 +74,7 @@ pub enum Subcommand {
} }
/// handle conf command /// handle conf command
pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> { pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
// match subcommand // match subcommand
match command { match command {
Subcommand::Where => { Subcommand::Where => {
...@@ -90,9 +85,19 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> ...@@ -90,9 +85,19 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError>
} }
Subcommand::Show => { Subcommand::Show => {
println!("{}", data.cfg); 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 => { Subcommand::Save => {
confy::store(APP_NAME, None, &data.cfg).expect("unable to write config"); save(&data.cfg);
} }
Subcommand::Default => { Subcommand::Default => {
confy::store(APP_NAME, None, Config::default()).expect("unable to write config"); confy::store(APP_NAME, None, Config::default()).expect("unable to write config");
...@@ -101,3 +106,8 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> ...@@ -101,3 +106,8 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError>
Ok(()) Ok(())
} }
pub fn save(cfg: &Config) {
confy::store(APP_NAME, None, cfg).expect("unable to write config");
println!("Configuration updated!");
}
use std::str::FromStr; use crate::commands::vault;
use crate::*; use crate::*;
use indexer::Indexer; use indexer::Indexer;
use sea_orm::DatabaseConnection;
// consts // consts
pub const LOCAL_DUNITER_ENDPOINT: &str = "ws://localhost:9944"; pub const LOCAL_DUNITER_ENDPOINT: &str = "ws://localhost:9944";
pub const LOCAL_INDEXER_ENDPOINT: &str = "http://localhost:4350/graphql"; 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")] #[cfg(feature = "gdev")]
pub const GDEV_DUNITER_ENDPOINTS: [&str; 5] = [ pub const GDEV_DUNITER_ENDPOINTS: [&str; 5] = [
...@@ -17,36 +19,40 @@ pub const GDEV_DUNITER_ENDPOINTS: [&str; 5] = [ ...@@ -17,36 +19,40 @@ pub const GDEV_DUNITER_ENDPOINTS: [&str; 5] = [
]; ];
#[cfg(feature = "gdev")] #[cfg(feature = "gdev")]
pub const GDEV_INDEXER_ENDPOINTS: [&str; 2] = [ pub const GDEV_INDEXER_ENDPOINTS: [&str; 2] = [
"https://subsquid.gdev.coinduf.eu/graphql", // "https://squid.gdev.coinduf.eu/v1/graphql",
"https://gdev-squid.axiom-team.fr/graphql", "https://squid.gdev.gyroi.de/v1/graphql",
"https://gdev-squid.axiom-team.fr/v1/graphql",
]; ];
// data derived from command arguments // data derived from command arguments
/// Data of current command /// Data of current command
/// can also include fetched information /// can also include fetched information
#[derive(Default)]
pub struct Data { pub struct Data {
// command line arguments /// command line arguments
pub args: Args, pub args: Args,
// config /// config
pub cfg: conf::Config, pub cfg: conf::Config,
// rpc to substrate client /// database connection
connection: Option<DatabaseConnection>,
/// rpc to substrate client
pub client: Option<Client>, pub client: Option<Client>,
// graphql to duniter-indexer /// graphql to duniter-indexer
pub indexer: Option<Indexer>, pub indexer: Option<Indexer>,
// user keypair /// user keypair
pub keypair: Option<KeyPair>, pub keypair: Option<KeyPair>,
// user identity index /// user identity index
pub idty_index: Option<IdtyId>, pub idty_index: Option<IdtyId>,
// token decimals /// token decimals
pub token_decimals: u32, pub token_decimals: u32,
// token symbol /// token symbol
pub token_symbol: String, pub token_symbol: String,
// genesis hash /// genesis hash
pub genesis_hash: Hash, pub genesis_hash: Hash,
// indexer genesis hash /// indexer genesis hash
pub indexer_genesis_hash: Hash, pub indexer_genesis_hash: Hash,
/// gcli base path
pub project_dir: directories::ProjectDirs,
} }
/// system properties defined in client specs /// system properties defined in client specs
...@@ -57,22 +63,53 @@ struct SystemProperties { ...@@ -57,22 +63,53 @@ struct SystemProperties {
token_symbol: String, 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 // implement helper functions for Data
impl Data { impl Data {
/// --- constructor --- /// --- constructor ---
pub fn new(args: Args) -> Self { pub async fn new(args: Args) -> Result<Self, GcliError> {
Self { let mut data = Self {
args, args,
cfg: conf::load_conf(), cfg: conf::load_conf(),
token_decimals: 0, token_decimals: 0,
token_symbol: "tokens".into(), token_symbol: "tokens".into(),
..Default::default() ..Default::default()
} };
.overwrite_from_args() //Necessary to support checking "vault names" in the base arguments
.build_from_config() data = data.build_connection().await?;
data = data.overwrite_from_args().await?;
Ok(data)
} }
// --- getters --- // --- getters ---
// the "unwrap" should not fail if data is well prepared // 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 { pub fn client(&self) -> &Client {
self.client.as_ref().expect("must build client first") self.client.as_ref().expect("must build client first")
} }
...@@ -82,10 +119,29 @@ impl Data { ...@@ -82,10 +119,29 @@ impl Data {
pub fn address(&self) -> AccountId { pub fn address(&self) -> AccountId {
self.cfg.address.clone().expect("an address is needed") self.cfg.address.clone().expect("an address is needed")
} }
pub fn keypair(&self) -> KeyPair { pub async fn keypair(&self) -> KeyPair {
match self.keypair.clone() { match self.keypair.clone() {
Some(keypair) => keypair, Some(keypair) => keypair,
None => prompt_secret(self.args.secret_format), None => loop {
match fetch_or_get_keypair(self, self.cfg.address.clone(), None).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 { pub fn idty_index(&self) -> IdtyId {
...@@ -93,16 +149,21 @@ impl Data { ...@@ -93,16 +149,21 @@ impl Data {
} }
// --- methods --- // --- methods ---
pub fn format_balance(&self, amount: Balance) -> String { pub fn format_balance(&self, amount: Balance) -> String {
let base: u32 = 10; let base: u64 = 10;
let integer_part = amount / base.pow(self.token_decimals);
let fractional_part = amount % base.pow(self.token_decimals);
format!( format!(
"{} {}", "{}.{:0left_padding$} {}",
(amount as f32) / (base.pow(self.token_decimals) as f32), integer_part,
self.token_symbol fractional_part,
self.token_symbol,
left_padding = self.token_decimals as usize
) )
} }
// --- mutators --- // --- mutators ---
/// use arguments to overwrite config /// use arguments to overwrite config
pub fn overwrite_from_args(mut self) -> Self { pub async fn overwrite_from_args(mut self) -> Result<Self, GcliError> {
// network // network
if let Some(network) = self.args.network.clone() { if let Some(network) = self.args.network.clone() {
// a network was provided as arugment // a network was provided as arugment
...@@ -145,73 +206,54 @@ impl Data { ...@@ -145,73 +206,54 @@ impl Data {
self.cfg.indexer_endpoint = indexer_endpoint self.cfg.indexer_endpoint = indexer_endpoint
} }
// secret format and value // secret format and value
if self.args.secret_format == SecretFormat::Predefined { if let Some(secret_format) = self.args.secret_format {
// predefined secret format overwrites secret with mnemonic let keypair = get_keypair(
match self.args.secret.clone() { secret_format,
None => {} self.args.secret.as_deref(),
Some(derivation) => { self.args.crypto_scheme,
self.cfg.secret = Some(predefined_mnemonic(&derivation)); )?;
} self.cfg.address = Some(keypair.address());
}; self.keypair = Some(keypair);
} else if self.args.secret_format == SecretFormat::Cesium {
// cesium secret format also overwrites, to force valid prompt
self.cfg.secret = None
} else if let Some(secret) = self.args.secret.clone() {
// other secret type
self.cfg.secret = Some(secret);
} }
// address // address
if let Some(address) = self.args.address.clone() { if let Some(address) = self.args.address.clone() {
self.cfg.address = Some(AccountId::from_str(&address).expect("invalid address")); self.cfg.address = Some(address.clone());
// if giving address, cancel secret // if giving address, cancel secret
self.cfg.secret = None self.keypair = None
}
self
}
/// build from config
pub fn build_from_config(mut self) -> Self {
let secret_format = self.args.secret_format;
// prevent incoherent state
if secret_format == SecretFormat::Cesium && self.cfg.secret.is_some() {
panic!("incompatible input: secret arg with cesium format");
}
// if secret format is cesium, force a prompt now and record keypair
if secret_format == SecretFormat::Cesium {
let keypair = prompt_secret(SecretFormat::Cesium);
self.cfg.address = Some(keypair.address());
self.keypair = Some(keypair);
} }
// if a secret is defined (format should not be cesium), build keypair and silently overwrite address // (vault)name
if let Some(secret) = self.cfg.secret.clone() { if let Some(name) = self.args.name.clone() {
let keypair = pair_from_secret(secret_format, &secret).expect("invalid secret"); let account = vault::retrieve_vault_account_for_name(self.connect_db(), &name).await?;
self.cfg.address = Some(keypair.public().into());
self.keypair = Some(keypair.into()); self.cfg.address = Some(account.address.0.clone());
// if giving (vault)name, cancel secret
self.keypair = None
} }
self Ok(self)
} }
/// build a client from url /// build a client from url
pub async fn build_client(mut self) -> Result<Self, GcliError> { pub async fn build_client(mut self) -> Result<Self, GcliError> {
let duniter_endpoint = self.cfg.duniter_endpoint.clone(); let duniter_endpoint = &self.cfg.duniter_endpoint;
self.client = Some(Client::from_url(&duniter_endpoint).await.map_err(|e| { let client = Client::from_url(duniter_endpoint).await.map_err(|e| {
GcliError::Duniter(format!( // to get more details TODO fixme, see issue #18
"could not establish connection with the server {}, due to error {}", dbg!(e);
duniter_endpoint, GcliError::Duniter(format!("can not connect to duniter {duniter_endpoint}",))
dbg!(e) // needed to get more details TODO fixme })?;
)) self.client = Some(client);
})?);
self.genesis_hash = commands::blockchain::fetch_genesis_hash(&self).await?; self.genesis_hash = commands::blockchain::fetch_genesis_hash(&self).await?;
Ok(self) Ok(self)
} }
/// build an indexer if not disabled /// build an indexer if not disabled
pub async fn build_indexer(mut self) -> Result<Self, anyhow::Error> { pub async fn build_indexer(mut self) -> Result<Self, GcliError> {
if self.args.no_indexer { if self.args.no_indexer {
log::info!("called build_indexer while providing no_indexer"); log::info!("called build_indexer while providing no_indexer");
self.indexer = None; self.indexer = None;
} else { } else {
self.indexer = Some(Indexer { self.indexer = Some(Indexer {
gql_client: reqwest::Client::builder() gql_client: reqwest::Client::builder()
.user_agent("gcli/0.1.0") .user_agent(format!("gcli/{PKG_VERSION}"))
.build()?, .build()
.unwrap(),
gql_url: self.cfg.indexer_endpoint.clone(), gql_url: self.cfg.indexer_endpoint.clone(),
}); });
self.indexer_genesis_hash = self.indexer().fetch_genesis_hash().await?; self.indexer_genesis_hash = self.indexer().fetch_genesis_hash().await?;
...@@ -221,7 +263,16 @@ impl Data { ...@@ -221,7 +263,16 @@ impl Data {
}; };
Ok(self) Ok(self)
} }
/// get issuer index
/// 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 /// needs address and client first
pub async fn fetch_idty_index(mut self) -> Result<Self, GcliError> { pub async fn fetch_idty_index(mut self) -> Result<Self, GcliError> {
self.idty_index = Some( self.idty_index = Some(
...@@ -234,11 +285,15 @@ impl Data { ...@@ -234,11 +285,15 @@ impl Data {
Ok(self) Ok(self)
} }
/// get properties /// get properties
pub async fn fetch_system_properties(mut self) -> Result<Self, anyhow::Error> { 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 = self.legacy_rpc_methods().await.system_properties().await?;
let system_properties = serde_json::from_value::<SystemProperties>( let system_properties = serde_json::from_value::<SystemProperties>(
serde_json::Value::Object(system_properties), 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_decimals = system_properties.token_decimals;
self.token_symbol = system_properties.token_symbol; self.token_symbol = system_properties.token_symbol;
Ok(self) Ok(self)
......
This diff is collapsed.
pub mod vault_account;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.