mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-11-13 10:17:02 +00:00
Added sendp2sh command (experimental)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use json::{object};
|
use json::{object};
|
||||||
|
use base58::{FromBase58};
|
||||||
|
|
||||||
use crate::lightclient::LightClient;
|
use crate::lightclient::LightClient;
|
||||||
|
|
||||||
@@ -548,6 +549,125 @@ impl Command for SendCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SendP2shCommand {}
|
||||||
|
impl Command for SendP2shCommand {
|
||||||
|
fn help(&self) -> String {
|
||||||
|
let mut h = vec![];
|
||||||
|
h.push("Send ARRR to a given address(es)");
|
||||||
|
h.push("Usage:");
|
||||||
|
h.push("send '{'input': <address>, 'output': [{'address': <address>, 'amount': <amount in zatoshis>, 'memo': <optional memo>, 'script': <redeem script>}, ...]}");
|
||||||
|
h.push("");
|
||||||
|
h.push("NOTE: The fee required to send this transaction (currently ZEC 0.0001) is additionally detected from your balance.");
|
||||||
|
h.push("Example:");
|
||||||
|
h.push("send '{\"input\":\"ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d\", \"output\": [{ \"address\": \"ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d\", \"amount\": 200000, \"memo\": \"Hello from the command line\", \"script\": \"acbdef\"}]}'");
|
||||||
|
h.push("");
|
||||||
|
h.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_help(&self) -> String {
|
||||||
|
"Send ARRR to the given P2SH address, including supplied redeem script".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
|
||||||
|
// Parse the args.
|
||||||
|
// A single argument in the form of a JSON string that is "{input: address, output: [{address: address, value: value, memo: memo},...], fee: fee}"
|
||||||
|
|
||||||
|
// 1 - Destination address. T or Z address
|
||||||
|
if args.len() != 1 {
|
||||||
|
return self.help();
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
|
||||||
|
|
||||||
|
// Check for a single argument that can be parsed as JSON
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Check for a fee key and convert to u64
|
||||||
|
let fee: u64 = if json_args.has_key("fee") {
|
||||||
|
match json_args["fee"].as_u64() {
|
||||||
|
Some(f) => f.clone(),
|
||||||
|
None => DEFAULT_FEE.try_into().unwrap()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEFAULT_FEE.try_into().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
//Check for a input key and convert to str
|
||||||
|
let from = if json_args.has_key("input") {
|
||||||
|
json_args["input"].as_str().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
return format!("Error: {}\n{}", "Need input address", self.help());
|
||||||
|
};
|
||||||
|
|
||||||
|
//Check for output key
|
||||||
|
let json_tos = if json_args.has_key("output") {
|
||||||
|
&json_args["output"]
|
||||||
|
} else {
|
||||||
|
return format!("Error: {}\n{}", "Need output address", self.help());
|
||||||
|
};
|
||||||
|
|
||||||
|
//Check output is in the form of an array
|
||||||
|
if !json_tos.is_array() {
|
||||||
|
return format!("Couldn't parse argument as array\n{}", self.help());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for output script and convert to a string
|
||||||
|
let script58 = if json_args.has_key("script") {
|
||||||
|
json_args["script"].as_str().unwrap().to_string().clone()
|
||||||
|
} else {
|
||||||
|
return format!("Error: {}\n{}", "Need script", self.help());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decode base58 encoded string
|
||||||
|
let script_vec = script58.from_base58().unwrap();
|
||||||
|
let script_bytes = &script_vec[..];
|
||||||
|
|
||||||
|
//Check array for manadantory address and amount keys
|
||||||
|
let maybe_send_args = json_tos.members().map( |j| {
|
||||||
|
if !j.has_key("address") || !j.has_key("amount") {
|
||||||
|
Err(format!("Need 'address' and 'amount'\n"))
|
||||||
|
} else {
|
||||||
|
let amount = match j["amount"].as_str() {
|
||||||
|
Some("entire-verified-zbalance") => lightclient.wallet.read().unwrap().verified_zbalance(None).checked_sub(fee),
|
||||||
|
_ => Some(j["amount"].as_u64().unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
match amount {
|
||||||
|
Some(amt) => Ok((j["address"].as_str().unwrap().to_string().clone(), amt, j["memo"].as_str().map(|s| s.to_string().clone()))),
|
||||||
|
None => Err(format!("Not enough in wallet to pay transaction fee"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect::<Result<Vec<(String, u64, Option<String>)>, String>>();
|
||||||
|
|
||||||
|
let send_args = match maybe_send_args {
|
||||||
|
Ok(a) => a.clone(),
|
||||||
|
Err(s) => { return format!("Error: {}\n{}", s, self.help()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
match lightclient.do_sync(true) {
|
||||||
|
Ok(_) => {
|
||||||
|
// 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_p2sh(from, tos, &fee, script_bytes) {
|
||||||
|
Ok(txid) => { object!{ "txid" => txid } },
|
||||||
|
Err(e) => { object!{ "error" => e } }
|
||||||
|
}.pretty(2)
|
||||||
|
},
|
||||||
|
Err(e) => e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct SaveCommand {}
|
struct SaveCommand {}
|
||||||
impl Command for SaveCommand {
|
impl Command for SaveCommand {
|
||||||
fn help(&self) -> String {
|
fn help(&self) -> String {
|
||||||
@@ -859,6 +979,7 @@ pub fn get_commands() -> Box<HashMap<String, Box<dyn Command>>> {
|
|||||||
map.insert("export".to_string(), Box::new(ExportCommand{}));
|
map.insert("export".to_string(), Box::new(ExportCommand{}));
|
||||||
map.insert("info".to_string(), Box::new(InfoCommand{}));
|
map.insert("info".to_string(), Box::new(InfoCommand{}));
|
||||||
map.insert("send".to_string(), Box::new(SendCommand{}));
|
map.insert("send".to_string(), Box::new(SendCommand{}));
|
||||||
|
map.insert("sendp2sh".to_string(), Box::new(SendP2shCommand{}));
|
||||||
map.insert("save".to_string(), Box::new(SaveCommand{}));
|
map.insert("save".to_string(), Box::new(SaveCommand{}));
|
||||||
map.insert("quit".to_string(), Box::new(QuitCommand{}));
|
map.insert("quit".to_string(), Box::new(QuitCommand{}));
|
||||||
map.insert("list".to_string(), Box::new(TransactionsCommand{}));
|
map.insert("list".to_string(), Box::new(TransactionsCommand{}));
|
||||||
|
|||||||
@@ -1722,6 +1722,30 @@ impl LightClient {
|
|||||||
|
|
||||||
result.map(|(txid, _)| txid)
|
result.map(|(txid, _)| txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn do_send_p2sh(&self, from: &str, addrs: Vec<(&str, u64, Option<String>)>, fee: &u64, script: &[u8]) -> Result<String, String> {
|
||||||
|
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
|
||||||
|
error!("Wallet is locked");
|
||||||
|
return Err("Wallet is locked".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Creating P2SH transaction");
|
||||||
|
|
||||||
|
let result = {
|
||||||
|
let _lock = self.sync_lock.lock().unwrap();
|
||||||
|
|
||||||
|
self.wallet.write().unwrap().send_to_p2sh_with_redeem_script(
|
||||||
|
u32::from_str_radix(&self.config.consensus_branch_id, 16).unwrap(),
|
||||||
|
&self.sapling_spend, &self.sapling_output,
|
||||||
|
from, addrs, script, fee,
|
||||||
|
|txbytes| broadcast_raw_tx(&self.get_server_uri(), txbytes)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Transaction Complete");
|
||||||
|
|
||||||
|
result.map(|(txid, _)| txid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user