wallet: ability to specify address for invoice to encrypt slatepack message

This commit is contained in:
ardocrat
2026-06-20 15:12:43 +03:00
parent a91d9016a8
commit 8524084c47
12 changed files with 206 additions and 146 deletions
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Verbindungsproblem
disconnected: Verbindung getrennt
receiver_address: 'Empfängeraddresse:'
sender_address: 'Absenderadresse:'
incorrect_addr_err: 'Eingegebene Addresse ist inkorrekt:'
tor_send_error: Beim Senden über Tor ist ein Fehler aufgetreten. Stellen Sie sicher, dass der Empfänger online ist. Die Transaktion wurde abgebrochen.
tor_autorun_desc: Gibt an, ob beim Öffnen des Wallets der Tor-Dienst gestartet werden soll, um Transaktionen synchron zu empfangen.
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Connection error
disconnected: Disconnected
receiver_address: 'Address of the receiver:'
sender_address: 'Address of the sender:'
incorrect_addr_err: 'Entered address is incorrect:'
tor_send_error: An error occurred during sending over Tor, make sure receiver is online, transaction was canceled.
tor_autorun_desc: Whether to launch Tor service on wallet opening to receive transactions synchronously.
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Erreur de connexion
disconnected: Déconnecté
receiver_address: 'Adresse du destinataire:'
sender_address: "Adresse de l'expéditeur:"
incorrect_addr_err: 'Adresse entrée incorrecte:'
tor_send_error: "Une erreur s'est produite lors de l'envoi via Tor. Assurez-vous que le destinataire est en ligne, la transaction a été annulée."
tor_autorun_desc: "Lancer automatiquement le service Tor à l'ouverture du portefeuille pour recevoir les transactions de manière synchronisée."
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Ошибка подключения
disconnected: Отключено
receiver_address: 'Адрес получателя:'
sender_address: 'Адрес отправителя:'
incorrect_addr_err: 'Введённый адрес неверен:'
tor_send_error: Во время отправки через Tor произошла ошибка, убедитесь, что получатель находится онлайн, транзакция была отменена.
tor_autorun_desc: Запускать ли Tor сервис при открытии кошелька для синхронного получения транзакций.
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Bagalanti hatasi
disconnected: Baglanti yok
receiver_address: 'Alicinin adresi:'
sender_address: 'Gönderici adresi:'
incorrect_addr_err: 'Girilen adres hatali:'
tor_send_error: Tor adresi uzerinden gonderimde aksaklik olustu, alici online olmasi gerek, islem iptal edildi.
tor_autorun_desc: Islemleri Tor adresi olarak AL,bunun için cuzdan acilisinda Tor hizmetinin baslatilip baslatilmayacagi.
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: 连接错误
disconnected: 已断开连接
receiver_address: '接收者的地址:'
sender_address: '发件人地址:'
incorrect_addr_err: '输入的地址不正确:'
tor_send_error: 通过 Tor 发送时出错,请确保接收方在线, 交易已取消.
tor_autorun_desc: 是否在开钱包时启动 Tor 服务以同步接收交易.
+37 -1
View File
@@ -25,8 +25,8 @@ use std::thread;
use crate::gui::Colors;
use crate::gui::icons::CAMERA_ROTATE;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::gui::views::types::{QrScanResult, QrScanState};
use crate::gui::views::{Modal, View};
use crate::wallet::WalletUtils;
use crate::wallet::types::PhraseSize;
@@ -88,6 +88,42 @@ impl CameraContent {
ui.ctx().request_repaint();
}
/// Draw modal camera content.
pub fn modal_ui(
&mut self,
ui: &mut egui::Ui,
cb: &dyn PlatformCallbacks,
mut on_result: impl FnMut(Option<QrScanResult>),
) {
if let Some(result) = self.qr_scan_result() {
on_result(Some(result));
} else {
ui.add_space(6.0);
self.ui(ui, cb);
ui.add_space(6.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
// Show buttons to close modal or come back to sending input.
ui.columns(2, |cols| {
cols[0].vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
cb.stop_camera();
on_result(None);
Modal::close();
});
});
cols[1].vertical_centered_justified(|ui| {
View::button(ui, t!("back"), Colors::white_or_black(false), || {
on_result(None);
});
});
});
ui.add_space(6.0);
}
}
/// Draw camera image.
fn image_ui(&mut self, ui: &mut egui::Ui, mut img: DynamicImage, rotation: u32) -> Rect {
// Setup image rotation.
+88 -11
View File
@@ -12,25 +12,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{Id, RichText};
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, TextEdit, View};
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
use crate::wallet::Wallet;
use crate::wallet::types::WalletTask;
use egui::{Id, RichText};
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use grin_wallet_libwallet::SlatepackAddress;
/// Invoice request creation content.
pub struct InvoiceRequestContent {
/// Amount to receive.
amount_edit: String,
/// Sender address.
address_edit: String,
/// Flag to check if entered address is incorrect.
address_error: bool,
/// Address QR code scanner content.
address_scan_content: Option<CameraContent>,
}
impl Default for InvoiceRequestContent {
fn default() -> Self {
Self {
amount_edit: "".to_string(),
address_edit: "".to_string(),
address_error: false,
address_scan_content: None,
}
}
}
@@ -50,14 +61,36 @@ impl InvoiceRequestContent {
return;
}
if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) {
m.amount_edit = "".to_string();
wallet.task(WalletTask::Receive(a));
let addr_str = m.address_edit.as_str();
let addr = if let Ok(r) = SlatepackAddress::try_from(addr_str.trim()) {
Some(r)
} else {
None
};
wallet.task(WalletTask::Receive(a, addr));
Modal::close();
}
};
ui.add_space(6.0);
// Draw QR code scanner content if requested.
if let Some(content) = self.address_scan_content.as_mut() {
let mut close_scan = true;
content.modal_ui(ui, cb, |result| {
if let Some(result) = result {
self.address_edit = result.text();
} else {
modal.enable_closing();
close_scan = true;
}
});
if close_scan {
self.address_scan_content = None;
}
return;
}
// Draw amount input content.
ui.vertical_centered(|ui| {
ui.label(
@@ -72,11 +105,9 @@ impl InvoiceRequestContent {
let amount_edit_before = self.amount_edit.clone();
let mut amount_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id))
.h_center()
.numeric();
.numeric()
.focus(Modal::first_draw());
amount_edit.ui(ui, &mut self.amount_edit, cb);
if amount_edit.enter_pressed {
on_continue(self);
}
// Check value if input was changed.
if amount_edit_before != self.amount_edit {
@@ -109,7 +140,54 @@ impl InvoiceRequestContent {
}
}
ui.add_space(8.0);
// Show address error or input description.
ui.vertical_centered(|ui| {
if self.address_error {
ui.label(
RichText::new(t!("transport.incorrect_addr_err"))
.size(17.0)
.color(Colors::red()),
);
} else {
ui.label(
RichText::new(t!("transport.sender_address"))
.size(17.0)
.color(Colors::gray()),
);
}
});
ui.add_space(6.0);
// Show address text edit.
let addr_edit_before = self.address_edit.clone();
let address_edit_id = Id::from(modal.id)
.with("_address")
.with(wallet.get_config().id);
let mut address_edit = TextEdit::new(address_edit_id)
.paste()
.focus(false)
.scan_qr();
if amount_edit.enter_pressed {
address_edit.focus_request();
}
address_edit.ui(ui, &mut self.address_edit, cb);
// Check if scan button was pressed.
if address_edit.scan_pressed {
modal.disable_closing();
self.address_scan_content = Some(CameraContent::default());
}
ui.add_space(12.0);
// Check value if input was changed.
if addr_edit_before != self.address_edit {
self.address_error = false;
}
// Continue on Enter press.
if address_edit.enter_pressed {
on_continue(self);
}
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
@@ -121,7 +199,6 @@ impl InvoiceRequestContent {
t!("modal.cancel"),
Colors::white_or_black(false),
|| {
self.amount_edit = "".to_string();
Modal::close();
},
);
+11 -33
View File
@@ -90,40 +90,18 @@ impl SendRequestContent {
ui.add_space(6.0);
// Draw QR code scanner content if requested.
if let Some(scanner) = self.address_scan_content.as_mut() {
let on_stop = || {
cb.stop_camera();
modal.enable_closing();
};
if let Some(result) = scanner.qr_scan_result() {
self.address_edit = result.text();
on_stop();
if let Some(content) = self.address_scan_content.as_mut() {
let mut close_scan = true;
content.modal_ui(ui, cb, |result| {
if let Some(result) = result {
self.address_edit = result.text();
} else {
modal.enable_closing();
close_scan = true;
}
});
if close_scan {
self.address_scan_content = None;
} else {
ui.add_space(6.0);
scanner.ui(ui, cb);
ui.add_space(6.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
// Show buttons to close modal or come back to sending input.
ui.columns(2, |cols| {
cols[0].vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
on_stop();
self.close();
});
});
cols[1].vertical_centered_justified(|ui| {
View::button(ui, t!("back"), Colors::white_or_black(false), || {
on_stop();
self.address_scan_content = None;
});
});
});
ui.add_space(6.0);
}
return;
}
+35 -31
View File
@@ -168,10 +168,12 @@ impl WalletTransactionContent {
cb: &dyn PlatformCallbacks,
) {
if self.message.is_none() {
let slatepack_path = wallet
.get_config()
.get_slate_path(tx.data.tx_slate_id.unwrap(), &tx.state);
self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string()));
if let Some(slate_state) = tx.data.tx_slate_state.as_ref() {
let slatepack_path = wallet
.get_config()
.get_slate_path(tx.data.tx_slate_id.unwrap(), slate_state);
self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string()));
}
}
if let Some(m) = &self.message {
if m.is_empty() {
@@ -260,29 +262,31 @@ impl WalletTransactionContent {
});
// Draw button to share response as file.
ui.add_space(8.0);
ui.vertical_centered(|ui| {
let share_text = format!("{} {}", FILE_TEXT, t!("share"));
View::colored_text_button(
ui,
share_text,
Colors::blue(),
Colors::white_or_black(false),
|| {
if let Some(slate_id) = tx.data.tx_slate_id {
let name = format!("{}.{}.slatepack", slate_id, tx.state);
let data = m.as_bytes().to_vec();
cb.share_data(name, data).unwrap_or_default();
// Show message input or close modal.
if tx.can_finalize() {
finalization_needed = true;
} else {
Modal::close();
}
}
},
);
});
if let Some(slate_id) = tx.data.tx_slate_id {
if let Some(slate_state) = tx.data.tx_slate_state.as_ref() {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
let share_text = format!("{} {}", FILE_TEXT, t!("share"));
View::colored_text_button(
ui,
share_text,
Colors::blue(),
Colors::white_or_black(false),
|| {
let name = format!("{}.{}.slatepack", slate_id, slate_state);
let data = m.as_bytes().to_vec();
cb.share_data(name, data).unwrap_or_default();
// Show message input or close modal.
if tx.can_finalize() {
finalization_needed = true;
} else {
Modal::close();
}
},
);
});
}
}
if finalization_needed {
Modal::new(MessageInputContent::MODAL_ID)
@@ -370,13 +374,13 @@ impl WalletTransactionContent {
info_item_ui(ui, kernel.0.to_hex(), label, true, cb);
}
// Show receiver or sender address.
let addr = if tx.data.tx_type == TxLogEntryType::TxSent {
&tx.receiver
let (addr, label) = if tx.data.tx_type == TxLogEntryType::TxSent {
(&tx.receiver, t!("transport.receiver_address"))
} else {
&tx.sender
(&tx.sender, t!("transport.sender_address"))
};
if let Some(addr) = addr {
let label = format!("{} {}", CIRCLE_HALF, t!("network_mining.address"));
let label = format!("{} {}", CIRCLE_HALF, label.replace(":", ""));
info_item_ui(ui, addr.to_string(), label, true, cb);
}
}
+6 -18
View File
@@ -213,8 +213,6 @@ pub enum WalletTxAction {
pub struct WalletTx {
/// Information from database.
pub data: TxLogEntry,
/// State of transaction Slate.
pub state: SlateState,
/// Payment proof.
pub(crate) proof: Option<PaymentProof>,
@@ -240,7 +238,6 @@ impl WalletTx {
pub fn new(
tx: TxLogEntry,
proof: Option<PaymentProof>,
wallet: &Wallet,
height: Option<u64>,
broadcasting_height: Option<u64>,
action: Option<WalletTxAction>,
@@ -263,9 +260,8 @@ impl WalletTx {
sender = Some(addr);
}
}
let mut t = Self {
let t = Self {
data: tx,
state: SlateState::Unknown,
proof,
amount,
receiver,
@@ -275,15 +271,6 @@ impl WalletTx {
action,
action_error,
};
// Update Slate state for unconfirmed.
if !t.data.confirmed
&& (t.data.tx_type == TxLogEntryType::TxSent
|| t.data.tx_type == TxLogEntryType::TxReceived)
{
if let Some(slate_id) = t.data.tx_slate_id {
t.state = wallet.get_slate_state(slate_id, &t.data.tx_type)
}
}
t
}
@@ -294,15 +281,16 @@ impl WalletTx {
&& (!self.sending_tor() || self.action_error.is_some())
&& (self.data.tx_type == TxLogEntryType::TxSent
|| self.data.tx_type == TxLogEntryType::TxReceived)
&& (self.state == SlateState::Invoice1 || self.state == SlateState::Standard1)
&& (self.data.tx_slate_state == Some(SlateState::Invoice1)
|| self.data.tx_slate_state == Some(SlateState::Standard1))
}
/// Check if transaction was finalized.
pub fn finalized(&self) -> bool {
(self.data.tx_type == TxLogEntryType::TxSent
|| self.data.tx_type == TxLogEntryType::TxReceived)
&& self.state == SlateState::Invoice3
|| self.state == SlateState::Standard3
&& self.data.tx_slate_state == Some(SlateState::Invoice3)
|| self.data.tx_slate_state == Some(SlateState::Standard3)
}
/// Check if transaction is sending over Tor.
@@ -420,7 +408,7 @@ pub enum WalletTask {
SendTor(TxLogEntry, SlatepackAddress),
/// Invoice creation.
/// * amount
Receive(u64),
Receive(u64, Option<SlatepackAddress>),
/// Transaction finalization.
/// * tx id
Finalize(u32),
+23 -52
View File
@@ -45,7 +45,7 @@ use grin_wallet_libwallet::{
VersionedSlate, WalletBackend, WalletInitStatus, WalletInst, WalletLCProvider, address,
};
use grin_wallet_util::OnionV3Address;
use log::error;
use log::{debug, error};
use num_bigint::BigInt;
use parking_lot::RwLock;
use rand::Rng;
@@ -936,7 +936,7 @@ impl Wallet {
fn create_slatepack_message(
&self,
slate: &Slate,
_: Option<SlatepackAddress>,
address: Option<SlatepackAddress>,
) -> Result<String, Error> {
let mut message = "".to_string();
let r_inst = self.instance.as_ref().read();
@@ -947,11 +947,11 @@ impl Wallet {
self.keychain_mask().as_ref(),
Some(&mut api),
|api, m| {
// let recipients = match dest {
// Some(a) => vec![a],
// None => vec![],
// };
message = api.create_slatepack_message(m, &slate, Some(0), vec![])?;
let addrs = match address {
Some(a) => vec![a],
None => vec![],
};
message = api.create_slatepack_message(m, &slate, Some(0), addrs)?;
Ok(())
},
)?;
@@ -970,40 +970,6 @@ impl Wallet {
fs::exists(slatepack_path).unwrap_or(false)
}
/// Get possible state from tx type.
pub fn get_slate_state(&self, slate_id: Uuid, tx_type: &TxLogEntryType) -> SlateState {
let mut slate = Slate::blank(1, false);
slate.id = slate_id;
slate.state = match tx_type {
TxLogEntryType::TxReceived => SlateState::Invoice3,
_ => SlateState::Standard3,
};
// Transaction was finalized.
if self.slatepack_exists(&slate) {
slate.state
} else {
slate.state = match tx_type {
TxLogEntryType::TxReceived => SlateState::Standard2,
_ => SlateState::Invoice2,
};
// Transaction signed to be finalized.
if self.slatepack_exists(&slate) {
slate.state
} else {
// Transaction just was created.
slate.state = match tx_type {
TxLogEntryType::TxReceived => SlateState::Invoice1,
_ => SlateState::Standard1,
};
if self.slatepack_exists(&slate) {
slate.state
} else {
SlateState::Unknown
}
}
}
}
/// Calculate transaction fee for provided amount.
fn calculate_fee(&self, a: u64) -> Result<u64, Error> {
let r_inst = self.instance.as_ref().read();
@@ -1057,7 +1023,7 @@ impl Wallet {
controller::owner_single_use(None, keychain_mask.as_ref(), Some(&mut api), |api, m| {
let s = api.init_send_tx(m, args)?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&s, dest)?;
let _ = self.create_slatepack_message(&s, None)?;
// Lock outputs to for this transaction.
api.tx_lock_outputs(m, &s)?;
slate = Some(s);
@@ -1118,7 +1084,11 @@ impl Wallet {
}
/// Initialize an invoice transaction to receive amount, return request for funds sender.
fn issue_invoice(&self, amount: u64) -> Result<Slate, Error> {
fn issue_invoice(
&self,
amount: u64,
address: Option<SlatepackAddress>,
) -> Result<Slate, Error> {
let args = IssueInvoiceTxArgs {
dest_acct_name: None,
amount,
@@ -1130,7 +1100,7 @@ impl Wallet {
let slate = api.issue_invoice_tx(self.keychain_mask().as_ref(), args)?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&slate, None)?;
let _ = self.create_slatepack_message(&slate, address)?;
Ok(slate)
}
@@ -1313,11 +1283,12 @@ impl Wallet {
fn get_tx_slate(&self, tx_id: u32) -> Option<Slate> {
if let Some(tx) = self.retrieve_tx_by_id(Some(tx_id), None) {
if let Some(slate_id) = tx.tx_slate_id {
let slate_state = self.get_slate_state(slate_id, &tx.tx_type);
let slatepack_path = self.get_config().get_slate_path(slate_id, &slate_state);
let msg = fs::read_to_string(slatepack_path).unwrap_or("".to_string());
if let Ok((slate, _)) = self.parse_slatepack(&msg) {
return Some(slate);
if let Some(slate_state) = tx.tx_slate_state {
let slatepack_path = self.get_config().get_slate_path(slate_id, &slate_state);
let msg = fs::read_to_string(slatepack_path).unwrap_or("".to_string());
if let Ok((slate, _)) = self.parse_slatepack(&msg) {
return Some(slate);
}
}
}
}
@@ -1889,9 +1860,10 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
send_tor(tx.clone(), &s, r).await;
}
}
WalletTask::Receive(a) => {
WalletTask::Receive(amount, address) => {
w.invoice_creating.store(true, Ordering::Relaxed);
if let Ok(s) = w.issue_invoice(*a) {
debug!("receive: {} at {:?}", amount, address.clone());
if let Ok(s) = w.issue_invoice(*amount, address.clone()) {
sync_wallet_data(&w, false);
let tx = w.retrieve_tx_by_id(None, Some(s.id));
if let Some(tx) = tx {
@@ -2143,7 +2115,6 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
let mut new = WalletTx::new(
tx.clone(),
proof.clone(),
wallet,
height,
broadcasting_height,
action,