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
  • master
1 result

Target

Select target project
  • tuxmain/gcli
1 result
Select Git revision
  • license
  • master
2 results
Show changes
Commits on Source (13)
[alias]
rr = "run --release --"
\ No newline at end of file
This diff is collapsed.
......@@ -9,14 +9,14 @@ description = "A command line client written in Rust that use Duniter GVA API."
[dependencies]
anyhow = "1.0.32"
graphql_client = "0.9.0"
reqwest = { version = "0.10.9", features = ["blocking", "json"] }
dubp-client = { git = "https://git.duniter.org/libs/dubp-rs-client-lib", branch = "master", features = ["blocking"], default-features = false }
#dubp-client= { path = "../dubp-rs-client-lib", features = ["blocking"], default-features = false }
read_input = "0.8.4"
rpassword = "5.0.1"
serde = { version = "1.0.105", features = ["derive"] }
structopt = "0.3.18"
[dev-dependencies]
dubp-client = { git = "https://git.duniter.org/libs/dubp-rs-client-lib", branch = "master", features = ["blocking", "mock"], default-features = false }
#dubp-client= { path = "../dubp-rs-client-lib", features = ["blocking", "mock"], default-features = false }
mockall = "0.8.0"
[build-dependencies]
duniter-gva-gql = { git = "https://git.duniter.org/nodes/typescript/duniter", branch = "dev" }
#duniter-gva-gql = { path = "../duniter/rust-libs/modules/gva/gql" }
......@@ -2,6 +2,89 @@
A simple command line client written in Rust that use Duniter GVA API.
## Use
### Generate new wallet
Use command `wallet gen`:
```bash
gcli wallet gen
Mnemonic: sibling pelican sense stereo plastic helmet book expand tube whale census multiply
Password:
DEWIF: AAAABAAAAAEMGzFNV+j80uJkBuAfazfFWZfz0ARlA5T/Ea3ZQTo7zjTiALR/jkf/pcBLFlsqtERCLozLcRP8Q74qj1NSFZ79Fw==
```
You can generate a mnemonic only with option `--mnemonic-only`:
```bash
gcli wallet gen --mnemonic-only
sibling pelican sense stereo plastic helmet book expand tube whale census multiply
```
You can choose another language, change mnemonic length, etc. See `wallet gen --help` for details.
#### Non-Interactive
You can choose a password in a non-interactive way with option `--password-stdin`:
```bash
echo "1234" | gcli wallet gen --password-stdin
Mnemonic: sibling pelican sense stereo plastic helmet book expand tube whale census multiply
DEWIF: AAAABAAAAAEMGzFNV+j80uJkBuAfazfFWZfz0ARlA5T/Ea3ZQTo7zjTiALR/jkf/pcBLFlsqtERCLozLcRP8Q74qj1NSFZ79Fw==
```
### Import a wallet form mnemonic
```bash
gcli wallet import
Mnemonic: sibling pelican sense stereo plastic helmet book expand tube whale census multiply
Password:
DEWIF: AAAABAAAAAEMGzFNV+j80uJkBuAfazfFWZfz0ARlA5T/Ea3ZQTo7zjTiALR/jkf/pcBLFlsqtERCLozLcRP8Q74qj1NSFZ79Fw==
```
You can choose another language, and other parameters. See `wallet import --help` for details.
#### Non-Interactive
You can choose a mnemonic and password in a non-interactive way with environment variable `MNEMONIC` and option `--password-stdin`:
```bash
echo "1234" | MNEMONIC="sibling pelican sense stereo plastic helmet book expand tube whale census multiply" gcli wallet import --password-stdin
DEWIF: AAAABAAAAAEMGzFNV+j80uJkBuAfazfFWZfz0ARlA5T/Ea3ZQTo7zjTiALR/jkf/pcBLFlsqtERCLozLcRP8Q74qj1NSFZ79Fw==
```
### Get a public key (=address)
To obtain the public key of the sub-account whose derivation number is `3`:
```bash
gcli wallet get-pubkey --dewif AAAABAAAAAEMGzFNV+j80uJkBuAfazfFWZfz0ARlA5T/Ea3ZQTo7zjTiALR/jkf/pcBLFlsqtERCLozLcRP8Q74qj1NSFZ79Fw== 3
Password:
2S7wAUzaXfG3Z1SWNULqBotnUb9QbucmeuVkqVL4X8FB
```
To obtain the master external* public key of the sub-account whose derivation number is `3`:
```bash
gcli wallet get-pubkey --external --dewif AAAABAAAAAEMGzFNV+j80uJkBuAfazfFWZfz0ARlA5T/Ea3ZQTo7zjTiALR/jkf/pcBLFlsqtERCLozLcRP8Q74qj1NSFZ79Fw== 3
Password:
9KV3gJFcsEH98FQhF3vPW4YG15VGRybHZuzAeD3oVicx
```
* The master external public key allows you to generate all payment receipt addresses by public key derivation. This is the key to be configured on your server to generate the payment addresses that will be provided to your customers.
You can also obtain an external or internal address in particular by its derivation index, see `wallet get-pubkey --help` for details.
#### Non-Interactive
You can enter your password in a non-interactive way with option `--password-stdin`:
```bash
echo "1234" | gcli wallet get-pubkey --dewif AAAABAAAAAEMGzFNV+j80uJkBuAfazfFWZfz0ARlA5T/Ea3ZQTo7zjTiALR/jkf/pcBLFlsqtERCLozLcRP8Q74qj1NSFZ79Fw== 3
2S7wAUzaXfG3Z1SWNULqBotnUb9QbucmeuVkqVL4X8FB
```
## Contribute
Contributions are welcome :)
......
fn main() {
let schema_sdl = duniter_gva_gql::get_schema_definition();
std::fs::write("gql/gva_schema.gql", schema_sdl.as_bytes())
.expect("Fail to write gva schema in file");
println!("cargo:rerun-if-changed=build.rs");
}
......@@ -17,7 +17,7 @@ skip-tree = [
[licenses]
unlicensed = "deny"
# We want really high confidence when inferring licenses from text
confidence-threshold = 0.92
confidence-threshold = 0.8
allow = [
"AGPL-3.0",
"Apache-2.0",
......
query Balance($script: String!, $withUd: Boolean!) {
balance(script: $script) {
amount
}
currentUd @include(if: $withUd) {
amount
}
}
query CurrentUd {
currentUd {
amount
}
}
query MembersCount {
currentBlock {
membersCount
}
}
......@@ -15,10 +15,13 @@
pub mod balance;
pub mod current_ud;
pub mod idty;
pub mod members_count;
pub mod wallet;
use crate::*;
#[allow(clippy::large_enum_variant)]
#[derive(StructOpt)]
pub(crate) enum Command {
/// Get account balance
......@@ -29,6 +32,13 @@ pub(crate) enum Command {
},
/// Get current UD value
CurrentUd,
/// Get identity
Idty { pubkey: String },
/// Get current number of WoT members
MembersCount,
/// Create or update a wallet
Wallet {
#[structopt(subcommand)]
command: wallet::WalletCommand,
},
}
......@@ -15,36 +15,46 @@
use crate::*;
pub(crate) fn balance<W: Write>(
client: &Client,
pub(crate) fn balance<C: GvaClient, W: Write>(
gva_client: &C,
out: &mut W,
pubkey_or_script: &str,
ud_unit: bool,
) -> anyhow::Result<()> {
let request_body = Balance::build_query(balance::Variables {
script: pubkey_or_script.to_owned(),
with_ud: ud_unit,
});
let pubkey_or_script = PubkeyOrScript::from_str(pubkey_or_script)?;
let balance::ResponseData {
balance: balance::BalanceBalance { amount },
current_ud: current_ud_opt,
} = client.send_gql_query(&request_body)?;
let req_time = Instant::now();
if let Some(AccountBalance {
amount,
ud_amount_opt,
}) = gva_client.account_balance(&pubkey_or_script, ud_unit)?
{
println!(
"The server responded in {} ms.",
req_time.elapsed().as_millis()
);
if let Some(balance::BalanceCurrentUd { amount: ud_amount }) = current_ud_opt {
if let Some(ud_amount) = ud_amount_opt {
writeln!(
out,
"The balance of account '{}' is {:.2} UDĞ1 !",
pubkey_or_script,
amount as f64 / ud_amount as f64,
pubkey_or_script.to_string(),
amount.amount() as f64 / ud_amount.amount() as f64,
)?;
} else {
writeln!(
out,
"The balance of account '{}' is {}.{:02} Ğ1 !",
pubkey_or_script,
amount / 100,
amount % 100
pubkey_or_script.to_string(),
amount.amount() / 100,
amount.amount() % 100
)?;
}
} else {
writeln!(
out,
"Account '{}' not exist !",
pubkey_or_script.to_string(),
)?;
}
Ok(())
......@@ -53,43 +63,43 @@ pub(crate) fn balance<W: Write>(
#[cfg(test)]
mod tests {
use super::*;
use dubp_client::wallet::prelude::SourceAmount;
#[test]
fn test_balance() -> anyhow::Result<()> {
let mut client = Client::default();
client
.expect_send_gql_query::<graphql_client::QueryBody<balance::Variables>, _>()
.returning(|_| {
Ok(balance::ResponseData {
balance: balance::BalanceBalance { amount: 2_046 },
current_ud: None,
})
let mut client = MockGvaClient::default();
client.expect_account_balance().returning(|_, _| {
Ok(Some(AccountBalance {
amount: SourceAmount::new(2046, 0),
ud_amount_opt: None,
}))
});
let mut out = Vec::new();
balance(&client, &mut out, "toto", false)?;
let output = std::str::from_utf8(&out)?;
assert_eq!(output, "The balance of account 'toto' is 20.46 Ğ1 !\n");
assert_eq!(output, "The balance of account 'SIG(toto)' is 20.46 Ğ1 !\n");
Ok(())
}
#[test]
fn test_balance_with_ud_unit() -> anyhow::Result<()> {
let mut client = Client::default();
client
.expect_send_gql_query::<graphql_client::QueryBody<balance::Variables>, _>()
.returning(|_| {
Ok(balance::ResponseData {
balance: balance::BalanceBalance { amount: 2_046 },
current_ud: Some(balance::BalanceCurrentUd { amount: 1_023 }),
})
let mut client = MockGvaClient::default();
client.expect_account_balance().returning(|_, _| {
Ok(Some(AccountBalance {
amount: SourceAmount::new(2_046, 0),
ud_amount_opt: Some(SourceAmount::new(1_023, 0)),
}))
});
let mut out = Vec::new();
balance(&client, &mut out, "toto", true)?;
let output = std::str::from_utf8(&out)?;
assert_eq!(output, "The balance of account 'toto' is 2.00 UDĞ1 !\n");
assert_eq!(
output,
"The balance of account 'SIG(toto)' is 2.00 UDĞ1 !\n"
);
Ok(())
}
......
......@@ -15,15 +15,19 @@
use crate::*;
pub(crate) fn current_ud<W: Write>(client: &Client, out: &mut W) -> anyhow::Result<()> {
let request_body = CurrentUd::build_query(current_ud::Variables);
if let current_ud::ResponseData {
current_ud: Some(current_ud::CurrentUdCurrentUd { amount }),
} = client.send_gql_query(&request_body)?
{
let int_part = amount / 100;
let dec_part = amount % 100;
pub(crate) fn current_ud<C: GvaClient, W: Write>(
gva_client: &C,
out: &mut W,
) -> anyhow::Result<()> {
let req_time = Instant::now();
if let Some(current_ud) = gva_client.current_ud()? {
println!(
"The server responded in {} ms.",
req_time.elapsed().as_millis()
);
let int_part = current_ud / 100;
let dec_part = current_ud % 100;
writeln!(
out,
"The current UD value is {}.{:02} Ğ1 !",
......@@ -42,14 +46,8 @@ mod tests {
#[test]
fn test_current_ud() -> anyhow::Result<()> {
let mut client = Client::default();
client
.expect_send_gql_query::<graphql_client::QueryBody<current_ud::Variables>, _>()
.returning(|_| {
Ok(current_ud::ResponseData {
current_ud: Some(current_ud::CurrentUdCurrentUd { amount: 1_023 }),
})
});
let mut client = MockGvaClient::default();
client.expect_current_ud().returning(|| Ok(Some(1_023)));
let mut out = Vec::new();
current_ud(&client, &mut out)?;
let output = std::str::from_utf8(&out)?;
......
// Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
pub(crate) fn idty<C: GvaClient, W: Write>(
gva_client: &C,
out: &mut W,
pubkey: &str,
) -> anyhow::Result<()> {
let pubkey = PublicKey::from_base58(pubkey)?;
let req_time = Instant::now();
if let Some(Idty {
is_member,
username,
..
}) = gva_client.idty_by_pubkey(pubkey)?
{
println!(
"The server responded in {} ms.",
req_time.elapsed().as_millis()
);
writeln!(out, "Found identity for pubkey:")?;
writeln!(out, "username: {}", username)?;
writeln!(out, "is_member: {}", is_member)?;
} else {
writeln!(out, "No identity for pubkey {}", pubkey)?;
}
Ok(())
}
......@@ -15,12 +15,16 @@
use crate::*;
pub(crate) fn members_count<W: Write>(client: &Client, out: &mut W) -> anyhow::Result<()> {
let request_body = MembersCount::build_query(members_count::Variables);
let members_count::ResponseData {
current_block: members_count::MembersCountCurrentBlock { members_count },
} = client.send_gql_query(&request_body)?;
pub(crate) fn members_count<C: GvaClient, W: Write>(
gva_client: &C,
out: &mut W,
) -> anyhow::Result<()> {
let req_time = Instant::now();
let members_count = gva_client.members_count()?;
println!(
"The server responded in {} ms.",
req_time.elapsed().as_millis()
);
writeln!(
out,
......@@ -39,16 +43,8 @@ mod tests {
#[test]
fn test_member_count() -> anyhow::Result<()> {
let mut client = Client::default();
client
.expect_send_gql_query::<graphql_client::QueryBody<members_count::Variables>, _>()
.returning(|_| {
Ok(members_count::ResponseData {
current_block: members_count::MembersCountCurrentBlock {
members_count: 10_000,
},
})
});
let mut client = MockGvaClient::default();
client.expect_members_count().returning(|| Ok(10_000));
let mut out = Vec::new();
members_count(&client, &mut out)?;
let output = std::str::from_utf8(&out)?;
......
// Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use dubp_client::{
crypto::{
bases::b58::ToBase58,
dewif::{
read_dewif_file_content, write_dewif_v4_content, Currency, ExpectedCurrency,
G1_CURRENCY,
},
keys::{
ed25519::bip32::{KeyPair, PrivateDerivationPath},
KeyPair as _, KeyPairEnum,
},
mnemonic::{mnemonic_to_seed, Language, Mnemonic, MnemonicType},
utils::U31,
},
wallet::prelude::SourceAmount,
};
#[allow(clippy::large_enum_variant)]
#[derive(StructOpt)]
pub enum WalletCommand {
/// Generate a wallet
Gen {
#[structopt(short, long, default_value = "en")]
lang: Language,
#[structopt(long, default_value = "12")]
log_n: u8,
#[structopt(short, long)]
mnemonic_only: bool,
#[structopt(long)]
password_stdin: bool,
/// Mnemonic word count
#[structopt(short, long, parse(try_from_str = parse_mnemonic_len), default_value = "12")]
word_count: MnemonicType,
},
/// Get a public key
Pubkey {
account_index: u32,
address_index: Option<u32>,
#[structopt(long)]
dewif: Option<String>,
#[structopt(long, parse(from_os_str))]
dewif_file: Option<PathBuf>,
#[structopt(short, long)]
external: bool,
#[structopt(short, long, default_value = "en")]
lang: Language,
#[structopt(long)]
password_stdin: bool,
},
/// Import a wallet from mnemonic
Import {
#[structopt(short, long, default_value = "en")]
lang: Language,
#[structopt(long, default_value = "12")]
log_n: u8,
#[structopt(long)]
password_stdin: bool,
},
/// send money
Pay {
account_index: u32,
#[structopt(short, long)]
amount: f64,
#[structopt(short, long)]
base: Option<u64>,
#[structopt(short, long)]
comment: Option<String>,
#[structopt(long)]
dewif: Option<String>,
#[structopt(long, parse(from_os_str))]
dewif_file: Option<PathBuf>,
#[structopt(short, long, default_value = "en")]
lang: Language,
#[structopt(long)]
password_stdin: bool,
#[structopt(short, long)]
recipient: PkOrScript,
/// UD units
#[structopt(short, long)]
ud_units: bool,
/// Auto confirm (for non-interactive use)
#[structopt(short, long)]
yes: bool,
},
}
pub(crate) fn wallet<C: GvaClient, W: Write>(
gva_client: &C,
out: &mut W,
command: WalletCommand,
) -> anyhow::Result<()> {
match command {
WalletCommand::Gen {
lang,
mnemonic_only,
log_n,
password_stdin,
word_count,
} => {
let mnemonic = Mnemonic::new(word_count, lang)
.map_err(|_| anyhow::Error::msg("unspecified rand error"))?;
if mnemonic_only {
writeln!(out, "{}", mnemonic.phrase())?;
} else {
writeln!(out, "Mnemonic: {}", mnemonic.phrase())?;
gen_dewif(out, log_n, mnemonic, password_stdin)?;
}
Ok(())
}
WalletCommand::Import {
lang,
log_n,
password_stdin,
} => {
let mnemonic_phrase = if let Some(mnemonic_env) = var_os("MNEMONIC") {
mnemonic_env
.into_string()
.map_err(|_| anyhow::Error::msg("invalid utf8 string"))?
} else {
rpassword::prompt_password_stdout("Mnemonic: ")?
};
let mnemonic = Mnemonic::from_phrase(mnemonic_phrase, lang)?;
gen_dewif(out, log_n, mnemonic, password_stdin)?;
Ok(())
}
WalletCommand::Pubkey {
account_index,
address_index,
dewif,
dewif_file,
external,
lang,
password_stdin,
} => {
let keypair = get_master_keypair(dewif, dewif_file, lang, password_stdin)?;
let derivation_path = match account_index % 3 {
0 => PrivateDerivationPath::transparent(U31::new(account_index)?)?,
1 => todo!(),
2 => PrivateDerivationPath::opaque(
U31::new(account_index)?,
external,
if let Some(address_index) = address_index {
Some(U31::new(address_index)?)
} else {
None
},
)?,
_ => unreachable!(),
};
let derived_keypair = keypair.derive(derivation_path);
writeln!(out, "{}", derived_keypair.public_key().to_base58())?;
Ok(())
}
WalletCommand::Pay {
account_index,
amount,
base,
comment,
dewif,
dewif_file,
lang,
password_stdin,
recipient,
ud_units,
yes,
} => {
let recipient = recipient.0;
let keypair = get_master_keypair(dewif, dewif_file, lang, password_stdin)?;
let (amount, amount_str) = if ud_units {
(Amount::Uds(amount), format!("{:.2} UDğ1", amount))
} else {
(
Amount::Cents(SourceAmount::new(
(amount * 100.0).round() as i64,
base.unwrap_or_default() as i64,
)),
format!("{:.2} Ğ1", amount),
)
};
match account_index % 3 {
0 => {
let trasparent_keypair = keypair.derive(PrivateDerivationPath::transparent(
U31::new(account_index)?,
)?);
let confirm = if yes {
true
} else {
let res = input()
.msg(format!(
"Send {} to {} from transparent account {}? [y/N]",
amount_str,
recipient.to_string(),
trasparent_keypair.public_key().to_base58()
))
.default('N')
.get();
res == 'y' || res == 'Y'
};
if confirm {
let req_time = Instant::now();
let payment_result = gva_client.simple_payment(
amount,
&trasparent_keypair.generate_signator(),
recipient,
comment,
None,
)?;
if let PaymentResult::Errors(errors) = payment_result {
writeln!(out, "All or part of the payment failed, errors: \n")?;
for error in errors {
writeln!(out, "- {:?}", error)?;
}
todo!()
} else {
let duration = req_time.elapsed();
writeln!(
out,
"Payment succesfully processed in {}.{} ms.",
duration.as_millis(),
duration.subsec_micros() % 1_000
)?;
}
}
Ok(())
}
1 => todo!(),
2 => todo!(),
_ => unreachable!(),
}
}
}
}
fn parse_mnemonic_len(s: &str) -> anyhow::Result<MnemonicType> {
Ok(MnemonicType::for_word_count(s.parse()?)?)
}
fn gen_dewif<W: Write>(
out: &mut W,
log_n: u8,
mnemonic: Mnemonic,
password_stdin: bool,
) -> anyhow::Result<()> {
let seed = mnemonic_to_seed(&mnemonic);
let keypair = KeyPair::from_seed(seed.clone());
let password = if password_stdin {
rpassword::read_password()?
} else {
rpassword::prompt_password_stdout("Password: ")?
};
//println!("TMP DEBUG: password={}", password);
let dewif = write_dewif_v4_content(
Currency::from(G1_CURRENCY),
log_n,
&password,
&keypair.public_key(),
seed,
);
writeln!(out, "DEWIF: {}", dewif)?;
Ok(())
}
fn get_master_keypair(
dewif_opt: Option<String>,
dewif_file_opt: Option<PathBuf>,
lang: Language,
password_stdin: bool,
) -> anyhow::Result<KeyPair> {
if dewif_opt.is_some() || dewif_file_opt.is_some() {
let dewif_content = get_dewif_content(dewif_opt, dewif_file_opt)?;
get_master_keypair_from_dewif(dewif_content, password_stdin)
} else {
let mnemonic_phrase = rpassword::prompt_password_stdout("Mnemonic: ")?;
let mnemonic = Mnemonic::from_phrase(mnemonic_phrase, lang)?;
Ok(KeyPair::from_seed(mnemonic_to_seed(&mnemonic)))
}
}
fn get_dewif_content(
dewif_opt: Option<String>,
dewif_file_opt: Option<PathBuf>,
) -> anyhow::Result<String> {
if let Some(dewif_env) = var_os("DEWIF") {
dewif_env
.into_string()
.map_err(|_| anyhow::Error::msg("invalid utf8 string"))
} else if let Some(dewif_file_env) = var_os("DEWIF_FILE") {
let mut buf_reader = BufReader::new(File::open(dewif_file_env)?);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?;
Ok(contents)
} else if let Some(dewif) = dewif_opt {
Ok(dewif)
} else if let Some(dewif_file) = dewif_file_opt {
let mut buf_reader = BufReader::new(File::open(dewif_file)?);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?;
Ok(contents)
} else {
Err(anyhow::Error::msg("no DEWIF provided"))
}
}
fn get_master_keypair_from_dewif(
dewif_content: String,
password_stdin: bool,
) -> anyhow::Result<KeyPair> {
let password = if password_stdin {
rpassword::read_password()?
} else {
rpassword::prompt_password_stdout("Password: ")?
};
let mut keypairs = read_dewif_file_content(
ExpectedCurrency::Specific(Currency::from(G1_CURRENCY)),
&dewif_content,
&password,
)?;
if let Some(KeyPairEnum::Bip32Ed25519(keypair)) = keypairs.next() {
Ok(keypair)
} else {
Err(anyhow::Error::msg("DEWIF corrupted"))
}
}
......@@ -14,50 +14,21 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use dubp_client::{
crypto::keys::PublicKey as _,
documents_parser::{wallet_script_from_str, TextParseError},
};
#[cfg_attr(test, allow(dead_code))]
pub(crate) struct Client {
inner: reqwest::blocking::Client,
server_url: String,
}
#[cfg_attr(test, mockall::automock, allow(dead_code))]
impl Client {
pub(crate) fn new(server_url: String) -> Self {
Client {
inner: reqwest::blocking::Client::new(),
server_url,
}
}
pub(crate) fn send_gql_query<
Req: 'static + serde::Serialize,
ResData: 'static + serde::de::DeserializeOwned,
>(
&self,
request_body: &Req,
) -> anyhow::Result<ResData> {
let request = self.inner.post(&self.server_url).json(request_body);
pub struct PkOrScript(pub(crate) WalletScriptV10);
let start_time = Instant::now();
let response = request.send()?;
let req_duration = Instant::now().duration_since(start_time);
println!("The server responded in {} ms.", req_duration.as_millis());
impl FromStr for PkOrScript {
type Err = TextParseError;
let mut gql_response: Response<ResData> = response.json()?;
if let Some(errors) = gql_response.errors.take() {
print_server_errors(errors);
Err(anyhow::Error::msg(""))
} else if let Some(data) = gql_response.data {
Ok(data)
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(pubkey) = PublicKey::from_base58(s) {
Ok(Self(WalletScriptV10::single_sig(pubkey)))
} else {
Err(anyhow::Error::msg("server response contains no data"))
Ok(Self(wallet_script_from_str(s)?))
}
}
}
fn print_server_errors(errors: Vec<graphql_client::Error>) {
println!("Server errors:");
for error in errors {
println!("{}", error);
}
}
......@@ -24,35 +24,35 @@
unused_import_braces
)]
mod client;
mod commands;
mod inputs;
#[cfg(not(test))]
use crate::client::Client;
#[cfg(test)]
use crate::client::MockClient as Client;
use crate::commands::Command;
use commands::{balance::balance, current_ud::current_ud, members_count::members_count};
use graphql_client::GraphQLQuery;
use graphql_client::Response;
use std::io::Write;
use std::time::Instant;
use crate::inputs::PkOrScript;
use commands::{
balance::balance, current_ud::current_ud, idty::idty, members_count::members_count,
wallet::wallet,
};
#[cfg(test)]
use dubp_client::MockGvaClient;
use dubp_client::{
crypto::keys::{ed25519::PublicKey, PublicKey as _},
wallet::prelude::*,
AccountBalance, Amount, GvaClient, Idty, NaiveGvaClient, PaymentResult, PubkeyOrScript,
};
use read_input::prelude::*;
use std::{
env::var_os,
fs::File,
io::{BufReader, Read, Write},
path::PathBuf,
str::FromStr,
time::Instant,
};
use structopt::StructOpt;
const DEFAULT_GVA_SERVER: &str = "https://g1.librelois.fr/gva";
#[derive(Debug, Clone, Copy, GraphQLQuery)]
#[graphql(schema_path = "gql/gva_schema.gql", query_path = "gql/gva_queries.gql")]
pub struct Balance;
#[derive(Debug, Clone, Copy, GraphQLQuery)]
#[graphql(schema_path = "gql/gva_schema.gql", query_path = "gql/gva_queries.gql")]
pub struct CurrentUd;
#[derive(Debug, Clone, Copy, GraphQLQuery)]
#[graphql(schema_path = "gql/gva_schema.gql", query_path = "gql/gva_queries.gql")]
pub struct MembersCount;
#[derive(StructOpt)]
#[structopt(name = "rust-gva-client", about = "Client use GVA API of Duniter.")]
struct CliArgs {
......@@ -66,16 +66,18 @@ struct CliArgs {
fn main() -> anyhow::Result<()> {
let cli_args = CliArgs::from_args();
let client = Client::new(cli_args.server);
let gva_client = NaiveGvaClient::new(&cli_args.server)?;
let mut out = std::io::stdout();
match cli_args.command {
Command::Balance {
pubkey_or_script,
ud_unit,
} => balance(&client, &mut out, &pubkey_or_script, ud_unit)?,
Command::CurrentUd => current_ud(&client, &mut out)?,
Command::MembersCount => members_count(&client, &mut out)?,
} => balance(&gva_client, &mut out, &pubkey_or_script, ud_unit)?,
Command::CurrentUd => current_ud(&gva_client, &mut out)?,
Command::Idty { pubkey } => idty(&gva_client, &mut out, &pubkey)?,
Command::MembersCount => members_count(&gva_client, &mut out)?,
Command::Wallet { command } => wallet(&gva_client, &mut out, command)?,
}
Ok(())
}