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
Select Git revision
  • 48-error-base-58-requirement-is-violated
  • hugo/dev
  • hugo/tx-comments
  • json-output
  • master
  • no-rename
  • nostr
  • poka/dev
  • tuxmain/mail
  • 0.1.0
  • 0.2.0
  • 0.2.1
  • 0.2.10
  • 0.2.12
  • 0.2.13
  • 0.2.14
  • 0.2.15
  • 0.2.16
  • 0.2.17
  • 0.2.3
  • 0.2.4
  • 0.2.5
  • 0.2.6
  • 0.2.7
  • 0.2.8
  • 0.2.9
  • 0.3.0
  • 0.4.0
  • 0.4.1
  • 0.4.2
  • 0.4.3-RC1
  • 0.4.3-RC2
32 results

Target

Select target project
  • clients/rust/gcli-v2s
  • d0p1/gcli-v2s
  • flebon/gcli-v2s
  • zicmama/gcli-v2s
  • Nicolas80/gcli-v2s
5 results
Select Git revision
  • hugo/dev
  • hugo/tx-comments
  • master
  • poka/dev
  • tuxmain/mail
  • vault-support-for-all-secret-format
  • 0.1.0
  • 0.2.0
  • 0.2.1
  • 0.2.10
  • 0.2.12
  • 0.2.13
  • 0.2.14
  • 0.2.15
  • 0.2.16
  • 0.2.17
  • 0.2.3
  • 0.2.4
  • 0.2.5
  • 0.2.6
  • 0.2.7
  • 0.2.8
  • 0.2.9
  • 0.3.0
