wallet: fix for already canceled invoice, transaction info modal, ability to finalize from list

This commit is contained in:
ardocrat 2024-04-24 01:42:56 +03:00
parent 92e1da511d
commit 01b5b21488
10 changed files with 591 additions and 157 deletions

12
Cargo.lock generated
View file

@ -2808,7 +2808,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_api" name = "grin_wallet_api"
version = "5.2.0-beta.1" version = "5.2.0-beta.1"
source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#75363a9a258bc1fb0cf60bfb4c88a8a653b122f2" source = "git+https://github.com/yeastplume/grin-wallet?branch=prevent_double_pay#6e6b16a61c53825447f27ad49ba654c922cf9702"
dependencies = [ dependencies = [
"base64 0.12.3", "base64 0.12.3",
"chrono", "chrono",
@ -2833,7 +2833,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_config" name = "grin_wallet_config"
version = "5.2.0-beta.1" version = "5.2.0-beta.1"
source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#75363a9a258bc1fb0cf60bfb4c88a8a653b122f2" source = "git+https://github.com/yeastplume/grin-wallet?branch=prevent_double_pay#6e6b16a61c53825447f27ad49ba654c922cf9702"
dependencies = [ dependencies = [
"dirs 2.0.2", "dirs 2.0.2",
"grin_core", "grin_core",
@ -2848,7 +2848,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_controller" name = "grin_wallet_controller"
version = "5.2.0-beta.1" version = "5.2.0-beta.1"
source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#75363a9a258bc1fb0cf60bfb4c88a8a653b122f2" source = "git+https://github.com/yeastplume/grin-wallet?branch=prevent_double_pay#6e6b16a61c53825447f27ad49ba654c922cf9702"
dependencies = [ dependencies = [
"chrono", "chrono",
"easy-jsonrpc-mw", "easy-jsonrpc-mw",
@ -2882,7 +2882,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_impls" name = "grin_wallet_impls"
version = "5.2.0-beta.1" version = "5.2.0-beta.1"
source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#75363a9a258bc1fb0cf60bfb4c88a8a653b122f2" source = "git+https://github.com/yeastplume/grin-wallet?branch=prevent_double_pay#6e6b16a61c53825447f27ad49ba654c922cf9702"
dependencies = [ dependencies = [
"base64 0.12.3", "base64 0.12.3",
"blake2-rfc", "blake2-rfc",
@ -2921,7 +2921,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_libwallet" name = "grin_wallet_libwallet"
version = "5.2.0-beta.1" version = "5.2.0-beta.1"
source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#75363a9a258bc1fb0cf60bfb4c88a8a653b122f2" source = "git+https://github.com/yeastplume/grin-wallet?branch=prevent_double_pay#6e6b16a61c53825447f27ad49ba654c922cf9702"
dependencies = [ dependencies = [
"age", "age",
"base64 0.9.3", "base64 0.9.3",
@ -2958,7 +2958,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_util" name = "grin_wallet_util"
version = "5.2.0-beta.1" version = "5.2.0-beta.1"
source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#75363a9a258bc1fb0cf60bfb4c88a8a653b122f2" source = "git+https://github.com/yeastplume/grin-wallet?branch=prevent_double_pay#6e6b16a61c53825447f27ad49ba654c922cf9702"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"ed25519-dalek", "ed25519-dalek",

View file

@ -26,11 +26,11 @@ grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
## wallet ## wallet
grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } grin_wallet_impls = { git = "https://github.com/yeastplume/grin-wallet", branch = "prevent_double_pay" }
grin_wallet_api = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } grin_wallet_api = { git = "https://github.com/yeastplume/grin-wallet", branch = "prevent_double_pay" }
grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } grin_wallet_libwallet = { git = "https://github.com/yeastplume/grin-wallet", branch = "prevent_double_pay" }
grin_wallet_util = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } grin_wallet_util = { git = "https://github.com/yeastplume/grin-wallet", branch = "prevent_double_pay" }
grin_wallet_controller = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } grin_wallet_controller = { git = "https://github.com/yeastplume/grin-wallet", branch = "prevent_double_pay" }
## ui ## ui
egui = { version = "0.27.2", default-features = false } egui = { version = "0.27.2", default-features = false }

View file

@ -10,6 +10,8 @@ show: Show
delete: Delete delete: Delete
clear: Clear clear: Clear
create: Create create: Create
id: Identifier
kernel: Kernel
wallets: wallets:
await_conf_amount: Awaiting confirmation await_conf_amount: Awaiting confirmation
await_fin_amount: Awaiting finalization await_fin_amount: Awaiting finalization
@ -65,11 +67,11 @@ wallets:
tx_finalizing: Finalizing tx_finalizing: Finalizing
tx_confirmed: Confirmed tx_confirmed: Confirmed
txs: Transactions txs: Transactions
input_finalize_desc: 'Enter message to finalize the transaction:'
messages: Messages messages: Messages
transport: Transport transport: Transport
input_slatepack_desc: 'Enter message to create response or finalize the transaction:' input_slatepack_desc: 'Enter message to create response or finalize the transaction:'
send_slatepack_desc: 'Send message to receiver of funds to finalize the transaction:' parse_slatepack_err: 'An error occurred during reading of the message, check input data:'
parse_slatepack_err: 'An error occurred during handling of the message, check input data:'
pay_balance_error: 'Account balance is insufficient to pay %{amount} ツ and network fee.' pay_balance_error: 'Account balance is insufficient to pay %{amount} ツ and network fee.'
parse_i1_slatepack_desc: 'To pay %{amount} ツ send this message to the receiver:' parse_i1_slatepack_desc: 'To pay %{amount} ツ send this message to the receiver:'
parse_i2_slatepack_desc: 'Finalize transaction to receive %{amount} ツ' parse_i2_slatepack_desc: 'Finalize transaction to receive %{amount} ツ'
@ -78,7 +80,8 @@ wallets:
parse_s2_slatepack_desc: 'Finalize transaction to send %{amount} ツ' parse_s2_slatepack_desc: 'Finalize transaction to send %{amount} ツ'
parse_s3_slatepack_desc: 'Post transaction to finalize sending of %{amount} ツ' parse_s3_slatepack_desc: 'Post transaction to finalize sending of %{amount} ツ'
resp_slatepack_err: 'An error occurred during creation of the response, check input data:' resp_slatepack_err: 'An error occurred during creation of the response, check input data:'
resp_exists_err: 'Such transaction already exists.' resp_exists_err: Such transaction already exists.
resp_canceled_err: Such transaction was already canceled.
create_request_desc: 'Create request to send or receive the funds:' create_request_desc: 'Create request to send or receive the funds:'
send_request_desc: 'You have created a request to send %{amount} ツ. Send this message to the receiver:' send_request_desc: 'You have created a request to send %{amount} ツ. Send this message to the receiver:'
send_slatepack_err: An error occurred during creation of request to send funds, check input data. send_slatepack_err: An error occurred during creation of request to send funds, check input data.

View file

@ -10,6 +10,8 @@ show: Показать
delete: Удалить delete: Удалить
clear: Очистить clear: Очистить
create: Создать create: Создать
id: Идентификатор
kernel: Ядро
wallets: wallets:
await_conf_amount: Ожидает подтверждения await_conf_amount: Ожидает подтверждения
await_fin_amount: Ожидает завершения await_fin_amount: Ожидает завершения
@ -65,11 +67,11 @@ wallets:
tx_finalizing: Завершение tx_finalizing: Завершение
tx_confirmed: Подтверждено tx_confirmed: Подтверждено
txs: Транзакции txs: Транзакции
input_finalize_desc: 'Введите полученное сообщение для завершения транзакции:'
messages: Сообщения messages: Сообщения
transport: Транспорт transport: Транспорт
input_slatepack_desc: 'Введите полученное сообщение для создания ответа или завершения транзакции:' input_slatepack_desc: 'Введите полученное сообщение для создания ответа или завершения транзакции:'
send_slatepack_desc: 'Отправьте сообщение получателю средств для завершения транзакции:' parse_slatepack_err: 'Во время чтения сообщения произошла ошибка, проверьте входные данные:'
parse_slatepack_err: 'Во время обработки сообщения произошла ошибка, проверьте входные данные:'
pay_balance_error: 'Средств на аккаунте недостаточно для оплаты %{amount} ツ и комиссии сети.' pay_balance_error: 'Средств на аккаунте недостаточно для оплаты %{amount} ツ и комиссии сети.'
parse_i1_slatepack_desc: 'Для оплаты %{amount} ツ отправьте это сообщение получателю:' parse_i1_slatepack_desc: 'Для оплаты %{amount} ツ отправьте это сообщение получателю:'
parse_i2_slatepack_desc: 'Завершите транзакцию для получения %{amount} ツ' parse_i2_slatepack_desc: 'Завершите транзакцию для получения %{amount} ツ'
@ -78,8 +80,9 @@ wallets:
parse_s2_slatepack_desc: 'Завершите транзакцию для отправки %{amount} ツ' parse_s2_slatepack_desc: 'Завершите транзакцию для отправки %{amount} ツ'
parse_s3_slatepack_desc: 'Опубликуйте транзакцию для завершения отправки %{amount} ツ' parse_s3_slatepack_desc: 'Опубликуйте транзакцию для завершения отправки %{amount} ツ'
resp_slatepack_err: 'Во время создания ответа произошла ошибка, проверьте входные данные:' resp_slatepack_err: 'Во время создания ответа произошла ошибка, проверьте входные данные:'
resp_exists_err: 'Такая транзакция уже существует.' resp_exists_err: Такая транзакция уже существует.
create_request_desc: 'Cоздать запрос на отправку или получение средств:' resp_canceled_err: Такая транзакция уже была отменена.
create_request_desc: 'Создайте запрос на отправку или получение средств:'
send_request_desc: 'Вы создали запрос на отправку %{amount} ツ. Отправьте это сообщение получателю:' send_request_desc: 'Вы создали запрос на отправку %{amount} ツ. Отправьте это сообщение получателю:'
send_slatepack_err: Во время создания запроса на отправку средств произошла ошибка, проверьте входные данные. send_slatepack_err: Во время создания запроса на отправку средств произошла ошибка, проверьте входные данные.
invoice_desc: 'Вы создали запрос на получение %{amount} ツ. Отправьте это сообщение отправителю:' invoice_desc: 'Вы создали запрос на получение %{amount} ツ. Отправьте это сообщение отправителю:'

View file

@ -23,7 +23,7 @@ use crate::gui::icons::{BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, FOLDER_USER,
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Root, View}; use crate::gui::views::{Modal, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions}; use crate::gui::views::types::{ModalPosition, TextEditOptions};
use crate::gui::views::wallets::{WalletInfo, WalletMessages, WalletTransport, WalletSettings}; use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport, WalletSettings};
use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType}; use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType};
use crate::node::Node; use crate::node::Node;
use crate::wallet::{Wallet, WalletConfig}; use crate::wallet::{Wallet, WalletConfig};
@ -52,7 +52,7 @@ impl Default for WalletContent {
account_creating: false, account_creating: false,
account_label_edit: "".to_string(), account_label_edit: "".to_string(),
account_creation_error: false, account_creation_error: false,
current_tab: Box::new(WalletInfo::default()) current_tab: Box::new(WalletTransactions::default())
} }
} }
} }
@ -349,7 +349,7 @@ impl WalletContent {
ui.columns(4, |columns| { ui.columns(4, |columns| {
columns[0].vertical_centered_justified(|ui| { columns[0].vertical_centered_justified(|ui| {
View::tab_button(ui, GRAPH, current_type == WalletTabType::Txs, || { View::tab_button(ui, GRAPH, current_type == WalletTabType::Txs, || {
self.current_tab = Box::new(WalletInfo::default()); self.current_tab = Box::new(WalletTransactions::default());
}); });
}); });
columns[1].vertical_centered_justified(|ui| { columns[1].vertical_centered_justified(|ui| {

View file

@ -146,14 +146,14 @@ impl WalletMessages {
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(4.0); ui.add_space(3.0);
// Show creation of request to send or receive funds. // Show creation of request to send or receive funds.
self.request_ui(ui, cb); self.request_ui(ui, cb);
ui.add_space(12.0); ui.add_space(12.0);
View::horizontal_line(ui, Colors::ITEM_STROKE); View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(8.0); ui.add_space(6.0);
// Show Slatepack message input field. // Show Slatepack message input field.
self.input_slatepack_ui(ui, wallet, cb); self.input_slatepack_ui(ui, wallet, cb);
@ -326,11 +326,10 @@ impl WalletMessages {
} else { } else {
show_dandelion = true; show_dandelion = true;
View::button(ui, t!("wallets.finalize"), Colors::GOLD, || { View::button(ui, t!("wallets.finalize"), Colors::GOLD, || {
let message = self.message_edit.clone();
let slate = self.message_slate.clone().unwrap(); let slate = self.message_slate.clone().unwrap();
if slate.state == SlateState::Invoice3 || if slate.state == SlateState::Invoice3 ||
slate.state == SlateState::Standard3 { slate.state == SlateState::Standard3 {
if let Ok(_) = wallet.post(&slate, self.dandelion) { if wallet.post(&slate, self.dandelion).is_ok() {
self.message_edit.clear(); self.message_edit.clear();
self.message_slate = None; self.message_slate = None;
} else { } else {
@ -341,7 +340,8 @@ impl WalletMessages {
); );
} }
} else { } else {
if let Ok(_) = wallet.finalize(message, self.dandelion) { let r = wallet.finalize(&self.message_edit, self.dandelion);
if r.is_ok() {
self.message_edit.clear(); self.message_edit.clear();
self.message_slate = None; self.message_slate = None;
} else { } else {
@ -397,7 +397,7 @@ impl WalletMessages {
if self.message_edit.is_empty() { if self.message_edit.is_empty() {
return; return;
} }
if let Ok(mut slate) = wallet.parse_slatepack(self.message_edit.clone()) { if let Ok(mut slate) = wallet.parse_slatepack(&self.message_edit) {
println!("parse_message: {}", slate); println!("parse_message: {}", slate);
// Try to setup empty amount from transaction by id. // Try to setup empty amount from transaction by id.
@ -423,13 +423,23 @@ impl WalletMessages {
match slate.state { match slate.state {
SlateState::Standard1 | SlateState::Invoice1 => { SlateState::Standard1 | SlateState::Invoice1 => {
let resp = if slate.state == SlateState::Standard1 { let resp = if slate.state == SlateState::Standard1 {
wallet.receive(self.message_edit.clone()) wallet.receive(&self.message_edit)
} else { } else {
wallet.pay(self.message_edit.clone()) wallet.pay(&self.message_edit)
}; };
if resp.is_ok() { if resp.is_ok() {
self.response_edit = resp.unwrap(); self.response_edit = resp.unwrap();
} else { } else {
match resp.err().unwrap() {
grin_wallet_libwallet::Error::TransactionWasCancelled {..} => {
// Set already canceled transaction error message.
self.message_error = Some(
MessageError::Response(t!("wallets.resp_canceled_err"))
);
return;
}
_ => {}
}
// Check if tx with same slate id already exists. // Check if tx with same slate id already exists.
let exists_tx = wallet.tx_by_slate(&slate).is_some(); let exists_tx = wallet.tx_by_slate(&slate).is_some();
if exists_tx { if exists_tx {
@ -726,7 +736,7 @@ impl WalletMessages {
// Button to cancel transaction. // Button to cancel transaction.
let cancel = format!("{} {}", PROHIBIT, t!("modal.cancel")); let cancel = format!("{} {}", PROHIBIT, t!("modal.cancel"));
View::colored_text_button(ui, cancel, Colors::RED, Colors::BUTTON, || { View::colored_text_button(ui, cancel, Colors::RED, Colors::BUTTON, || {
if let Ok(slate) = wallet.parse_slatepack(self.request_edit.clone()) { if let Ok(slate) = wallet.parse_slatepack(&self.request_edit) {
if let Some(tx) = wallet.tx_by_slate(&slate) { if let Some(tx) = wallet.tx_by_slate(&slate) {
wallet.cancel(tx.data.id); wallet.cancel(tx.data.id);
} }

View file

@ -15,7 +15,7 @@
pub mod types; pub mod types;
mod txs; mod txs;
pub use txs::WalletInfo; pub use txs::WalletTransactions;
mod messages; mod messages;
pub use messages::WalletMessages; pub use messages::WalletMessages;

View file

@ -15,24 +15,50 @@
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea}; use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
use egui::scroll_area::ScrollBarVisibility; use egui::scroll_area::ScrollBarVisibility;
use grin_core::core::amount_to_hr_string; use grin_core::core::amount_to_hr_string;
use grin_util::ToHex;
use grin_wallet_libwallet::{Slate, SlateState, TxLogEntryType}; use grin_wallet_libwallet::{Slate, SlateState, TxLogEntryType};
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, ARROW_CLOCKWISE, ARROWS_CLOCKWISE, BRIDGE, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK_CIRCLE, DOTS_THREE_CIRCLE, FILE_TEXT, GEAR_FINE, PROHIBIT, X_CIRCLE}; use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, ARROW_CLOCKWISE, BRIDGE, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, CHECK_CIRCLE, CLIPBOARD_TEXT, COPY, DOTS_THREE_CIRCLE, FILE_ARCHIVE, FILE_TEXT, GEAR_FINE, HASH_STRAIGHT, PROHIBIT, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Root, View}; use crate::gui::views::{Modal, Root, View};
use crate::gui::views::types::ModalPosition;
use crate::gui::views::wallets::types::WalletTab; use crate::gui::views::wallets::types::WalletTab;
use crate::gui::views::wallets::wallet::types::{GRIN, WalletTabType}; use crate::gui::views::wallets::wallet::types::{GRIN, SLATEPACK_MESSAGE_HINT, WalletTabType};
use crate::gui::views::wallets::wallet::WalletContent; use crate::gui::views::wallets::wallet::WalletContent;
use crate::wallet::types::{WalletData, WalletTransaction}; use crate::wallet::types::{WalletData, WalletTransaction};
use crate::wallet::Wallet; use crate::wallet::Wallet;
/// Wallet transactions tab content.
pub struct WalletTransactions {
/// Transaction identifier to user at [`Modal`].
tx_info_id: Option<u32>,
/// Transaction [`Slate`] to use at [`Modal`].
tx_info_slate: Option<Slate>,
/// Response Slatepack message input value at [`Modal`].
tx_info_response_edit: String,
/// Finalization Slatepack message input value at [`Modal`].
tx_info_finalize_edit: String,
/// Flag to check if error happened during transaction finalization at [`Modal`].
tx_info_finalize_error: bool,
/// Flag to check if tx finalization requested at [`Modal`].
tx_info_finalize: bool,
}
/// Wallet info tab content. impl Default for WalletTransactions {
#[derive(Default)] fn default() -> Self {
pub struct WalletInfo; Self {
tx_info_id: None,
tx_info_slate: None,
tx_info_response_edit: "".to_string(),
tx_info_finalize_edit: "".to_string(),
tx_info_finalize_error: false,
tx_info_finalize: false,
}
}
}
impl WalletTab for WalletInfo { impl WalletTab for WalletTransactions {
fn get_type(&self) -> WalletTabType { fn get_type(&self) -> WalletTabType {
WalletTabType::Txs WalletTabType::Txs
} }
@ -41,11 +67,14 @@ impl WalletTab for WalletInfo {
ui: &mut egui::Ui, ui: &mut egui::Ui,
_: &mut eframe::Frame, _: &mut eframe::Frame,
wallet: &mut Wallet, wallet: &mut Wallet,
_: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
if WalletContent::sync_ui(ui, wallet) { if WalletContent::sync_ui(ui, wallet) {
return; return;
} }
// Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb);
// Show wallet transactions panel. // Show wallet transactions panel.
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(egui::Frame { .frame(egui::Frame {
@ -62,23 +91,32 @@ impl WalletTab for WalletInfo {
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
let data = wallet.get_data().unwrap(); let data = wallet.get_data().unwrap();
self.txs_ui(ui, wallet, &data); self.txs_ui(ui, wallet, &data, cb);
}); });
}); });
} }
} }
impl WalletInfo { /// Identifier for transaction information [`Modal`].
/// Draw transactions content. const TX_INFO_MODAL: &'static str = "tx_info_modal";
fn txs_ui(&self, ui: &mut egui::Ui, wallet: &mut Wallet, data: &WalletData) {
let txs_size = data.txs.len();
// Show transactions info. /// Height of transaction list item.
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| { const TX_ITEM_HEIGHT: f32 = 76.0;
impl WalletTransactions {
/// Draw transactions content.
fn txs_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
data: &WalletData,
cb: &dyn PlatformCallbacks) {
let amount_awaiting_conf = data.info.amount_awaiting_confirmation; let amount_awaiting_conf = data.info.amount_awaiting_confirmation;
let amount_awaiting_fin = data.info.amount_awaiting_finalization; let amount_awaiting_fin = data.info.amount_awaiting_finalization;
let amount_locked = data.info.amount_locked; let amount_locked = data.info.amount_locked;
// Show transactions info.
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
// Show non-zero awaiting confirmation amount. // Show non-zero awaiting confirmation amount.
if amount_awaiting_conf != 0 { if amount_awaiting_conf != 0 {
let awaiting_conf = amount_to_hr_string(amount_awaiting_conf, true); let awaiting_conf = amount_to_hr_string(amount_awaiting_conf, true);
@ -117,7 +155,7 @@ impl WalletInfo {
} }
// Show message when wallet txs are empty. // Show message when wallet txs are empty.
if txs_size == 0 { if data.txs.is_empty() {
View::center_content(ui, 96.0, |ui| { View::center_content(ui, 96.0, |ui| {
let empty_text = t!( let empty_text = t!(
"wallets.txs_empty", "wallets.txs_empty",
@ -137,36 +175,51 @@ impl WalletInfo {
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible) .scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
.id_source(Id::from("txs_content").with(wallet.get_config().id)) .id_source(Id::from("txs_content").with(wallet.get_config().id))
.auto_shrink([false; 2]) .auto_shrink([false; 2])
.show_rows(ui, TX_ITEM_HEIGHT, txs_size, |ui, row_range| { .show_rows(ui, TX_ITEM_HEIGHT, data.txs.len(), |ui, row_range| {
ui.add_space(3.0); ui.add_space(3.0);
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| { View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
let amount_awaiting_conf = data.info.amount_awaiting_confirmation;
let amount_awaiting_fin = data.info.amount_awaiting_finalization;
let amount_locked = data.info.amount_locked;
let extra_padding = amount_awaiting_conf != 0 || amount_awaiting_fin != 0 || let extra_padding = amount_awaiting_conf != 0 || amount_awaiting_fin != 0 ||
amount_locked != 0; amount_locked != 0;
for index in row_range { for index in row_range {
let tx = data.txs.get(index).unwrap();
// Setup item rounding.
let item_rounding = View::item_rounding(index, txs_size, false);
// Show transaction item. // Show transaction item.
tx_item_ui(ui, tx, item_rounding, extra_padding, &data, wallet); let tx = data.txs.get(index).unwrap();
let rounding = View::item_rounding(index, data.txs.len(), false);
self.tx_item_ui(ui, tx, rounding, extra_padding, true, &data, wallet, cb);
} }
}); });
}); });
} }
}
/// Height of transaction list item. /// Draw [`Modal`] content for this ui container.
const TX_ITEM_HEIGHT: f32 = 76.0; fn modal_content_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
match Modal::opened() {
None => {}
Some(id) => {
match id {
TX_INFO_MODAL => {
Modal::ui(ui.ctx(), |ui, modal| {
self.tx_info_modal_ui(ui, wallet, modal, cb);
});
}
_ => {}
}
}
}
}
/// Draw transaction item. /// Draw transaction item.
fn tx_item_ui(ui: &mut egui::Ui, fn tx_item_ui(&mut self,
ui: &mut egui::Ui,
tx: &WalletTransaction, tx: &WalletTransaction,
mut rounding: Rounding, mut rounding: Rounding,
extra_padding: bool, extra_padding: bool,
can_show_info: bool,
data: &WalletData, data: &WalletData,
wallet: &mut Wallet) { wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
// Setup layout size. // Setup layout size.
let mut rect = ui.available_rect_before_wrap(); let mut rect = ui.available_rect_before_wrap();
if extra_padding { if extra_padding {
@ -177,38 +230,61 @@ fn tx_item_ui(ui: &mut egui::Ui,
// Draw round background. // Draw round background.
let bg_rect = rect.clone(); let bg_rect = rect.clone();
ui.painter().rect(bg_rect, rounding, Colors::BUTTON, View::ITEM_STROKE); let color = if can_show_info {
Colors::BUTTON
} else {
Colors::FILL
};
ui.painter().rect(bg_rect, rounding, color, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
if extra_padding {
ui.add_space(-6.0);
}
// Draw button to show transaction info. // Draw button to show transaction info.
if can_show_info {
rounding.nw = 0.0; rounding.nw = 0.0;
rounding.sw = 0.0; rounding.sw = 0.0;
View::item_button(ui, rounding, FILE_TEXT, None, || { View::item_button(ui, rounding, FILE_TEXT, None, || {
//TODO: Show tx info self.tx_info_finalize = false;
self.show_tx_info_modal(wallet, tx);
}); });
}
// Setup flag to repost unconfirmed posting transaction after min confirmation time. // Draw cancel button for tx that can be reposted and canceled.
let last_height = data.info.last_confirmed_height; if tx.can_repost(data) || tx.can_cancel() {
let min_conf = data.info.minimum_confirmations; let cancel_rounding = if can_show_info {
let can_repost = tx.posting && tx.repost_height.is_some() && Rounding::default()
last_height - tx.repost_height.unwrap() > min_conf; } else {
rounding.nw = 0.0;
// Draw cancel button for txs to repost or also non-cancelled, non-posting. rounding.sw = 0.0;
if can_repost || (!tx.posting && !tx.data.confirmed && rounding
tx.data.tx_type != TxLogEntryType::TxReceivedCancelled };
&& tx.data.tx_type != TxLogEntryType::TxSentCancelled) { View::item_button(ui, cancel_rounding, PROHIBIT, Some(Colors::RED), || {
View::item_button(ui, Rounding::default(), PROHIBIT, Some(Colors::RED), || {
wallet.cancel(tx.data.id); wallet.cancel(tx.data.id);
}); });
} }
// Draw finalization button for tx that can be finalized.
if tx.can_finalize {
let (icon, color) = if !can_show_info && self.tx_info_finalize {
(FILE_TEXT, None)
} else {
(CHECK, Some(Colors::GREEN))
};
View::item_button(ui, Rounding::default(), icon, color, || {
if !can_show_info && self.tx_info_finalize {
self.tx_info_finalize = false;
return;
}
self.tx_info_finalize = true;
// Show transaction information modal.
if can_show_info {
self.show_tx_info_modal(wallet, tx);
cb.show_keyboard();
}
});
}
// Draw button to repost transaction. // Draw button to repost transaction.
if can_repost { if tx.can_repost(data) {
View::item_button(ui, View::item_button(ui,
Rounding::default(), Rounding::default(),
ARROW_CLOCKWISE, ARROW_CLOCKWISE,
@ -222,7 +298,7 @@ fn tx_item_ui(ui: &mut egui::Ui,
}; };
// Post tx after getting slate from slatepack file. // Post tx after getting slate from slatepack file.
if let Some(sp) = wallet.read_slatepack(&slate) { if let Some(sp) = wallet.read_slatepack(&slate) {
if let Ok(s) = wallet.parse_slatepack(sp) { if let Ok(s) = wallet.parse_slatepack(&sp) {
let _ = wallet.post(&s, wallet.can_use_dandelion()); let _ = wallet.post(&s, wallet.can_use_dandelion());
} }
} }
@ -231,11 +307,7 @@ fn tx_item_ui(ui: &mut egui::Ui,
let layout_size = ui.available_size(); let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
if extra_padding {
ui.add_space(12.0);
} else {
ui.add_space(6.0); ui.add_space(6.0);
}
ui.vertical(|ui| { ui.vertical(|ui| {
ui.add_space(3.0); ui.add_space(3.0);
@ -294,13 +366,14 @@ fn tx_item_ui(ui: &mut egui::Ui,
format!("{} {}", CHECK_CIRCLE, t!("wallets.tx_confirmed")) format!("{} {}", CHECK_CIRCLE, t!("wallets.tx_confirmed"))
}, },
TxLogEntryType::TxSent | TxLogEntryType::TxReceived => { TxLogEntryType::TxSent | TxLogEntryType::TxReceived => {
let min_conf = data.info.minimum_confirmations;
if data.info.last_confirmed_height - tx_height > min_conf { if data.info.last_confirmed_height - tx_height > min_conf {
let (icon, text) = if tx.data.tx_type == TxLogEntryType::TxSent { let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent {
(ARROW_CIRCLE_UP, t!("wallets.tx_sent")) (ARROW_CIRCLE_UP, t!("wallets.tx_sent"))
} else { } else {
(ARROW_CIRCLE_DOWN, t!("wallets.tx_received")) (ARROW_CIRCLE_DOWN, t!("wallets.tx_received"))
}; };
format!("{} {}", icon, text) format!("{} {}", i, t)
} else { } else {
let h = data.info.last_confirmed_height; let h = data.info.last_confirmed_height;
let left_conf = h - tx_height; let left_conf = h - tx_height;
@ -343,8 +416,269 @@ fn tx_item_ui(ui: &mut egui::Ui,
let tx_time = View::format_time(tx.data.creation_ts.timestamp()); let tx_time = View::format_time(tx.data.creation_ts.timestamp());
let tx_time_text = format!("{} {}", CALENDAR_CHECK, tx_time); let tx_time_text = format!("{} {}", CALENDAR_CHECK, tx_time);
ui.label(RichText::new(tx_time_text).size(15.0).color(Colors::GRAY)); ui.label(RichText::new(tx_time_text).size(15.0).color(Colors::GRAY));
ui.add_space(3.0);
}); });
}); });
}); });
}
/// Show transaction information [`Modal`].
fn show_tx_info_modal(&mut self, wallet: &Wallet, tx: &WalletTransaction) {
self.tx_info_response_edit = "".to_string();
self.tx_info_finalize_edit = "".to_string();
self.tx_info_finalize_error = false;
self.tx_info_id = Some(tx.data.id);
// Setup slate and message from transaction.
if let Some((slate, message)) = wallet.read_slate_by_tx(tx) {
self.tx_info_response_edit = message;
self.tx_info_slate = Some(slate);
}
// Show transaction information modal.
Modal::new(TX_INFO_MODAL)
.position(ModalPosition::CenterTop)
.show();
}
/// Draw transaction info [`Modal`] content.
fn tx_info_modal_ui(&mut self,
ui: &mut egui::Ui,
wallet: &mut Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
// Check values and setup transaction data.
let wallet_data = wallet.get_data();
if wallet_data.is_none() {
modal.close();
return;
}
let data = wallet_data.unwrap();
let tx_id = self.tx_info_id.unwrap();
let txs = data.txs.iter()
.filter(|tx| tx.data.id == tx_id)
.collect::<Vec<&WalletTransaction>>();
if txs.is_empty() {
modal.close();
return;
}
let tx = txs.get(0).unwrap();
ui.add_space(6.0);
// Show transaction amount status and time.
let rounding = View::item_rounding(0, 2, false);
self.tx_item_ui(ui, tx, rounding, false, false, &data, wallet, cb);
// Show transaction ID info.
if let Some(id) = tx.data.tx_slate_id {
let label = format!("{} {}", HASH_STRAIGHT, t!("id"));
Self::tx_info_modal_item_ui(ui, id.to_string(), label, true, cb);
}
// Show transaction kernel info.
if let Some(kernel) = tx.data.kernel_excess {
let label = format!("{} {}", FILE_ARCHIVE, t!("kernel"));
Self::tx_info_modal_item_ui(ui, kernel.0.to_hex(), label, true, cb);
}
// Show transaction Slatepack message response or finalization input.
if !tx.posting && !tx.data.confirmed && (tx.data.tx_type == TxLogEntryType::TxSent ||
tx.data.tx_type == TxLogEntryType::TxReceived) {
self.tx_info_modal_slate_ui(ui, tx, wallet, modal, cb);
}
ui.add_space(8.0);
// Show button to close modal.
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::WHITE, || {
self.tx_info_id = None;
self.tx_info_finalize = false;
cb.hide_keyboard();
modal.close();
}); });
});
ui.add_space(6.0);
}
/// Draw transaction information [`Modal`] item content.
fn tx_info_modal_item_ui(ui: &mut egui::Ui,
value: String,
label: String,
copy: bool,
cb: &dyn PlatformCallbacks) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(50.0);
// Draw round background.
let bg_rect = rect.clone();
let mut rounding = View::item_rounding(1, 3, false);
ui.painter().rect(bg_rect, rounding, Colors::FILL, View::ITEM_STROKE);
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to copy transaction info value.
if copy {
rounding.nw = 0.0;
rounding.sw = 0.0;
View::item_button(ui, rounding, COPY, None, || {
cb.copy_string_to_buffer(value.clone());
});
}
// Draw value information.
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
View::ellipsize_text(ui, value, 15.0, Colors::TITLE);
ui.label(RichText::new(label).size(15.0).color(Colors::GRAY));
ui.add_space(3.0);
});
});
});
}
/// Draw Slate content to show response or generate payment proof.
fn tx_info_modal_slate_ui(&mut self,
ui: &mut egui::Ui,
tx: &WalletTransaction,
wallet: &Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
if self.tx_info_slate.is_none() {
return;
}
let slate = self.tx_info_slate.clone().unwrap();
let amount = amount_to_hr_string(tx.amount, true);
// Draw Slatepack message input or output description text.
ui.add_space(6.0);
ui.vertical_centered(|ui| {
if self.tx_info_finalize {
let desc_text = if self.tx_info_finalize_error {
t!("wallets.finalize_slatepack_err")
} else {
if tx.data.tx_type == TxLogEntryType::TxSent {
t!("wallets.parse_s2_slatepack_desc", "amount" => amount)
} else {
t!("wallets.parse_i2_slatepack_desc", "amount" => amount)
}
};
let desc_color = if self.tx_info_finalize_error {
Colors::RED
} else {
Colors::INACTIVE_TEXT
};
ui.label(RichText::new(desc_text).size(16.0).color(desc_color));
} else {
let desc_text = if tx.can_finalize {
if tx.data.tx_type == TxLogEntryType::TxSent {
t!("wallets.send_request_desc", "amount" => amount)
} else {
t!("wallets.invoice_desc", "amount" => amount)
}
} else {
if tx.data.tx_type == TxLogEntryType::TxSent {
t!("wallets.parse_i1_slatepack_desc", "amount" => amount)
} else {
t!("wallets.parse_s1_slatepack_desc", "amount" => amount)
}
};
ui.label(RichText::new(desc_text).size(16.0).color(Colors::INACTIVE_TEXT));
}
});
ui.add_space(4.0);
ui.vertical_centered(|ui| {
let message_edit = if self.tx_info_finalize {
&mut self.tx_info_finalize_edit
} else {
&mut self.tx_info_response_edit
};
let message_before = message_edit.clone();
// Draw Slatepack message text input or output.
let input_id = Id::from("tx_info_slatepack_message").with(slate.id).with(tx.data.id);
View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(3.0);
ScrollArea::vertical()
.max_height(128.0)
.id_source(input_id)
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.add_space(7.0);
egui::TextEdit::multiline(message_edit)
.font(egui::TextStyle::Small)
.desired_rows(5)
.interactive(self.tx_info_finalize)
.hint_text(SLATEPACK_MESSAGE_HINT)
.desired_width(f32::INFINITY)
.show(ui);
ui.add_space(6.0);
});
ui.add_space(2.0);
View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(8.0);
if self.tx_info_finalize {
// Draw paste button.
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
View::button(ui, paste_text, Colors::BUTTON, || {
self.tx_info_finalize_edit = cb.get_string_from_buffer();
});
// Callback on finalization message input change.
if message_before != self.tx_info_finalize_edit {
self.on_finalization_input_change(tx, wallet, cb);
}
} else {
// Draw copy button.
let copy_text = format!("{} {}", COPY, t!("copy"));
View::button(ui, copy_text, Colors::BUTTON, || {
cb.copy_string_to_buffer(self.tx_info_response_edit.clone());
self.tx_info_finalize_edit = "".to_string();
if tx.can_finalize {
self.tx_info_finalize = true;
} else {
modal.close();
}
});
}
});
}
/// Parse Slatepack message on transaction finalization input change.
fn on_finalization_input_change(&mut self,
tx: &WalletTransaction,
wallet: &Wallet,
cb: &dyn PlatformCallbacks) {
let message = &self.tx_info_finalize_edit;
if message.is_empty() {
self.tx_info_finalize_error = false;
} else {
if let Ok(slate) = wallet.parse_slatepack(message) {
let send = slate.state == SlateState::Standard2 &&
tx.data.tx_type == TxLogEntryType::TxSent;
let receive = slate.state == SlateState::Invoice2 &&
tx.data.tx_type == TxLogEntryType::TxReceived;
if Some(slate.id) == tx.data.tx_slate_id && (send || receive) {
match wallet.finalize(message, wallet.can_use_dandelion()) {
Ok(_) => {
self.tx_info_finalize = false;
self.tx_info_finalize_edit = "".to_string();
cb.hide_keyboard();
}
Err(_) => {
self.tx_info_finalize_error = true;
}
}
} else {
self.tx_info_finalize_error = true;
}
} else {
self.tx_info_finalize_error = true;
}
}
}
} }

View file

@ -17,7 +17,7 @@ use std::sync::Arc;
use grin_keychain::ExtKeychain; use grin_keychain::ExtKeychain;
use grin_util::Mutex; use grin_util::Mutex;
use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient};
use grin_wallet_libwallet::{TxLogEntry, WalletInfo, WalletInst}; use grin_wallet_libwallet::{TxLogEntry, TxLogEntryType, WalletInfo, WalletInst};
/// Mnemonic phrase setup mode. /// Mnemonic phrase setup mode.
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
@ -149,6 +149,25 @@ pub struct WalletTransaction {
pub amount: u64, pub amount: u64,
/// Flag to check if transaction is posting after finalization. /// Flag to check if transaction is posting after finalization.
pub posting: bool, pub posting: bool,
/// Flag to check if transaction can be finalized based on Slatepack message state.
pub can_finalize: bool,
/// Last wallet block height of transaction reposting. /// Last wallet block height of transaction reposting.
pub repost_height: Option<u64> pub repost_height: Option<u64>
} }
impl WalletTransaction {
/// Check if transaction can be cancelled.
pub fn can_cancel(&self) -> bool {
!self.posting && !self.data.confirmed &&
self.data.tx_type != TxLogEntryType::TxReceivedCancelled
&& self.data.tx_type != TxLogEntryType::TxSentCancelled
}
/// Check if transaction can be reposted.
pub fn can_repost(&self, data: &WalletData) -> bool {
let last_height = data.info.last_confirmed_height;
let min_conf = data.info.minimum_confirmations;
self.posting && self.repost_height.is_some() &&
last_height - self.repost_height.unwrap() > min_conf
}
}

View file

@ -479,9 +479,9 @@ impl Wallet {
} }
/// Parse Slatepack message into [`Slate`]. /// Parse Slatepack message into [`Slate`].
pub fn parse_slatepack(&self, message: String) -> Result<Slate, Error> { pub fn parse_slatepack(&self, message: &String) -> Result<Slate, Error> {
let api = Owner::new(self.instance.clone().unwrap(), None); let api = Owner::new(self.instance.clone().unwrap(), None);
api.slate_from_slatepack_message(None, message, vec![]) api.slate_from_slatepack_message(None, message.clone(), vec![])
} }
/// Create Slatepack message from provided slate. /// Create Slatepack message from provided slate.
@ -493,7 +493,7 @@ impl Wallet {
Ok(()) Ok(())
})?; })?;
// Save slatepack. // Write Slatepack message to file.
let slatepack_dir = self.get_config().get_slatepack_path(&slate); let slatepack_dir = self.get_config().get_slatepack_path(&slate);
let mut output = File::create(slatepack_dir)?; let mut output = File::create(slatepack_dir)?;
output.write_all(message.as_bytes())?; output.write_all(message.as_bytes())?;
@ -510,7 +510,51 @@ impl Wallet {
} }
} }
/// Get transaction by slate id. /// Get last stored [`Slate`] for transaction.
pub fn read_slate_by_tx(&self, tx: &WalletTransaction) -> Option<(Slate, String)> {
let mut slate = None;
if let Some(slate_id) = tx.data.tx_slate_id {
// Get slate state based on tx state and status.
let state = if tx.posting {
if tx.data.tx_type == TxLogEntryType::TxSent {
Some(SlateState::Standard3)
} else {
Some(SlateState::Invoice3)
}
} else if !tx.data.confirmed && (tx.data.tx_type == TxLogEntryType::TxSent ||
tx.data.tx_type == TxLogEntryType::TxReceived) {
if tx.can_finalize {
if tx.data.tx_type == TxLogEntryType::TxSent {
Some(SlateState::Standard1)
} else {
Some(SlateState::Invoice1)
}
} else {
if tx.data.tx_type == TxLogEntryType::TxReceived {
Some(SlateState::Standard2)
} else {
Some(SlateState::Invoice2)
}
}
} else {
None
};
// Get slate from state by reading Slatepack message file.
if let Some(st) = state {
let mut s = Slate::blank(0, false);
s.id = slate_id;
s.state = st;
if let Some(m) = self.read_slatepack(&s) {
if let Ok(s) = self.parse_slatepack(&m) {
slate = Some((s, m));
}
}
}
}
slate
}
/// Get transaction for [`Slate`] id.
pub fn tx_by_slate(&self, slate: &Slate) -> Option<WalletTransaction> { pub fn tx_by_slate(&self, slate: &Slate) -> Option<WalletTransaction> {
if let Some(data) = self.get_data() { if let Some(data) = self.get_data() {
let txs = data.txs.clone().iter().map(|tx| tx.clone()).filter(|tx| { let txs = data.txs.clone().iter().map(|tx| tx.clone()).filter(|tx| {
@ -571,7 +615,7 @@ impl Wallet {
} }
/// Handle message from the invoice issuer to send founds, return response for funds receiver. /// Handle message from the invoice issuer to send founds, return response for funds receiver.
pub fn pay(&self, message: String) -> Result<String, Error> { pub fn pay(&self, message: &String) -> Result<String, Error> {
let slate = self.parse_slatepack(message)?; let slate = self.parse_slatepack(message)?;
let config = self.get_config(); let config = self.get_config();
let args = InitTxArgs { let args = InitTxArgs {
@ -595,7 +639,7 @@ impl Wallet {
} }
/// Handle message to receive funds, return response to sender. /// Handle message to receive funds, return response to sender.
pub fn receive(&self, message: String) -> Result<String, Error> { pub fn receive(&self, message: &String) -> Result<String, Error> {
let mut slate = self.parse_slatepack(message)?; let mut slate = self.parse_slatepack(message)?;
let api = Owner::new(self.instance.clone().unwrap(), None); let api = Owner::new(self.instance.clone().unwrap(), None);
controller::foreign_single_use(api.wallet_inst.clone(), None, |api| { controller::foreign_single_use(api.wallet_inst.clone(), None, |api| {
@ -612,7 +656,7 @@ impl Wallet {
} }
/// Finalize transaction from provided message as sender or invoice issuer with Dandelion. /// Finalize transaction from provided message as sender or invoice issuer with Dandelion.
pub fn finalize(&self, message: String, dandelion: bool) -> Result<Slate, Error> { pub fn finalize(&self, message: &String, dandelion: bool) -> Result<Slate, Error> {
let mut slate = self.parse_slatepack(message)?; let mut slate = self.parse_slatepack(message)?;
let api = Owner::new(self.instance.clone().unwrap(), None); let api = Owner::new(self.instance.clone().unwrap(), None);
slate = api.finalize_tx(None, &slate)?; slate = api.finalize_tx(None, &slate)?;
@ -629,7 +673,7 @@ impl Wallet {
// Post transaction to blockchain. // Post transaction to blockchain.
let api = Owner::new(self.instance.clone().unwrap(), None); let api = Owner::new(self.instance.clone().unwrap(), None);
api.post_tx(None, slate, dandelion)?; api.post_tx(None, slate, dandelion)?;
// Setup transaction repost height and posting flag. // Setup transaction repost height, posting flag and ability to finalize.
let mut slate = slate.clone(); let mut slate = slate.clone();
if slate.state == SlateState::Invoice2 { if slate.state == SlateState::Invoice2 {
slate.state = SlateState::Invoice3 slate.state = SlateState::Invoice3
@ -643,6 +687,7 @@ impl Wallet {
if t.data.id == tx.data.id { if t.data.id == tx.data.id {
t.repost_height = Some(data.info.last_confirmed_height); t.repost_height = Some(data.info.last_confirmed_height);
t.posting = true; t.posting = true;
t.can_finalize = false;
} }
} }
*w_data = Some(data); *w_data = Some(data);
@ -656,12 +701,13 @@ impl Wallet {
pub fn cancel(&mut self, id: u32) { pub fn cancel(&mut self, id: u32) {
let instance = self.instance.clone().unwrap(); let instance = self.instance.clone().unwrap();
let _ = cancel_tx(instance, None, &None, Some(id), None); let _ = cancel_tx(instance, None, &None, Some(id), None);
// Set cancelling status. // Setup cancelling status, posting flag, and ability to finalize.
{
let mut w_data = self.data.write().unwrap(); let mut w_data = self.data.write().unwrap();
let mut data = w_data.clone().unwrap(); let mut data = w_data.clone().unwrap();
let txs = data.txs.iter_mut().map(|tx| { let txs = data.txs.iter_mut().map(|tx| {
if tx.data.id == id { if tx.data.id == id {
tx.posting = false;
tx.can_finalize = false;
tx.data.tx_type = if tx.data.tx_type == TxLogEntryType::TxReceived { tx.data.tx_type = if tx.data.tx_type == TxLogEntryType::TxReceived {
TxLogEntryType::TxReceivedCancelled TxLogEntryType::TxReceivedCancelled
} else { } else {
@ -672,7 +718,6 @@ impl Wallet {
}).collect::<Vec<WalletTransaction>>(); }).collect::<Vec<WalletTransaction>>();
data.txs = txs; data.txs = txs;
*w_data = Some(data); *w_data = Some(data);
}
// Refresh wallet info to update statuses. // Refresh wallet info to update statuses.
self.sync(); self.sync();
} }
@ -995,7 +1040,6 @@ fn sync_wallet_data(wallet: &Wallet) {
// Create wallet txs. // Create wallet txs.
let mut new_txs: Vec<WalletTransaction> = vec![]; let mut new_txs: Vec<WalletTransaction> = vec![];
for tx in &filter_txs { for tx in &filter_txs {
println!("{}", serde_json::to_string(tx).unwrap());
// Setup transaction amount. // Setup transaction amount.
let amount = if tx.amount_debited > tx.amount_credited { let amount = if tx.amount_debited > tx.amount_credited {
tx.amount_debited - tx.amount_credited tx.amount_debited - tx.amount_credited
@ -1003,15 +1047,20 @@ fn sync_wallet_data(wallet: &Wallet) {
tx.amount_credited - tx.amount_debited tx.amount_credited - tx.amount_debited
}; };
// Setup transaction posting flag based on slate state. let unconfirmed_sent_or_received = tx.tx_slate_id.is_some() &&
let posting = if (tx.tx_type == TxLogEntryType::TxSent || !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent ||
tx.tx_type == TxLogEntryType::TxReceived) && tx.tx_type == TxLogEntryType::TxReceived);
!tx.confirmed && tx.tx_slate_id.is_some() {
// Setup transaction posting status based on slate state.
let posting = if unconfirmed_sent_or_received {
println!("{}", serde_json::to_string(tx).unwrap());
// Create slate to check existing file. // Create slate to check existing file.
let mut slate = Slate::blank(1, false); let is_invoice = tx.tx_type == TxLogEntryType::TxReceived;
let mut slate = Slate::blank(0, is_invoice);
slate.id = tx.tx_slate_id.unwrap(); slate.id = tx.tx_slate_id.unwrap();
slate.state = match tx.tx_type { slate.state = match is_invoice {
TxLogEntryType::TxReceived => SlateState::Invoice3, true => SlateState::Invoice3,
_ => SlateState::Standard3 _ => SlateState::Standard3
}; };
@ -1033,6 +1082,20 @@ fn sync_wallet_data(wallet: &Wallet) {
false false
}; };
// Setup flag for ability to finalize transaction.
let can_finalize = if !posting && unconfirmed_sent_or_received {
// Create slate to check existing file.
let mut slate = Slate::blank(1, false);
slate.id = tx.tx_slate_id.unwrap();
slate.state = match tx.tx_type {
TxLogEntryType::TxReceived => SlateState::Invoice1,
_ => SlateState::Standard1
};
wallet.read_slatepack(&slate).is_some()
} else {
false
};
// Setup reposting height. // Setup reposting height.
let mut repost_height = None; let mut repost_height = None;
if posting { if posting {
@ -1046,10 +1109,12 @@ fn sync_wallet_data(wallet: &Wallet) {
} }
} }
// Add transaction to list.
new_txs.push(WalletTransaction { new_txs.push(WalletTransaction {
data: tx.clone(), data: tx.clone(),
amount, amount,
posting, posting,
can_finalize,
repost_height, repost_height,
}) })
} }