From b0d5e5e84b0ee092624f4376126fa38fa2745a2a Mon Sep 17 00:00:00 2001 From: librelois <c@elo.tf> Date: Sat, 20 Mar 2021 03:38:33 +0100 Subject: [PATCH] feat (wallet): add pay sub-command --- Cargo.lock | 73 +++++++++++------- Cargo.toml | 4 +- src/commands/wallet.rs | 168 ++++++++++++++++++++++++++++++++++------- src/main.rs | 4 +- 4 files changed, 192 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97e06d3..099be32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,17 +101,6 @@ dependencies = [ "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]] name = "atty" version = "0.2.14" @@ -155,6 +144,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "bitflags" version = "1.2.1" @@ -217,9 +216,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" @@ -351,9 +350,9 @@ checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" [[package]] name = "dubp" -version = "0.48.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e3fe6a250aaa17de9d86c77028ae2b22f4f7d3b9716659d89d82fb72b2db68" +checksum = "1b9617091ba0d0a0cc95392655b0b7136fee3b63101d64c0fd0a5c2741983f18" dependencies = [ "dubp-common", "dubp-documents", @@ -365,23 +364,25 @@ dependencies = [ [[package]] name = "dubp-client" 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 = [ - "async-trait", + "bincode", "dubp", + "duniter-bca-types", "graphql_client", "maybe-async", "mockall 0.9.1", "reqwest", "serde", + "static_assertions", "thiserror", ] [[package]] name = "dubp-common" -version = "0.48.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83386c1914b8f3d4a1fb2895b6e31899019b05de3fb6c7ec45d0f13238e6c64" +checksum = "da764b34a61ecb52fa90dc11f5a44d6bc9599b043d37592b5102b95d993fd677" dependencies = [ "dup-crypto", "serde", @@ -392,9 +393,9 @@ dependencies = [ [[package]] name = "dubp-documents" -version = "0.48.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeabf1395e31c3e2a1dfabc1f97a933c361312e65f65ca843092b94a32e4ce19" +checksum = "8810039b6dd6a101109aebc9836ddec58366052803f824c56016889d9444f5cf" dependencies = [ "beef", "dubp-wallet", @@ -406,9 +407,9 @@ dependencies = [ [[package]] name = "dubp-documents-parser" -version = "0.48.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f441e42514531d4b04001ff95e20d9b681198bc8f13872af24281c350d2e96f2" +checksum = "c791c77a3f9c670666dc41f861481cc80eb1ad193584727adfee792350fd3129" dependencies = [ "dubp-documents", "json-pest-parser", @@ -420,9 +421,9 @@ dependencies = [ [[package]] name = "dubp-wallet" -version = "0.48.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89b1d3262a26156de5edcd97e20c2cd747a24ee1244e929b5ac3aada5519b94" +checksum = "2ef8e7c5334ddbc7899f83911026ebc324ba7fc7232c7ca5265e611490dbd282" dependencies = [ "byteorder", "dubp-common", @@ -432,11 +433,23 @@ dependencies = [ "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]] name = "dup-crypto" -version = "0.48.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093aa174cc3792e029e5deabf83986842595279840e5eab58e845efe33d75b48" +checksum = "0be04829b31b18bacf5317001366d807e5fbd02085ee6348508c1299b5bcaf6c" dependencies = [ "aes", "arrayvec", @@ -1458,6 +1471,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index a2bd143..0a29130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,14 @@ description = "A command line client written in Rust that use Duniter GVA API." [dependencies] 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 } 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"] } +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" diff --git a/src/commands/wallet.rs b/src/commands/wallet.rs index c2f4009..a4c596c 100644 --- a/src/commands/wallet.rs +++ b/src/commands/wallet.rs @@ -14,17 +14,21 @@ // 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, +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, PublicKey, + }, + mnemonic::{mnemonic_to_seed, Language, Mnemonic, MnemonicType}, + utils::U31, }, - keys::{ - ed25519::bip32::{KeyPair, PrivateDerivationPath}, - KeyPair as _, KeyPairEnum, - }, - mnemonic::{mnemonic_to_seed, Language, Mnemonic, MnemonicType}, - utils::U31, + wallet::prelude::SourceAmount, }; #[derive(StructOpt)] @@ -44,7 +48,7 @@ pub enum WalletCommand { word_count: MnemonicType, }, /// Get a public key - GetPubkey { + Pubkey { account_index: u32, address_index: Option<u32>, #[structopt(long)] @@ -53,6 +57,8 @@ pub enum WalletCommand { dewif_file: Option<PathBuf>, #[structopt(short, long)] external: bool, + #[structopt(short, long, default_value = "en")] + lang: Language, #[structopt(long)] password_stdin: bool, }, @@ -65,9 +71,33 @@ pub enum WalletCommand { #[structopt(long)] 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 { WalletCommand::Gen { lang, @@ -104,26 +134,31 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R gen_dewif(out, log_n, mnemonic, password_stdin)?; Ok(()) } - WalletCommand::GetPubkey { + WalletCommand::Pubkey { account_index, address_index, dewif, dewif_file, external, + lang, password_stdin, } => { - let dewif_content = get_dewif_content(dewif, dewif_file)?; - let keypair = get_keypair_from_dewif(dewif_content, password_stdin)?; - - let derivation_path = PrivateDerivationPath::opaque( - U31::new(account_index)?, - external, - if let Some(address_index) = address_index { - Some(U31::new(address_index)?) - } else { - None - }, - )?; + 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); @@ -131,6 +166,68 @@ pub(crate) fn wallet<W: Write>(out: &mut W, command: WalletCommand) -> anyhow::R 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>( 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>, @@ -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 { rpassword::read_password()? } else { diff --git a/src/main.rs b/src/main.rs index f192aa4..cebd558 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ use commands::{ use dubp_client::MockGvaClient; use dubp_client::{ crypto::keys::{ed25519::PublicKey, PublicKey as _}, - AccountBalance, GvaClient, Idty, NaiveGvaClient, PubkeyOrScript, + AccountBalance, GvaClient, Idty, NaiveGvaClient, PaymentResult, PubkeyOrScript, }; use std::{ env::var_os, @@ -73,7 +73,7 @@ fn main() -> anyhow::Result<()> { 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(&mut out, command)?, + Command::Wallet { command } => wallet(&gva_client, &mut out, command)?, } Ok(()) } -- GitLab