From a212b325fe84fcb18e2ab578704d4eb580f24017 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Sun, 12 May 2024 20:37:14 +0200 Subject: [PATCH] feat(core/ui): T3T1 send flow [no changelog] --- core/embed/rust/librust_qstr.h | 13 +- .../generated/translated_string.rs | 43 ++-- .../src/ui/model_mercury/component/frame.rs | 1 + .../ui/model_mercury/flow/confirm_output.rs | 158 ++++++++++++ .../ui/model_mercury/flow/confirm_summary.rs | 169 +++++++++++++ .../rust/src/ui/model_mercury/flow/mod.rs | 6 + .../rust/src/ui/model_mercury/flow/util.rs | 236 ++++++++++++++++++ .../embed/rust/src/ui/model_mercury/layout.rs | 26 +- core/embed/rust/src/ui/model_tr/layout.rs | 2 +- core/mocks/generated/trezorui2.pyi | 24 ++ core/mocks/trezortranslate_keys.pyi | 14 +- core/src/apps/bitcoin/sign_tx/approvers.py | 6 +- core/src/apps/bitcoin/sign_tx/bitcoin.py | 4 +- core/src/apps/bitcoin/sign_tx/decred.py | 4 +- core/src/apps/bitcoin/sign_tx/helpers.py | 11 +- core/src/apps/bitcoin/sign_tx/layout.py | 6 +- core/src/apps/eos/layout.py | 2 +- .../src/trezor/ui/layouts/mercury/__init__.py | 152 +++++------ core/src/trezor/ui/layouts/tr/__init__.py | 9 +- core/src/trezor/ui/layouts/tt/__init__.py | 13 +- core/tests/test_apps.bitcoin.approver.py | 5 +- ...pps.bitcoin.segwit.signtx.native_p2wpkh.py | 6 +- ...bitcoin.segwit.signtx.native_p2wpkh_grs.py | 6 +- ...ps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py | 8 +- ...itcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py | 6 +- .../test_apps.bitcoin.signtx.fee_threshold.py | 2 +- core/tests/test_apps.bitcoin.signtx.py | 2 +- core/tests/test_apps.bitcoin.signtx_decred.py | 2 +- core/tests/test_apps.bitcoin.signtx_grs.py | 2 +- core/tools/translations/rules.json | 6 +- core/translations/cs.json | 8 +- core/translations/de.json | 8 +- core/translations/en.json | 16 +- core/translations/es.json | 8 +- core/translations/fr.json | 8 +- core/translations/order.json | 14 +- core/translations/signatures.json | 6 +- 37 files changed, 843 insertions(+), 169 deletions(-) create mode 100644 core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs create mode 100644 core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs create mode 100644 core/embed/rust/src/ui/model_mercury/flow/util.rs diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 639ec4d910..e042e1e74c 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -19,7 +19,9 @@ static void _librust_qstrs(void) { MP_QSTR___dict__; MP_QSTR___name__; MP_QSTR_account; + MP_QSTR_account_items; MP_QSTR_account_label; + MP_QSTR_account_path; MP_QSTR_accounts; MP_QSTR_action; MP_QSTR_active; @@ -175,6 +177,7 @@ static void _librust_qstrs(void) { MP_QSTR_confirm_reset_device; MP_QSTR_confirm_total; MP_QSTR_confirm_total__fee_rate; + MP_QSTR_confirm_total__fee_rate_colon; MP_QSTR_confirm_total__sending_from_account; MP_QSTR_confirm_total__title_fee; MP_QSTR_confirm_total__title_sending_from; @@ -205,6 +208,7 @@ static void _librust_qstrs(void) { MP_QSTR_experimental_mode__title; MP_QSTR_extra; MP_QSTR_fee_amount; + MP_QSTR_fee_items; MP_QSTR_fee_label; MP_QSTR_fee_rate_amount; MP_QSTR_fee_title; @@ -212,9 +216,11 @@ static void _librust_qstrs(void) { MP_QSTR_fingerprint; MP_QSTR_firmware_update__title; MP_QSTR_firmware_update__title_fingerprint; + MP_QSTR_flow_confirm_output; MP_QSTR_flow_confirm_reset_create; MP_QSTR_flow_confirm_reset_recover; MP_QSTR_flow_confirm_set_new_pin; + MP_QSTR_flow_confirm_summary; MP_QSTR_flow_get_address; MP_QSTR_flow_prompt_backup; MP_QSTR_flow_show_share_words; @@ -248,6 +254,7 @@ static void _librust_qstrs(void) { MP_QSTR_inputs__space; MP_QSTR_instructions__continue_in_app; MP_QSTR_instructions__hold_to_confirm; + MP_QSTR_instructions__hold_to_sign; MP_QSTR_instructions__swipe_up; MP_QSTR_instructions__tap_to_confirm; MP_QSTR_is_type_of; @@ -521,11 +528,15 @@ static void _librust_qstrs(void) { MP_QSTR_select_word; MP_QSTR_select_word_count; MP_QSTR_send__address_path; + MP_QSTR_send__cancel_sign; MP_QSTR_send__confirm_sending; MP_QSTR_send__from_multiple_accounts; + MP_QSTR_send__incl_transaction_fee; MP_QSTR_send__including_fee; MP_QSTR_send__maximum_fee; MP_QSTR_send__receiving_to_multisig; + MP_QSTR_send__send_from; + MP_QSTR_send__sign_transaction; MP_QSTR_send__title_confirm_sending; MP_QSTR_send__title_joint_transaction; MP_QSTR_send__title_receiving_to; @@ -534,6 +545,7 @@ static void _librust_qstrs(void) { MP_QSTR_send__title_sending_to; MP_QSTR_send__to_the_total_amount; MP_QSTR_send__total_amount; + MP_QSTR_send__total_amount_colon; MP_QSTR_send__transaction_id; MP_QSTR_send__you_are_contributing; MP_QSTR_share_words; @@ -833,7 +845,6 @@ static void _librust_qstrs(void) { MP_QSTR_eos__requirement; MP_QSTR_eos__sell_ram; MP_QSTR_eos__sender; - MP_QSTR_eos__sign_transaction; MP_QSTR_eos__threshold; MP_QSTR_eos__to; MP_QSTR_eos__transfer; diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs index a79e554bda..537d2b976b 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs +++ b/core/embed/rust/src/translations/generated/translated_string.rs @@ -351,9 +351,9 @@ pub enum TranslatedString { coinjoin__title_do_not_disconnect = 217, // "Do not disconnect your trezor!" coinjoin__title_progress = 218, // "Coinjoin in progress" coinjoin__waiting_for_others = 219, // "Waiting for others" - confirm_total__fee_rate = 220, // "Fee rate:" + confirm_total__fee_rate_colon = 220, // "Fee rate:" confirm_total__sending_from_account = 221, // "Sending from account:" - confirm_total__title_fee = 222, // "Fee information" + confirm_total__title_fee = 222, // "Fee info" confirm_total__title_sending_from = 223, // "Sending from" debug__loading_seed = 224, // "Loading seed" debug__loading_seed_not_recommended = 225, // "Loading private seed is not recommended." @@ -420,8 +420,7 @@ pub enum TranslatedString { eos__sell_ram = 258, // "Sell RAM" #[cfg(feature = "universal_fw")] eos__sender = 259, // "Sender:" - #[cfg(feature = "universal_fw")] - eos__sign_transaction = 260, // "Sign transaction" + send__sign_transaction = 260, // "Sign transaction" #[cfg(feature = "universal_fw")] eos__threshold = 261, // "Threshold:" #[cfg(feature = "universal_fw")] @@ -940,7 +939,7 @@ pub enum TranslatedString { send__title_sending_amount = 650, // "Sending amount" send__title_sending_to = 651, // "Sending to" send__to_the_total_amount = 652, // "To the total amount:" - send__total_amount = 653, // "Total amount:" + send__total_amount_colon = 653, // "Total amount:" send__transaction_id = 654, // "Transaction ID:" send__you_are_contributing = 655, // "You are contributing:" share_words__words_in_order = 656, // " words in order." @@ -1271,6 +1270,12 @@ pub enum TranslatedString { pin__cancel_description = 870, // "Continue without PIN" pin__cancel_info = 871, // "Without a PIN, anyone can access this device." pin__cancel_setup = 872, // "Cancel PIN setup" + send__cancel_sign = 873, // "Cancel sign" + send__send_from = 874, // "Send from" + instructions__hold_to_sign = 875, // "Hold to sign" + confirm_total__fee_rate = 876, // "Fee rate" + send__incl_transaction_fee = 877, // "incl. Transaction fee" + send__total_amount = 878, // "Total amount" } impl TranslatedString { @@ -1617,9 +1622,9 @@ impl TranslatedString { Self::coinjoin__title_do_not_disconnect => "Do not disconnect your trezor!", Self::coinjoin__title_progress => "Coinjoin in progress", Self::coinjoin__waiting_for_others => "Waiting for others", - Self::confirm_total__fee_rate => "Fee rate:", + Self::confirm_total__fee_rate_colon => "Fee rate:", Self::confirm_total__sending_from_account => "Sending from account:", - Self::confirm_total__title_fee => "Fee information", + Self::confirm_total__title_fee => "Fee info", Self::confirm_total__title_sending_from => "Sending from", Self::debug__loading_seed => "Loading seed", Self::debug__loading_seed_not_recommended => "Loading private seed is not recommended.", @@ -1686,8 +1691,7 @@ impl TranslatedString { Self::eos__sell_ram => "Sell RAM", #[cfg(feature = "universal_fw")] Self::eos__sender => "Sender:", - #[cfg(feature = "universal_fw")] - Self::eos__sign_transaction => "Sign transaction", + Self::send__sign_transaction => "Sign transaction", #[cfg(feature = "universal_fw")] Self::eos__threshold => "Threshold:", #[cfg(feature = "universal_fw")] @@ -2206,7 +2210,7 @@ impl TranslatedString { Self::send__title_sending_amount => "Sending amount", Self::send__title_sending_to => "Sending to", Self::send__to_the_total_amount => "To the total amount:", - Self::send__total_amount => "Total amount:", + Self::send__total_amount_colon => "Total amount:", Self::send__transaction_id => "Transaction ID:", Self::send__you_are_contributing => "You are contributing:", Self::share_words__words_in_order => " words in order.", @@ -2537,6 +2541,12 @@ impl TranslatedString { Self::pin__cancel_description => "Continue without PIN", Self::pin__cancel_info => "Without a PIN, anyone can access this device.", Self::pin__cancel_setup => "Cancel PIN setup", + Self::send__cancel_sign => "Cancel sign", + Self::send__send_from => "Send from", + Self::instructions__hold_to_sign => "Hold to sign", + Self::confirm_total__fee_rate => "Fee rate", + Self::send__incl_transaction_fee => "incl. Transaction fee", + Self::send__total_amount => "Total amount", } } @@ -2884,7 +2894,7 @@ impl TranslatedString { Qstr::MP_QSTR_coinjoin__title_do_not_disconnect => Some(Self::coinjoin__title_do_not_disconnect), Qstr::MP_QSTR_coinjoin__title_progress => Some(Self::coinjoin__title_progress), Qstr::MP_QSTR_coinjoin__waiting_for_others => Some(Self::coinjoin__waiting_for_others), - Qstr::MP_QSTR_confirm_total__fee_rate => Some(Self::confirm_total__fee_rate), + Qstr::MP_QSTR_confirm_total__fee_rate_colon => Some(Self::confirm_total__fee_rate_colon), Qstr::MP_QSTR_confirm_total__sending_from_account => Some(Self::confirm_total__sending_from_account), Qstr::MP_QSTR_confirm_total__title_fee => Some(Self::confirm_total__title_fee), Qstr::MP_QSTR_confirm_total__title_sending_from => Some(Self::confirm_total__title_sending_from), @@ -2953,8 +2963,7 @@ impl TranslatedString { Qstr::MP_QSTR_eos__sell_ram => Some(Self::eos__sell_ram), #[cfg(feature = "universal_fw")] Qstr::MP_QSTR_eos__sender => Some(Self::eos__sender), - #[cfg(feature = "universal_fw")] - Qstr::MP_QSTR_eos__sign_transaction => Some(Self::eos__sign_transaction), + Qstr::MP_QSTR_send__sign_transaction => Some(Self::send__sign_transaction), #[cfg(feature = "universal_fw")] Qstr::MP_QSTR_eos__threshold => Some(Self::eos__threshold), #[cfg(feature = "universal_fw")] @@ -3473,7 +3482,7 @@ impl TranslatedString { Qstr::MP_QSTR_send__title_sending_amount => Some(Self::send__title_sending_amount), Qstr::MP_QSTR_send__title_sending_to => Some(Self::send__title_sending_to), Qstr::MP_QSTR_send__to_the_total_amount => Some(Self::send__to_the_total_amount), - Qstr::MP_QSTR_send__total_amount => Some(Self::send__total_amount), + Qstr::MP_QSTR_send__total_amount_colon => Some(Self::send__total_amount_colon), Qstr::MP_QSTR_send__transaction_id => Some(Self::send__transaction_id), Qstr::MP_QSTR_send__you_are_contributing => Some(Self::send__you_are_contributing), Qstr::MP_QSTR_share_words__words_in_order => Some(Self::share_words__words_in_order), @@ -3804,6 +3813,12 @@ impl TranslatedString { Qstr::MP_QSTR_pin__cancel_description => Some(Self::pin__cancel_description), Qstr::MP_QSTR_pin__cancel_info => Some(Self::pin__cancel_info), Qstr::MP_QSTR_pin__cancel_setup => Some(Self::pin__cancel_setup), + Qstr::MP_QSTR_send__cancel_sign => Some(Self::send__cancel_sign), + Qstr::MP_QSTR_send__send_from => Some(Self::send__send_from), + Qstr::MP_QSTR_instructions__hold_to_sign => Some(Self::instructions__hold_to_sign), + Qstr::MP_QSTR_confirm_total__fee_rate => Some(Self::confirm_total__fee_rate), + Qstr::MP_QSTR_send__incl_transaction_fee => Some(Self::send__incl_transaction_fee), + Qstr::MP_QSTR_send__total_amount => Some(Self::send__total_amount), _ => None, } } diff --git a/core/embed/rust/src/ui/model_mercury/component/frame.rs b/core/embed/rust/src/ui/model_mercury/component/frame.rs index 26c50e9d56..b2049fab07 100644 --- a/core/embed/rust/src/ui/model_mercury/component/frame.rs +++ b/core/embed/rust/src/ui/model_mercury/component/frame.rs @@ -104,6 +104,7 @@ where pub fn with_warning_button(self) -> Self { self.with_button(theme::ICON_WARNING, CancelInfoConfirmMsg::Info, false) + .button_styled(theme::button_danger()) } pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self { diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs new file mode 100644 index 0000000000..9e55a15978 --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs @@ -0,0 +1,158 @@ +use crate::{ + error, + micropython::qstr::Qstr, + strutil::TString, + translations::TR, + ui::{ + component::{ComponentExt, SwipeDirection}, + flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage}, + }, +}; + +use super::super::{ + component::{ + AddressDetails, Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg, + }, + theme, +}; + +use super::util::ConfirmBlobParams; + +#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] +pub enum ConfirmOutput { + Address, + Amount, + // Tap, + Menu, + AccountInfo, + CancelTap, +} + +impl FlowState for ConfirmOutput { + fn handle_swipe(&self, direction: SwipeDirection) -> Decision { + match (self, direction) { + (ConfirmOutput::Address | ConfirmOutput::Amount, SwipeDirection::Left) => { + Decision::Goto(ConfirmOutput::Menu, direction) + } + (ConfirmOutput::Address, SwipeDirection::Up) => { + Decision::Goto(ConfirmOutput::Amount, direction) + } + (ConfirmOutput::Amount, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed), + (ConfirmOutput::Amount, SwipeDirection::Down) => { + Decision::Goto(ConfirmOutput::Address, direction) + } + (ConfirmOutput::Menu, SwipeDirection::Right) => { + Decision::Goto(ConfirmOutput::Address, direction) + } + (ConfirmOutput::Menu, SwipeDirection::Left) => { + Decision::Goto(ConfirmOutput::AccountInfo, direction) + } + (ConfirmOutput::AccountInfo | ConfirmOutput::CancelTap, SwipeDirection::Right) => { + Decision::Goto(ConfirmOutput::Menu, direction) + } + _ => Decision::Nothing, + } + } + + fn handle_event(&self, msg: FlowMsg) -> Decision { + match (self, msg) { + (_, FlowMsg::Info) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Left), + (ConfirmOutput::Menu, FlowMsg::Choice(0)) => { + Decision::Goto(ConfirmOutput::AccountInfo, SwipeDirection::Left) + } + (ConfirmOutput::Menu, FlowMsg::Choice(1)) => { + Decision::Goto(ConfirmOutput::CancelTap, SwipeDirection::Left) + } + (ConfirmOutput::Menu, FlowMsg::Cancelled) => { + Decision::Goto(ConfirmOutput::Address, SwipeDirection::Right) + } + (ConfirmOutput::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled), + (_, FlowMsg::Cancelled) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Right), + _ => Decision::Nothing, + } + } +} + +use crate::{ + micropython::{map::Map, obj::Obj, util}, + ui::layout::obj::LayoutObj, +}; + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmOutput::new_obj) } +} + +impl ConfirmOutput { + const EXTRA_PADDING: i16 = 6; + + fn new_obj(_args: &[Obj], kwargs: &Map) -> Result { + let title: Option = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?; + let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; + let account_path: Option = + kwargs.get(Qstr::MP_QSTR_account_path)?.try_into_option()?; + + let address: Obj = kwargs.get(Qstr::MP_QSTR_address)?; + let amount: Obj = kwargs.get(Qstr::MP_QSTR_amount)?; + + let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; + let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; + + // Address + let content_address = ConfirmBlobParams::new(TR::words__address.into(), address, None) + .with_subtitle(title) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_chunkify(chunkify) + .with_text_mono(text_mono) + .into_layout()?; + // .one_button_request(ButtonRequestCode::ConfirmOutput, br_type); + + // Amount + let content_amount = ConfirmBlobParams::new(TR::words__amount.into(), amount, None) + .with_subtitle(title) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_text_mono(text_mono) + .into_layout()?; + // .one_button_request(ButtonRequestCode::ConfirmOutput, br_type); + + // Menu + let content_menu = Frame::left_aligned( + "".into(), + VerticalMenu::empty() + .item(theme::ICON_CHEVRON_RIGHT, "Account info".into()) + .danger(theme::ICON_CANCEL, "Cancel sign".into()), + ) + .with_cancel_button() + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + // AccountInfo + let ad = AddressDetails::new(TR::send__send_from.into(), account, account_path)?; + let content_account = SwipePage::horizontal(ad).map(|_| Some(FlowMsg::Cancelled)); + + // CancelTap + let content_cancel_tap = Frame::left_aligned( + TR::send__cancel_sign.into(), + PromptScreen::new_tap_to_cancel(), + ) + .with_cancel_button() + .with_footer(TR::instructions__tap_to_confirm.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + let store = flow_store() + .add(content_address)? + .add(content_amount)? + .add(content_menu)? + .add(content_account)? + .add(content_cancel_tap)?; + let res = SwipeFlow::new(ConfirmOutput::Address, store)?; + Ok(LayoutObj::new(res)?.into()) + } +} diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs new file mode 100644 index 0000000000..2b465394cd --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_summary.rs @@ -0,0 +1,169 @@ +use crate::{ + error, + micropython::{iter::IterBuf, qstr::Qstr}, + strutil::TString, + translations::TR, + ui::{ + component::{ComponentExt, SwipeDirection}, + flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, + }, +}; + +use super::super::{ + component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg}, + theme, +}; + +use super::util::ShowInfoParams; + +#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] +pub enum ConfirmSummary { + Summary, + Hold, + Menu, + FeeInfo, + AccountInfo, + CancelTap, +} + +impl FlowState for ConfirmSummary { + fn handle_swipe(&self, direction: SwipeDirection) -> Decision { + match (self, direction) { + (ConfirmSummary::Summary | ConfirmSummary::Hold, SwipeDirection::Left) => { + Decision::Goto(ConfirmSummary::Menu, direction) + } + (ConfirmSummary::Summary, SwipeDirection::Up) => { + Decision::Goto(ConfirmSummary::Hold, direction) + } + (ConfirmSummary::Hold, SwipeDirection::Down) => { + Decision::Goto(ConfirmSummary::Summary, direction) + } + (ConfirmSummary::Menu, SwipeDirection::Right) => { + Decision::Goto(ConfirmSummary::Summary, direction) + } + (ConfirmSummary::Menu, SwipeDirection::Left) => { + Decision::Goto(ConfirmSummary::FeeInfo, direction) + } + ( + ConfirmSummary::AccountInfo | ConfirmSummary::FeeInfo | ConfirmSummary::CancelTap, + SwipeDirection::Right, + ) => Decision::Goto(ConfirmSummary::Menu, direction), + _ => Decision::Nothing, + } + } + + fn handle_event(&self, msg: FlowMsg) -> Decision { + match (self, msg) { + (_, FlowMsg::Info) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Left), + (ConfirmSummary::Hold, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed), + (ConfirmSummary::Menu, FlowMsg::Choice(0)) => { + Decision::Goto(ConfirmSummary::FeeInfo, SwipeDirection::Left) + } + (ConfirmSummary::Menu, FlowMsg::Choice(1)) => { + Decision::Goto(ConfirmSummary::AccountInfo, SwipeDirection::Left) + } + (ConfirmSummary::Menu, FlowMsg::Choice(2)) => { + Decision::Goto(ConfirmSummary::CancelTap, SwipeDirection::Left) + } + (ConfirmSummary::Menu, FlowMsg::Cancelled) => { + Decision::Goto(ConfirmSummary::Summary, SwipeDirection::Right) + } + (ConfirmSummary::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled), + (_, FlowMsg::Cancelled) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Right), + _ => Decision::Nothing, + } + } +} + +use crate::{ + micropython::{map::Map, obj::Obj, util}, + ui::layout::obj::LayoutObj, +}; + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmSummary::new_obj) } +} + +impl ConfirmSummary { + fn new_obj(_args: &[Obj], kwargs: &Map) -> Result { + let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; + let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + let account_items: Obj = kwargs.get(Qstr::MP_QSTR_account_items)?; + let fee_items: Obj = kwargs.get(Qstr::MP_QSTR_fee_items)?; + + // Summary + let mut summary = ShowInfoParams::new(title) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None); + for pair in IterBuf::new().try_iterate(items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + summary = unwrap!(summary.add(label, value)); + } + let content_summary = summary.into_layout()?; + + // Hold to confirm + let content_hold = Frame::left_aligned( + TR::send__sign_transaction.into(), + PromptScreen::new_hold_to_confirm(), + ) + .with_menu_button() + .with_footer(TR::instructions__hold_to_sign.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + FrameMsg::Button(_) => Some(FlowMsg::Info), + }); + + // Menu + let content_menu = Frame::left_aligned( + "".into(), + VerticalMenu::empty() + .item(theme::ICON_CHEVRON_RIGHT, "Fee info".into()) + .item(theme::ICON_CHEVRON_RIGHT, "Account info".into()) + .danger(theme::ICON_CANCEL, "Cancel sign".into()), + ) + .with_cancel_button() + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + // FeeInfo + let mut fee = ShowInfoParams::new(TR::confirm_total__title_fee.into()).with_cancel_button(); + for pair in IterBuf::new().try_iterate(fee_items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + fee = unwrap!(fee.add(label, value)); + } + let content_fee = fee.into_layout()?; + + // AccountInfo + let mut account = ShowInfoParams::new(TR::send__send_from.into()).with_cancel_button(); + for pair in IterBuf::new().try_iterate(account_items)? { + let [label, value]: [TString; 2] = util::iter_into_array(pair)?; + account = unwrap!(account.add(label, value)); + } + let content_account = account.into_layout()?; + + // CancelTap + let content_cancel_tap = Frame::left_aligned( + TR::send__cancel_sign.into(), + PromptScreen::new_tap_to_cancel(), + ) + .with_cancel_button() + .with_footer(TR::instructions__tap_to_confirm.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); + + let store = flow_store() + .add(content_summary)? + .add(content_hold)? + .add(content_menu)? + .add(content_fee)? + .add(content_account)? + .add(content_cancel_tap)?; + let res = SwipeFlow::new(ConfirmSummary::Summary, store)?; + Ok(LayoutObj::new(res)?.into()) + } +} diff --git a/core/embed/rust/src/ui/model_mercury/flow/mod.rs b/core/embed/rust/src/ui/model_mercury/flow/mod.rs index cf7533821b..ba950ea663 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/mod.rs @@ -1,16 +1,22 @@ pub mod confirm_action; +pub mod confirm_output; pub mod confirm_reset_create; pub mod confirm_reset_recover; pub mod confirm_set_new_pin; +pub mod confirm_summary; pub mod get_address; pub mod prompt_backup; pub mod show_share_words; pub mod warning_hi_prio; pub use confirm_action::new_confirm_action; +mod util; + +pub use confirm_output::new_confirm_output; pub use confirm_reset_create::ConfirmResetCreate; pub use confirm_reset_recover::ConfirmResetRecover; pub use confirm_set_new_pin::SetNewPin; +pub use confirm_summary::new_confirm_summary; pub use get_address::GetAddress; pub use prompt_backup::PromptBackup; pub use show_share_words::ShowShareWords; diff --git a/core/embed/rust/src/ui/model_mercury/flow/util.rs b/core/embed/rust/src/ui/model_mercury/flow/util.rs new file mode 100644 index 0000000000..41c41fa7bc --- /dev/null +++ b/core/embed/rust/src/ui/model_mercury/flow/util.rs @@ -0,0 +1,236 @@ +use super::super::{ + component::{Frame, FrameMsg}, + theme, +}; +use crate::{ + error::Error, + maybe_trace::MaybeTrace, + micropython::obj::Obj, + strutil::TString, + ui::{ + component::{ + base::ComponentExt, + text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt}, + Component, + }, + flow::{FlowMsg, Swipable, SwipePage}, + layout::util::ConfirmBlob, + }, +}; +use heapless::Vec; + +pub struct ConfirmBlobParams { + title: TString<'static>, + subtitle: Option>, + footer_instruction: Option>, + footer_description: Option>, + data: Obj, + description: Option>, + extra: Option>, + menu_button: bool, + chunkify: bool, + text_mono: bool, +} + +impl ConfirmBlobParams { + pub const fn new( + title: TString<'static>, + data: Obj, + description: Option>, + ) -> Self { + Self { + title, + subtitle: None, + footer_instruction: None, + footer_description: None, + data, + description, + extra: None, + menu_button: false, + chunkify: false, + text_mono: true, + } + } + + pub const fn with_extra(mut self, extra: Option>) -> Self { + self.extra = extra; + self + } + + pub const fn with_subtitle(mut self, subtitle: Option>) -> Self { + self.subtitle = subtitle; + self + } + + pub const fn with_menu_button(mut self) -> Self { + self.menu_button = true; + self + } + + pub const fn with_footer( + mut self, + instruction: TString<'static>, + description: Option>, + ) -> Self { + self.footer_instruction = Some(instruction); + self.footer_description = description; + self + } + + pub const fn with_chunkify(mut self, chunkify: bool) -> Self { + self.chunkify = chunkify; + self + } + + pub const fn with_text_mono(mut self, text_mono: bool) -> Self { + self.text_mono = text_mono; + self + } + + pub fn into_layout( + self, + ) -> Result + Swipable + MaybeTrace, Error> { + let paragraphs = ConfirmBlob { + description: self.description.unwrap_or("".into()), + extra: self.extra.unwrap_or("".into()), + data: self.data.try_into()?, + description_font: &theme::TEXT_NORMAL, + extra_font: &theme::TEXT_DEMIBOLD, + data_font: if self.chunkify { + let data: TString = self.data.try_into()?; + theme::get_chunkified_text_style(data.len()) + } else if self.text_mono { + &theme::TEXT_MONO + } else { + &theme::TEXT_NORMAL + }, + } + .into_paragraphs(); + + let page = SwipePage::vertical(paragraphs); + let mut frame = Frame::left_aligned(self.title, page); + if let Some(subtitle) = self.subtitle { + frame = frame.with_subtitle(subtitle); + } + if self.menu_button { + frame = frame.with_menu_button(); + } + if let Some(instruction) = self.footer_instruction { + frame = frame.with_footer(instruction, self.footer_description); + } + Ok(frame.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))) + } +} + +pub struct ShowInfoParams { + title: TString<'static>, + subtitle: Option>, + menu_button: bool, + cancel_button: bool, + footer_instruction: Option>, + footer_description: Option>, + chunkify: bool, + items: Vec<(TString<'static>, TString<'static>), 4>, +} + +impl ShowInfoParams { + pub const fn new(title: TString<'static>) -> Self { + Self { + title, + subtitle: None, + menu_button: false, + cancel_button: false, + footer_instruction: None, + footer_description: None, + chunkify: false, + items: Vec::new(), + } + } + + pub fn add(mut self, key: TString<'static>, value: TString<'static>) -> Option { + if self.items.push((key, value)).is_ok() { + Some(self) + } else { + None + } + } + + pub const fn with_subtitle(mut self, subtitle: Option>) -> Self { + self.subtitle = subtitle; + self + } + + pub const fn with_menu_button(mut self) -> Self { + self.menu_button = true; + self + } + + pub const fn with_cancel_button(mut self) -> Self { + self.cancel_button = true; + self + } + + pub const fn with_footer( + mut self, + instruction: TString<'static>, + description: Option>, + ) -> Self { + self.footer_instruction = Some(instruction); + self.footer_description = description; + self + } + + pub const fn with_chunkify(mut self, chunkify: bool) -> Self { + self.chunkify = chunkify; + self + } + + pub fn into_layout( + self, + ) -> Result + Swipable + MaybeTrace, Error> { + let mut paragraphs = ParagraphVecShort::new(); + let mut first: bool = true; + for item in self.items { + // FIXME: padding: + if !first { + paragraphs.add(Paragraph::new::>( + &theme::TEXT_SUB_GREY, + " ".into(), + )); + } + first = false; + paragraphs.add(Paragraph::new(&theme::TEXT_SUB_GREY, item.0).no_break()); + if self.chunkify { + paragraphs.add(Paragraph::new( + theme::get_chunkified_text_style(item.1.len()), + item.1, + )); + } else { + paragraphs.add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, item.1)); + } + } + + let mut frame = Frame::left_aligned( + self.title, + SwipePage::vertical(paragraphs.into_paragraphs()), + ); + if let Some(subtitle) = self.subtitle { + frame = frame.with_subtitle(subtitle); + } + if self.cancel_button { + frame = frame.with_cancel_button(); + } else if self.menu_button { + frame = frame.with_menu_button(); + } + if let Some(instruction) = self.footer_instruction { + frame = frame.with_footer(instruction, self.footer_description); + } + Ok(frame.map(move |msg| { + matches!(msg, FrameMsg::Button(_)).then_some(if self.cancel_button { + FlowMsg::Cancelled + } else { + FlowMsg::Info + }) + })) + } +} diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 5e498151f0..9cbf6bfbd5 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -893,6 +893,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?; let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; + let action: Option = kwargs.get(Qstr::MP_QSTR_button)?.try_into_option()?; let content = SwipeUpScreen::new(Paragraphs::new([ Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description), @@ -901,8 +902,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map let obj = LayoutObj::new( Frame::left_aligned(title, content) .with_warning_button() - .button_styled(theme::button_warning_low()) - .with_footer(TR::instructions__swipe_up.into(), None), + .with_footer(TR::instructions__swipe_up.into(), action), )?; Ok(obj.into()) }; @@ -1992,6 +1992,28 @@ pub static mp_module_trezorui2: Module = obj_module! { /// ) -> LayoutObj[UiResult]: /// """Warning modal with multiple steps to confirm.""" Qstr::MP_QSTR_flow_warning_hi_prio => obj_fn_kw!(0, flow::warning_hi_prio::new_warning_hi_prio).as_obj(), + + /// def flow_confirm_output( + /// *, + /// title: str | None, + /// address: str, + /// amount: str, + /// chunkify: bool, + /// account: str | None, + /// account_path: str | None, + /// ) -> LayoutObj[UiResult]: + /// """Confirm recipient.""" + Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, flow::new_confirm_output).as_obj(), + + /// def flow_confirm_summary( + /// *, + /// title: str, + /// items: Iterable[tuple[str, str]], + /// account_items: Iterable[tuple[str, str]], + /// fee_items: Iterable[tuple[str, str]], + /// ) -> LayoutObj[UiResult]: + /// """Total summary and hold to confirm.""" + Qstr::MP_QSTR_flow_confirm_summary => obj_fn_kw!(0, flow::new_confirm_summary).as_obj(), }; #[cfg(test)] diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 4749e61a3f..bc56bd6570 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -658,7 +658,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma .newline() .newline() .newline_half() - .text_bold(TR::confirm_total__fee_rate) + .text_bold(TR::confirm_total__fee_rate_colon) .newline() .text_mono(fee_rate_amount); diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 97f0eef915..94e8f6b6f0 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -549,6 +549,30 @@ def flow_warning_hi_prio( value: str = "", ) -> LayoutObj[UiResult]: """Warning modal with multiple steps to confirm.""" + + +# rust/src/ui/model_mercury/layout.rs +def flow_confirm_output( + *, + title: str | None, + address: str, + amount: str, + chunkify: bool, + account: str | None, + account_path: str | None, +) -> LayoutObj[UiResult]: + """Confirm recipient.""" + + +# rust/src/ui/model_mercury/layout.rs +def flow_confirm_summary( + *, + title: str, + items: Iterable[tuple[str, str]], + account_items: Iterable[tuple[str, str]], + fee_items: Iterable[tuple[str, str]], +) -> LayoutObj[UiResult]: + """Total summary and hold to confirm.""" CONFIRMED: UiResult CANCELLED: UiResult INFO: UiResult diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index f0c83db410..3e9bb6e33d 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -235,9 +235,10 @@ class TR: coinjoin__title_do_not_disconnect: str = "Do not disconnect your trezor!" coinjoin__title_progress: str = "Coinjoin in progress" coinjoin__waiting_for_others: str = "Waiting for others" - confirm_total__fee_rate: str = "Fee rate:" + confirm_total__fee_rate: str = "Fee rate" + confirm_total__fee_rate_colon: str = "Fee rate:" confirm_total__sending_from_account: str = "Sending from account:" - confirm_total__title_fee: str = "Fee information" + confirm_total__title_fee: str = "Fee info" confirm_total__title_sending_from: str = "Sending from" debug__loading_seed: str = "Loading seed" debug__loading_seed_not_recommended: str = "Loading private seed is not recommended." @@ -275,7 +276,6 @@ class TR: eos__requirement: str = "Requirement:" eos__sell_ram: str = "Sell RAM" eos__sender: str = "Sender:" - eos__sign_transaction: str = "Sign transaction" eos__threshold: str = "Threshold:" eos__to: str = "To:" eos__transfer: str = "Transfer:" @@ -364,6 +364,7 @@ class TR: inputs__space: str = "SPACE" instructions__continue_in_app: str = "Continue in the app" instructions__hold_to_confirm: str = "Hold to confirm" + instructions__hold_to_sign: str = "Hold to sign" instructions__swipe_up: str = "Swipe up" instructions__tap_to_confirm: str = "Tap to confirm" joint__title: str = "Joint transaction" @@ -675,11 +676,15 @@ class TR: sd_card__wanna_format: str = "Do you really want to format the SD card?" sd_card__wrong_sd_card: str = "Wrong SD card." send__address_path: str = "address path" + send__cancel_sign: str = "Cancel sign" send__confirm_sending: str = "Sending amount" send__from_multiple_accounts: str = "Sending from multiple accounts." + send__incl_transaction_fee: str = "incl. Transaction fee" send__including_fee: str = "Including fee:" send__maximum_fee: str = "Maximum fee:" send__receiving_to_multisig: str = "Receiving to a multisig address." + send__send_from: str = "Send from" + send__sign_transaction: str = "Sign transaction" send__title_confirm_sending: str = "Confirm sending" send__title_joint_transaction: str = "Joint transaction" send__title_receiving_to: str = "Receiving to" @@ -687,7 +692,8 @@ class TR: send__title_sending_amount: str = "Sending amount" send__title_sending_to: str = "Sending to" send__to_the_total_amount: str = "To the total amount:" - send__total_amount: str = "Total amount:" + send__total_amount: str = "Total amount" + send__total_amount_colon: str = "Total amount:" send__transaction_id: str = "Transaction ID:" send__you_are_contributing: str = "You are contributing:" share_words__words_in_order: str = " words in order." diff --git a/core/src/apps/bitcoin/sign_tx/approvers.py b/core/src/apps/bitcoin/sign_tx/approvers.py index bdbadf8042..38078478ba 100644 --- a/core/src/apps/bitcoin/sign_tx/approvers.py +++ b/core/src/apps/bitcoin/sign_tx/approvers.py @@ -121,6 +121,7 @@ class Approver: self, txo: TxOutput, script_pubkey: bytes, + tx_info: TxInfo | None, orig_txo: TxOutput | None = None, ) -> None: await self._add_output(txo, script_pubkey) @@ -193,11 +194,12 @@ class BasicApprover(Approver): self, txo: TxOutput, script_pubkey: bytes, + tx_info: TxInfo | None, orig_txo: TxOutput | None = None, ) -> None: from trezor.enums import OutputScriptType - await super().add_external_output(txo, script_pubkey, orig_txo) + await super().add_external_output(txo, script_pubkey, tx_info, orig_txo) if orig_txo: if txo.amount < orig_txo.amount: @@ -230,6 +232,7 @@ class BasicApprover(Approver): "Adding new OP_RETURN outputs in replacement transactions is not supported." ) elif txo.payment_req_index is None or self.show_payment_req_details: + source_path = tx_info.wallet_path.get_path() if tx_info else None # Ask user to confirm output, unless it is part of a payment # request, which gets confirmed separately. await helpers.confirm_output( @@ -238,6 +241,7 @@ class BasicApprover(Approver): self.amount_unit, self.external_output_index, self.chunkify, + source_path, ) self.external_output_index += 1 diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index ec31da941e..08c3aea391 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -530,7 +530,9 @@ class Bitcoin: # Output is change and does not need approval. await approver.add_change_output(txo, script_pubkey) else: - await approver.add_external_output(txo, script_pubkey, orig_txo) + await approver.add_external_output( + txo, script_pubkey, self.tx_info, orig_txo + ) self.tx_info.add_output(txo, script_pubkey) diff --git a/core/src/apps/bitcoin/sign_tx/decred.py b/core/src/apps/bitcoin/sign_tx/decred.py index 5ba638d99f..6c842f38ba 100644 --- a/core/src/apps/bitcoin/sign_tx/decred.py +++ b/core/src/apps/bitcoin/sign_tx/decred.py @@ -92,7 +92,9 @@ class DecredApprover(BasicApprover): ) -> None: # NOTE: The following calls Approver.add_external_output(), not BasicApprover.add_external_output(). # This is needed to skip calling helpers.confirm_output(), which is what BasicApprover would do. - await super(BasicApprover, self).add_external_output(txo, script_pubkey, None) + await super(BasicApprover, self).add_external_output( + txo, script_pubkey, None, None + ) await helpers.confirm_decred_sstx_submission(txo, self.coin, self.amount_unit) diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index a27ef12387..5fa6f0a8a5 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -45,12 +45,14 @@ class UiConfirmOutput(UiConfirm): amount_unit: AmountUnit, output_index: int, chunkify: bool, + address_n: Bip32Path | None, ): self.output = output self.coin = coin self.amount_unit = amount_unit self.output_index = output_index self.chunkify = chunkify + self.address_n = address_n def confirm_dialog(self) -> Awaitable[Any]: return layout.confirm_output( @@ -59,6 +61,7 @@ class UiConfirmOutput(UiConfirm): self.amount_unit, self.output_index, self.chunkify, + self.address_n, ) @@ -241,8 +244,12 @@ class UiConfirmMultipleAccounts(UiConfirm): return layout.confirm_multiple_accounts() -def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool) -> Awaitable[None]: # type: ignore [awaitable-return-type] - return (yield UiConfirmOutput(output, coin, amount_unit, output_index, chunkify)) # type: ignore [awaitable-return-type] +def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool, address_n: Bip32Path | None) -> Awaitable[None]: # type: ignore [awaitable-return-type] + return ( + yield UiConfirmOutput( # type: ignore [awaitable-return-type] + output, coin, amount_unit, output_index, chunkify, address_n + ) + ) def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-return-type] diff --git a/core/src/apps/bitcoin/sign_tx/layout.py b/core/src/apps/bitcoin/sign_tx/layout.py index fdb55141ef..cf4044cc8a 100644 --- a/core/src/apps/bitcoin/sign_tx/layout.py +++ b/core/src/apps/bitcoin/sign_tx/layout.py @@ -62,6 +62,7 @@ async def confirm_output( amount_unit: AmountUnit, output_index: int, chunkify: bool, + address_n: Bip32Path | None, ) -> None: from trezor.enums import OutputScriptType @@ -119,6 +120,8 @@ async def confirm_output( address_label=address_label, output_index=output_index, chunkify=chunkify, + source_account=account_label(coin, address_n), + source_account_path=address_n_to_str(address_n) if address_n else None, ) await layout @@ -245,7 +248,8 @@ async def confirm_total( format_coin_amount(spending, coin, amount_unit), format_coin_amount(fee, coin, amount_unit), fee_rate_amount=format_fee_rate(fee_rate, coin) if fee_rate >= 0 else None, - account_label=account_label(coin, address_n), + source_account=account_label(coin, address_n), + source_account_path=address_n_to_str(address_n) if address_n else None, ) diff --git a/core/src/apps/eos/layout.py b/core/src/apps/eos/layout.py index dc12df31c2..994394f0e8 100644 --- a/core/src/apps/eos/layout.py +++ b/core/src/apps/eos/layout.py @@ -14,7 +14,7 @@ async def require_sign_tx(num_actions: int) -> None: await confirm_action( "confirm_tx", - TR.eos__sign_transaction, + TR.send__sign_transaction, description=TR.eos__about_to_sign_template, description_param=format_plural( "{count} {plural}", num_actions, TR.plurals__sign_x_actions diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index fc6c3f6e44..c150b7fff1 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -187,7 +187,7 @@ class RustLayout(ui.Layout): # Turn the brightness on again. ui.backlight_fade(self.BACKLIGHT_LEVEL) - def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator] + def handle_input_and_rendering(self) -> loop.Task: from trezor import workflow touch = loop.wait(io.TOUCH) @@ -203,7 +203,7 @@ class RustLayout(ui.Layout): raise ui.Result(msg) self._paint() - def handle_timers(self) -> loop.Task: # type: ignore [awaitable-is-generator] + def handle_timers(self) -> loop.Task: while True: # Using `yield` instead of `await` to avoid allocations. token = yield self.timer @@ -499,9 +499,9 @@ async def show_warning( interact( RustLayout( trezorui2.show_warning( - title=content, - description=subheader or "", - button=button, + title=TR.words__important, + value=content, + button=subheader or TR.words__continue_anyway, ) ), br_type, @@ -539,58 +539,35 @@ async def confirm_output( address_label: str | None = None, output_index: int | None = None, chunkify: bool = False, + source_account: str | None = None, + source_account_path: str | None = None, ) -> None: - if title is not None: - # TODO: handle translation - if title.upper().startswith("CONFIRM "): - title = title[len("CONFIRM ") :] - amount_title = title - recipient_title = title + if address_label is not None: + title = address_label + elif title is not None: + pass elif output_index is not None: - amount_title = f"{TR.words__amount} #{output_index + 1}" - recipient_title = f"{TR.words__recipient} #{output_index + 1}" + title = f"{TR.words__recipient} #{output_index + 1}" else: - amount_title = TR.send__confirm_sending - recipient_title = TR.send__title_sending_to + title = TR.send__title_sending_to - while True: - result = await interact( + # TODO: this should send 2x ButtonRequest + await raise_if_not_confirmed( + interact( RustLayout( - trezorui2.confirm_value( - title=recipient_title, - subtitle=address_label, - description=None, - value=address, - verb=TR.buttons__continue, - hold=False, - info_button=False, + trezorui2.flow_confirm_output( + address=address, + amount=amount, + title=title, chunkify=chunkify, + account=source_account, + account_path=source_account_path, ) ), "confirm_output", br_code, ) - if result is not CONFIRMED: - raise ActionCancelled - - result = await interact( - RustLayout( - trezorui2.confirm_value( - title=amount_title, - subtitle=None, - description=None, - value=amount, - verb=None if hold else TR.buttons__confirm, - verb_cancel="^", - hold=hold, - info_button=False, - ) - ), - "confirm_output", - br_code, - ) - if result is CONFIRMED: - return + ) async def should_show_payment_request_details( @@ -627,7 +604,7 @@ async def should_show_payment_request_details( async def should_show_more( title: str, - para: Iterable[tuple[int, str]], + para: Iterable[tuple[int, str | bytes]], button_text: str | None = None, br_type: str = "should_show_more", br_code: ButtonRequestType = BR_TYPE_OTHER, @@ -876,31 +853,42 @@ async def confirm_total( title: str | None = None, total_label: str | None = None, fee_label: str | None = None, - account_label: str | None = None, + source_account: str | None = None, + source_account_path: str | None = None, fee_rate_amount: str | None = None, br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> None: title = title or TR.words__title_summary # def_arg total_label = total_label or TR.send__total_amount # def_arg - fee_label = fee_label or TR.send__including_fee # def_arg + fee_label = fee_label or TR.send__incl_transaction_fee # def_arg items = [ (total_label, total_amount), (fee_label, fee_amount), ] - info_items = [] - if account_label: - info_items.append((TR.confirm_total__sending_from_account, account_label)) + fee_items = [] + account_items = [] + if source_account: + account_items.append((TR.confirm_total__sending_from_account, source_account)) + if source_account_path: + account_items.append((TR.address_details__derivation_path, source_account_path)) if fee_rate_amount: - info_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) + fee_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) - await confirm_summary( - items, - TR.words__title_summary, - info_items=info_items, - br_type=br_type, - br_code=br_code, + await raise_if_not_confirmed( + interact( + RustLayout( + trezorui2.flow_confirm_summary( + title=title, + items=items, + fee_items=fee_items, + account_items=account_items, + ) + ), + br_type, + br_code, + ) ) @@ -912,23 +900,21 @@ async def confirm_summary( br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> None: + # TODO: info_title title = title or TR.words__title_summary # def_arg - total_layout = RustLayout( - trezorui2.confirm_total( - title=title, - items=items, - info_button=bool(info_items), + await raise_if_not_confirmed( + RustLayout( + trezorui2.flow_confirm_summary( + title=title, + items=items or (), + fee_items=(), + account_items=info_items or (), + ) ) + # TODO br_type, + # TODO br_code, ) - info_items = info_items or [] - info_layout = RustLayout( - trezorui2.show_info_with_cancel( - title=info_title if info_title else TR.words__title_information, - items=info_items, - ) - ) - await raise_if_not_confirmed(with_info(total_layout, info_layout, br_type, br_code)) if not utils.BITCOIN_ONLY: @@ -1046,20 +1032,14 @@ if not utils.BITCOIN_ONLY: async def confirm_joint_total(spending_amount: str, total_amount: str) -> None: - await raise_if_not_confirmed( - interact( - RustLayout( - trezorui2.confirm_total( - title=TR.send__title_joint_transaction, - items=[ - (TR.send__you_are_contributing, spending_amount), - (TR.send__to_the_total_amount, total_amount), - ], - ) - ), - "confirm_joint_total", - ButtonRequestType.SignTx, - ) + await confirm_summary( + items=[ + (TR.send__you_are_contributing, spending_amount), + (TR.send__to_the_total_amount, total_amount), + ], + title=TR.send__title_joint_transaction, + br_type="confirm_joint_total", + br_code=ButtonRequestType.SignTx, ) diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index c3de1166d1..8101160d52 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -734,6 +734,8 @@ async def confirm_output( address_label: str | None = None, output_index: int | None = None, chunkify: bool = False, + source_account: str | None = None, # ignored on safe 3 + source_account_path: str | None = None, # ignored on safe 3 ) -> None: title = title or TR.send__confirm_sending # def_arg address_title = TR.words__recipient @@ -1096,11 +1098,12 @@ def confirm_total( title: str | None = None, total_label: str | None = None, fee_label: str | None = None, - account_label: str | None = None, + source_account: str | None = None, + source_account_path: str | None = None, br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> Awaitable[None]: - total_label = total_label or TR.send__total_amount # def_arg + total_label = total_label or TR.send__total_amount_colon # def_arg fee_label = fee_label or TR.send__including_fee # def_arg return raise_if_not_confirmed( interact( @@ -1110,7 +1113,7 @@ def confirm_total( total_amount=total_amount, # type: ignore [No parameter named] fee_amount=fee_amount, # type: ignore [No parameter named] fee_rate_amount=fee_rate_amount, # type: ignore [No parameter named] - account_label=account_label, # type: ignore [No parameter named] + account_label=source_account, # type: ignore [No parameter named] total_label=total_label, # type: ignore [No parameter named] fee_label=fee_label, # type: ignore [No parameter named] ) diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index 0735cffcd9..aa954e13c9 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -641,6 +641,8 @@ async def confirm_output( address_label: str | None = None, output_index: int | None = None, chunkify: bool = False, + source_account: str | None = None, # ignored on model t + source_account_path: str | None = None, # ignored on model t ) -> None: if title is not None: # TODO: handle translation: @@ -978,13 +980,14 @@ def confirm_total( title: str | None = None, total_label: str | None = None, fee_label: str | None = None, - account_label: str | None = None, + source_account: str | None = None, + source_account_path: str | None = None, fee_rate_amount: str | None = None, br_type: str = "confirm_total", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> Awaitable[None]: title = title or TR.words__title_summary # def_arg - total_label = total_label or TR.send__total_amount # def_arg + total_label = total_label or TR.send__total_amount_colon # def_arg fee_label = fee_label or TR.send__including_fee # def_arg items = [ @@ -992,10 +995,10 @@ def confirm_total( (fee_label, fee_amount), ] info_items = [] - if account_label: - info_items.append((TR.confirm_total__sending_from_account, account_label)) + if source_account: + info_items.append((TR.confirm_total__sending_from_account, source_account)) if fee_rate_amount: - info_items.append((TR.confirm_total__fee_rate, fee_rate_amount)) + info_items.append((TR.confirm_total__fee_rate_colon, fee_rate_amount)) return confirm_summary( items, diff --git a/core/tests/test_apps.bitcoin.approver.py b/core/tests/test_apps.bitcoin.approver.py index fbbee64d6b..41a2a98e33 100644 --- a/core/tests/test_apps.bitcoin.approver.py +++ b/core/tests/test_apps.bitcoin.approver.py @@ -175,6 +175,7 @@ class TestApprover(unittest.TestCase): authorization = CoinJoinAuthorization(self.msg_auth) approver = CoinJoinApprover(tx, self.coin, authorization) signer = Bitcoin(tx, None, self.coin, approver) + tx_info = TxInfo(signer, tx) for txi in inputs: if txi.script_type == InputScriptType.EXTERNAL: @@ -186,9 +187,9 @@ class TestApprover(unittest.TestCase): if txo.address_n: await_result(approver.add_change_output(txo, script_pubkey=bytes(22))) else: - await_result(approver.add_external_output(txo, script_pubkey=bytes(22))) + await_result(approver.add_external_output(txo, script_pubkey=bytes(22), tx_info=tx_info)) - await_result(approver.approve_tx(TxInfo(signer, tx), [], None)) + await_result(approver.approve_tx(tx_info, [], None)) def test_coinjoin_input_account_depth_mismatch(self): txi = TxInput( diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py index cd58acee78..c7f0e42cd6 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh.py @@ -112,7 +112,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -120,7 +120,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]), True, helpers.UiConfirmTotal( 12300000, 11000, fee_rate, coin, AmountUnit.BITCOIN, inp1.address_n[:3] @@ -309,7 +309,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py index 0294a41f59..6860dd7977 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.native_p2wpkh_grs.py @@ -123,7 +123,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(84), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -131,7 +131,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(84), H_(1), H_(0)]), True, helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False), True, @@ -332,7 +332,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(84), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py index 228f4a8011..8e0f592871 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh.py @@ -114,7 +114,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -122,7 +122,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]), True, helpers.UiConfirmTotal( 123445789 + 11000, @@ -317,7 +317,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -531,7 +531,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py index 7e45cf8a94..0abf34cacb 100644 --- a/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py +++ b/core/tests/test_apps.bitcoin.segwit.signtx.p2wpkh_in_p2sh_grs.py @@ -123,7 +123,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, @@ -131,7 +131,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out2)), - helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False), + helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]), True, helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False), True, @@ -336,7 +336,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]), True, TxRequest( request_type=TXOUTPUT, diff --git a/core/tests/test_apps.bitcoin.signtx.fee_threshold.py b/core/tests/test_apps.bitcoin.signtx.fee_threshold.py index e5e0320bb4..9aebc7b07a 100644 --- a/core/tests/test_apps.bitcoin.signtx.fee_threshold.py +++ b/core/tests/test_apps.bitcoin.signtx.fee_threshold.py @@ -180,7 +180,7 @@ class TestSignTxFeeThreshold(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False, None), True, helpers.UiConfirmMultipleAccounts(), True, diff --git a/core/tests/test_apps.bitcoin.signtx.py b/core/tests/test_apps.bitcoin.signtx.py index e9e094696b..1d8a0339a6 100644 --- a/core/tests/test_apps.bitcoin.signtx.py +++ b/core/tests/test_apps.bitcoin.signtx.py @@ -111,7 +111,7 @@ class TestSignTx(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False, [H_(44), H_(0), H_(0)]), True, helpers.UiConfirmTotal( 3_801_747, diff --git a/core/tests/test_apps.bitcoin.signtx_decred.py b/core/tests/test_apps.bitcoin.signtx_decred.py index a8f4fc7e0d..490538e0b8 100644 --- a/core/tests/test_apps.bitcoin.signtx_decred.py +++ b/core/tests/test_apps.bitcoin.signtx_decred.py @@ -112,7 +112,7 @@ class TestSignTxDecred(unittest.TestCase): ), ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN, 0, False, [H_(44), H_(1), H_(0)]), True, helpers.UiConfirmTotal( 200_000_000, diff --git a/core/tests/test_apps.bitcoin.signtx_grs.py b/core/tests/test_apps.bitcoin.signtx_grs.py index 573e3017ff..8fd6e7cd0d 100644 --- a/core/tests/test_apps.bitcoin.signtx_grs.py +++ b/core/tests/test_apps.bitcoin.signtx_grs.py @@ -112,7 +112,7 @@ class TestSignTx_GRS(unittest.TestCase): serialized=EMPTY_SERIALIZED, ), TxAckOutput(tx=TxAckOutputWrapper(output=out1)), - helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False), + helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(44), H_(17), H_(0)]), True, helpers.UiConfirmTotal( 210016, 192, fee_rate, coin, AmountUnit.BITCOIN, inp1.address_n[:3] diff --git a/core/tools/translations/rules.json b/core/tools/translations/rules.json index 5893eebdaa..7b87a61fcd 100644 --- a/core/tools/translations/rules.json +++ b/core/tools/translations/rules.json @@ -219,7 +219,7 @@ "coinjoin__title_do_not_disconnect": "title,2", "coinjoin__title_progress": "title,1", "coinjoin__waiting_for_others": "text,1", - "confirm_total__fee_rate": "text,1", + "confirm_total__fee_rate_colon": "text,1", "confirm_total__sending_from_account": "text,1", "confirm_total__title_fee": "title,1", "confirm_total__title_sending_from": "title,1", @@ -259,7 +259,7 @@ "eos__requirement": "text,1", "eos__sell_ram": "text,1", "eos__sender": "text,1", - "eos__sign_transaction": "text,1", + "send__sign_transaction": "text,1", "eos__threshold": "text,1", "eos__to": "text,1", "eos__transfer": "text,1", @@ -654,7 +654,7 @@ "send__title_sending_amount": "title,1", "send__title_sending_to": "title,1", "send__to_the_total_amount": "text,1", - "send__total_amount": "text,1", + "send__total_amount_colon": "text,1", "send__transaction_id": "text,1", "send__you_are_contributing": "text,1", "share_words__words_in_order": "text,2", diff --git a/core/translations/cs.json b/core/translations/cs.json index 5e0605513b..c2beae0e28 100644 --- a/core/translations/cs.json +++ b/core/translations/cs.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "Neodpojujte trezor!", "coinjoin__title_progress": "Probíhá coinjoin", "coinjoin__waiting_for_others": "Čeká se na ostatní", - "confirm_total__fee_rate": "Výše poplatku:", + "confirm_total__fee_rate": "Výše poplatku", + "confirm_total__fee_rate_colon": "Výše poplatku:", "confirm_total__sending_from_account": "Odeslání z účtu:", "confirm_total__title_fee": "Info o poplatcích", "confirm_total__title_sending_from": "Odeslání z", @@ -297,7 +298,7 @@ "eos__requirement": "Požadavek:", "eos__sell_ram": "Prodat RAM", "eos__sender": "Odesílatel:", - "eos__sign_transaction": "Podepsat transakci", + "send__sign_transaction": "Podepsat transakci", "eos__threshold": "Počet částí zálohy pro obnovu:", "eos__to": "Komu:", "eos__transfer": "Převod:", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Odesílání částky", "send__title_sending_to": "Odesílání", "send__to_the_total_amount": "Do celkové částky:", - "send__total_amount": "Celková částka:", + "send__total_amount": "Celková částka", + "send__total_amount_colon": "Celková částka:", "send__transaction_id": "ID transakce:", "send__you_are_contributing": "Přispíváte:", "share_words__words_in_order": " slova v pořadí.", diff --git a/core/translations/de.json b/core/translations/de.json index 6fca28d28f..683e5273c5 100644 --- a/core/translations/de.json +++ b/core/translations/de.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "Trenne deinen trezor nicht!", "coinjoin__title_progress": "Coinjoin läuft", "coinjoin__waiting_for_others": "Auf andere warten", - "confirm_total__fee_rate": "Gebührensatz:", + "confirm_total__fee_rate": "Gebührensatz", + "confirm_total__fee_rate_colon": "Gebührensatz:", "confirm_total__sending_from_account": "Gesendet von Konto:", "confirm_total__title_fee": "Gebühren-info", "confirm_total__title_sending_from": "Gesendet von", @@ -297,7 +298,7 @@ "eos__requirement": "Anforderung:", "eos__sell_ram": "RAM verkaufen", "eos__sender": "Sender:", - "eos__sign_transaction": "Transaktion signieren", + "send__sign_transaction": "Transaktion signieren", "eos__threshold": "Schwelle:", "eos__to": "An:", "eos__transfer": "Überweisung:", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Betrag senden", "send__title_sending_to": "Senden an", "send__to_the_total_amount": "Gesamtbetrag:", - "send__total_amount": "Gesamtbetrag:", + "send__total_amount": "Gesamtbetrag", + "send__total_amount_colon": "Gesamtbetrag:", "send__transaction_id": "Transaktions-ID:", "send__you_are_contributing": "Dein Anteil:", "share_words__words_in_order": " Wörter der Reihe nach notiert.", diff --git a/core/translations/en.json b/core/translations/en.json index adca071828..63c9f4b939 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -237,9 +237,10 @@ "coinjoin__title_do_not_disconnect": "Do not disconnect your trezor!", "coinjoin__title_progress": "Coinjoin in progress", "coinjoin__waiting_for_others": "Waiting for others", - "confirm_total__fee_rate": "Fee rate:", + "confirm_total__fee_rate": "Fee rate", + "confirm_total__fee_rate_colon": "Fee rate:", "confirm_total__sending_from_account": "Sending from account:", - "confirm_total__title_fee": "Fee information", + "confirm_total__title_fee": "Fee info", "confirm_total__title_sending_from": "Sending from", "debug__loading_seed": "Loading seed", "debug__loading_seed_not_recommended": "Loading private seed is not recommended.", @@ -277,7 +278,6 @@ "eos__requirement": "Requirement:", "eos__sell_ram": "Sell RAM", "eos__sender": "Sender:", - "eos__sign_transaction": "Sign transaction", "eos__threshold": "Threshold:", "eos__to": "To:", "eos__transfer": "Transfer:", @@ -365,9 +365,10 @@ "inputs__show": "SHOW", "inputs__space": "SPACE", "instructions__continue_in_app": "Continue in the app", + "instructions__hold_to_confirm": "Hold to confirm", + "instructions__hold_to_sign": "Hold to sign", "instructions__swipe_up": "Swipe up", "instructions__tap_to_confirm": "Tap to confirm", - "instructions__hold_to_confirm": "Hold to confirm", "joint__title": "Joint transaction", "joint__to_the_total_amount": "To the total amount:", "joint__you_are_contributing": "You are contributing:", @@ -677,11 +678,15 @@ "sd_card__wanna_format": "Do you really want to format the SD card?", "sd_card__wrong_sd_card": "Wrong SD card.", "send__address_path": "address path", + "send__cancel_sign": "Cancel sign", "send__confirm_sending": "Sending amount", "send__from_multiple_accounts": "Sending from multiple accounts.", + "send__incl_transaction_fee": "incl. Transaction fee", "send__including_fee": "Including fee:", "send__maximum_fee": "Maximum fee:", "send__receiving_to_multisig": "Receiving to a multisig address.", + "send__send_from": "Send from", + "send__sign_transaction": "Sign transaction", "send__title_confirm_sending": "Confirm sending", "send__title_joint_transaction": "Joint transaction", "send__title_receiving_to": "Receiving to", @@ -689,7 +694,8 @@ "send__title_sending_amount": "Sending amount", "send__title_sending_to": "Sending to", "send__to_the_total_amount": "To the total amount:", - "send__total_amount": "Total amount:", + "send__total_amount": "Total amount", + "send__total_amount_colon": "Total amount:", "send__transaction_id": "Transaction ID:", "send__you_are_contributing": "You are contributing:", "share_words__words_in_order": " words in order.", diff --git a/core/translations/es.json b/core/translations/es.json index 4f57043244..fb42195c67 100644 --- a/core/translations/es.json +++ b/core/translations/es.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "¡No desconectes el trezor!", "coinjoin__title_progress": "Coinjoin en curso", "coinjoin__waiting_for_others": "Esperando a los demás", - "confirm_total__fee_rate": "Comisión:", + "confirm_total__fee_rate": "Comisión", + "confirm_total__fee_rate_colon": "Comisión:", "confirm_total__sending_from_account": "Envío desde cuenta:", "confirm_total__title_fee": "Info. comisión", "confirm_total__title_sending_from": "Envío desde", @@ -297,7 +298,7 @@ "eos__requirement": "Requisito:", "eos__sell_ram": "Vender RAM", "eos__sender": "Remitente:", - "eos__sign_transaction": "Firmar transacción", + "send__sign_transaction": "Firmar transacción", "eos__threshold": "Umbral:", "eos__to": "Para:", "eos__transfer": "Transferencia:", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Importe envío", "send__title_sending_to": "Envío a", "send__to_the_total_amount": "Al importe total:", - "send__total_amount": "Importe total:", + "send__total_amount": "Importe total", + "send__total_amount_colon": "Importe total:", "send__transaction_id": "ID de la transacción:", "send__you_are_contributing": "Estás aportando:", "share_words__words_in_order": " palabras en orden.", diff --git a/core/translations/fr.json b/core/translations/fr.json index 2a33588dfc..8ce8d483e5 100644 --- a/core/translations/fr.json +++ b/core/translations/fr.json @@ -257,7 +257,8 @@ "coinjoin__title_do_not_disconnect": "Ne déconnectez pas votre trezor !", "coinjoin__title_progress": "Coinjoin en cours", "coinjoin__waiting_for_others": "En attente des autres", - "confirm_total__fee_rate": "Taux des frais :", + "confirm_total__fee_rate": "Taux des frais", + "confirm_total__fee_rate_colon": "Taux des frais :", "confirm_total__sending_from_account": "Compte d'envoi :", "confirm_total__title_fee": "Infos sur les frais", "confirm_total__title_sending_from": "Envoi depuis", @@ -297,7 +298,7 @@ "eos__requirement": "Exigence :", "eos__sell_ram": "Vendre de la RAM", "eos__sender": "Expéditeur :", - "eos__sign_transaction": "Signer la transaction", + "send__sign_transaction": "Signer la transaction", "eos__threshold": "Seuil :", "eos__to": "À :", "eos__transfer": "Transfert :", @@ -699,7 +700,8 @@ "send__title_sending_amount": "Montant de l'envoi", "send__title_sending_to": "Envoi à", "send__to_the_total_amount": "Au montant total :", - "send__total_amount": "Montant total :", + "send__total_amount": "Montant total", + "send__total_amount_colon": "Montant total :", "send__transaction_id": "ID de transaction :", "send__you_are_contributing": "Votre contribution :", "share_words__words_in_order": " mots dans l'ordre.", diff --git a/core/translations/order.json b/core/translations/order.json index 6d15f658a3..c24bf027a4 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -219,7 +219,7 @@ "217": "coinjoin__title_do_not_disconnect", "218": "coinjoin__title_progress", "219": "coinjoin__waiting_for_others", - "220": "confirm_total__fee_rate", + "220": "confirm_total__fee_rate_colon", "221": "confirm_total__sending_from_account", "222": "confirm_total__title_fee", "223": "confirm_total__title_sending_from", @@ -259,7 +259,7 @@ "257": "eos__requirement", "258": "eos__sell_ram", "259": "eos__sender", - "260": "eos__sign_transaction", + "260": "send__sign_transaction", "261": "eos__threshold", "262": "eos__to", "263": "eos__transfer", @@ -652,7 +652,7 @@ "650": "send__title_sending_amount", "651": "send__title_sending_to", "652": "send__to_the_total_amount", - "653": "send__total_amount", + "653": "send__total_amount_colon", "654": "send__transaction_id", "655": "send__you_are_contributing", "656": "share_words__words_in_order", @@ -871,5 +871,11 @@ "869": "address__confirmed", "870": "pin__cancel_description", "871": "pin__cancel_info", - "872": "pin__cancel_setup" + "872": "pin__cancel_setup", + "873": "send__cancel_sign", + "874": "send__send_from", + "875": "instructions__hold_to_sign", + "876": "confirm_total__fee_rate", + "877": "send__incl_transaction_fee", + "878": "send__total_amount" } diff --git a/core/translations/signatures.json b/core/translations/signatures.json index 2e93c3afb8..626bf5fa71 100644 --- a/core/translations/signatures.json +++ b/core/translations/signatures.json @@ -1,8 +1,8 @@ { "current": { - "merkle_root": "928438526b993d433d52359d86099848d570c13fbe6aac72a2f5a29a4e8e94c5", - "datetime": "2024-05-17T10:55:04.124405", - "commit": "1409ed27df07827a2a3e3756420db1b41fe108e5" + "merkle_root": "329b06dbf2564bf17ba46d2b3304f91df15abc42794763a55ea33bc04281ac42", + "datetime": "2024-05-20T15:37:23.831427", + "commit": "2a5dc6f8d54701e86c32451fb154d3e40a778ca9" }, "history": [ {