24 results
Show changes
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 BalancesCall = runtime::runtime_types::pallet_balances::pallet::Call;
......@@ -10,35 +10,58 @@ pub async fn transfer(
balance: u64,
dest: AccountId,
keep_alive: bool,
is_ud: bool,
) -> Result<(), subxt::Error> {
let progress = if keep_alive {
data.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().balances().transfer(dest.into(), balance),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?
} else {
data.client()
.tx()
.sign_and_submit_then_watch(
match (keep_alive, is_ud) {
(true, false) => {
submit_call_and_look_event::<
runtime::balances::events::Transfer,
StaticPayload<runtime::balances::calls::types::TransferKeepAlive>,
>(
data,
&runtime::tx()
.balances()
.transfer_keep_alive(dest.into(), balance),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?
};
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::balances::events::Transfer>()? {
println!("{e:?}");
.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
......@@ -57,22 +80,10 @@ pub async fn transfer_multiple(
})
})
.collect();
// wrap these calls in a batch call
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().utility().batch(transactions),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
// TODO all transfer
if let Some(e) = events.find_first::<runtime::balances::events::Transfer>()? {
println!("{e:?}");
}
Ok(())
submit_call_and_look_event::<
runtime::utility::events::BatchCompleted,
StaticPayload<runtime::utility::calls::types::Batch>,
>(data, &runtime::tx().utility().batch(transactions))
.await
}
......@@ -9,13 +9,13 @@ pub enum Subcommand {
}
/// handle ud commands
pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<()> {
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?;
let data = data.build_client().await?.fetch_system_properties().await?;
// match subcommand
match command {
Subcommand::Claim => {
claim_ud(data).await?;
claim_ud(&data).await?;
}
};
......@@ -23,21 +23,10 @@ pub async fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<(
}
/// claim universal dividend
pub async fn claim_ud(data: Data) -> Result<(), anyhow::Error> {
let progress = data
.client()
.tx()
.sign_and_submit_then_watch(
&runtime::tx().universal_dividend().claim_uds(),
&PairSigner::new(data.keypair()),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
let events = track_progress(progress).await?;
if let Some(e) = events.find_first::<runtime::universal_dividend::events::UdsClaimed>()? {
println!("{e:?}");
}
Ok(())
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> {
// 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 serde::{Deserialize, Serialize};
......@@ -6,14 +8,13 @@ const APP_NAME: &str = "gcli";
/// defines structure of config file
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
// duniter endpoint
/// duniter endpoint
pub duniter_endpoint: String,
// indexer endpoint
/// indexer endpoint
pub indexer_endpoint: String,
// user address
/// user address
/// to perform actions, user must provide secret
pub address: Option<AccountId>,
// user secret (substrate format)
pub secret: Option<String>,
}
impl std::default::Default for Config {
......@@ -22,11 +23,24 @@ impl std::default::Default for Config {
duniter_endpoint: String::from(data::LOCAL_DUNITER_ENDPOINT),
indexer_endpoint: String::from(data::LOCAL_INDEXER_ENDPOINT),
address: None,
secret: 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) {
......@@ -55,10 +69,12 @@ pub enum Subcommand {
Show,
/// Save config as modified by command line arguments
Save,
/// Rest config to default
Default,
}
/// handle conf command
pub fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<()> {
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
// match subcommand
match command {
Subcommand::Where => {
......@@ -68,12 +84,30 @@ pub fn handle_command(data: Data, command: Subcommand) -> anyhow::Result<()> {
);
}
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 => {
confy::store(APP_NAME, None, &data.cfg).expect("unable to write config");
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 std::str::FromStr;
use crate::commands::vault;
use crate::*;
use indexer::Indexer;
use sea_orm::DatabaseConnection;
// consts
pub const SUBSTRATE_MNEMONIC: &str =
"bottom drive obey lake curtain smoke basket hold race lonely fit walk";
pub const TEST_MNEMONIC: &str =
"pipe paddle ketchup filter life ice feel embody glide quantum ride usage";
pub const LOCAL_DUNITER_ENDPOINT: &str = "ws://localhost:9944";
pub const LOCAL_INDEXER_ENDPOINT: &str = "http://localhost:8080/v1/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")]
pub const GDEV_DUNITER_ENDPOINTS: [&str; 5] = [
......@@ -21,36 +19,40 @@ pub const GDEV_DUNITER_ENDPOINTS: [&str; 5] = [
];
#[cfg(feature = "gdev")]
pub const GDEV_INDEXER_ENDPOINTS: [&str; 2] = [
"https://gdev-indexer.p2p.legal/v1/graphql",
"https://hasura.gdev.coinduf.eu/v1/graphql",
// "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
#[derive(Default)]
pub struct Data {
// command line arguments
/// command line arguments
pub args: Args,
// config
/// config
pub cfg: conf::Config,
// rpc to substrate client
/// database connection
connection: Option<DatabaseConnection>,
/// rpc to substrate client
pub client: Option<Client>,
// graphql to duniter-indexer
/// graphql to duniter-indexer
pub indexer: Option<Indexer>,
// user keypair
pub keypair: Option<Pair>,
// user identity index
pub idty_index: Option<u32>,
// token decimals
/// user keypair
pub keypair: Option<KeyPair>,
/// user identity index
pub idty_index: Option<IdtyId>,
/// token decimals
pub token_decimals: u32,
// token symbol
/// token symbol
pub token_symbol: String,
// genesis hash
/// genesis hash
pub genesis_hash: Hash,
// indexer genesis hash
/// indexer genesis hash
pub indexer_genesis_hash: Hash,
/// gcli base path
pub project_dir: directories::ProjectDirs,
}
/// system properties defined in client specs
......@@ -61,22 +63,53 @@ struct SystemProperties {
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 fn new(args: Args) -> Self {
Self {
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()
}
.overwrite_from_args()
.build_from_config()
};
//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")
}
......@@ -86,27 +119,51 @@ impl Data {
pub fn address(&self) -> AccountId {
self.cfg.address.clone().expect("an address is needed")
}
pub fn keypair(&self) -> Pair {
pub async fn keypair(&self) -> KeyPair {
match self.keypair.clone() {
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());
}
_ => {}
}
pub fn idty_index(&self) -> u32 {
}
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: 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!(
"{} {}",
(amount as f32) / (base.pow(self.token_decimals) as f32),
self.token_symbol
"{}.{:0left_padding$} {}",
integer_part,
fractional_part,
self.token_symbol,
left_padding = self.token_decimals as usize
)
}
// --- mutators ---
/// 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
if let Some(network) = self.args.network.clone() {
// a network was provided as arugment
......@@ -148,75 +205,55 @@ impl Data {
if let Some(indexer_endpoint) = self.args.indexer.clone() {
self.cfg.indexer_endpoint = indexer_endpoint
}
// predefined secret
if self.args.secret_format == SecretFormat::Predefined {
match self.args.secret.clone() {
None => {}
Some(derivation) => {
if derivation.starts_with("test") {
let derivation = match &derivation[..] {
"test1" => "2",
"test2" => "4",
"test3" => "3",
_ => ""
};
self.cfg.secret = Some(format!("{TEST_MNEMONIC}//{derivation}"));
} else {
self.cfg.secret = Some(format!("{SUBSTRATE_MNEMONIC}//{derivation}"));
}
}
};
} else if let Some(secret) = self.args.secret.clone() {
// other secret type
self.cfg.secret = Some(secret);
// 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.args.crypto_scheme,
)?;
self.cfg.address = Some(keypair.address());
self.keypair = Some(keypair);
}
// address
if let Some(address) = self.args.address.clone() {
self.cfg.address = Some(AccountId::from_str(&address).expect("invalid address"));
}
self
}
/// build from config
pub fn build_from_config(mut self) -> Self {
// if a secret is defined, build keypair
if let Some(secret) = self.cfg.secret.clone() {
let (address, keypair) =
addr_and_pair_from_secret(SecretFormat::Predefined, &secret).unwrap();
// if an address is already defined and differs from secret, warns user
if let Some(address_) = self.cfg.address {
if address_ != address {
println!("overwriting address ({address_}) from secret ({address})");
}
self.cfg.address = Some(address.clone());
// if giving address, cancel secret
self.keypair = None
}
self.cfg.address = Some(address);
self.cfg.secret = Some(secret);
self.keypair = Some(keypair);
// (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
}
self
Ok(self)
}
/// build a client from url
pub async fn build_client(mut self) -> Result<Self, GcliError> {
let duniter_endpoint = self.cfg.duniter_endpoint.clone();
self.client = Some(Client::from_url(&duniter_endpoint).await.map_err(|e| {
GcliError::Duniter(format!(
"could not establish connection with the server {}, due to error {}",
duniter_endpoint,
dbg!(e) // needed to get more details TODO fixme
))
})?);
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, anyhow::Error> {
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("gcli/0.1.0")
.build()?,
.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?;
......@@ -226,22 +263,37 @@ impl Data {
};
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
pub async fn fetch_idty_index(mut self) -> Result<Self, anyhow::Error> {
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(anyhow::anyhow!("needs to be member to use this command"))?,
.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, anyhow::Error> {
let system_properties = self.client().rpc().system_properties().await?;
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)
......@@ -259,3 +311,18 @@ impl Data {
// );
// );
}
// 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(())
}
This diff is collapsed.
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 {
// IF NEEDED
// #[subxt(substitute_type = "spcore::sr25519::Signature")]
// use crate::gdev::runtime_types::sp_core::sr25519::Signature;
}
pub mod runtime {}
// declare custom types
pub type Client = subxt::OnlineClient<Runtime>;
pub type AccountId = subxt::ext::sp_runtime::AccountId32;
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>;
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 Index = u32;
type BlockNumber = u32;
type AssetId = ();
type Hash = Hash;
type Hashing = subxt::ext::sp_runtime::traits::BlakeTwo256;
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>;
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
......
This diff is collapsed.