use crate::api::TLSConfig;
use crate::config::{EpicboxConfig, TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME};
use crate::core::{core, global};
use crate::error::Error;
use crate::impls::{
create_sender, EpicboxChannel, EpicboxListenChannel, KeybaseAllChannels, SlateGetter as _,
SlateReceiver as _,
};
use crate::impls::{EmojiSlate, PathToSlate, SlatePutter};
use crate::keychain;
use crate::libwallet::{
address, Error as LibwalletError, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof,
WalletInst, WalletLCProvider,
};
use crate::util::secp::key::SecretKey;
use crate::util::{to_hex, Mutex, ZeroingString};
use crate::{controller, display};
use serde_json as json;
use std::fs::File;
use std::io::{Read, Write};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use uuid::Uuid;
fn show_recovery_phrase(phrase: ZeroingString) {
println!("Your recovery phrase is:");
println!();
println!("{}", &*phrase);
println!();
println!("Please back-up these words in a non-digital format.");
}
#[derive(Clone)]
pub struct GlobalArgs {
pub account: String,
pub api_secret: Option<String>,
pub node_api_secret: Option<String>,
pub show_spent: bool,
pub chain_type: global::ChainTypes,
pub password: Option<ZeroingString>,
pub tls_conf: Option<TLSConfig>,
}
pub struct InitArgs {
pub list_length: usize,
pub password: ZeroingString,
pub config: WalletConfig,
pub recovery_phrase: Option<ZeroingString>,
pub restore: bool,
}
pub fn init<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
g_args: &GlobalArgs,
args: InitArgs,
) -> Result<(), epic_wallet_libwallet::Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let mut w_lock = wallet.lock();
let p = w_lock.lc_provider()?;
p.create_config(
&g_args.chain_type,
WALLET_CONFIG_FILE_NAME,
None,
None,
None,
None,
)?;
p.create_wallet(
None,
args.recovery_phrase,
args.list_length,
args.password.clone(),
false,
)?;
let m = p.get_mnemonic(None, args.password)?;
show_recovery_phrase(m);
Ok(())
}
pub struct RecoverArgs {
pub passphrase: ZeroingString,
}
pub fn recover<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
args: RecoverArgs,
) -> Result<(), epic_wallet_libwallet::Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let mut w_lock = wallet.lock();
let p = w_lock.lc_provider()?;
let m = p.get_mnemonic(None, args.passphrase)?;
show_recovery_phrase(m);
Ok(())
}
pub struct ListenArgs {
pub method: String,
}
pub fn listen<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
config: &WalletConfig,
tor_config: &TorConfig,
epicbox_config: &EpicboxConfig,
args: &ListenArgs,
g_args: &GlobalArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let res = match args.method.as_str() {
"http" => controller::foreign_listener(
wallet.clone(),
keychain_mask,
&config.api_listen_addr(),
g_args.tls_conf.clone(),
tor_config.use_tor_listener,
),
"keybase" => KeybaseAllChannels::new().unwrap().listen(
wallet.clone(),
keychain_mask.clone(),
config.clone(),
),
"epicbox" => {
let mut reconnections = 0;
loop {
let listener = EpicboxListenChannel::new()?.listen(
wallet.clone(),
keychain_mask.clone(),
epicbox_config.clone(),
&mut reconnections,
);
info!("Reconnect to epicbox");
match listener {
Err(e) => {
error!("Error in listener loop ({})", e);
}
Ok(_) => (),
}
if reconnections >= 5 {
break;
}
let duration = std::time::Duration::from_secs(20);
std::thread::sleep(duration);
}
return Err(LibwalletError::EpicboxReconnectLimit);
}
method => {
return Err(LibwalletError::ArgumentError(format!(
"No listener for method {}",
method
)));
}
};
debug!("{}", args.method.clone());
if let Err(e) = res {
return Err(LibwalletError::LibWallet(format!("{}", e)));
}
Ok(())
}
pub fn owner_api<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<SecretKey>,
config: &WalletConfig,
tor_config: &TorConfig,
epicbox_config: &EpicboxConfig,
g_args: &GlobalArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + Send + Sync + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let km = Arc::new(Mutex::new(keychain_mask));
let res = controller::owner_listener(
wallet,
km,
config.owner_api_listen_addr().as_str(),
g_args.api_secret.clone(),
g_args.tls_conf.clone(),
config.owner_api_include_foreign.clone(),
Some(tor_config.clone()),
Some(epicbox_config.clone()),
);
if let Err(e) = res {
return Err(LibwalletError::LibWallet(format!("{}", e)));
}
Ok(())
}
pub struct AccountArgs {
pub create: Option<String>,
}
pub fn account<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: AccountArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
if args.create.is_none() {
let res = controller::owner_single_use(wallet, keychain_mask, |api, m| {
let acct_mappings = api.accounts(m)?;
thread::sleep(Duration::from_millis(200));
display::accounts(acct_mappings);
Ok(())
});
if let Err(e) = res {
error!("Error listing accounts: {}", e);
return Err(LibwalletError::LibWallet(format!("{}", e)));
}
} else {
let label = args.create.unwrap();
let res = controller::owner_single_use(wallet, keychain_mask, |api, m| {
api.create_account_path(m, &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(LibwalletError::LibWallet(format!("{}", e)));
}
}
Ok(())
}
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,
pub target_slate_version: Option<u16>,
pub payment_proof_address: Option<String>,
pub ttl_blocks: Option<u64>,
}
pub fn send<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
tor_config: Option<TorConfig>,
epicbox_config: Option<EpicboxConfig>,
args: SendArgs,
dark_scheme: bool,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
if args.estimate_selection_strategies {
let strategies = vec!["smallest", "all"]
.into_iter()
.map(|strategy| {
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.init_send_tx(m, init_args).unwrap();
(strategy, slate.amount, slate.fee)
})
.collect();
display::estimate(args.amount, strategies, dark_scheme);
} else {
let payment_proof_recipient_address = match args.payment_proof_address {
Some(ref p) => Some(address::ed25519_parse_pubkey(p)?),
None => None,
};
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,
payment_proof_recipient_address,
ttl_blocks: args.ttl_blocks,
send_args: None,
..Default::default()
};
let result = api.init_send_tx(m, init_args);
let mut slate = match result {
Ok(s) => {
info!(
"Tx created: {} epic 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);
}
};
match args.method.as_str() {
"emoji" => {
println!("{}", EmojiSlate().encode(&slate));
api.tx_lock_outputs(m, &slate, 0)?;
return Ok(());
}
"file" => {
PathToSlate((&args.dest).into()).put_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
return Ok(());
}
"self" => {
api.tx_lock_outputs(m, &slate, 0)?;
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
};
controller::foreign_single_use(wallet, km, |api| {
slate = api.receive_tx(&slate, Some(&args.dest), None)?;
Ok(())
})?;
}
"epicbox" => {
let epicbox_channel = Box::new(EpicboxChannel::new(&args.dest, epicbox_config))
.expect("error starting epicbox");
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
};
slate = epicbox_channel.send(wallet, km, &slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
return Ok(());
}
method => {
let sender = create_sender(method, &args.dest, tor_config)?;
slate = sender.send_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
}
}
api.verify_slate_messages(m, &slate).map_err(|e| {
error!("Error validating participant messages: {}", e);
e
})?;
slate = api.finalize_tx(m, &slate)?;
let result = api.post_tx(m, &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(())
}
pub struct ReceiveArgs {
pub input: String,
pub message: Option<String>,
pub method: String,
}
pub fn receive<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
g_args: &GlobalArgs,
args: ReceiveArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K>,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let method = args.method.as_str();
let mut slate;
if method == "emoji" {
slate = EmojiSlate().decode(&args.input.as_str())?;
} else {
slate = PathToSlate((&args.input).into()).get_tx()?;
}
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
};
controller::foreign_single_use(wallet, km, |api| {
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.receive_tx(&slate, Some(&g_args.account), args.message.clone())?;
Ok(())
})?;
if method == "emoji" {
println!("\n\nThis is your response emoji string. Please send it back to the payer to finalize the transaction:\n\n{}", EmojiSlate().encode(&slate));
info!("Response emoji.response generated, and can be sent back to the transaction originator.");
} else {
PathToSlate(format!("{}.response", args.input).into()).put_tx(&slate)?;
info!(
"Response file {}.response generated, and can be sent back to the transaction originator.",
args.input
);
}
Ok(())
}
pub struct FinalizeArgs {
pub method: String,
pub input: String,
pub fluff: bool,
pub nopost: bool,
pub dest: Option<String>,
}
pub fn finalize<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: FinalizeArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let method = args.method.as_str();
let mut slate;
if method == "emoji" {
slate = EmojiSlate().decode(&args.input.as_str())?;
} else {
slate = PathToSlate((&args.input).into()).get_tx()?;
}
let part_data = slate.participant_with_id(1);
let is_invoice = {
match part_data {
None => {
error!("Expected slate participant data missing");
return Err(Error::ArgumentError(format!(
"Expected Slate participant data missing"
)))?;
}
Some(p) => !p.is_complete(),
}
};
if is_invoice {
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
};
controller::foreign_single_use(wallet.clone(), km, |api| {
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.finalize_invoice_tx(&mut slate)?;
Ok(())
})?;
} else {
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
if let Err(e) = api.verify_slate_messages(m, &slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.finalize_tx(m, &mut slate)?;
Ok(())
})?;
}
if !args.nopost {
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let result = api.post_tx(m, &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)
}
}
})?;
}
if args.dest.is_some() {
PathToSlate((&args.dest.unwrap()).into()).put_tx(&slate)?;
}
Ok(())
}
pub struct IssueInvoiceArgs {
pub dest: String,
pub issue_args: IssueInvoiceTxArgs,
}
pub fn issue_invoice_tx<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: IssueInvoiceArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let slate = api.issue_invoice_tx(m, args.issue_args)?;
PathToSlate((&args.dest).into()).put_tx(&slate)?;
Ok(())
})?;
Ok(())
}
pub struct ProcessInvoiceArgs {
pub message: Option<String>,
pub minimum_confirmations: u64,
pub selection_strategy: String,
pub method: String,
pub dest: String,
pub max_outputs: usize,
pub input: String,
pub estimate_selection_strategies: bool,
pub ttl_blocks: Option<u64>,
}
pub fn process_invoice<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
tor_config: Option<TorConfig>,
args: ProcessInvoiceArgs,
dark_scheme: bool,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let slate = PathToSlate((&args.input).into()).get_tx()?;
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
if args.estimate_selection_strategies {
let strategies = vec!["smallest", "all"]
.into_iter()
.map(|strategy| {
let init_args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: args.minimum_confirmations,
max_outputs: args.max_outputs as u32,
num_change_outputs: 1u32,
selection_strategy_is_use_all: strategy == "all",
estimate_only: Some(true),
..Default::default()
};
let slate = api.init_send_tx(m, init_args).unwrap();
(strategy, slate.amount, slate.fee)
})
.collect();
display::estimate(slate.amount, strategies, dark_scheme);
} else {
let init_args = InitTxArgs {
src_acct_name: None,
amount: 0,
minimum_confirmations: args.minimum_confirmations,
max_outputs: args.max_outputs as u32,
num_change_outputs: 1u32,
selection_strategy_is_use_all: args.selection_strategy == "all",
message: args.message.clone(),
ttl_blocks: args.ttl_blocks,
send_args: None,
..Default::default()
};
if let Err(e) = api.verify_slate_messages(m, &slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
let result = api.process_invoice_tx(m, &slate, init_args);
let mut slate = match result {
Ok(s) => {
info!(
"Invoice processed: {} epic to {} (strategy '{}')",
core::amount_to_hr_string(slate.amount, false),
args.dest,
args.selection_strategy,
);
s
}
Err(e) => {
info!("Tx not created: {}", e);
return Err(e);
}
};
match args.method.as_str() {
"file" => {
let slate_putter = PathToSlate((&args.dest).into());
slate_putter.put_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
}
"self" => {
api.tx_lock_outputs(m, &slate, 0)?;
let km = match keychain_mask.as_ref() {
None => None,
Some(&m) => Some(m.to_owned()),
};
controller::foreign_single_use(wallet, km, |api| {
slate = api.finalize_invoice_tx(&slate)?;
Ok(())
})?;
}
method => {
let sender = create_sender(method, &args.dest, tor_config)?;
slate = sender.send_tx(&slate)?;
api.tx_lock_outputs(m, &slate, 0)?;
}
}
}
Ok(())
})?;
Ok(())
}
pub struct InfoArgs {
pub minimum_confirmations: u64,
}
pub fn info<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
g_args: &GlobalArgs,
args: InfoArgs,
dark_scheme: bool,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let (validated, wallet_info) =
api.retrieve_summary_info(m, true, args.minimum_confirmations)?;
display::info(&g_args.account, &wallet_info, validated, dark_scheme);
Ok(())
})?;
Ok(())
}
pub struct OutputsArgs {
pub show_full_history: bool,
}
pub fn outputs<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
g_args: &GlobalArgs,
args: OutputsArgs,
dark_scheme: bool,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let res = api.node_height(m)?;
let (validated, outputs) =
api.retrieve_outputs(m, g_args.show_spent, true, args.show_full_history, None)?;
display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?;
Ok(())
})?;
Ok(())
}
pub struct TxsArgs {
pub id: Option<u32>,
pub tx_slate_id: Option<Uuid>,
}
pub fn txs<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
g_args: &GlobalArgs,
args: TxsArgs,
dark_scheme: bool,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let res = api.node_height(m)?;
let (validated, txs) = api.retrieve_txs(m, true, args.id, args.tx_slate_id)?;
let include_status = !args.id.is_some() && !args.tx_slate_id.is_some();
display::txs(
&g_args.account,
res.height,
validated,
&txs,
include_status,
dark_scheme,
)?;
let id = if args.id.is_some() {
args.id
} else if args.tx_slate_id.is_some() {
if let Some(tx) = txs.iter().find(|t| t.tx_slate_id == args.tx_slate_id) {
Some(tx.id)
} else {
println!("Could not find a transaction matching given txid.\n");
None
}
} else {
None
};
if id.is_some() {
let (_, outputs) = api.retrieve_outputs(m, true, false, false, id)?;
display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?;
for tx in txs {
display::tx_messages(&tx, dark_scheme)?;
display::payment_proof(&tx)?;
}
}
Ok(())
})?;
Ok(())
}
pub struct PostArgs {
pub input: String,
pub fluff: bool,
}
pub fn post<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: PostArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let slate = PathToSlate((&args.input).into()).get_tx()?;
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
api.post_tx(m, &slate.tx, args.fluff)?;
info!("Posted transaction");
return Ok(());
})?;
Ok(())
}
pub struct RepostArgs {
pub id: u32,
pub dump_file: Option<String>,
pub fluff: bool,
}
pub fn repost<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: RepostArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let (_, txs) = api.retrieve_txs(m, true, Some(args.id), None)?;
let stored_tx = api.get_stored_tx(m, &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(m, &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(())
}
pub struct CancelArgs {
pub tx_id: Option<u32>,
pub tx_slate_id: Option<Uuid>,
pub tx_id_string: String,
}
pub fn cancel<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: CancelArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let result = api.cancel_tx(m, 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 struct CheckArgs {
pub delete_unconfirmed: bool,
pub start_height: Option<u64>,
}
pub fn scan<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: CheckArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
warn!("Starting output scan ...",);
let result = api.scan(m, args.start_height, args.delete_unconfirmed);
match result {
Ok(_) => {
warn!("Wallet check complete",);
Ok(())
}
Err(e) => {
error!("Wallet check failed: {}", e);
error!("Backtrace: {}", e);
Err(e)
}
}
})?;
Ok(())
}
pub fn address<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
g_args: &GlobalArgs,
keychain_mask: Option<&SecretKey>,
epicbox_config: EpicboxConfig,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let pub_key = api.get_public_proof_address(m, 0)?;
let result = address::onion_v3_from_pubkey(&pub_key);
let address = api.get_public_address(m, 0)?;
match result {
Ok(a) => {
println!();
println!("Epicbox address for account - {}", g_args.account);
println!("-------------------------------------");
println!(
"{}@{}",
address.public_key,
epicbox_config.epicbox_domain.unwrap()
);
println!();
println!("Public Proof Address for account - {}", g_args.account);
println!("-------------------------------------");
println!("{}", to_hex(pub_key.as_bytes().to_vec()));
println!();
println!("TOR Onion V3 Address for account - {}", g_args.account);
println!("-------------------------------------");
println!("{}", a);
println!();
Ok(())
}
Err(e) => {
error!("Address retrieval failed: {}", e);
error!("Backtrace: {}", e);
Err(e)
}
}
})?;
Ok(())
}
pub struct ProofExportArgs {
pub output_file: String,
pub id: Option<u32>,
pub tx_slate_id: Option<Uuid>,
}
pub fn proof_export<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: ProofExportArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let result = api.retrieve_payment_proof(m, true, args.id, args.tx_slate_id);
match result {
Ok(p) => {
let mut proof_file = File::create(args.output_file.clone())?;
proof_file.write_all(json::to_string_pretty(&p).unwrap().as_bytes())?;
proof_file.sync_all()?;
warn!("Payment proof exported to {}", args.output_file);
Ok(())
}
Err(e) => {
error!("Proof export failed: {}", e);
Err(e)
}
}
})?;
Ok(())
}
pub struct ProofVerifyArgs {
pub input_file: String,
}
pub fn proof_verify<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
args: ProofVerifyArgs,
) -> Result<(), LibwalletError>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
let mut proof_f = match File::open(&args.input_file) {
Ok(p) => p,
Err(e) => {
let msg = format!("{}", e);
error!(
"Unable to open payment proof file at {}: {}",
args.input_file, e
);
return Err(LibwalletError::PaymentProofParsing(msg));
}
};
let mut proof = String::new();
proof_f.read_to_string(&mut proof)?;
let proof: PaymentProof = match json::from_str(&proof) {
Ok(p) => p,
Err(e) => {
let msg = format!("{}", e);
error!("Unable to parse payment proof file: {}", e);
return Err(LibwalletError::PaymentProofParsing(msg));
}
};
let result = api.verify_payment_proof(m, &proof);
match result {
Ok((iam_sender, iam_recipient)) => {
println!("Payment proof's signatures are valid.");
if iam_sender {
println!("The proof's sender address belongs to this wallet.");
}
if iam_recipient {
println!("The proof's recipient address belongs to this wallet.");
}
if !iam_recipient && !iam_sender {
println!(
"Neither the proof's sender nor recipient address belongs to this wallet."
);
}
Ok(())
}
Err(e) => {
error!("Proof not valid: {}", e);
Err(e)
}
}
})?;
Ok(())
}