mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
Add file based transaction in owner API (#1484)
* Add file based transaction in owner API * Add finalize tx in owner API * Code cleanup and placed file_receive in correct API * Output an explicit error when http dest seems incorrect * Add doc on send method * Add cancel tx endpoint in owner API * Add dump stored tx endpoint in owner API * Add missing parameters
This commit is contained in:
parent
4030b3d2f1
commit
dd1cef2148
7 changed files with 270 additions and 118 deletions
|
@ -144,6 +144,12 @@ Outputs in your wallet will appear as unconfirmed or locked until the transactio
|
|||
|
||||
Other flags here are:
|
||||
|
||||
* `-m` 'Method', which can be 'http' or 'file'. In the first case, the transaction will be sent to the IP address which follows the `-d` flag. In the second case, Grin wallet will generate a partial transaction file under the file name specified in the `-d` flag. This file needs to be signed by the recipient using the `grin wallet receive -i filename` command and finalize by the sender using the `grin wallet finalize -i filename.response` command. To create a partial transaction file, use:
|
||||
|
||||
```
|
||||
[host]$ grin wallet send -d "transaction" -m file 60.00
|
||||
```
|
||||
|
||||
* `-s` 'Selection strategy', which can be 'all' or 'smallest'. Since it's advantageous for outputs to be removed from the Grin chain,
|
||||
the default strategy for selecting inputs in Step 1 above is to use as many outputs as possible to consolidate your balance into a
|
||||
couple of outputs. This also drastically reduces your wallet size, so everyone wins. The downside is that the entire contents of
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde_json as json;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
/// Wallet commands processing
|
||||
use std::process::exit;
|
||||
|
@ -166,7 +169,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
|||
wallet_config.clone(),
|
||||
passphrase,
|
||||
)));
|
||||
let res = controller::owner_single_use(wallet, |api| {
|
||||
let res = controller::owner_single_use(wallet.clone(), |api| {
|
||||
match wallet_args.subcommand() {
|
||||
("send", Some(send_args)) => {
|
||||
let amount = send_args
|
||||
|
@ -182,6 +185,9 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
|||
let selection_strategy = send_args
|
||||
.value_of("selection_strategy")
|
||||
.expect("Selection strategy required");
|
||||
let method = send_args
|
||||
.value_of("method")
|
||||
.expect("Payment method required");
|
||||
let dest = send_args
|
||||
.value_of("dest")
|
||||
.expect("Destination wallet address required");
|
||||
|
@ -192,54 +198,63 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
|||
.expect("Failed to parse number of change outputs.");
|
||||
let fluff = send_args.is_present("fluff");
|
||||
let max_outputs = 500;
|
||||
if dest.starts_with("http") {
|
||||
let result = api.issue_send_tx(
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
max_outputs,
|
||||
change_outputs,
|
||||
selection_strategy == "all",
|
||||
);
|
||||
let slate = match result {
|
||||
Ok(s) => {
|
||||
info!(
|
||||
LOGGER,
|
||||
"Tx created: {} grin to {} (strategy '{}')",
|
||||
core::amount_to_hr_string(amount, false),
|
||||
dest,
|
||||
selection_strategy,
|
||||
);
|
||||
s
|
||||
}
|
||||
Err(e) => {
|
||||
error!(LOGGER, "Tx not created: {:?}", e);
|
||||
match e.kind() {
|
||||
// user errors, don't backtrace
|
||||
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
|
||||
libwallet::ErrorKind::FeeDispute { .. } => {}
|
||||
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
|
||||
_ => {
|
||||
// otherwise give full dump
|
||||
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
||||
}
|
||||
};
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
let result = api.post_tx(&slate, fluff);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
info!(LOGGER, "Tx sent",);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!(LOGGER, "Tx not sent: {:?}", e);
|
||||
Err(e)
|
||||
if method == "http" {
|
||||
if dest.starts_with("http://") {
|
||||
let result = api.issue_send_tx(
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
max_outputs,
|
||||
change_outputs,
|
||||
selection_strategy == "all",
|
||||
);
|
||||
let slate = match result {
|
||||
Ok(s) => {
|
||||
info!(
|
||||
LOGGER,
|
||||
"Tx created: {} grin to {} (strategy '{}')",
|
||||
core::amount_to_hr_string(amount, false),
|
||||
dest,
|
||||
selection_strategy,
|
||||
);
|
||||
s
|
||||
}
|
||||
Err(e) => {
|
||||
error!(LOGGER, "Tx not created: {:?}", e);
|
||||
match e.kind() {
|
||||
// user errors, don't backtrace
|
||||
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
|
||||
libwallet::ErrorKind::FeeDispute { .. } => {}
|
||||
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
|
||||
_ => {
|
||||
// otherwise give full dump
|
||||
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
||||
}
|
||||
};
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
let result = api.post_tx(&slate, fluff);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
info!(LOGGER, "Tx sent",);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!(LOGGER, "Tx not sent: {:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
LOGGER,
|
||||
"HTTP Destination should start with http://: {}", dest
|
||||
);
|
||||
panic!();
|
||||
}
|
||||
} else {
|
||||
api.file_send_tx(
|
||||
} else if method == "file" {
|
||||
api.send_tx(
|
||||
true,
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
|
@ -248,21 +263,36 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
|||
selection_strategy == "all",
|
||||
).expect("Send failed");
|
||||
Ok(())
|
||||
} else {
|
||||
error!(LOGGER, "unsupported payment method: {}", method);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
("receive", Some(send_args)) => {
|
||||
let tx_file = send_args
|
||||
.value_of("input")
|
||||
.expect("Transaction file required");
|
||||
api.file_receive_tx(tx_file).expect("Receive failed");
|
||||
Ok(())
|
||||
let mut receive_result: Result<(), grin_wallet::libwallet::Error> = Ok(());
|
||||
let res = controller::foreign_single_use(wallet, |api| {
|
||||
let tx_file = send_args
|
||||
.value_of("input")
|
||||
.expect("Transaction file required");
|
||||
receive_result = api.file_receive_tx(tx_file);
|
||||
Ok(())
|
||||
});
|
||||
if res.is_err() {
|
||||
exit(1);
|
||||
}
|
||||
receive_result
|
||||
}
|
||||
("finalize", Some(send_args)) => {
|
||||
let fluff = send_args.is_present("fluff");
|
||||
let tx_file = send_args
|
||||
.value_of("input")
|
||||
.expect("Receiver's transaction file required");
|
||||
let slate = api.file_finalize_tx(tx_file).expect("Finalize failed");
|
||||
let mut pub_tx_f = File::open(tx_file)?;
|
||||
let mut content = String::new();
|
||||
pub_tx_f.read_to_string(&mut content)?;
|
||||
let mut slate: grin_wallet::libtx::slate::Slate = json::from_str(&content)
|
||||
.map_err(|_| grin_wallet::libwallet::ErrorKind::Format)?;
|
||||
let _ = api.finalize_tx(&mut slate).expect("Finalize failed");
|
||||
|
||||
let result = api.post_tx(&slate, fluff);
|
||||
match result {
|
||||
|
@ -377,7 +407,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
|||
}
|
||||
}
|
||||
Some(f) => {
|
||||
let result = api.dump_stored_tx(tx_id, f);
|
||||
let result = api.dump_stored_tx(tx_id, true, f);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
warn!(LOGGER, "Dumped transaction data for tx {} to {}", tx_id, f);
|
||||
|
|
|
@ -213,6 +213,13 @@ fn main() {
|
|||
.long("change_outputs")
|
||||
.default_value("1")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("method")
|
||||
.help("Method for sending this transaction.")
|
||||
.short("m")
|
||||
.long("method")
|
||||
.possible_values(&["http", "file"])
|
||||
.default_value("http")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("dest")
|
||||
.help("Send the transaction to the provided server (start with http://) or save as file.")
|
||||
.short("d")
|
||||
|
|
|
@ -185,15 +185,16 @@ where
|
|||
|
||||
/// Write a transaction to send to file so a user can transmit it to the
|
||||
/// receiver in whichever way they see fit (aka carrier pigeon mode).
|
||||
pub fn file_send_tx(
|
||||
pub fn send_tx(
|
||||
&mut self,
|
||||
write_to_disk: bool,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
dest: &str,
|
||||
max_outputs: usize,
|
||||
num_change_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<Slate, Error> {
|
||||
let mut w = self.wallet.lock().unwrap();
|
||||
w.open_with_credentials()?;
|
||||
|
||||
|
@ -205,10 +206,11 @@ where
|
|||
num_change_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
)?;
|
||||
|
||||
let mut pub_tx = File::create(dest)?;
|
||||
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
||||
pub_tx.sync_all()?;
|
||||
if write_to_disk {
|
||||
let mut pub_tx = File::create(dest)?;
|
||||
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
||||
pub_tx.sync_all()?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut batch = w.batch()?;
|
||||
|
@ -221,60 +223,19 @@ where
|
|||
// lock our inputs
|
||||
lock_fn(&mut **w, &tx_hex)?;
|
||||
w.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A sender provided a transaction file with appropriate public keys and
|
||||
/// metadata. Complete the receivers' end of it to generate another file
|
||||
/// to send back.
|
||||
pub fn file_receive_tx(&mut self, source: &str) -> Result<(), Error> {
|
||||
let mut pub_tx_f = File::open(source)?;
|
||||
let mut content = String::new();
|
||||
pub_tx_f.read_to_string(&mut content)?;
|
||||
let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?;
|
||||
|
||||
let mut wallet = self.wallet.lock().unwrap();
|
||||
wallet.open_with_credentials()?;
|
||||
|
||||
// create an output using the amount in the slate
|
||||
let (_, mut context, receiver_create_fn) =
|
||||
selection::build_recipient_output_with_slate(&mut **wallet, &mut slate)?;
|
||||
|
||||
// fill public keys
|
||||
let _ = slate.fill_round_1(
|
||||
wallet.keychain(),
|
||||
&mut context.sec_key,
|
||||
&context.sec_nonce,
|
||||
1,
|
||||
)?;
|
||||
|
||||
// perform partial sig
|
||||
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?;
|
||||
|
||||
// save to file
|
||||
let mut pub_tx = File::create(source.to_owned() + ".response")?;
|
||||
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
||||
|
||||
// Save output in wallet
|
||||
let _ = receiver_create_fn(&mut wallet);
|
||||
Ok(())
|
||||
Ok(slate)
|
||||
}
|
||||
|
||||
/// Sender finalization of the transaction. Takes the file returned by the
|
||||
/// sender as well as the private file generate on the first send step.
|
||||
/// Builds the complete transaction and sends it to a grin node for
|
||||
/// propagation.
|
||||
pub fn file_finalize_tx(&mut self, receiver_file: &str) -> Result<Slate, Error> {
|
||||
let mut pub_tx_f = File::open(receiver_file)?;
|
||||
let mut content = String::new();
|
||||
pub_tx_f.read_to_string(&mut content)?;
|
||||
let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?;
|
||||
|
||||
pub fn finalize_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock().unwrap();
|
||||
w.open_with_credentials()?;
|
||||
|
||||
let context = w.get_private_context(slate.id.as_bytes())?;
|
||||
tx::complete_tx(&mut **w, &mut slate, &context)?;
|
||||
tx::complete_tx(&mut **w, slate, &context)?;
|
||||
{
|
||||
let mut batch = w.batch()?;
|
||||
batch.delete_private_context(slate.id.as_bytes())?;
|
||||
|
@ -282,7 +243,7 @@ where
|
|||
}
|
||||
|
||||
w.close()?;
|
||||
Ok(slate)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Roll back a transaction and all associated outputs with a given
|
||||
|
@ -342,7 +303,12 @@ where
|
|||
}
|
||||
|
||||
/// Writes stored transaction data to a given file
|
||||
pub fn dump_stored_tx(&self, tx_id: u32, dest: &str) -> Result<(), Error> {
|
||||
pub fn dump_stored_tx(
|
||||
&self,
|
||||
tx_id: u32,
|
||||
write_to_disk: bool,
|
||||
dest: &str,
|
||||
) -> Result<Transaction, Error> {
|
||||
let (confirmed, tx_hex) = {
|
||||
let mut w = self.wallet.lock().unwrap();
|
||||
w.open_with_credentials()?;
|
||||
|
@ -365,10 +331,12 @@ where
|
|||
}
|
||||
let tx_bin = util::from_hex(tx_hex.unwrap()).unwrap();
|
||||
let tx = ser::deserialize::<Transaction>(&mut &tx_bin[..])?;
|
||||
let mut tx_file = File::create(dest)?;
|
||||
tx_file.write_all(json::to_string(&tx).unwrap().as_bytes())?;
|
||||
tx_file.sync_all()?;
|
||||
Ok(())
|
||||
if write_to_disk {
|
||||
let mut tx_file = File::create(dest)?;
|
||||
tx_file.write_all(json::to_string(&tx).unwrap().as_bytes())?;
|
||||
tx_file.sync_all()?;
|
||||
}
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// (Re)Posts a transaction that's already been stored to the chain
|
||||
|
@ -498,6 +466,42 @@ where
|
|||
res
|
||||
}
|
||||
|
||||
/// A sender provided a transaction file with appropriate public keys and
|
||||
/// metadata. Complete the receivers' end of it to generate another file
|
||||
/// to send back.
|
||||
pub fn file_receive_tx(&mut self, source: &str) -> Result<(), Error> {
|
||||
let mut pub_tx_f = File::open(source)?;
|
||||
let mut content = String::new();
|
||||
pub_tx_f.read_to_string(&mut content)?;
|
||||
let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?;
|
||||
|
||||
let mut wallet = self.wallet.lock().unwrap();
|
||||
wallet.open_with_credentials()?;
|
||||
|
||||
// create an output using the amount in the slate
|
||||
let (_, mut context, receiver_create_fn) =
|
||||
selection::build_recipient_output_with_slate(&mut **wallet, &mut slate)?;
|
||||
|
||||
// fill public keys
|
||||
let _ = slate.fill_round_1(
|
||||
wallet.keychain(),
|
||||
&mut context.sec_key,
|
||||
&context.sec_nonce,
|
||||
1,
|
||||
)?;
|
||||
|
||||
// perform partial sig
|
||||
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?;
|
||||
|
||||
// save to file
|
||||
let mut pub_tx = File::create(source.to_owned() + ".response")?;
|
||||
pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?;
|
||||
|
||||
// Save output in wallet
|
||||
let _ = receiver_create_fn(&mut wallet);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive a transaction from a sender
|
||||
pub fn receive_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
||||
let mut w = self.wallet.lock().unwrap();
|
||||
|
|
|
@ -28,6 +28,7 @@ use hyper::{Body, Request, Response, StatusCode};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
|
||||
use core::core::Transaction;
|
||||
use keychain::Keychain;
|
||||
use libtx::slate::Slate;
|
||||
use libwallet::api::{APIForeign, APIOwner};
|
||||
|
@ -229,6 +230,35 @@ where
|
|||
api.retrieve_txs(update_from_node, id)
|
||||
}
|
||||
|
||||
fn dump_stored_tx(
|
||||
&self,
|
||||
req: &Request<Body>,
|
||||
api: APIOwner<T, C, K>,
|
||||
) -> Result<Transaction, Error> {
|
||||
let params = parse_params(req);
|
||||
if let Some(id_string) = params.get("id") {
|
||||
match id_string[0].parse() {
|
||||
Ok(id) => match api.dump_stored_tx(id, false, "") {
|
||||
Ok(tx) => Ok(tx),
|
||||
Err(e) => {
|
||||
error!(LOGGER, "dump_stored_tx: failed with error: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!(LOGGER, "dump_stored_tx: could not parse id: {}", e);
|
||||
Err(ErrorKind::TransactionDumpError(
|
||||
"dump_stored_tx: cannot dump transaction. Could not parse id in request.",
|
||||
).into())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ErrorKind::TransactionDumpError(
|
||||
"dump_stored_tx: Cannot dump transaction. Missing id param in request.",
|
||||
).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn retrieve_summary_info(
|
||||
&self,
|
||||
req: &Request<Body>,
|
||||
|
@ -260,6 +290,7 @@ where
|
|||
"retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)?),
|
||||
"node_height" => json_response(&self.node_height(req, api)?),
|
||||
"retrieve_txs" => json_response(&self.retrieve_txs(req, api)?),
|
||||
"dump_stored_tx" => json_response(&self.dump_stored_tx(req, api)?),
|
||||
_ => response(StatusCode::BAD_REQUEST, ""),
|
||||
})
|
||||
}
|
||||
|
@ -270,17 +301,77 @@ where
|
|||
mut api: APIOwner<T, C, K>,
|
||||
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
||||
Box::new(parse_body(req).and_then(move |args: SendTXArgs| {
|
||||
api.issue_send_tx(
|
||||
args.amount,
|
||||
args.minimum_confirmations,
|
||||
&args.dest,
|
||||
args.max_outputs,
|
||||
args.num_change_outputs,
|
||||
args.selection_strategy_is_use_all,
|
||||
)
|
||||
if args.method == "http" {
|
||||
api.issue_send_tx(
|
||||
args.amount,
|
||||
args.minimum_confirmations,
|
||||
&args.dest,
|
||||
args.max_outputs,
|
||||
args.num_change_outputs,
|
||||
args.selection_strategy_is_use_all,
|
||||
)
|
||||
} else if args.method == "file" {
|
||||
api.send_tx(
|
||||
false,
|
||||
args.amount,
|
||||
args.minimum_confirmations,
|
||||
&args.dest,
|
||||
args.max_outputs,
|
||||
args.num_change_outputs,
|
||||
args.selection_strategy_is_use_all,
|
||||
)
|
||||
} else {
|
||||
error!(LOGGER, "unsupported payment method: {}", args.method);
|
||||
return Err(ErrorKind::ClientCallback("unsupported payment method"))?;
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn finalize_tx(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
mut api: APIOwner<T, C, K>,
|
||||
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
||||
Box::new(
|
||||
parse_body(req).and_then(move |mut slate| match api.finalize_tx(&mut slate) {
|
||||
Ok(_) => ok(slate.clone()),
|
||||
Err(e) => {
|
||||
error!(LOGGER, "finalize_tx: failed with error: {}", e);
|
||||
err(e)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn cancel_tx(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
mut api: APIOwner<T, C, K>,
|
||||
) -> Box<Future<Item = (), Error = Error> + Send> {
|
||||
let params = parse_params(&req);
|
||||
if let Some(id_string) = params.get("id") {
|
||||
Box::new(match id_string[0].parse() {
|
||||
Ok(id) => match api.cancel_tx(id) {
|
||||
Ok(_) => ok(()),
|
||||
Err(e) => {
|
||||
error!(LOGGER, "finalize_tx: failed with error: {}", e);
|
||||
err(e)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!(LOGGER, "finalize_tx: could not parse id: {}", e);
|
||||
err(ErrorKind::TransactionCancellationError(
|
||||
"finalize_tx: cannot cancel transaction. Could not parse id in request.",
|
||||
).into())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Box::new(err(ErrorKind::TransactionCancellationError(
|
||||
"finalize_tx: Cannot cancel transaction. Missing id param in request.",
|
||||
).into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn issue_burn_tx(
|
||||
&self,
|
||||
_req: Request<Body>,
|
||||
|
@ -307,6 +398,14 @@ where
|
|||
self.issue_send_tx(req, api)
|
||||
.and_then(|slate| ok(json_response_pretty(&slate))),
|
||||
),
|
||||
"finalize_tx" => Box::new(
|
||||
self.finalize_tx(req, api)
|
||||
.and_then(|slate| ok(json_response_pretty(&slate))),
|
||||
),
|
||||
"cancel_tx" => Box::new(
|
||||
self.cancel_tx(req, api)
|
||||
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
||||
),
|
||||
"issue_burn_tx" => Box::new(
|
||||
self.issue_burn_tx(req, api)
|
||||
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
||||
|
|
|
@ -152,6 +152,10 @@ pub enum ErrorKind {
|
|||
#[fail(display = "Cancellation Error: {}", _0)]
|
||||
TransactionCancellationError(&'static str),
|
||||
|
||||
/// Cancellation error
|
||||
#[fail(display = "Tx dump Error: {}", _0)]
|
||||
TransactionDumpError(&'static str),
|
||||
|
||||
/// Attempt to repost a transaction that's already confirmed
|
||||
#[fail(display = "Transaction already confirmed error")]
|
||||
TransactionAlreadyConfirmed,
|
||||
|
|
|
@ -642,6 +642,8 @@ pub struct SendTXArgs {
|
|||
pub amount: u64,
|
||||
/// minimum confirmations
|
||||
pub minimum_confirmations: u64,
|
||||
/// payment method
|
||||
pub method: String,
|
||||
/// destination url
|
||||
pub dest: String,
|
||||
/// Max number of outputs
|
||||
|
|
Loading…
Reference in a new issue