Commit b0d5e5e8 authored by Éloïs's avatar Éloïs
Browse files

feat (wallet): add pay sub-command

parent ce062ab7
Pipeline #11355 passed with stages
in 10 minutes and 10 seconds
......@@ -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"
......
......@@ -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"
......@@ -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 {
......
......@@ -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(())
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment