2019-02-13 18:05:19 +03:00
|
|
|
// Copyright 2018 The Grin Developers
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
use crate::util::{Mutex, ZeroingString};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
/// Grin wallet command-line function implementations
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Write;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use std::thread;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
use serde_json as json;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
use crate::api::TLSConfig;
|
|
|
|
use crate::core::core;
|
|
|
|
use crate::keychain;
|
|
|
|
|
2019-03-17 22:14:58 +03:00
|
|
|
use crate::config::WalletConfig;
|
2019-02-13 18:05:19 +03:00
|
|
|
use crate::error::{Error, ErrorKind};
|
2019-03-17 22:14:58 +03:00
|
|
|
use crate::impls::{
|
|
|
|
instantiate_wallet, FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter,
|
|
|
|
LMDBBackend, NullWalletCommAdapter,
|
2019-02-13 18:05:19 +03:00
|
|
|
};
|
2019-03-17 22:14:58 +03:00
|
|
|
use crate::impls::{HTTPNodeClient, WalletSeed};
|
2019-04-24 11:27:14 +03:00
|
|
|
use crate::libwallet::{InitTxArgs, NodeClient, WalletInst};
|
2019-03-17 22:14:58 +03:00
|
|
|
use crate::{controller, display};
|
2019-02-13 18:05:19 +03:00
|
|
|
|
|
|
|
/// Arguments common to all wallet commands
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct GlobalArgs {
|
|
|
|
pub account: String,
|
|
|
|
pub node_api_secret: Option<String>,
|
|
|
|
pub show_spent: bool,
|
|
|
|
pub password: Option<ZeroingString>,
|
|
|
|
pub tls_conf: Option<TLSConfig>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Arguments for init command
|
|
|
|
pub struct InitArgs {
|
|
|
|
/// BIP39 recovery phrase length
|
|
|
|
pub list_length: usize,
|
|
|
|
pub password: ZeroingString,
|
|
|
|
pub config: WalletConfig,
|
|
|
|
pub recovery_phrase: Option<ZeroingString>,
|
|
|
|
pub restore: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn init(g_args: &GlobalArgs, args: InitArgs) -> Result<(), Error> {
|
|
|
|
WalletSeed::init_file(
|
|
|
|
&args.config,
|
|
|
|
args.list_length,
|
|
|
|
args.recovery_phrase,
|
|
|
|
&args.password,
|
|
|
|
)?;
|
|
|
|
info!("Wallet seed file created");
|
|
|
|
let client_n = HTTPNodeClient::new(
|
|
|
|
&args.config.check_node_api_http_addr,
|
|
|
|
g_args.node_api_secret.clone(),
|
|
|
|
);
|
|
|
|
let _: LMDBBackend<HTTPNodeClient, keychain::ExtKeychain> =
|
|
|
|
LMDBBackend::new(args.config.clone(), &args.password, client_n)?;
|
|
|
|
info!("Wallet database backend created");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Argument for recover
|
|
|
|
pub struct RecoverArgs {
|
|
|
|
pub recovery_phrase: Option<ZeroingString>,
|
|
|
|
pub passphrase: ZeroingString,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check whether seed file exists
|
|
|
|
pub fn wallet_seed_exists(config: &WalletConfig) -> Result<(), Error> {
|
|
|
|
let res = WalletSeed::seed_file_exists(&config)?;
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn recover(config: &WalletConfig, args: RecoverArgs) -> Result<(), Error> {
|
|
|
|
if args.recovery_phrase.is_none() {
|
|
|
|
let res = WalletSeed::from_file(config, &args.passphrase);
|
|
|
|
if let Err(e) = res {
|
|
|
|
error!("Error loading wallet seed (check password): {}", e);
|
2019-03-17 22:14:58 +03:00
|
|
|
return Err(e.into());
|
2019-02-13 18:05:19 +03:00
|
|
|
}
|
|
|
|
let _ = res.unwrap().show_recovery_phrase();
|
|
|
|
} else {
|
|
|
|
let res = WalletSeed::recover_from_phrase(
|
|
|
|
&config,
|
|
|
|
&args.recovery_phrase.as_ref().unwrap(),
|
|
|
|
&args.passphrase,
|
|
|
|
);
|
|
|
|
if let Err(e) = res {
|
|
|
|
error!("Error recovering seed - {}", e);
|
2019-03-17 22:14:58 +03:00
|
|
|
return Err(e.into());
|
2019-02-13 18:05:19 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Arguments for listen command
|
|
|
|
pub struct ListenArgs {
|
|
|
|
pub method: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn listen(config: &WalletConfig, args: &ListenArgs, g_args: &GlobalArgs) -> Result<(), Error> {
|
|
|
|
let mut params = HashMap::new();
|
|
|
|
params.insert("api_listen_addr".to_owned(), config.api_listen_addr());
|
|
|
|
if let Some(t) = g_args.tls_conf.as_ref() {
|
|
|
|
params.insert("certificate".to_owned(), t.certificate.clone());
|
|
|
|
params.insert("private_key".to_owned(), t.private_key.clone());
|
|
|
|
}
|
2019-03-17 22:14:58 +03:00
|
|
|
let res = match args.method.as_str() {
|
|
|
|
"http" => {
|
|
|
|
// HTTP adapter can't use the listen trait method because of the
|
|
|
|
// crate structure. May be able to fix when V1 API is deprecated
|
|
|
|
let node_client = HTTPNodeClient::new(
|
|
|
|
&config.check_node_api_http_addr,
|
|
|
|
g_args.node_api_secret.clone(),
|
|
|
|
);
|
|
|
|
let wallet = instantiate_wallet(
|
|
|
|
config.clone(),
|
|
|
|
node_client,
|
|
|
|
&g_args.password.clone().unwrap(),
|
|
|
|
&g_args.account,
|
|
|
|
)?;
|
|
|
|
let listen_addr = params.get("api_listen_addr").unwrap();
|
|
|
|
let tls_conf = match params.get("certificate") {
|
2019-03-26 19:02:31 +03:00
|
|
|
Some(s) => Some(TLSConfig::new(
|
2019-03-17 22:14:58 +03:00
|
|
|
s.to_owned(),
|
|
|
|
params.get("private_key").unwrap().to_owned(),
|
|
|
|
)),
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
controller::foreign_listener(wallet.clone(), &listen_addr, tls_conf)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
"keybase" => {
|
|
|
|
let adapter = KeybaseWalletCommAdapter::new();
|
|
|
|
adapter.listen(
|
|
|
|
params,
|
|
|
|
config.clone(),
|
|
|
|
&g_args.password.clone().unwrap(),
|
|
|
|
&g_args.account,
|
|
|
|
g_args.node_api_secret.clone(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
_ => Ok(()),
|
2019-02-13 18:05:19 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
if let Err(e) = res {
|
|
|
|
return Err(ErrorKind::LibWallet(e.kind(), e.cause_string()).into());
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn owner_api(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
config: &WalletConfig,
|
|
|
|
g_args: &GlobalArgs,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let res = controller::owner_listener(
|
|
|
|
wallet,
|
|
|
|
config.owner_api_listen_addr().as_str(),
|
|
|
|
g_args.node_api_secret.clone(),
|
|
|
|
g_args.tls_conf.clone(),
|
|
|
|
config.owner_api_include_foreign.clone(),
|
|
|
|
);
|
|
|
|
if let Err(e) = res {
|
|
|
|
return Err(ErrorKind::LibWallet(e.kind(), e.cause_string()).into());
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Arguments for account command
|
|
|
|
pub struct AccountArgs {
|
|
|
|
pub create: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn account(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
args: AccountArgs,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
if args.create.is_none() {
|
|
|
|
let res = controller::owner_single_use(wallet, |api| {
|
|
|
|
let acct_mappings = api.accounts()?;
|
|
|
|
// give logging thread a moment to catch up
|
|
|
|
thread::sleep(Duration::from_millis(200));
|
|
|
|
display::accounts(acct_mappings);
|
|
|
|
Ok(())
|
|
|
|
});
|
|
|
|
if let Err(e) = res {
|
|
|
|
error!("Error listing accounts: {}", e);
|
|
|
|
return Err(ErrorKind::LibWallet(e.kind(), e.cause_string()).into());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let label = args.create.unwrap();
|
|
|
|
let res = controller::owner_single_use(wallet, |api| {
|
|
|
|
api.create_account_path(&label)?;
|
|
|
|
thread::sleep(Duration::from_millis(200));
|
|
|
|
info!("Account: '{}' Created!", label);
|
|
|
|
Ok(())
|
|
|
|
});
|
|
|
|
if let Err(e) = res {
|
|
|
|
thread::sleep(Duration::from_millis(200));
|
|
|
|
error!("Error creating account '{}': {}", label, e);
|
|
|
|
return Err(ErrorKind::LibWallet(e.kind(), e.cause_string()).into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Arguments for the send command
|
|
|
|
pub struct SendArgs {
|
|
|
|
pub amount: u64,
|
|
|
|
pub message: Option<String>,
|
|
|
|
pub minimum_confirmations: u64,
|
|
|
|
pub selection_strategy: String,
|
|
|
|
pub estimate_selection_strategies: bool,
|
|
|
|
pub method: String,
|
|
|
|
pub dest: String,
|
|
|
|
pub change_outputs: usize,
|
|
|
|
pub fluff: bool,
|
|
|
|
pub max_outputs: usize,
|
2019-03-12 19:48:14 +03:00
|
|
|
pub target_slate_version: Option<u16>,
|
2019-02-13 18:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn send(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
args: SendArgs,
|
|
|
|
dark_scheme: bool,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
|
|
|
if args.estimate_selection_strategies {
|
|
|
|
let strategies = vec!["smallest", "all"]
|
|
|
|
.into_iter()
|
|
|
|
.map(|strategy| {
|
2019-03-29 19:00:02 +03:00
|
|
|
let init_args = InitTxArgs {
|
|
|
|
src_acct_name: None,
|
|
|
|
amount: args.amount,
|
|
|
|
minimum_confirmations: args.minimum_confirmations,
|
|
|
|
max_outputs: args.max_outputs as u32,
|
|
|
|
num_change_outputs: args.change_outputs as u32,
|
|
|
|
selection_strategy_is_use_all: strategy == "all",
|
|
|
|
estimate_only: Some(true),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let slate = api.initiate_tx(init_args).unwrap();
|
|
|
|
(strategy, slate.amount, slate.fee)
|
2019-02-13 18:05:19 +03:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
display::estimate(args.amount, strategies, dark_scheme);
|
|
|
|
} else {
|
2019-03-29 19:00:02 +03:00
|
|
|
let init_args = InitTxArgs {
|
|
|
|
src_acct_name: None,
|
|
|
|
amount: args.amount,
|
|
|
|
minimum_confirmations: args.minimum_confirmations,
|
|
|
|
max_outputs: args.max_outputs as u32,
|
|
|
|
num_change_outputs: args.change_outputs as u32,
|
|
|
|
selection_strategy_is_use_all: args.selection_strategy == "all",
|
|
|
|
message: args.message.clone(),
|
|
|
|
target_slate_version: args.target_slate_version,
|
|
|
|
send_args: None,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let result = api.initiate_tx(init_args);
|
2019-03-14 18:05:13 +03:00
|
|
|
let mut slate = match result {
|
2019-02-13 18:05:19 +03:00
|
|
|
Ok(s) => {
|
|
|
|
info!(
|
|
|
|
"Tx created: {} grin to {} (strategy '{}')",
|
|
|
|
core::amount_to_hr_string(args.amount, false),
|
|
|
|
args.dest,
|
|
|
|
args.selection_strategy,
|
|
|
|
);
|
|
|
|
s
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
info!("Tx not created: {}", e);
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let adapter = match args.method.as_str() {
|
|
|
|
"http" => HTTPWalletCommAdapter::new(),
|
|
|
|
"file" => FileWalletCommAdapter::new(),
|
|
|
|
"keybase" => KeybaseWalletCommAdapter::new(),
|
|
|
|
"self" => NullWalletCommAdapter::new(),
|
|
|
|
_ => NullWalletCommAdapter::new(),
|
|
|
|
};
|
|
|
|
if adapter.supports_sync() {
|
|
|
|
slate = adapter.send_tx_sync(&args.dest, &slate)?;
|
2019-03-14 18:05:13 +03:00
|
|
|
api.tx_lock_outputs(&slate)?;
|
2019-02-13 18:05:19 +03:00
|
|
|
if args.method == "self" {
|
|
|
|
controller::foreign_single_use(wallet, |api| {
|
2019-03-29 11:46:12 +03:00
|
|
|
slate = api.receive_tx(&slate, Some(&args.dest), None)?;
|
2019-02-13 18:05:19 +03:00
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
if let Err(e) = api.verify_slate_messages(&slate) {
|
|
|
|
error!("Error validating participant messages: {}", e);
|
|
|
|
return Err(e);
|
|
|
|
}
|
2019-03-22 15:03:25 +03:00
|
|
|
slate = api.finalize_tx(&slate)?;
|
2019-02-13 18:05:19 +03:00
|
|
|
} else {
|
|
|
|
adapter.send_tx_async(&args.dest, &slate)?;
|
2019-03-14 18:05:13 +03:00
|
|
|
api.tx_lock_outputs(&slate)?;
|
2019-02-13 18:05:19 +03:00
|
|
|
}
|
|
|
|
if adapter.supports_sync() {
|
|
|
|
let result = api.post_tx(&slate.tx, args.fluff);
|
|
|
|
match result {
|
|
|
|
Ok(_) => {
|
|
|
|
info!("Tx sent ok",);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Tx sent fail: {}", e);
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Receive command argument
|
|
|
|
pub struct ReceiveArgs {
|
|
|
|
pub input: String,
|
|
|
|
pub message: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn receive(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
g_args: &GlobalArgs,
|
|
|
|
args: ReceiveArgs,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let adapter = FileWalletCommAdapter::new();
|
|
|
|
let mut slate = adapter.receive_tx_async(&args.input)?;
|
|
|
|
controller::foreign_single_use(wallet, |api| {
|
|
|
|
if let Err(e) = api.verify_slate_messages(&slate) {
|
|
|
|
error!("Error validating participant messages: {}", e);
|
|
|
|
return Err(e);
|
|
|
|
}
|
2019-03-29 11:46:12 +03:00
|
|
|
slate = api.receive_tx(&slate, Some(&g_args.account), args.message.clone())?;
|
2019-02-13 18:05:19 +03:00
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
let send_tx = format!("{}.response", args.input);
|
|
|
|
adapter.send_tx_async(&send_tx, &slate)?;
|
|
|
|
info!(
|
|
|
|
"Response file {}.response generated, sending it back to the transaction originator.",
|
|
|
|
args.input
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finalize command args
|
|
|
|
pub struct FinalizeArgs {
|
|
|
|
pub input: String,
|
|
|
|
pub fluff: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn finalize(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
args: FinalizeArgs,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let adapter = FileWalletCommAdapter::new();
|
|
|
|
let mut slate = adapter.receive_tx_async(&args.input)?;
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
|
|
|
if let Err(e) = api.verify_slate_messages(&slate) {
|
|
|
|
error!("Error validating participant messages: {}", e);
|
|
|
|
return Err(e);
|
|
|
|
}
|
2019-03-22 15:03:25 +03:00
|
|
|
slate = api.finalize_tx(&mut slate).expect("Finalize failed");
|
2019-02-13 18:05:19 +03:00
|
|
|
|
|
|
|
let result = api.post_tx(&slate.tx, args.fluff);
|
|
|
|
match result {
|
|
|
|
Ok(_) => {
|
|
|
|
info!("Transaction sent successfully, check the wallet again for confirmation.");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Tx not sent: {}", e);
|
|
|
|
Err(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Info command args
|
|
|
|
pub struct InfoArgs {
|
|
|
|
pub minimum_confirmations: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn info(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
g_args: &GlobalArgs,
|
|
|
|
args: InfoArgs,
|
|
|
|
dark_scheme: bool,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
|
|
|
let (validated, wallet_info) =
|
|
|
|
api.retrieve_summary_info(true, args.minimum_confirmations)?;
|
|
|
|
display::info(&g_args.account, &wallet_info, validated, dark_scheme);
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn outputs(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
g_args: &GlobalArgs,
|
|
|
|
dark_scheme: bool,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
2019-03-29 19:00:02 +03:00
|
|
|
let res = api.node_height()?;
|
2019-02-13 18:05:19 +03:00
|
|
|
let (validated, outputs) = api.retrieve_outputs(g_args.show_spent, true, None)?;
|
2019-03-29 19:00:02 +03:00
|
|
|
display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?;
|
2019-02-13 18:05:19 +03:00
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Txs command args
|
|
|
|
pub struct TxsArgs {
|
|
|
|
pub id: Option<u32>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn txs(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
g_args: &GlobalArgs,
|
|
|
|
args: TxsArgs,
|
|
|
|
dark_scheme: bool,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
2019-03-29 19:00:02 +03:00
|
|
|
let res = api.node_height()?;
|
2019-02-13 18:05:19 +03:00
|
|
|
let (validated, txs) = api.retrieve_txs(true, args.id, None)?;
|
|
|
|
let include_status = !args.id.is_some();
|
|
|
|
display::txs(
|
|
|
|
&g_args.account,
|
2019-03-29 19:00:02 +03:00
|
|
|
res.height,
|
2019-02-13 18:05:19 +03:00
|
|
|
validated,
|
|
|
|
&txs,
|
|
|
|
include_status,
|
|
|
|
dark_scheme,
|
|
|
|
)?;
|
|
|
|
// if given a particular transaction id, also get and display associated
|
|
|
|
// inputs/outputs and messages
|
|
|
|
if args.id.is_some() {
|
|
|
|
let (_, outputs) = api.retrieve_outputs(true, false, args.id)?;
|
2019-03-29 19:00:02 +03:00
|
|
|
display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?;
|
2019-02-13 18:05:19 +03:00
|
|
|
// should only be one here, but just in case
|
2019-04-25 09:59:45 +03:00
|
|
|
for tx in txs {
|
|
|
|
display::tx_messages(&tx, dark_scheme)?;
|
2019-02-13 18:05:19 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Repost
|
|
|
|
pub struct RepostArgs {
|
|
|
|
pub id: u32,
|
|
|
|
pub dump_file: Option<String>,
|
|
|
|
pub fluff: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn repost(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
args: RepostArgs,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
|
|
|
let (_, txs) = api.retrieve_txs(true, Some(args.id), None)?;
|
|
|
|
let stored_tx = api.get_stored_tx(&txs[0])?;
|
|
|
|
if stored_tx.is_none() {
|
|
|
|
error!(
|
|
|
|
"Transaction with id {} does not have transaction data. Not reposting.",
|
|
|
|
args.id
|
|
|
|
);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
match args.dump_file {
|
|
|
|
None => {
|
|
|
|
if txs[0].confirmed {
|
|
|
|
error!(
|
|
|
|
"Transaction with id {} is confirmed. Not reposting.",
|
|
|
|
args.id
|
|
|
|
);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
api.post_tx(&stored_tx.unwrap(), args.fluff)?;
|
|
|
|
info!("Reposted transaction at {}", args.id);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Some(f) => {
|
|
|
|
let mut tx_file = File::create(f.clone())?;
|
|
|
|
tx_file.write_all(json::to_string(&stored_tx).unwrap().as_bytes())?;
|
|
|
|
tx_file.sync_all()?;
|
|
|
|
info!("Dumped transaction data for tx {} to {}", args.id, f);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Cancel
|
|
|
|
pub struct CancelArgs {
|
|
|
|
pub tx_id: Option<u32>,
|
|
|
|
pub tx_slate_id: Option<Uuid>,
|
|
|
|
pub tx_id_string: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn cancel(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
args: CancelArgs,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
|
|
|
let result = api.cancel_tx(args.tx_id, args.tx_slate_id);
|
|
|
|
match result {
|
|
|
|
Ok(_) => {
|
|
|
|
info!("Transaction {} Cancelled", args.tx_id_string);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("TX Cancellation failed: {}", e);
|
|
|
|
Err(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn restore(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
|
|
|
let result = api.restore();
|
|
|
|
match result {
|
|
|
|
Ok(_) => {
|
|
|
|
warn!("Wallet restore complete",);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Wallet restore failed: {}", e);
|
|
|
|
error!("Backtrace: {}", e.backtrace().unwrap());
|
|
|
|
Err(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-03-07 19:19:47 +03:00
|
|
|
/// wallet check
|
|
|
|
pub struct CheckArgs {
|
|
|
|
pub delete_unconfirmed: bool,
|
|
|
|
}
|
|
|
|
|
2019-02-13 18:05:19 +03:00
|
|
|
pub fn check_repair(
|
|
|
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
2019-03-07 19:19:47 +03:00
|
|
|
args: CheckArgs,
|
2019-02-13 18:05:19 +03:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
controller::owner_single_use(wallet.clone(), |api| {
|
|
|
|
warn!("Starting wallet check...",);
|
|
|
|
warn!("Updating all wallet outputs, please wait ...",);
|
2019-03-07 19:19:47 +03:00
|
|
|
let result = api.check_repair(args.delete_unconfirmed);
|
2019-02-13 18:05:19 +03:00
|
|
|
match result {
|
|
|
|
Ok(_) => {
|
|
|
|
warn!("Wallet check complete",);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Wallet check failed: {}", e);
|
|
|
|
error!("Backtrace: {}", e.backtrace().unwrap());
|
|
|
|
Err(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
Ok(())
|
|
|
|
}
|