Skip to content
Snippets Groups Projects
Commit b0d5e5e8 authored by Éloïs's avatar Éloïs
Browse files

feat (wallet): add pay sub-command

parent ce062ab7
No related branches found
No related tags found
No related merge requests found
Pipeline #11355 passed
...@@ -101,17 +101,6 @@ dependencies = [ ...@@ -101,17 +101,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "async-trait"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
...@@ -155,6 +144,16 @@ version = "0.5.0" ...@@ -155,6 +144,16 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409"
[[package]]
name = "bincode"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772"
dependencies = [
"byteorder",
"serde",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
...@@ -217,9 +216,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" ...@@ -217,9 +216,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]] [[package]]
name = "bytes" name = "bytes"
...@@ -351,9 +350,9 @@ checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" ...@@ -351,9 +350,9 @@ checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d"
[[package]] [[package]]
name = "dubp" name = "dubp"
version = "0.48.0" version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e3fe6a250aaa17de9d86c77028ae2b22f4f7d3b9716659d89d82fb72b2db68" checksum = "1b9617091ba0d0a0cc95392655b0b7136fee3b63101d64c0fd0a5c2741983f18"
dependencies = [ dependencies = [
"dubp-common", "dubp-common",
"dubp-documents", "dubp-documents",
...@@ -365,23 +364,25 @@ dependencies = [ ...@@ -365,23 +364,25 @@ dependencies = [
[[package]] [[package]]
name = "dubp-client" name = "dubp-client"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.duniter.org/libs/dubp-rs-client-lib?branch=master#787229ae4a94fdb4278e86f8f47af15388345f90" source = "git+https://git.duniter.org/libs/dubp-rs-client-lib?branch=master#398b44b8b61e6cf631c1842ca44c9f3916c1c66f"
dependencies = [ dependencies = [
"async-trait", "bincode",
"dubp", "dubp",
"duniter-bca-types",
"graphql_client", "graphql_client",
"maybe-async", "maybe-async",
"mockall 0.9.1", "mockall 0.9.1",
"reqwest", "reqwest",
"serde", "serde",
"static_assertions",
"thiserror", "thiserror",
] ]
[[package]] [[package]]
name = "dubp-common" name = "dubp-common"
version = "0.48.0" version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83386c1914b8f3d4a1fb2895b6e31899019b05de3fb6c7ec45d0f13238e6c64" checksum = "da764b34a61ecb52fa90dc11f5a44d6bc9599b043d37592b5102b95d993fd677"
dependencies = [ dependencies = [
"dup-crypto", "dup-crypto",
"serde", "serde",
...@@ -392,9 +393,9 @@ dependencies = [ ...@@ -392,9 +393,9 @@ dependencies = [
[[package]] [[package]]
name = "dubp-documents" name = "dubp-documents"
version = "0.48.0" version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeabf1395e31c3e2a1dfabc1f97a933c361312e65f65ca843092b94a32e4ce19" checksum = "8810039b6dd6a101109aebc9836ddec58366052803f824c56016889d9444f5cf"
dependencies = [ dependencies = [
"beef", "beef",
"dubp-wallet", "dubp-wallet",
...@@ -406,9 +407,9 @@ dependencies = [ ...@@ -406,9 +407,9 @@ dependencies = [
[[package]] [[package]]
name = "dubp-documents-parser" name = "dubp-documents-parser"
version = "0.48.0" version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f441e42514531d4b04001ff95e20d9b681198bc8f13872af24281c350d2e96f2" checksum = "c791c77a3f9c670666dc41f861481cc80eb1ad193584727adfee792350fd3129"
dependencies = [ dependencies = [
"dubp-documents", "dubp-documents",
"json-pest-parser", "json-pest-parser",
...@@ -420,9 +421,9 @@ dependencies = [ ...@@ -420,9 +421,9 @@ dependencies = [
[[package]] [[package]]
name = "dubp-wallet" name = "dubp-wallet"
version = "0.48.0" version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b89b1d3262a26156de5edcd97e20c2cd747a24ee1244e929b5ac3aada5519b94" checksum = "2ef8e7c5334ddbc7899f83911026ebc324ba7fc7232c7ca5265e611490dbd282"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"dubp-common", "dubp-common",
...@@ -432,11 +433,23 @@ dependencies = [ ...@@ -432,11 +433,23 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "duniter-bca-types"
version = "0.1.0"
source = "git+https://git.duniter.org/nodes/typescript/duniter?branch=bca#fd6d221857461f75d059c5fc2afc24daab4d5545"
dependencies = [
"bincode",
"dubp",
"serde",
"smallvec",
"thiserror",
]
[[package]] [[package]]
name = "dup-crypto" name = "dup-crypto"
version = "0.48.0" version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093aa174cc3792e029e5deabf83986842595279840e5eab58e845efe33d75b48" checksum = "0be04829b31b18bacf5317001366d807e5fbd02085ee6348508c1299b5bcaf6c"
dependencies = [ dependencies = [
"aes", "aes",
"arrayvec", "arrayvec",
...@@ -1458,6 +1471,12 @@ version = "0.5.2" ...@@ -1458,6 +1471,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
......
...@@ -9,14 +9,14 @@ description = "A command line client written in Rust that use Duniter GVA API." ...@@ -9,14 +9,14 @@ description = "A command line client written in Rust that use Duniter GVA API."
[dependencies] [dependencies]
anyhow = "1.0.32" anyhow = "1.0.32"
dubp-client = { git = "https://git.duniter.org/libs/dubp-rs-client-lib", branch = "master", features = ["blocking"] } 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 } #dubp-client= { path = "../dubp-rs-client-lib", features = ["blocking"], default-features = false }
rpassword = "5.0.1" rpassword = "5.0.1"
serde = { version = "1.0.105", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] }
structopt = "0.3.18" structopt = "0.3.18"
[dev-dependencies] [dev-dependencies]
dubp-client = { git = "https://git.duniter.org/libs/dubp-rs-client-lib", branch = "master", features = ["blocking", "mock"] } 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 } #dubp-client= { path = "../dubp-rs-client-lib", features = ["blocking", "mock"], default-features = false }
mockall = "0.8.0" mockall = "0.8.0"
...@@ -14,17 +14,21 @@ ...@@ -14,17 +14,21 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*; use crate::*;
use dubp_client::crypto::{ use dubp_client::{
crypto::{
bases::b58::ToBase58, bases::b58::ToBase58,
dewif::{ dewif::{
read_dewif_file_content, write_dewif_v4_content, Currency, ExpectedCurrency, G1_CURRENCY, read_dewif_file_content, write_dewif_v4_content, Currency, ExpectedCurrency,
G1_CURRENCY,
}, },
keys::{ keys::{
ed25519::bip32::{KeyPair, PrivateDerivationPath}, ed25519::bip32::{KeyPair, PrivateDerivationPath},
KeyPair as _, KeyPairEnum, KeyPair as _, KeyPairEnum, PublicKey,
}, },
mnemonic::{mnemonic_to_seed, Language, Mnemonic, MnemonicType}, mnemonic::{mnemonic_to_seed, Language, Mnemonic, MnemonicType},
utils::U31, utils::U31,
},
wallet::prelude::SourceAmount,
}; };
#[derive(StructOpt)] #[derive(StructOpt)]
...@@ -44,7 +48,7 @@ pub enum WalletCommand { ...@@ -44,7 +48,7 @@ pub enum WalletCommand {
word_count: MnemonicType, word_count: MnemonicType,
}, },
/// Get a public key /// Get a public key
GetPubkey { Pubkey {
account_index: u32, account_index: u32,
address_index: Option<u32>, address_index: Option<u32>,
#[structopt(long)] #[structopt(long)]
...@@ -53,6 +57,8 @@ pub enum WalletCommand { ...@@ -53,6 +57,8 @@ pub enum WalletCommand {
dewif_file: Option<PathBuf>, dewif_file: Option<PathBuf>,
#[structopt(short, long)] #[structopt(short, long)]
external: bool, external: bool,
#[structopt(short, long, default_value = "en")]
lang: Language,
#[structopt(long)] #[structopt(long)]
password_stdin: bool, password_stdin: bool,
}, },
...@@ -65,9 +71,33 @@ pub enum WalletCommand { ...@@ -65,9 +71,33 @@ pub enum WalletCommand {
#[structopt(long)] #[structopt(long)]
password_stdin: bool, password_stdin: bool,
}, },
/// send money
Pay {
account_index: u32,
#[structopt(short, long)]
amount: u64,
#[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: String,
},
} }
pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::Result<()> { pub(crate) fn wallet<C: GvaClient, W: Write>(
gva_client: &C,
out: &mut W,
command: WalletCommand,
) -> anyhow::Result<()> {
match command { match command {
WalletCommand::Gen { WalletCommand::Gen {
lang, lang,
...@@ -104,18 +134,21 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R ...@@ -104,18 +134,21 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R
gen_dewif(out, log_n, mnemonic, password_stdin)?; gen_dewif(out, log_n, mnemonic, password_stdin)?;
Ok(()) Ok(())
} }
WalletCommand::GetPubkey { WalletCommand::Pubkey {
account_index, account_index,
address_index, address_index,
dewif, dewif,
dewif_file, dewif_file,
external, external,
lang,
password_stdin, password_stdin,
} => { } => {
let dewif_content = get_dewif_content(dewif, dewif_file)?; let keypair = get_master_keypair(dewif, dewif_file, lang, password_stdin)?;
let keypair = get_keypair_from_dewif(dewif_content, password_stdin)?;
let derivation_path = PrivateDerivationPath::opaque( let derivation_path = match account_index % 3 {
0 => PrivateDerivationPath::transparent(U31::new(account_index)?)?,
1 => todo!(),
2 => PrivateDerivationPath::opaque(
U31::new(account_index)?, U31::new(account_index)?,
external, external,
if let Some(address_index) = address_index { if let Some(address_index) = address_index {
...@@ -123,7 +156,9 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R ...@@ -123,7 +156,9 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R
} else { } else {
None None
}, },
)?; )?,
_ => unreachable!(),
};
let derived_keypair = keypair.derive(derivation_path); let derived_keypair = keypair.derive(derivation_path);
...@@ -131,6 +166,68 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R ...@@ -131,6 +166,68 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R
Ok(()) Ok(())
} }
WalletCommand::Pay {
account_index,
amount,
base,
comment,
dewif,
dewif_file,
lang,
password_stdin,
recipient,
} => {
let recipient = PublicKey::from_base58(&recipient)?;
let keypair = get_master_keypair(dewif, dewif_file, lang, password_stdin)?;
match account_index % 3 {
0 => {
let trasparent_keypair = keypair.derive(PrivateDerivationPath::transparent(
U31::new(account_index)?,
)?);
writeln!(
out,
"Sending {}.{} Ğ1 to {} from transparent account {} …",
amount / 100,
amount % 100,
recipient,
trasparent_keypair.public_key().to_base58()
)?;
let req_time = Instant::now();
let payment_result = gva_client.simple_payment(
SourceAmount::new(amount as i64, base.unwrap_or_default() as i64),
&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 {
writeln!(out, "Payment succesfully sent.")?;
let duration = req_time.elapsed();
println!(
"Payment processed in {}.{} ms.",
duration.as_millis(),
duration.subsec_micros() % 1_000
);
}
Ok(())
}
1 => todo!(),
2 => todo!(),
_ => unreachable!(),
}
}
} }
} }
...@@ -166,6 +263,22 @@ fn gen_dewif<W: Write>( ...@@ -166,6 +263,22 @@ fn gen_dewif<W: Write>(
Ok(()) 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( fn get_dewif_content(
dewif_opt: Option<String>, dewif_opt: Option<String>,
dewif_file_opt: Option<PathBuf>, dewif_file_opt: Option<PathBuf>,
...@@ -191,7 +304,10 @@ fn get_dewif_content( ...@@ -191,7 +304,10 @@ fn get_dewif_content(
} }
} }
fn get_keypair_from_dewif(dewif_content: String, password_stdin: bool) -> anyhow::Result<KeyPair> { fn get_master_keypair_from_dewif(
dewif_content: String,
password_stdin: bool,
) -> anyhow::Result<KeyPair> {
let password = if password_stdin { let password = if password_stdin {
rpassword::read_password()? rpassword::read_password()?
} else { } else {
......
...@@ -35,7 +35,7 @@ use commands::{ ...@@ -35,7 +35,7 @@ use commands::{
use dubp_client::MockGvaClient; use dubp_client::MockGvaClient;
use dubp_client::{ use dubp_client::{
crypto::keys::{ed25519::PublicKey, PublicKey as _}, crypto::keys::{ed25519::PublicKey, PublicKey as _},
AccountBalance, GvaClient, Idty, NaiveGvaClient, PubkeyOrScript, AccountBalance, GvaClient, Idty, NaiveGvaClient, PaymentResult, PubkeyOrScript,
}; };
use std::{ use std::{
env::var_os, env::var_os,
...@@ -73,7 +73,7 @@ fn main() -> anyhow::Result<()> { ...@@ -73,7 +73,7 @@ fn main() -> anyhow::Result<()> {
Command::CurrentUd => current_ud(&gva_client, &mut out)?, Command::CurrentUd => current_ud(&gva_client, &mut out)?,
Command::Idty { pubkey } => idty(&gva_client, &mut out, &pubkey)?, Command::Idty { pubkey } => idty(&gva_client, &mut out, &pubkey)?,
Command::MembersCount => members_count(&gva_client, &mut out)?, Command::MembersCount => members_count(&gva_client, &mut out)?,
Command::Wallet { command } => wallet(&mut out, command)?, Command::Wallet { command } => wallet(&gva_client, &mut out, command)?,
} }
Ok(()) Ok(())
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment