|
|
@ -5,6 +5,8 @@ use json::{object}; |
|
|
|
|
|
|
|
use crate::lightclient::LightClient; |
|
|
|
use crate::lightwallet::LightWallet; |
|
|
|
use zcash_primitives::transaction::components::amount::DEFAULT_FEE; |
|
|
|
use std::convert::TryInto; |
|
|
|
|
|
|
|
pub trait Command { |
|
|
|
fn help(&self) -> String; |
|
|
@ -489,69 +491,82 @@ impl Command for SendCommand { |
|
|
|
// Parse the args. There are two argument types.
|
|
|
|
// 1 - A set of 2(+1 optional) arguments for a single address send representing address, value, memo?
|
|
|
|
// 2 - A single argument in the form of a JSON string that is "[{address: address, value: value, memo: memo},...]"
|
|
|
|
|
|
|
|
// 1 - Destination address. T or Z address
|
|
|
|
if args.len() < 1 || args.len() > 3 { |
|
|
|
return self.help(); |
|
|
|
} |
|
|
|
|
|
|
|
// Check for a single argument that can be parsed as JSON
|
|
|
|
let send_args = if args.len() == 1 { |
|
|
|
let arg_list = args[0]; |
|
|
|
|
|
|
|
let json_args = match json::parse(&arg_list) { |
|
|
|
Ok(j) => j, |
|
|
|
Err(e) => { |
|
|
|
let es = format!("Couldn't understand JSON: {}", e); |
|
|
|
return format!("{}\n{}", es, self.help()); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
if !json_args.is_array() { |
|
|
|
return format!("Couldn't parse argument as array\n{}", self.help()); |
|
|
|
} |
|
|
|
|
|
|
|
let maybe_send_args = json_args.members().map( |j| { |
|
|
|
if !j.has_key("address") || !j.has_key("amount") { |
|
|
|
Err(format!("Need 'address' and 'amount'\n")) |
|
|
|
} else { |
|
|
|
Ok((j["address"].as_str().unwrap().to_string().clone(), j["amount"].as_u64().unwrap(), j["memo"].as_str().map(|s| s.to_string().clone()))) |
|
|
|
} |
|
|
|
}).collect::<Result<Vec<(String, u64, Option<String>)>, String>>(); |
|
|
|
|
|
|
|
match maybe_send_args { |
|
|
|
Ok(a) => a.clone(), |
|
|
|
Err(s) => { return format!("Error: {}\n{}", s, self.help()); } |
|
|
|
match json::parse(&arg_list) { |
|
|
|
Ok(json_args) => { |
|
|
|
// Check if the parsed JSON is an array.
|
|
|
|
if !json_args.is_array() { |
|
|
|
return format!("Couldn't parse argument as array\n{}", self.help()); |
|
|
|
} |
|
|
|
|
|
|
|
// Map each JSON object to a tuple (address, amount, memo, fee).
|
|
|
|
let maybe_send_args = json_args.members().map(|j| { |
|
|
|
if !j.has_key("address") || !j.has_key("amount") { |
|
|
|
Err(format!("Need 'address' and 'amount'\n")) |
|
|
|
} else { |
|
|
|
let fee = j["fee"].as_u64().unwrap_or(DEFAULT_FEE.try_into().unwrap()); |
|
|
|
Ok(( |
|
|
|
j["address"].as_str().unwrap().to_string(), |
|
|
|
j["amount"].as_u64().unwrap(), |
|
|
|
j["memo"].as_str().map(|s| s.to_string()), |
|
|
|
fee |
|
|
|
)) |
|
|
|
} |
|
|
|
}).collect::<Result<Vec<(String, u64, Option<String>, u64)>, String>>(); |
|
|
|
|
|
|
|
// Handle any errors that occurred during mapping.
|
|
|
|
match maybe_send_args { |
|
|
|
Ok(a) => a, |
|
|
|
Err(s) => return format!("Error: {}\n{}", s, self.help()), |
|
|
|
} |
|
|
|
}, |
|
|
|
Err(e) => return format!("Couldn't understand JSON: {}\n{}", e, self.help()), |
|
|
|
} |
|
|
|
} else if args.len() == 2 || args.len() == 3 { |
|
|
|
} else { |
|
|
|
// Handle the case where individual arguments are provided.
|
|
|
|
let address = args[0].to_string(); |
|
|
|
|
|
|
|
// Make sure we can parse the amount
|
|
|
|
let value = match args[1].parse::<u64>() { |
|
|
|
Ok(amt) => amt, |
|
|
|
Err(e) => { |
|
|
|
return format!("Couldn't parse amount: {}", e); |
|
|
|
} |
|
|
|
Err(e) => return format!("Couldn't parse amount: {}", e), |
|
|
|
}; |
|
|
|
|
|
|
|
let memo = if args.len() == 3 { Some(args[2].to_string()) } else { None }; |
|
|
|
|
|
|
|
// Memo has to be None if not sending to a shileded address
|
|
|
|
let memo = args.get(2).map(|m| m.to_string()); |
|
|
|
|
|
|
|
// Memo should be None if the address is not shielded.
|
|
|
|
if memo.is_some() && !LightWallet::is_shielded_address(&address, &lightclient.config) { |
|
|
|
return format!("Can't send a memo to the non-shielded address {}", address); |
|
|
|
} |
|
|
|
|
|
|
|
vec![(args[0].to_string(), value, memo)] |
|
|
|
} else { |
|
|
|
return self.help() |
|
|
|
|
|
|
|
// Create a vector with a single transaction (address, amount, memo).
|
|
|
|
vec![(address, value, memo, DEFAULT_FEE.try_into().unwrap())] |
|
|
|
}; |
|
|
|
|
|
|
|
// Transform transaction data into the required format (String -> &str).
|
|
|
|
let tos = send_args.iter().map(|(a, v, m, _)| (a.as_str(), *v, m.clone())).collect::<Vec<_>>(); |
|
|
|
|
|
|
|
// Calculate the total fee for all transactions.
|
|
|
|
// This assumes that all transactions have the same fee.
|
|
|
|
// If they can have different fees, you need to modify this logic.
|
|
|
|
|
|
|
|
let default_fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
|
|
|
let mut total_fee = default_fee;
|
|
|
|
|
|
|
|
// Convert to the right format. String -> &str.
|
|
|
|
let tos = send_args.iter().map(|(a, v, m)| (a.as_str(), *v, m.clone()) ).collect::<Vec<_>>(); |
|
|
|
match lightclient.do_send(tos) { |
|
|
|
Ok(txid) => { object!{ "txid" => txid } }, |
|
|
|
Err(e) => { object!{ "error" => e } } |
|
|
|
}.pretty(2) |
|
|
|
for (_, _, _, fee) in send_args.iter() { |
|
|
|
if *fee != default_fee{ |
|
|
|
total_fee = *fee; |
|
|
|
break;
|
|
|
|
} |
|
|
|
} |
|
|
|
// Execute the transaction and handle the result.
|
|
|
|
match lightclient.do_send(tos, &total_fee) { |
|
|
|
Ok(txid) => object!{ "txid" => txid }.pretty(2), |
|
|
|
Err(e) => object!{ "error" => e }.pretty(2), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|