1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 12:28:09 +00:00

feat(core/ethereum): new ETH contract flow

This commit is contained in:
Ioan Bizău 2024-10-18 12:25:56 +02:00 committed by Ioan Bizău
parent 0bacbdf355
commit c300576d6c
43 changed files with 2048 additions and 1423 deletions

View File

@ -0,0 +1 @@
New EVM call contract flow.

View File

@ -129,6 +129,7 @@ static void _librust_qstrs(void) {
MP_QSTR_button; MP_QSTR_button;
MP_QSTR_button_event; MP_QSTR_button_event;
MP_QSTR_button_request; MP_QSTR_button_request;
MP_QSTR_button_style_confirm;
MP_QSTR_buttons__abort; MP_QSTR_buttons__abort;
MP_QSTR_buttons__access; MP_QSTR_buttons__access;
MP_QSTR_buttons__again; MP_QSTR_buttons__again;
@ -167,6 +168,7 @@ static void _librust_qstrs(void) {
MP_QSTR_buttons__try_again; MP_QSTR_buttons__try_again;
MP_QSTR_buttons__turn_off; MP_QSTR_buttons__turn_off;
MP_QSTR_buttons__turn_on; MP_QSTR_buttons__turn_on;
MP_QSTR_buttons__view_all_data;
MP_QSTR_can_go_back; MP_QSTR_can_go_back;
MP_QSTR_cancel_arrow; MP_QSTR_cancel_arrow;
MP_QSTR_cancel_cross; MP_QSTR_cancel_cross;
@ -218,8 +220,10 @@ static void _librust_qstrs(void) {
MP_QSTR_debug__loading_seed; MP_QSTR_debug__loading_seed;
MP_QSTR_debug__loading_seed_not_recommended; MP_QSTR_debug__loading_seed_not_recommended;
MP_QSTR_decode; MP_QSTR_decode;
MP_QSTR_default_cancel;
MP_QSTR_deinit; MP_QSTR_deinit;
MP_QSTR_description; MP_QSTR_description;
MP_QSTR_description_font_green;
MP_QSTR_details_title; MP_QSTR_details_title;
MP_QSTR_device_name__change_template; MP_QSTR_device_name__change_template;
MP_QSTR_device_name__title; MP_QSTR_device_name__title;
@ -307,6 +311,7 @@ static void _librust_qstrs(void) {
MP_QSTR_instructions__swipe_up; MP_QSTR_instructions__swipe_up;
MP_QSTR_instructions__tap_to_confirm; MP_QSTR_instructions__tap_to_confirm;
MP_QSTR_instructions__tap_to_start; MP_QSTR_instructions__tap_to_start;
MP_QSTR_instructions__view_all_data;
MP_QSTR_is_type_of; MP_QSTR_is_type_of;
MP_QSTR_items; MP_QSTR_items;
MP_QSTR_items_title; MP_QSTR_items_title;
@ -349,6 +354,7 @@ static void _librust_qstrs(void) {
MP_QSTR_notification; MP_QSTR_notification;
MP_QSTR_notification_level; MP_QSTR_notification_level;
MP_QSTR_page_count; MP_QSTR_page_count;
MP_QSTR_page_limit;
MP_QSTR_pages; MP_QSTR_pages;
MP_QSTR_paint; MP_QSTR_paint;
MP_QSTR_passphrase__access_wallet; MP_QSTR_passphrase__access_wallet;
@ -730,6 +736,7 @@ static void _librust_qstrs(void) {
MP_QSTR_value; MP_QSTR_value;
MP_QSTR_verb; MP_QSTR_verb;
MP_QSTR_verb_cancel; MP_QSTR_verb_cancel;
MP_QSTR_verb_info;
MP_QSTR_verify; MP_QSTR_verify;
MP_QSTR_version; MP_QSTR_version;
MP_QSTR_warning; MP_QSTR_warning;
@ -980,6 +987,7 @@ static void _librust_qstrs(void) {
MP_QSTR_ethereum__data_size_template; MP_QSTR_ethereum__data_size_template;
MP_QSTR_ethereum__gas_limit; MP_QSTR_ethereum__gas_limit;
MP_QSTR_ethereum__gas_price; MP_QSTR_ethereum__gas_price;
MP_QSTR_ethereum__interaction_contract;
MP_QSTR_ethereum__max_gas_price; MP_QSTR_ethereum__max_gas_price;
MP_QSTR_ethereum__name_and_version; MP_QSTR_ethereum__name_and_version;
MP_QSTR_ethereum__new_contract; MP_QSTR_ethereum__new_contract;
@ -998,13 +1006,15 @@ static void _librust_qstrs(void) {
MP_QSTR_ethereum__staking_stake_intro; MP_QSTR_ethereum__staking_stake_intro;
MP_QSTR_ethereum__staking_unstake; MP_QSTR_ethereum__staking_unstake;
MP_QSTR_ethereum__staking_unstake_intro; MP_QSTR_ethereum__staking_unstake_intro;
MP_QSTR_ethereum__title_confirm_data;
MP_QSTR_ethereum__title_confirm_domain; MP_QSTR_ethereum__title_confirm_domain;
MP_QSTR_ethereum__title_confirm_message; MP_QSTR_ethereum__title_confirm_message;
MP_QSTR_ethereum__title_confirm_struct; MP_QSTR_ethereum__title_confirm_struct;
MP_QSTR_ethereum__title_confirm_typed_data; MP_QSTR_ethereum__title_confirm_typed_data;
MP_QSTR_ethereum__title_input_data;
MP_QSTR_ethereum__title_signing_address; MP_QSTR_ethereum__title_signing_address;
MP_QSTR_ethereum__token_contract;
MP_QSTR_ethereum__units_template; MP_QSTR_ethereum__units_template;
MP_QSTR_ethereum__unknown_contract_address;
MP_QSTR_ethereum__unknown_token; MP_QSTR_ethereum__unknown_token;
MP_QSTR_ethereum__valid_signature; MP_QSTR_ethereum__valid_signature;
MP_QSTR_fido__already_registered; MP_QSTR_fido__already_registered;

View File

@ -457,7 +457,7 @@ pub enum TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__name_and_version = 277, // "Name and version" ethereum__name_and_version = 277, // "Name and version"
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__new_contract = 278, // "new contract?" ethereum__new_contract = 278, // "New contract will be deployed"
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__no_message_field = 279, // "No message field" ethereum__no_message_field = 279, // "No message field"
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
@ -473,7 +473,7 @@ pub enum TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__sign_eip712 = 285, // "Really sign EIP-712 typed data?" ethereum__sign_eip712 = 285, // "Really sign EIP-712 typed data?"
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__title_confirm_data = 286, // "Confirm data" ethereum__title_input_data = 286, // "Input data"
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__title_confirm_domain = 287, // "Confirm domain" ethereum__title_confirm_domain = 287, // "Confirm domain"
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
@ -1371,6 +1371,14 @@ pub enum TranslatedString {
fido__title_credential_details = 965, // "Credential details" fido__title_credential_details = 965, // "Credential details"
address__public_key_confirmed = 966, // "Public key confirmed" address__public_key_confirmed = 966, // "Public key confirmed"
words__continue_anyway = 967, // "Continue anyway" words__continue_anyway = 967, // "Continue anyway"
#[cfg(feature = "universal_fw")]
ethereum__unknown_contract_address = 968, // "Unknown contract address. Continue only if you know what you are doing."
#[cfg(feature = "universal_fw")]
ethereum__token_contract = 970, // "Token contract"
buttons__view_all_data = 971, // "View all data"
instructions__view_all_data = 972, // "View all data in the menu."
#[cfg(feature = "universal_fw")]
ethereum__interaction_contract = 973, // "Interaction contract"
} }
impl TranslatedString { impl TranslatedString {
@ -1822,7 +1830,7 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__name_and_version => "Name and version", Self::ethereum__name_and_version => "Name and version",
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__new_contract => "new contract?", Self::ethereum__new_contract => "New contract will be deployed",
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__no_message_field => "No message field", Self::ethereum__no_message_field => "No message field",
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
@ -1838,7 +1846,7 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__sign_eip712 => "Really sign EIP-712 typed data?", Self::ethereum__sign_eip712 => "Really sign EIP-712 typed data?",
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__title_confirm_data => "Confirm data", Self::ethereum__title_input_data => "Input data",
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__title_confirm_domain => "Confirm domain", Self::ethereum__title_confirm_domain => "Confirm domain",
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
@ -2736,6 +2744,14 @@ impl TranslatedString {
Self::fido__title_credential_details => "Credential details", Self::fido__title_credential_details => "Credential details",
Self::address__public_key_confirmed => "Public key confirmed", Self::address__public_key_confirmed => "Public key confirmed",
Self::words__continue_anyway => "Continue anyway", Self::words__continue_anyway => "Continue anyway",
#[cfg(feature = "universal_fw")]
Self::ethereum__unknown_contract_address => "Unknown contract address. Continue only if you know what you are doing.",
#[cfg(feature = "universal_fw")]
Self::ethereum__token_contract => "Token contract",
Self::buttons__view_all_data => "View all data",
Self::instructions__view_all_data => "View all data in the menu.",
#[cfg(feature = "universal_fw")]
Self::ethereum__interaction_contract => "Interaction contract",
} }
} }
@ -3204,7 +3220,7 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__sign_eip712 => Some(Self::ethereum__sign_eip712), Qstr::MP_QSTR_ethereum__sign_eip712 => Some(Self::ethereum__sign_eip712),
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__title_confirm_data => Some(Self::ethereum__title_confirm_data), Qstr::MP_QSTR_ethereum__title_input_data => Some(Self::ethereum__title_input_data),
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__title_confirm_domain => Some(Self::ethereum__title_confirm_domain), Qstr::MP_QSTR_ethereum__title_confirm_domain => Some(Self::ethereum__title_confirm_domain),
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
@ -4102,6 +4118,14 @@ impl TranslatedString {
Qstr::MP_QSTR_fido__title_credential_details => Some(Self::fido__title_credential_details), Qstr::MP_QSTR_fido__title_credential_details => Some(Self::fido__title_credential_details),
Qstr::MP_QSTR_address__public_key_confirmed => Some(Self::address__public_key_confirmed), Qstr::MP_QSTR_address__public_key_confirmed => Some(Self::address__public_key_confirmed),
Qstr::MP_QSTR_words__continue_anyway => Some(Self::words__continue_anyway), Qstr::MP_QSTR_words__continue_anyway => Some(Self::words__continue_anyway),
#[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__unknown_contract_address => Some(Self::ethereum__unknown_contract_address),
#[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__token_contract => Some(Self::ethereum__token_contract),
Qstr::MP_QSTR_buttons__view_all_data => Some(Self::buttons__view_all_data),
Qstr::MP_QSTR_instructions__view_all_data => Some(Self::instructions__view_all_data),
#[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__interaction_contract => Some(Self::ethereum__interaction_contract),
_ => None, _ => None,
} }
} }

View File

@ -9,6 +9,9 @@ pub enum PageMsg<T> {
/// Cancelled using page controls. /// Cancelled using page controls.
Cancelled, Cancelled,
/// Info button pressed
Info,
/// Page component was configured to react to swipes and user swiped left. /// Page component was configured to react to swipes and user swiped left.
SwipeLeft, SwipeLeft,

View File

@ -13,6 +13,7 @@ pub struct SwipePage<T> {
axis: Axis, axis: Axis,
pages: usize, pages: usize,
current: usize, current: usize,
limit: Option<usize>,
} }
impl<T: Component + Paginate> SwipePage<T> { impl<T: Component + Paginate> SwipePage<T> {
@ -23,6 +24,7 @@ impl<T: Component + Paginate> SwipePage<T> {
axis: Axis::Vertical, axis: Axis::Vertical,
pages: 1, pages: 1,
current: 0, current: 0,
limit: None,
} }
} }
@ -33,12 +35,18 @@ impl<T: Component + Paginate> SwipePage<T> {
axis: Axis::Horizontal, axis: Axis::Horizontal,
pages: 1, pages: 1,
current: 0, current: 0,
limit: None,
} }
} }
pub fn inner(&self) -> &T { pub fn inner(&self) -> &T {
&self.inner &self.inner
} }
pub fn with_limit(mut self, limit: Option<usize>) -> Self {
self.limit = limit;
self
}
} }
impl<T: Component + Paginate> Component for SwipePage<T> { impl<T: Component + Paginate> Component for SwipePage<T> {
@ -47,6 +55,9 @@ impl<T: Component + Paginate> Component for SwipePage<T> {
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
self.bounds = self.inner.place(bounds); self.bounds = self.inner.place(bounds);
self.pages = self.inner.page_count(); self.pages = self.inner.page_count();
if let Some(limit) = self.limit {
self.pages = self.pages.min(limit);
}
self.bounds self.bounds
} }

View File

@ -150,6 +150,11 @@ where
self.with_button(theme::ICON_MENU, FlowMsg::Info, true) self.with_button(theme::ICON_MENU, FlowMsg::Info, true)
} }
pub fn with_danger_menu_button(self) -> Self {
self.with_button(theme::ICON_MENU, FlowMsg::Info, true)
.button_styled(theme::button_warning_high())
}
pub fn with_warning_low_icon(self) -> Self { pub fn with_warning_low_icon(self) -> Self {
self.with_button(theme::ICON_WARNING, FlowMsg::Info, false) self.with_button(theme::ICON_WARNING, FlowMsg::Info, false)
.button_styled(theme::button_warning_low()) .button_styled(theme::button_warning_low())

View File

@ -96,6 +96,83 @@ impl FlowController for ConfirmActionSimple {
} }
} }
/// Flow similar to ConfirmActionSimple, but having swipe up cancel the flow
/// rather than confirm. To confirm, the user needs to open the menu.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfirmActionSimpleDefaultCancel {
Intro,
Menu,
}
impl FlowController for ConfirmActionSimpleDefaultCancel {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: Direction) -> Decision {
match (self, direction) {
(Self::Intro, Direction::Left) => Self::Menu.swipe(direction),
(Self::Menu, Direction::Right) => Self::Intro.swipe(direction),
(Self::Intro, Direction::Up) => self.return_msg(FlowMsg::Cancelled),
_ => self.do_nothing(),
}
}
fn handle_event(&'static self, msg: FlowMsg) -> Decision {
match (self, msg) {
(Self::Intro, FlowMsg::Info) => Self::Menu.goto(),
(Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
(Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
(Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Confirmed),
_ => self.do_nothing(),
}
}
}
pub struct ConfirmActionMenu {
verb_cancel: Option<TString<'static>>,
info: bool,
verb_info: Option<TString<'static>>,
}
impl ConfirmActionMenu {
pub fn new(
verb_cancel: Option<TString<'static>>,
info: bool,
verb_info: Option<TString<'static>>,
) -> Self {
Self {
verb_cancel,
info,
verb_info,
}
}
}
pub struct ConfirmActionStrings {
title: TString<'static>,
subtitle: Option<TString<'static>>,
verb: Option<TString<'static>>,
prompt_screen: Option<TString<'static>>,
}
impl ConfirmActionStrings {
pub fn new(
title: TString<'static>,
subtitle: Option<TString<'static>>,
verb: Option<TString<'static>>,
prompt_screen: Option<TString<'static>>,
) -> Self {
Self {
title,
subtitle,
verb,
prompt_screen,
}
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, new_confirm_action_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, new_confirm_action_obj) }
@ -141,35 +218,43 @@ fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Err
new_confirm_action_simple( new_confirm_action_simple(
paragraphs, paragraphs,
title, ConfirmActionMenu::new(verb_cancel, false, None),
subtitle, ConfirmActionStrings::new(title, subtitle, None, prompt_screen.then_some(prompt_title)),
verb_cancel,
prompt_screen.then_some(prompt_title),
hold, hold,
false, None,
) )
} }
#[inline(never)] #[inline(never)]
pub fn new_confirm_action_uni<T: Component + MaybeTrace + 'static>( fn new_confirm_action_uni<T: Component + MaybeTrace + 'static>(
content: T, content: T,
title: TString<'static>, menu: ConfirmActionMenu,
subtitle: Option<TString<'static>>, strings: ConfirmActionStrings,
verb_cancel: Option<TString<'static>>,
prompt_screen: Option<TString<'static>>,
hold: bool, hold: bool,
info: bool, default_cancel: bool,
) -> Result<Obj, error::Error> { ) -> Result<Obj, error::Error> {
let (prompt_screen, prompt_pages, flow, page) = create_flow(title, prompt_screen, hold); let (prompt_screen, prompt_pages, flow, page) =
create_flow(strings.title, strings.prompt_screen, hold, default_cancel);
let mut content_intro = Frame::left_aligned(title, content) let mut content_intro = Frame::left_aligned(strings.title, content)
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.with_swipe(Direction::Up, SwipeSettings::default()) .with_swipe(Direction::Up, SwipeSettings::default())
.with_swipe(Direction::Left, SwipeSettings::default()) .with_swipe(Direction::Left, SwipeSettings::default())
.with_vertical_pages(); .with_vertical_pages();
if let Some(subtitle) = subtitle { if default_cancel {
content_intro = content_intro.title_styled(theme::TEXT_WARNING);
content_intro = content_intro.with_danger_menu_button();
content_intro = content_intro.with_footer(
TR::instructions__swipe_up.into(),
Some(TR::send__cancel_sign.into()),
);
} else {
content_intro = content_intro.with_menu_button();
// TODO: conditionally add the verb to the footer as well?
content_intro = content_intro.with_footer(TR::instructions__swipe_up.into(), None);
}
if let Some(subtitle) = strings.subtitle {
content_intro = content_intro.with_subtitle(subtitle); content_intro = content_intro.with_subtitle(subtitle);
} }
@ -182,13 +267,18 @@ pub fn new_confirm_action_uni<T: Component + MaybeTrace + 'static>(
let flow = flow?.with_page(page, content_intro)?; let flow = flow?.with_page(page, content_intro)?;
create_menu_and_confirm(subtitle, verb_cancel, hold, info, prompt_screen, flow) let flow = create_menu(flow, menu, default_cancel, prompt_screen)?;
let flow = create_confirm(flow, strings.subtitle, hold, prompt_screen)?;
Ok(LayoutObj::new(flow)?.into())
} }
fn create_flow( fn create_flow(
title: TString<'static>, title: TString<'static>,
prompt_screen: Option<TString<'static>>, prompt_screen: Option<TString<'static>>,
hold: bool, hold: bool,
default_cancel: bool,
) -> ( ) -> (
Option<TString<'static>>, Option<TString<'static>>,
usize, usize,
@ -198,52 +288,51 @@ fn create_flow(
let prompt_screen = prompt_screen.or_else(|| hold.then_some(title)); let prompt_screen = prompt_screen.or_else(|| hold.then_some(title));
let prompt_pages: usize = prompt_screen.is_some().into(); let prompt_pages: usize = prompt_screen.is_some().into();
let flow = if prompt_screen.is_some() { let (flow, page): (Result<SwipeFlow, Error>, &dyn FlowController) = if prompt_screen.is_some() {
SwipeFlow::new(&ConfirmAction::Intro) (SwipeFlow::new(&ConfirmAction::Intro), &ConfirmAction::Intro)
} else if default_cancel {
(
SwipeFlow::new(&ConfirmActionSimpleDefaultCancel::Intro),
&ConfirmActionSimpleDefaultCancel::Intro,
)
} else { } else {
SwipeFlow::new(&ConfirmActionSimple::Intro) (
}; SwipeFlow::new(&ConfirmActionSimple::Intro),
&ConfirmActionSimple::Intro,
let page: &dyn FlowController = if prompt_screen.is_some() { )
&ConfirmAction::Intro
} else {
&ConfirmActionSimple::Intro
}; };
(prompt_screen, prompt_pages, flow, page) (prompt_screen, prompt_pages, flow, page)
} }
fn create_menu_and_confirm(
subtitle: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>,
hold: bool,
info: bool,
prompt_screen: Option<TString<'static>>,
flow: SwipeFlow,
) -> Result<Obj, Error> {
let flow = create_menu(flow, verb_cancel, info, prompt_screen)?;
let flow = create_confirm(flow, subtitle, hold, prompt_screen)?;
Ok(LayoutObj::new(flow)?.into())
}
fn create_menu( fn create_menu(
flow: SwipeFlow, flow: SwipeFlow,
verb_cancel: Option<TString<'static>>, menu: ConfirmActionMenu,
info: bool, default_cancel: bool,
prompt_screen: Option<TString<'static>>, prompt_screen: Option<TString<'static>>,
) -> Result<SwipeFlow, Error> { ) -> Result<SwipeFlow, Error> {
let mut menu_choices = VerticalMenu::empty().danger( let mut menu_choices = VerticalMenu::empty();
if default_cancel {
menu_choices = menu_choices.item(
theme::ICON_CANCEL, theme::ICON_CANCEL,
verb_cancel.unwrap_or(TR::buttons__cancel.into()), menu.verb_cancel.unwrap_or(TR::buttons__cancel.into()),
); );
if info { menu_choices =
menu_choices.danger(theme::ICON_CHEVRON_RIGHT, TR::words__continue_anyway.into());
} else {
menu_choices = menu_choices.danger(
theme::ICON_CANCEL,
menu.verb_cancel.unwrap_or(TR::buttons__cancel.into()),
);
if menu.info {
menu_choices = menu_choices.item( menu_choices = menu_choices.item(
theme::ICON_CHEVRON_RIGHT, theme::ICON_CHEVRON_RIGHT,
TR::words__title_information.into(), menu.verb_info
.unwrap_or(TR::words__title_information.into()),
); );
} }
}
let content_menu = Frame::left_aligned("".into(), menu_choices) let content_menu = Frame::left_aligned("".into(), menu_choices)
.with_cancel_button() .with_cancel_button()
.with_swipe(Direction::Right, SwipeSettings::immediate()); .with_swipe(Direction::Right, SwipeSettings::immediate());
@ -304,20 +393,33 @@ fn create_confirm(
#[inline(never)] #[inline(never)]
pub fn new_confirm_action_simple<T: Component + Paginate + MaybeTrace + 'static>( pub fn new_confirm_action_simple<T: Component + Paginate + MaybeTrace + 'static>(
content: T, content: T,
title: TString<'static>, menu: ConfirmActionMenu,
subtitle: Option<TString<'static>>, strings: ConfirmActionStrings,
verb_cancel: Option<TString<'static>>,
prompt_screen: Option<TString<'static>>,
hold: bool, hold: bool,
info: bool, page_limit: Option<usize>,
) -> Result<Obj, error::Error> { ) -> Result<Obj, error::Error> {
new_confirm_action_uni( new_confirm_action_uni(
SwipeContent::new(SwipePage::vertical(content)), SwipeContent::new(SwipePage::vertical(content).with_limit(page_limit)),
title, menu,
subtitle, strings,
verb_cancel,
prompt_screen,
hold, hold,
info, false,
)
}
#[inline(never)]
pub fn new_confirm_action_simple_default_cancel<T: Component + Paginate + MaybeTrace + 'static>(
content: T,
menu: ConfirmActionMenu,
strings: ConfirmActionStrings,
hold: bool,
page_limit: Option<usize>,
) -> Result<Obj, error::Error> {
new_confirm_action_uni(
SwipeContent::new(SwipePage::vertical(content).with_limit(page_limit)),
menu,
strings,
hold,
true,
) )
} }

View File

@ -19,7 +19,10 @@ pub mod warning_hi_prio;
mod util; mod util;
pub use confirm_action::{new_confirm_action, new_confirm_action_simple}; pub use confirm_action::{
new_confirm_action, new_confirm_action_simple, new_confirm_action_simple_default_cancel,
ConfirmActionMenu, ConfirmActionStrings,
};
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
pub use confirm_fido::new_confirm_fido; pub use confirm_fido::new_confirm_fido;
pub use confirm_firmware_update::new_confirm_firmware_update; pub use confirm_firmware_update::new_confirm_firmware_update;

View File

@ -50,7 +50,10 @@ use crate::{
}, },
model_mercury::{ model_mercury::{
component::{check_homescreen_format, SwipeContent}, component::{check_homescreen_format, SwipeContent},
flow::new_confirm_action_simple, flow::{
new_confirm_action_simple, new_confirm_action_simple_default_cancel,
ConfirmActionMenu, ConfirmActionStrings,
},
theme::ICON_BULLET_CHECKMARK, theme::ICON_BULLET_CHECKMARK,
}, },
}, },
@ -238,14 +241,12 @@ extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *m
} }
} }
flow::new_confirm_action_simple( new_confirm_action_simple(
FormattedText::new(ops).vertically_centered(), FormattedText::new(ops).vertically_centered(),
title, ConfirmActionMenu::new(None, false, None),
None, ConfirmActionStrings::new(title, None, None, Some(title)),
None,
Some(title),
false,
false, false,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -256,14 +257,18 @@ struct ConfirmBlobParams {
subtitle: Option<TString<'static>>, subtitle: Option<TString<'static>>,
data: Obj, data: Obj,
description: Option<TString<'static>>, description: Option<TString<'static>>,
description_font: &'static TextStyle,
extra: Option<TString<'static>>, extra: Option<TString<'static>>,
verb: Option<TString<'static>>, verb: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>, verb_cancel: Option<TString<'static>>,
verb_info: Option<TString<'static>>,
info_button: bool, info_button: bool,
prompt: bool, prompt: bool,
hold: bool, hold: bool,
chunkify: bool, chunkify: bool,
text_mono: bool, text_mono: bool,
page_limit: Option<usize>,
default_cancel: bool,
} }
impl ConfirmBlobParams { impl ConfirmBlobParams {
@ -272,7 +277,7 @@ impl ConfirmBlobParams {
data: Obj, data: Obj,
description: Option<TString<'static>>, description: Option<TString<'static>>,
verb: Option<TString<'static>>, verb: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>, verb_info: Option<TString<'static>>,
prompt: bool, prompt: bool,
hold: bool, hold: bool,
) -> Self { ) -> Self {
@ -281,14 +286,18 @@ impl ConfirmBlobParams {
subtitle: None, subtitle: None,
data, data,
description, description,
description_font: &theme::TEXT_NORMAL,
extra: None, extra: None,
verb, verb,
verb_cancel, verb_cancel: None,
verb_info,
info_button: false, info_button: false,
prompt, prompt,
hold, hold,
chunkify: false, chunkify: false,
text_mono: true, text_mono: true,
page_limit: None,
default_cancel: false,
} }
} }
@ -302,6 +311,11 @@ impl ConfirmBlobParams {
self self
} }
fn with_verb_cancel(mut self, verb_cancel: Option<TString<'static>>) -> Self {
self.verb_cancel = verb_cancel;
self
}
fn with_info_button(mut self, info_button: bool) -> Self { fn with_info_button(mut self, info_button: bool) -> Self {
self.info_button = info_button; self.info_button = info_button;
self self
@ -317,12 +331,27 @@ impl ConfirmBlobParams {
self self
} }
fn with_page_limit(mut self, page_limit: Option<usize>) -> Self {
self.page_limit = page_limit;
self
}
fn with_default_cancel(mut self, default_cancel: bool) -> Self {
self.default_cancel = default_cancel;
self
}
fn with_description_font(mut self, description_font: &'static TextStyle) -> Self {
self.description_font = description_font;
self
}
fn into_flow(self) -> Result<Obj, Error> { fn into_flow(self) -> Result<Obj, Error> {
let paragraphs = ConfirmBlob { let paragraphs = ConfirmBlob {
description: self.description.unwrap_or("".into()), description: self.description.unwrap_or("".into()),
extra: self.extra.unwrap_or("".into()), extra: self.extra.unwrap_or("".into()),
data: self.data.try_into()?, data: self.data.try_into()?,
description_font: &theme::TEXT_NORMAL, description_font: self.description_font,
extra_font: &theme::TEXT_DEMIBOLD, extra_font: &theme::TEXT_DEMIBOLD,
data_font: if self.chunkify { data_font: if self.chunkify {
let data: TString = self.data.try_into()?; let data: TString = self.data.try_into()?;
@ -335,14 +364,23 @@ impl ConfirmBlobParams {
} }
.into_paragraphs(); .into_paragraphs();
flow::new_confirm_action_simple( let build_flow = if self.default_cancel {
new_confirm_action_simple_default_cancel
} else {
new_confirm_action_simple
};
build_flow(
paragraphs, paragraphs,
ConfirmActionMenu::new(self.verb_cancel, self.info_button, self.verb_info),
ConfirmActionStrings::new(
self.title, self.title,
self.subtitle, self.subtitle,
self.verb_cancel, self.verb,
self.prompt.then_some(self.title), self.prompt.then_some(self.title),
),
self.hold, self.hold,
self.info_button, self.page_limit,
) )
} }
} }
@ -353,7 +391,17 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
let description: Option<TString> = let description: Option<TString> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let extra: Option<TString> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; let description_font_green: bool =
kwargs.get_or(Qstr::MP_QSTR_description_font_green, false)?;
let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?;
let extra: Option<TString> = kwargs
.get(Qstr::MP_QSTR_extra)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let subtitle: Option<TString> = kwargs
.get(Qstr::MP_QSTR_subtitle)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb: Option<TString> = kwargs let verb: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb) .get(Qstr::MP_QSTR_verb)
.unwrap_or_else(|_| Obj::const_none()) .unwrap_or_else(|_| Obj::const_none())
@ -362,21 +410,44 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
.get(Qstr::MP_QSTR_verb_cancel) .get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none()) .unwrap_or_else(|_| Obj::const_none())
.try_into_option()?; .try_into_option()?;
let verb_info: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_info)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let info: bool = kwargs.get_or(Qstr::MP_QSTR_info, true)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, true)?; let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, true)?;
let default_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_default_cancel, false)?;
let page_limit: Option<usize> = kwargs
.get(Qstr::MP_QSTR_page_limit)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let description_font = if description_font_green {
&theme::TEXT_SUB_GREEN_LIME
} else {
&theme::TEXT_NORMAL
};
ConfirmBlobParams::new( ConfirmBlobParams::new(
title, title,
data, data,
description, description,
verb, verb,
verb_cancel, verb_info,
prompt_screen, prompt_screen,
hold, hold,
) )
.with_description_font(description_font)
.with_text_mono(text_mono)
.with_subtitle(subtitle)
.with_verb_cancel(verb_cancel)
.with_extra(extra) .with_extra(extra)
.with_info_button(info)
.with_chunkify(chunkify) .with_chunkify(chunkify)
.with_default_cancel(default_cancel)
.with_page_limit(page_limit)
.into_flow() .into_flow()
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -408,7 +479,13 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
} }
.into_paragraphs(); .into_paragraphs();
flow::new_confirm_action_simple(paragraphs, title, None, None, None, false, false) new_confirm_action_simple(
paragraphs,
ConfirmActionMenu::new(None, false, None),
ConfirmActionStrings::new(title, None, None, None),
false,
None,
)
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
@ -426,14 +503,12 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
&theme::TEXT_MONO, &theme::TEXT_MONO,
)?; )?;
flow::new_confirm_action_simple( new_confirm_action_simple(
paragraphs.into_paragraphs(), paragraphs.into_paragraphs(),
title, ConfirmActionMenu::new(None, false, None),
None, ConfirmActionStrings::new(title, None, None, hold.then_some(title)),
None,
hold.then_some(title),
hold, hold,
false, None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -457,12 +532,15 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
new_confirm_action_simple( new_confirm_action_simple(
paragraphs, paragraphs,
ConfirmActionMenu::new(None, false, None),
ConfirmActionStrings::new(
TR::homescreen__settings_title.into(), TR::homescreen__settings_title.into(),
Some(TR::homescreen__settings_subtitle.into()), Some(TR::homescreen__settings_subtitle.into()),
None, None,
Some(TR::homescreen__settings_title.into()), Some(TR::homescreen__settings_title.into()),
),
false, false,
false, None,
) )
} else { } else {
if !check_homescreen_format(jpeg) { if !check_homescreen_format(jpeg) {
@ -540,8 +618,9 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?;
ConfirmBlobParams::new(title, value, description, verb, verb_cancel, hold, hold) ConfirmBlobParams::new(title, value, description, verb, None, hold, hold)
.with_subtitle(subtitle) .with_subtitle(subtitle)
.with_verb_cancel(verb_cancel)
.with_info_button(info_button) .with_info_button(info_button)
.with_chunkify(chunkify) .with_chunkify(chunkify)
.with_text_mono(text_mono) .with_text_mono(text_mono)
@ -563,14 +642,12 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value)); paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
} }
flow::new_confirm_action_simple( new_confirm_action_simple(
paragraphs.into_paragraphs(), paragraphs.into_paragraphs(),
title, ConfirmActionMenu::new(None, true, None),
None, ConfirmActionStrings::new(title, None, None, Some(title)),
None,
Some(title),
true,
true, true,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -849,14 +926,17 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut
]) ])
.into_paragraphs(); .into_paragraphs();
flow::new_confirm_action_simple( new_confirm_action_simple(
paragraphs, paragraphs,
ConfirmActionMenu::new(None, false, None),
ConfirmActionStrings::new(
TR::coinjoin__title.into(), TR::coinjoin__title.into(),
None, None,
None, None,
Some(TR::coinjoin__title.into()), Some(TR::coinjoin__title.into()),
),
true, true,
false, None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1261,12 +1341,19 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str, /// title: str,
/// data: str | bytes, /// data: str | bytes,
/// description: str | None, /// description: str | None,
/// extra: str | None, /// description_font_green: bool = False,
/// text_mono: bool = True,
/// extra: str | None = None,
/// subtitle: str | None = None,
/// verb: str | None = None, /// verb: str | None = None,
/// verb_cancel: str | None = None, /// verb_cancel: str | None = None,
/// verb_info: str | None = None,
/// info: bool = True,
/// hold: bool = False, /// hold: bool = False,
/// chunkify: bool = False, /// chunkify: bool = False,
/// prompt_screen: bool = False, /// prompt_screen: bool = False,
/// default_cancel: bool = False,
/// page_limit: int | None = None,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data.""" /// """Confirm byte sequence data."""
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),

View File

@ -2,7 +2,7 @@ use crate::{
translations::TR, translations::TR,
ui::{ ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate}, component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate},
display::Color, display::{Color, Font},
geometry::{Insets, Rect}, geometry::{Insets, Rect},
shape::Renderer, shape::Renderer,
}, },
@ -13,21 +13,26 @@ use super::{
ButtonDetails, ButtonLayout, ButtonPos, ButtonDetails, ButtonLayout, ButtonPos,
}; };
#[derive(PartialEq)]
enum LastPageLayout {
Confirm,
ArmedConfirmPlusInfo,
}
pub struct ButtonPage<T> pub struct ButtonPage<T>
where where
T: Component + Paginate, T: Component + Paginate,
{ {
page_count: usize, page_count: usize,
active_page: usize, active_page: usize,
page_limit: Option<usize>,
last_page_layout: LastPageLayout,
content: Child<T>, content: Child<T>,
pad: Pad, pad: Pad,
/// Left button of the first screen
cancel_btn_details: Option<ButtonDetails>, cancel_btn_details: Option<ButtonDetails>,
/// Right button of the last screen
confirm_btn_details: Option<ButtonDetails>, confirm_btn_details: Option<ButtonDetails>,
/// Left button of every screen info_btn_details: Option<ButtonDetails>,
back_btn_details: Option<ButtonDetails>, back_btn_details: Option<ButtonDetails>,
/// Right button of every screen apart the last one
next_btn_details: Option<ButtonDetails>, next_btn_details: Option<ButtonDetails>,
buttons: Child<ButtonController>, buttons: Child<ButtonController>,
} }
@ -40,10 +45,17 @@ where
Self { Self {
page_count: 0, // will be set in place() page_count: 0, // will be set in place()
active_page: 0, active_page: 0,
page_limit: None,
last_page_layout: LastPageLayout::Confirm,
content: Child::new(content), content: Child::new(content),
pad: Pad::with_background(background).with_clear(), pad: Pad::with_background(background).with_clear(),
cancel_btn_details: Some(ButtonDetails::cancel_icon()), cancel_btn_details: Some(ButtonDetails::cancel_icon()),
confirm_btn_details: Some(ButtonDetails::text(TR::buttons__confirm.into())), confirm_btn_details: Some(ButtonDetails::text(TR::buttons__confirm.into())),
info_btn_details: Some(
ButtonDetails::text("i".into())
.with_fixed_width(theme::BUTTON_ICON_WIDTH)
.with_font(Font::NORMAL),
),
back_btn_details: Some(ButtonDetails::up_arrow_icon()), back_btn_details: Some(ButtonDetails::up_arrow_icon()),
next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()), next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()),
// Setting empty layout for now, we do not yet know the page count. // Setting empty layout for now, we do not yet know the page count.
@ -53,6 +65,12 @@ where
} }
} }
pub fn with_armed_confirm_plus_info(mut self) -> Self {
self.last_page_layout = LastPageLayout::ArmedConfirmPlusInfo;
self.confirm_btn_details = Some(ButtonDetails::armed_text(TR::words__confirm.into()));
self
}
pub fn with_cancel_btn(mut self, btn_details: Option<ButtonDetails>) -> Self { pub fn with_cancel_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
self.cancel_btn_details = btn_details; self.cancel_btn_details = btn_details;
self self
@ -73,6 +91,11 @@ where
self self
} }
pub fn with_page_limit(mut self, page_limit: Option<usize>) -> Self {
self.page_limit = page_limit;
self
}
pub fn has_next_page(&self) -> bool { pub fn has_next_page(&self) -> bool {
self.active_page < self.page_count - 1 self.active_page < self.page_count - 1
} }
@ -119,11 +142,11 @@ where
fn get_button_layout(&self, has_prev: bool, has_next: bool) -> ButtonLayout { fn get_button_layout(&self, has_prev: bool, has_next: bool) -> ButtonLayout {
let btn_left = self.get_left_button_details(!has_prev); let btn_left = self.get_left_button_details(!has_prev);
let btn_middle = self.get_middle_button_details(has_next);
let btn_right = self.get_right_button_details(has_next); let btn_right = self.get_right_button_details(has_next);
ButtonLayout::new(btn_left, None, btn_right) ButtonLayout::new(btn_left, btn_middle, btn_right)
} }
/// Get the left button details, depending whether the page is first or not.
fn get_left_button_details(&self, is_first: bool) -> Option<ButtonDetails> { fn get_left_button_details(&self, is_first: bool) -> Option<ButtonDetails> {
if is_first { if is_first {
self.cancel_btn_details.clone() self.cancel_btn_details.clone()
@ -132,13 +155,21 @@ where
} }
} }
/// Get the right button details, depending on whether there is a next fn get_middle_button_details(&self, has_next_page: bool) -> Option<ButtonDetails> {
/// page. if has_next_page || self.last_page_layout == LastPageLayout::Confirm {
None
} else {
self.confirm_btn_details.clone()
}
}
fn get_right_button_details(&self, has_next_page: bool) -> Option<ButtonDetails> { fn get_right_button_details(&self, has_next_page: bool) -> Option<ButtonDetails> {
if has_next_page { if has_next_page {
self.next_btn_details.clone() self.next_btn_details.clone()
} else { } else if self.last_page_layout == LastPageLayout::Confirm {
self.confirm_btn_details.clone() self.confirm_btn_details.clone()
} else {
self.info_btn_details.clone()
} }
} }
} }
@ -172,6 +203,10 @@ where
// Need to be called here, only after content is placed // Need to be called here, only after content is placed
// and we can calculate the page count. // and we can calculate the page count.
self.page_count = self.content.page_count(); self.page_count = self.content.page_count();
if let Some(limit) = self.page_limit {
self.page_count = self.page_count.min(limit);
}
self.set_buttons_for_initial_page(self.page_count); self.set_buttons_for_initial_page(self.page_count);
self.buttons.place(button_area); self.buttons.place(button_area);
bounds bounds
@ -191,17 +226,25 @@ where
return Some(PageMsg::Cancelled); return Some(PageMsg::Cancelled);
} }
} }
ButtonPos::Middle => {
return Some(PageMsg::Confirmed);
}
ButtonPos::Right => { ButtonPos::Right => {
if self.has_next_page() { if self.has_next_page() {
// Clicked NEXT. Scroll down. // Clicked NEXT. Scroll down.
self.go_to_next_page(); self.go_to_next_page();
self.change_page(ctx); self.change_page(ctx);
} else { } else {
// Clicked CONFIRM. Send result. match self.last_page_layout {
LastPageLayout::Confirm => {
return Some(PageMsg::Confirmed); return Some(PageMsg::Confirmed);
} }
LastPageLayout::ArmedConfirmPlusInfo => {
return Some(PageMsg::Info);
}
}
}
} }
_ => {}
} }
} }

View File

@ -94,6 +94,7 @@ where
match msg { match msg {
PageMsg::Confirmed => Ok(CONFIRMED.as_obj()), PageMsg::Confirmed => Ok(CONFIRMED.as_obj()),
PageMsg::Cancelled => Ok(CANCELLED.as_obj()), PageMsg::Cancelled => Ok(CANCELLED.as_obj()),
PageMsg::Info => Ok(INFO.as_obj()),
_ => Err(Error::TypeError), _ => Err(Error::TypeError),
} }
} }
@ -243,7 +244,9 @@ fn content_in_button_page<T: Component + Paginate + MaybeTrace + 'static>(
content: T, content: T,
verb: TString<'static>, verb: TString<'static>,
verb_cancel: Option<TString<'static>>, verb_cancel: Option<TString<'static>>,
info: bool,
hold: bool, hold: bool,
page_limit: Option<usize>,
) -> Result<Obj, Error> { ) -> Result<Obj, Error> {
// Left button - icon, text or nothing. // Left button - icon, text or nothing.
let cancel_btn = verb_cancel.map(ButtonDetails::from_text_possible_icon); let cancel_btn = verb_cancel.map(ButtonDetails::from_text_possible_icon);
@ -259,10 +262,15 @@ fn content_in_button_page<T: Component + Paginate + MaybeTrace + 'static>(
confirm_btn = confirm_btn.map(|btn| btn.with_default_duration()); confirm_btn = confirm_btn.map(|btn| btn.with_default_duration());
} }
let content = ButtonPage::new(content, theme::BG) let mut content = ButtonPage::new(content, theme::BG)
.with_cancel_btn(cancel_btn) .with_cancel_btn(cancel_btn)
.with_page_limit(page_limit)
.with_confirm_btn(confirm_btn); .with_confirm_btn(confirm_btn);
if info {
content = content.with_armed_confirm_plus_info();
}
let mut frame = ScrollableFrame::new(content); let mut frame = ScrollableFrame::new(content);
if !title.is_empty() { if !title.is_empty() {
frame = frame.with_title(title); frame = frame.with_title(title);
@ -303,7 +311,7 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
paragraphs.into_paragraphs() paragraphs.into_paragraphs()
}; };
content_in_button_page(title, paragraphs, verb, verb_cancel, hold) content_in_button_page(title, paragraphs, verb, verb_cancel, false, hold, None)
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
@ -314,15 +322,25 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
let description: Option<TString> = let description: Option<TString> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let extra: Option<TString> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; let extra: Option<TString> = kwargs
let verb: TString<'static> = .get(Qstr::MP_QSTR_extra)
kwargs.get_or(Qstr::MP_QSTR_verb, TR::buttons__confirm.into())?; .unwrap_or_else(|_| Obj::const_none())
let verb_cancel: Option<TString<'static>> = kwargs .try_into_option()?;
let verb: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb_cancel: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb_cancel) .get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none()) .unwrap_or_else(|_| Obj::const_none())
.try_into_option()?; .try_into_option()?;
let info: bool = kwargs.get_or(Qstr::MP_QSTR_info, false)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let page_limit: Option<usize> = kwargs
.get(Qstr::MP_QSTR_page_limit)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let style = if chunkify { let style = if chunkify {
// Chunkifying the address into smaller pieces when requested // Chunkifying the address into smaller pieces when requested
@ -341,7 +359,15 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
} }
.into_paragraphs(); .into_paragraphs();
content_in_button_page(title, paragraphs, verb, verb_cancel, hold) content_in_button_page(
title,
paragraphs,
verb.unwrap_or(TR::buttons__confirm.into()),
verb_cancel,
info,
hold,
page_limit,
)
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
@ -393,7 +419,9 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
paragraphs.into_paragraphs(), paragraphs.into_paragraphs(),
button_text, button_text,
Some("".into()), Some("".into()),
false,
hold, hold,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -423,7 +451,15 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
.text_bold(TR::reset__tos_link); .text_bold(TR::reset__tos_link);
let formatted = FormattedText::new(ops).vertically_centered(); let formatted = FormattedText::new(ops).vertically_centered();
content_in_button_page(title, formatted, button, Some("".into()), false) content_in_button_page(
title,
formatted,
button,
Some("".into()),
false,
false,
None,
)
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
@ -505,7 +541,9 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
paragraphs, paragraphs,
verb.unwrap_or(TR::buttons__confirm.into()), verb.unwrap_or(TR::buttons__confirm.into()),
Some("".into()), Some("".into()),
false,
hold, hold,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -528,7 +566,9 @@ extern "C" fn new_confirm_joint_total(n_args: usize, args: *const Obj, kwargs: *
paragraphs, paragraphs,
TR::buttons__hold_to_confirm.into(), TR::buttons__hold_to_confirm.into(),
Some("".into()), Some("".into()),
false,
true, true,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -559,6 +599,8 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
TR::buttons__confirm.into(), TR::buttons__confirm.into(),
Some("".into()), Some("".into()),
false, false,
false,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -932,6 +974,8 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m
TR::buttons__confirm.into(), TR::buttons__confirm.into(),
Some("".into()), Some("".into()),
false, false,
false,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1215,6 +1259,8 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
button, button,
Some("<".into()), Some("<".into()),
false, false,
false,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1240,7 +1286,9 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut
paragraphs, paragraphs,
TR::buttons__hold_to_confirm.into(), TR::buttons__hold_to_confirm.into(),
None, None,
false,
true, true,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1436,6 +1484,8 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
button, button,
Some("".into()), Some("".into()),
false, false,
false,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1488,6 +1538,8 @@ extern "C" fn new_show_group_share_success(
TR::buttons__continue.into(), TR::buttons__continue.into(),
None, None,
false, false,
false,
None,
) )
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1685,12 +1737,19 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str, /// title: str,
/// data: str | bytes, /// data: str | bytes,
/// description: str | None, /// description: str | None,
/// extra: str | None, /// description_font_green: bool = False,
/// text_mono: bool = True,
/// extra: str | None = None,
/// subtitle: str | None = None,
/// verb: str = "CONFIRM", /// verb: str = "CONFIRM",
/// verb_cancel: str | None = None, /// verb_cancel: str | None = None,
/// verb_info: str | None = None,
/// info: bool = True,
/// hold: bool = False, /// hold: bool = False,
/// chunkify: bool = False, /// chunkify: bool = False,
/// prompt_screen: bool = False, /// prompt_screen: bool = False,
/// default_cancel: bool = False,
/// page_limit: int | None = None,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data.""" /// """Confirm byte sequence data."""
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),

View File

@ -30,6 +30,7 @@ pub struct ButtonPage<T> {
/// Swipe controller. /// Swipe controller.
swipe: Swipe, swipe: Swipe,
scrollbar: ScrollBar, scrollbar: ScrollBar,
page_limit: Option<usize>,
/// Hold-to-confirm mode whenever this is `Some(loader)`. /// Hold-to-confirm mode whenever this is `Some(loader)`.
loader: Option<Loader>, loader: Option<Loader>,
button_cancel: Option<Button>, button_cancel: Option<Button>,
@ -71,6 +72,7 @@ where
pad: Pad::with_background(background), pad: Pad::with_background(background),
swipe: Swipe::new(), swipe: Swipe::new(),
scrollbar: ScrollBar::vertical(), scrollbar: ScrollBar::vertical(),
page_limit: None,
loader: None, loader: None,
button_cancel: Some(Button::with_icon(theme::ICON_CANCEL)), button_cancel: Some(Button::with_icon(theme::ICON_CANCEL)),
button_confirm: Button::with_icon(theme::ICON_CONFIRM).styled(theme::button_confirm()), button_confirm: Button::with_icon(theme::ICON_CONFIRM).styled(theme::button_confirm()),
@ -110,6 +112,11 @@ where
self self
} }
pub fn with_page_limit(mut self, page_limit: Option<usize>) -> Self {
self.page_limit = page_limit;
self
}
pub fn with_back_button(mut self) -> Self { pub fn with_back_button(mut self) -> Self {
self.cancel_from_any_page = true; self.cancel_from_any_page = true;
self.button_prev = Button::with_icon(theme::ICON_BACK).initially_enabled(false); self.button_prev = Button::with_icon(theme::ICON_BACK).initially_enabled(false);
@ -328,6 +335,11 @@ where
count // Content fits on a single page. count // Content fits on a single page.
} }
}; };
let page_count = if let Some(limit) = self.page_limit {
page_count.min(limit)
} else {
page_count
};
if page_count == 1 && self.button_cancel.is_none() { if page_count == 1 && self.button_cancel.is_none() {
self.button_confirm.place(layout.button_both); self.button_confirm.place(layout.button_both);

View File

@ -198,6 +198,7 @@ where
PageMsg::Content(_) => Err(Error::TypeError), PageMsg::Content(_) => Err(Error::TypeError),
PageMsg::Confirmed => Ok(CONFIRMED.as_obj()), PageMsg::Confirmed => Ok(CONFIRMED.as_obj()),
PageMsg::Cancelled => Ok(CANCELLED.as_obj()), PageMsg::Cancelled => Ok(CANCELLED.as_obj()),
PageMsg::Info => Ok(INFO.as_obj()),
PageMsg::SwipeLeft => Ok(INFO.as_obj()), PageMsg::SwipeLeft => Ok(INFO.as_obj()),
PageMsg::SwipeRight => Ok(CANCELLED.as_obj()), PageMsg::SwipeRight => Ok(CANCELLED.as_obj()),
} }
@ -412,6 +413,7 @@ struct ConfirmBlobParams {
hold: bool, hold: bool,
chunkify: bool, chunkify: bool,
text_mono: bool, text_mono: bool,
page_limit: Option<usize>,
} }
impl ConfirmBlobParams { impl ConfirmBlobParams {
@ -435,6 +437,7 @@ impl ConfirmBlobParams {
hold, hold,
chunkify: false, chunkify: false,
text_mono: true, text_mono: true,
page_limit: None,
} }
} }
@ -463,6 +466,11 @@ impl ConfirmBlobParams {
self self
} }
fn with_page_limit(mut self, page_limit: Option<usize>) -> Self {
self.page_limit = page_limit;
self
}
fn into_layout(self) -> Result<Obj, Error> { fn into_layout(self) -> Result<Obj, Error> {
let paragraphs = ConfirmBlob { let paragraphs = ConfirmBlob {
description: self.description.unwrap_or("".into()), description: self.description.unwrap_or("".into()),
@ -488,10 +496,12 @@ impl ConfirmBlobParams {
if self.hold { if self.hold {
page = page.with_hold()? page = page.with_hold()?
} }
page = page.with_page_limit(self.page_limit);
let mut frame = Frame::left_aligned(theme::label_title(), self.title, page); let mut frame = Frame::left_aligned(theme::label_title(), self.title, page);
if let Some(subtitle) = self.subtitle { if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(theme::label_subtitle(), subtitle); frame = frame.with_subtitle(theme::label_subtitle(), subtitle);
} }
if self.info_button { if self.info_button {
frame = frame.with_info_button(); frame = frame.with_info_button();
} }
@ -506,7 +516,11 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
let description: Option<TString> = let description: Option<TString> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let extra: Option<TString> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?;
let extra: Option<TString> = kwargs
.get(Qstr::MP_QSTR_extra)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let verb: Option<TString> = kwargs let verb: Option<TString> = kwargs
.get(Qstr::MP_QSTR_verb) .get(Qstr::MP_QSTR_verb)
.unwrap_or_else(|_| Obj::const_none()) .unwrap_or_else(|_| Obj::const_none())
@ -515,12 +529,20 @@ extern "C" fn new_confirm_blob(n_args: usize, args: *const Obj, kwargs: *mut Map
.get(Qstr::MP_QSTR_verb_cancel) .get(Qstr::MP_QSTR_verb_cancel)
.unwrap_or_else(|_| Obj::const_none()) .unwrap_or_else(|_| Obj::const_none())
.try_into_option()?; .try_into_option()?;
let info: bool = kwargs.get_or(Qstr::MP_QSTR_info, false)?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let page_limit: Option<usize> = kwargs
.get(Qstr::MP_QSTR_page_limit)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
ConfirmBlobParams::new(title, data, description, verb, verb_cancel, hold) ConfirmBlobParams::new(title, data, description, verb, verb_cancel, hold)
.with_text_mono(text_mono)
.with_extra(extra) .with_extra(extra)
.with_chunkify(chunkify) .with_chunkify(chunkify)
.with_info_button(info)
.with_page_limit(page_limit)
.into_layout() .into_layout()
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1139,6 +1161,8 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; let button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let button_style_confirm: bool =
kwargs.get_or(Qstr::MP_QSTR_button_style_confirm, false)?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let mut paragraphs = ParagraphVecLong::new(); let mut paragraphs = ParagraphVecLong::new();
@ -1155,7 +1179,11 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
title, title,
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG) ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
.with_cancel_confirm(None, Some(button)) .with_cancel_confirm(None, Some(button))
.with_confirm_style(theme::button_default()) .with_confirm_style(if button_style_confirm {
theme::button_confirm()
} else {
theme::button_default()
})
.with_back_button(), .with_back_button(),
))?; ))?;
Ok(obj.into()) Ok(obj.into())
@ -1762,12 +1790,19 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str, /// title: str,
/// data: str | bytes, /// data: str | bytes,
/// description: str | None, /// description: str | None,
/// extra: str | None, /// description_font_green: bool = False,
/// text_mono: bool = True,
/// extra: str | None = None,
/// subtitle: str | None = None,
/// verb: str | None = None, /// verb: str | None = None,
/// verb_cancel: str | None = None, /// verb_cancel: str | None = None,
/// verb_info: str | None = None,
/// info: bool = True,
/// hold: bool = False, /// hold: bool = False,
/// chunkify: bool = False, /// chunkify: bool = False,
/// prompt_screen: bool = False, /// prompt_screen: bool = False,
/// default_cancel: bool = False,
/// page_limit: int | None = None,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data.""" /// """Confirm byte sequence data."""
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(), Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),
@ -1959,6 +1994,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// *, /// *,
/// title: str, /// title: str,
/// button: str, /// button: str,
/// button_style_confirm: bool = False,
/// items: Iterable[tuple[int, str | bytes]], /// items: Iterable[tuple[int, str | bytes]],
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Confirm long content with the possibility to go back from any page. /// """Confirm long content with the possibility to go back from any page.

View File

@ -118,12 +118,19 @@ def confirm_blob(
title: str, title: str,
data: str | bytes, data: str | bytes,
description: str | None, description: str | None,
extra: str | None, description_font_green: bool = False,
text_mono: bool = True,
extra: str | None = None,
subtitle: str | None = None,
verb: str | None = None, verb: str | None = None,
verb_cancel: str | None = None, verb_cancel: str | None = None,
verb_info: str | None = None,
info: bool = True,
hold: bool = False, hold: bool = False,
chunkify: bool = False, chunkify: bool = False,
prompt_screen: bool = False, prompt_screen: bool = False,
default_cancel: bool = False,
page_limit: int | None = None,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Confirm byte sequence data.""" """Confirm byte sequence data."""
@ -675,12 +682,19 @@ def confirm_blob(
title: str, title: str,
data: str | bytes, data: str | bytes,
description: str | None, description: str | None,
extra: str | None, description_font_green: bool = False,
text_mono: bool = True,
extra: str | None = None,
subtitle: str | None = None,
verb: str = "CONFIRM", verb: str = "CONFIRM",
verb_cancel: str | None = None, verb_cancel: str | None = None,
verb_info: str | None = None,
info: bool = True,
hold: bool = False, hold: bool = False,
chunkify: bool = False, chunkify: bool = False,
prompt_screen: bool = False, prompt_screen: bool = False,
default_cancel: bool = False,
page_limit: int | None = None,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Confirm byte sequence data.""" """Confirm byte sequence data."""
@ -1230,12 +1244,19 @@ def confirm_blob(
title: str, title: str,
data: str | bytes, data: str | bytes,
description: str | None, description: str | None,
extra: str | None, description_font_green: bool = False,
text_mono: bool = True,
extra: str | None = None,
subtitle: str | None = None,
verb: str | None = None, verb: str | None = None,
verb_cancel: str | None = None, verb_cancel: str | None = None,
verb_info: str | None = None,
info: bool = True,
hold: bool = False, hold: bool = False,
chunkify: bool = False, chunkify: bool = False,
prompt_screen: bool = False, prompt_screen: bool = False,
default_cancel: bool = False,
page_limit: int | None = None,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Confirm byte sequence data.""" """Confirm byte sequence data."""
@ -1444,6 +1465,7 @@ def confirm_more(
*, *,
title: str, title: str,
button: str, button: str,
button_style_confirm: bool = False,
items: Iterable[tuple[int, str | bytes]], items: Iterable[tuple[int, str | bytes]],
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:
"""Confirm long content with the possibility to go back from any page. """Confirm long content with the possibility to go back from any page.

View File

@ -121,6 +121,7 @@ class TR:
buttons__try_again: str = "Try again" buttons__try_again: str = "Try again"
buttons__turn_off: str = "Turn off" buttons__turn_off: str = "Turn off"
buttons__turn_on: str = "Turn on" buttons__turn_on: str = "Turn on"
buttons__view_all_data: str = "View all data"
cardano__addr_base: str = "Base" cardano__addr_base: str = "Base"
cardano__addr_enterprise: str = "Enterprise" cardano__addr_enterprise: str = "Enterprise"
cardano__addr_legacy: str = "Legacy" cardano__addr_legacy: str = "Legacy"
@ -298,9 +299,10 @@ class TR:
ethereum__data_size_template: str = "Size: {0} bytes" ethereum__data_size_template: str = "Size: {0} bytes"
ethereum__gas_limit: str = "Gas limit" ethereum__gas_limit: str = "Gas limit"
ethereum__gas_price: str = "Gas price" ethereum__gas_price: str = "Gas price"
ethereum__interaction_contract: str = "Interaction contract"
ethereum__max_gas_price: str = "Max gas price" ethereum__max_gas_price: str = "Max gas price"
ethereum__name_and_version: str = "Name and version" ethereum__name_and_version: str = "Name and version"
ethereum__new_contract: str = "new contract?" ethereum__new_contract: str = "New contract will be deployed"
ethereum__no_message_field: str = "No message field" ethereum__no_message_field: str = "No message field"
ethereum__priority_fee: str = "Priority fee" ethereum__priority_fee: str = "Priority fee"
ethereum__show_full_array: str = "Show full array" ethereum__show_full_array: str = "Show full array"
@ -316,13 +318,15 @@ class TR:
ethereum__staking_stake_intro: str = "Stake ETH on Everstake?" ethereum__staking_stake_intro: str = "Stake ETH on Everstake?"
ethereum__staking_unstake: str = "Unstake" ethereum__staking_unstake: str = "Unstake"
ethereum__staking_unstake_intro: str = "Unstake ETH from Everstake?" ethereum__staking_unstake_intro: str = "Unstake ETH from Everstake?"
ethereum__title_confirm_data: str = "Confirm data"
ethereum__title_confirm_domain: str = "Confirm domain" ethereum__title_confirm_domain: str = "Confirm domain"
ethereum__title_confirm_message: str = "Confirm message" ethereum__title_confirm_message: str = "Confirm message"
ethereum__title_confirm_struct: str = "Confirm struct" ethereum__title_confirm_struct: str = "Confirm struct"
ethereum__title_confirm_typed_data: str = "Confirm typed data" ethereum__title_confirm_typed_data: str = "Confirm typed data"
ethereum__title_input_data: str = "Input data"
ethereum__title_signing_address: str = "Signing address" ethereum__title_signing_address: str = "Signing address"
ethereum__token_contract: str = "Token contract"
ethereum__units_template: str = "{0} units" ethereum__units_template: str = "{0} units"
ethereum__unknown_contract_address: str = "Unknown contract address. Continue only if you know what you are doing."
ethereum__unknown_token: str = "Unknown token" ethereum__unknown_token: str = "Unknown token"
ethereum__valid_signature: str = "The signature is valid." ethereum__valid_signature: str = "The signature is valid."
experimental_mode__enable: str = "Enable experimental features?" experimental_mode__enable: str = "Enable experimental features?"
@ -396,6 +400,7 @@ class TR:
instructions__swipe_up: str = "Swipe up" instructions__swipe_up: str = "Swipe up"
instructions__tap_to_confirm: str = "Tap to confirm" instructions__tap_to_confirm: str = "Tap to confirm"
instructions__tap_to_start: str = "Tap to start" instructions__tap_to_start: str = "Tap to start"
instructions__view_all_data: str = "View all data in the menu."
joint__title: str = "Joint transaction" joint__title: str = "Joint transaction"
joint__to_the_total_amount: str = "To the total amount:" joint__to_the_total_amount: str = "To the total amount:"
joint__you_are_contributing: str = "You are contributing:" joint__you_are_contributing: str = "You are contributing:"

View File

@ -4,6 +4,7 @@ from trezor import TR, ui
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.ui.layouts import ( from trezor.ui.layouts import (
confirm_blob, confirm_blob,
confirm_blob_with_optional_pagination,
confirm_ethereum_staking_tx, confirm_ethereum_staking_tx,
confirm_text, confirm_text,
should_show_more, should_show_more,
@ -35,18 +36,13 @@ async def require_confirm_tx(
fee_info_items: Iterable[tuple[str, str]], fee_info_items: Iterable[tuple[str, str]],
network: EthereumNetworkInfo, network: EthereumNetworkInfo,
token: EthereumTokenInfo | None, token: EthereumTokenInfo | None,
is_contract_interaction: bool,
chunkify: bool, chunkify: bool,
) -> None: ) -> None:
from trezor.ui.layouts import confirm_ethereum_tx from trezor.ui.layouts import confirm_ethereum_tx
if to_bytes: to_str = address_from_bytes(to_bytes, network) if to_bytes else None
to_str = address_from_bytes(to_bytes, network)
else:
to_str = TR.ethereum__new_contract
chunkify = False
total_amount = format_ethereum_amount(value, token, network) total_amount = format_ethereum_amount(value, token, network)
account, account_path = get_account_and_path(address_n) account, account_path = get_account_and_path(address_n)
await confirm_ethereum_tx( await confirm_ethereum_tx(
@ -56,6 +52,7 @@ async def require_confirm_tx(
account_path, account_path,
maximum_fee, maximum_fee,
fee_info_items, fee_info_items,
is_contract_interaction,
chunkify=chunkify, chunkify=chunkify,
) )
@ -145,17 +142,26 @@ async def require_confirm_claim(
) )
def require_confirm_unknown_token(address_bytes: bytes) -> Awaitable[None]: async def require_confirm_unknown_token(address_bytes: bytes):
from ubinascii import hexlify from ubinascii import hexlify
from trezor.ui.layouts import confirm_address from trezor.ui.layouts import confirm_address, show_warning
await show_warning(
"unknown_contract_warning",
TR.ethereum__unknown_contract_address,
default_cancel=True,
verb_cancel=TR.send__cancel_sign,
br_code=ButtonRequestType.Other,
)
contract_address_hex = "0x" + hexlify(address_bytes).decode() contract_address_hex = "0x" + hexlify(address_bytes).decode()
return confirm_address( await confirm_address(
TR.ethereum__unknown_token, TR.words__address,
contract_address_hex, contract_address_hex,
TR.ethereum__contract, subtitle=TR.ethereum__token_contract,
"unknown_token", verb=TR.buttons__continue,
br_name="unknown_token",
br_code=ButtonRequestType.SignTx, br_code=ButtonRequestType.SignTx,
) )
@ -174,13 +180,14 @@ def require_confirm_address(address_bytes: bytes) -> Awaitable[None]:
def require_confirm_other_data(data: bytes, data_total: int) -> Awaitable[None]: def require_confirm_other_data(data: bytes, data_total: int) -> Awaitable[None]:
return confirm_blob( return confirm_blob_with_optional_pagination(
"confirm_data", "confirm_data",
TR.ethereum__title_confirm_data, TR.ethereum__title_input_data,
data, data,
TR.ethereum__data_size_template.format(data_total), subtitle=TR.ethereum__data_size_template.format(data_total),
verb=TR.buttons__confirm,
verb_cancel=TR.send__cancel_sign,
br_code=ButtonRequestType.SignTx, br_code=ButtonRequestType.SignTx,
ask_pagination=True,
) )
@ -301,7 +308,6 @@ async def confirm_typed_value(
title, title,
data, data,
description, description,
ask_pagination=True,
) )
else: else:
await confirm_text( await confirm_text(

View File

@ -56,7 +56,7 @@ async def sign_tx(
raise DataError("Fee overflow") raise DataError("Fee overflow")
check_common_fields(msg) check_common_fields(msg)
# have a user confirm signing # have the user confirm signing
await paths.validate_path(keychain, msg.address_n) await paths.validate_path(keychain, msg.address_n)
address_bytes = bytes_from_address(msg.to) address_bytes = bytes_from_address(msg.to)
gas_price = int.from_bytes(msg.gas_price, "big") gas_price = int.from_bytes(msg.gas_price, "big")
@ -130,9 +130,11 @@ async def confirm_tx_data(
return return
# Handle ERC-20, currently only 'transfer' function # Handle ERC-20, currently only 'transfer' function
token, recipient, value = await handle_erc20_transfer(msg, defs, address_bytes) token, recipient, value = await _handle_erc20_transfer(msg, defs, address_bytes)
if token is None and data_total_len > 0: is_contract_interaction = token is None and data_total_len > 0
if is_contract_interaction:
await require_confirm_other_data(msg.data_initial_chunk, data_total_len) await require_confirm_other_data(msg.data_initial_chunk, data_total_len)
await require_confirm_tx( await require_confirm_tx(
@ -143,7 +145,8 @@ async def confirm_tx_data(
fee_items, fee_items,
defs.network, defs.network,
token, token,
bool(msg.chunkify), is_contract_interaction=is_contract_interaction,
chunkify=bool(msg.chunkify),
) )
@ -189,7 +192,7 @@ async def handle_staking(
return False return False
async def handle_erc20_transfer( async def _handle_erc20_transfer(
msg: MsgInSignTx, msg: MsgInSignTx,
definitions: Definitions, definitions: Definitions,
address_bytes: bytes, address_bytes: bytes,

View File

@ -31,10 +31,9 @@ async def get_ecdh_session_key(msg: GetECDHSessionKey) -> ECDHSessionKey:
# require_confirm_ecdh_session_key # require_confirm_ecdh_session_key
proto = msg_identity.proto.upper() if msg_identity.proto else "identity" proto = msg_identity.proto.upper() if msg_identity.proto else "identity"
await confirm_address( await confirm_address(
# TODO: translate? f"Decrypt {proto}", # TODO: translate?
f"Decrypt {proto}",
serialize_identity_without_proto(msg_identity), serialize_identity_without_proto(msg_identity),
"", chunkify=False,
) )
# END require_confirm_ecdh_session_key # END require_confirm_ecdh_session_key

View File

@ -61,7 +61,7 @@ async def _require_confirm_address(action: str, address: str) -> None:
await confirm_address( await confirm_address(
TR.nem__confirm_address, TR.nem__confirm_address,
address, address,
action, description=action,
"confirm_multisig", br_name="confirm_multisig",
ButtonRequestType.ConfirmOutput, br_code=ButtonRequestType.ConfirmOutput,
) )

View File

@ -24,8 +24,8 @@ async def require_confirm_init(
await layouts.confirm_address( await layouts.confirm_address(
TR.stellar__confirm_stellar, TR.stellar__confirm_stellar,
address, address,
description, description=description,
"confirm_init", br_name="confirm_init",
) )
# get_network_warning # get_network_warning

View File

@ -37,8 +37,8 @@ async def confirm_source_account(source_account: str) -> None:
await confirm_address( await confirm_address(
TR.stellar__confirm_operation, TR.stellar__confirm_operation,
source_account, source_account,
TR.stellar__source_account, description=TR.stellar__source_account,
"op_source_account", br_name="op_source_account",
) )
@ -57,8 +57,8 @@ async def confirm_account_merge_op(op: StellarAccountMergeOp) -> None:
await confirm_address( await confirm_address(
TR.stellar__account_merge, TR.stellar__account_merge,
op.destination_account, op.destination_account,
TR.stellar__all_will_be_sent_to, description=TR.stellar__all_will_be_sent_to,
"op_account_merge", br_name="op_account_merge",
) )
@ -240,8 +240,8 @@ async def confirm_set_options_op(op: StellarSetOptionsOp) -> None:
await confirm_address( await confirm_address(
TR.stellar__inflation, TR.stellar__inflation,
op.inflation_destination_account, op.inflation_destination_account,
TR.stellar__destination, description=TR.stellar__destination,
"op_inflation", br_name="op_inflation",
) )
if op.clear_flags: if op.clear_flags:
@ -339,6 +339,6 @@ async def confirm_asset_issuer(asset: StellarAsset) -> None:
await confirm_address( await confirm_address(
TR.stellar__confirm_issuer, TR.stellar__confirm_issuer,
asset.issuer, asset.issuer,
TR.stellar__issuer_template.format(asset.code), description=TR.stellar__issuer_template.format(asset.code),
"confirm_asset_issuer", br_name="confirm_asset_issuer",
) )

View File

@ -30,9 +30,9 @@ async def require_confirm_origination(address: str) -> None:
await confirm_address( await confirm_address(
TR.tezos__confirm_origination, TR.tezos__confirm_origination,
address, address,
f"{TR.words__address}:", description=f"{TR.words__address}:",
"confirm_origination", br_name="confirm_origination",
BR_SIGN_TX, br_code=BR_SIGN_TX,
) )
@ -52,9 +52,9 @@ async def require_confirm_delegation_baker(baker: str) -> None:
await confirm_address( await confirm_address(
TR.tezos__confirm_delegation, TR.tezos__confirm_delegation,
baker, baker,
TR.tezos__baker_address, description=TR.tezos__baker_address,
"confirm_delegation", br_name="confirm_delegation",
BR_SIGN_TX, br_code=BR_SIGN_TX,
) )
@ -121,9 +121,9 @@ async def require_confirm_delegation_manager_withdraw(address: str) -> None:
await confirm_address( await confirm_address(
TR.tezos__remove_delegation, TR.tezos__remove_delegation,
address, address,
TR.tezos__delegator, description=TR.tezos__delegator,
"confirm_undelegation", br_name="confirm_undelegation",
BR_SIGN_TX, br_code=BR_SIGN_TX,
) )

View File

@ -592,9 +592,26 @@ def show_warning(
content: str, content: str,
subheader: str | None = None, subheader: str | None = None,
button: str | None = None, button: str | None = None,
default_cancel: bool = False,
verb_cancel: str | None = None,
br_code: ButtonRequestType = ButtonRequestType.Warning, br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> Awaitable[None]: ) -> Awaitable[None]:
button = button or TR.buttons__continue # def_arg button = button or TR.buttons__continue # def_arg
if default_cancel:
# a kind of warning which makes it easy (swipe up) to cancel
# and makes it harder to continue
return confirm_blob(
br_name,
TR.words__warning,
content,
text_mono=False,
verb_cancel=verb_cancel,
default_cancel=True,
prompt_screen=False,
br_code=br_code,
)
else:
# traditional warning
return raise_if_not_confirmed( return raise_if_not_confirmed(
interact( interact(
RustLayout( RustLayout(
@ -750,43 +767,56 @@ async def should_show_more(
raise ActionCancelled raise ActionCancelled
async def _confirm_ask_pagination( async def confirm_blob_with_optional_pagination(
br_name: str, br_name: str,
title: str, title: str,
data: bytes | str, data: bytes | str,
description: str, subtitle: str | None = None,
br_code: ButtonRequestType, verb: str | None = None,
) -> None: verb_cancel: str | None = None,
paginated: ui.Layout | None = None br_code: ButtonRequestType = BR_CODE_OTHER,
# TODO: make should_show_more/confirm_more accept bytes directly chunkify: bool = False,
if isinstance(data, bytes):
from ubinascii import hexlify
data = hexlify(data).decode()
while True:
if not await should_show_more(
title,
para=[(ui.NORMAL, description), (ui.MONO, data)],
br_name=br_name,
br_code=br_code,
): ):
return # show first page first
layout = RustLayout(
if paginated is None: trezorui2.confirm_blob(
paginated = RustLayout(
trezorui2.confirm_more(
title=title, title=title,
button=TR.buttons__close, data=data,
items=[(ui.MONO, data)], description=TR.instructions__view_all_data,
description_font_green=True,
subtitle=subtitle,
verb=verb,
verb_cancel=verb_cancel,
verb_info=TR.buttons__view_all_data,
info=True,
hold=False,
chunkify=chunkify,
prompt_screen=False,
page_limit=1,
) )
) )
else: result = await interact(
paginated.request_complete_repaint() layout,
br_name,
result = await interact(paginated, br_name, br_code) br_code,
assert result in (CONFIRMED, CANCELLED) )
if result is INFO:
assert False # user requested to view the whole blob
await confirm_blob(
br_name=br_name,
title=title,
data=data,
description=None,
verb=None,
verb_cancel=verb_cancel,
info=False,
hold=False,
br_code=br_code,
chunkify=chunkify,
prompt_screen=False,
)
elif result is not CONFIRMED:
raise ActionCancelled
def confirm_blob( def confirm_blob(
@ -794,33 +824,33 @@ def confirm_blob(
title: str, title: str,
data: bytes | str, data: bytes | str,
description: str | None = None, description: str | None = None,
text_mono: bool = True,
subtitle: str | None = None,
verb: str | None = None, verb: str | None = None,
verb_cancel: str | None = None, verb_cancel: str | None = None,
info: bool = True,
hold: bool = False, hold: bool = False,
br_code: ButtonRequestType = BR_CODE_OTHER, br_code: ButtonRequestType = BR_CODE_OTHER,
ask_pagination: bool = False,
chunkify: bool = False, chunkify: bool = False,
default_cancel: bool = False,
prompt_screen: bool = True, prompt_screen: bool = True,
) -> Awaitable[None]: ) -> Awaitable[None]:
layout = RustLayout( layout = RustLayout(
trezorui2.confirm_blob( trezorui2.confirm_blob(
title=title, title=title,
description=description,
data=data, data=data,
extra=None, description=description,
hold=hold, text_mono=text_mono,
subtitle=subtitle,
verb=verb, verb=verb,
verb_cancel=verb_cancel, verb_cancel=verb_cancel,
info=info,
hold=hold,
chunkify=chunkify, chunkify=chunkify,
prompt_screen=prompt_screen, prompt_screen=prompt_screen,
default_cancel=default_cancel,
) )
) )
if ask_pagination and layout.page_count() > 1:
assert not hold
return _confirm_ask_pagination(br_name, title, data, description or "", br_code)
else:
return raise_if_not_confirmed( return raise_if_not_confirmed(
interact( interact(
layout, layout,
@ -833,7 +863,10 @@ def confirm_blob(
def confirm_address( def confirm_address(
title: str, title: str,
address: str, address: str,
subtitle: str | None = None,
description: str | None = None, description: str | None = None,
verb: str | None = None,
chunkify: bool = True,
br_name: str = "confirm_address", br_name: str = "confirm_address",
br_code: ButtonRequestType = BR_CODE_OTHER, br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]: ) -> Awaitable[None]:
@ -843,7 +876,9 @@ def confirm_address(
description or "", description or "",
br_name, br_name,
br_code, br_code,
verb=TR.buttons__confirm, subtitle=subtitle,
verb=(verb or TR.buttons__confirm),
chunkify=chunkify,
) )
@ -893,6 +928,7 @@ def confirm_value(
subtitle: str | None = None, subtitle: str | None = None,
hold: bool = False, hold: bool = False,
value_text_mono: bool = True, value_text_mono: bool = True,
chunkify: bool = False,
info_items: Iterable[tuple[str, str]] | None = None, info_items: Iterable[tuple[str, str]] | None = None,
info_title: str | None = None, info_title: str | None = None,
chunkify_info: bool = False, chunkify_info: bool = False,
@ -922,6 +958,7 @@ def confirm_value(
verb=verb, verb=verb,
hold=hold, hold=hold,
info_button=bool(info_items), info_button=bool(info_items),
chunkify=chunkify,
text_mono=value_text_mono, text_mono=value_text_mono,
) )
), ),
@ -1033,12 +1070,13 @@ def _confirm_summary(
if not utils.BITCOIN_ONLY: if not utils.BITCOIN_ONLY:
async def confirm_ethereum_tx( async def confirm_ethereum_tx(
recipient: str, recipient: str | None,
total_amount: str, total_amount: str,
account: str | None, account: str | None,
account_path: str | None, account_path: str | None,
maximum_fee: str, maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]], fee_info_items: Iterable[tuple[str, str]],
is_contract_interaction: bool,
br_name: str = "confirm_ethereum_tx", br_name: str = "confirm_ethereum_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx, br_code: ButtonRequestType = ButtonRequestType.SignTx,
chunkify: bool = False, chunkify: bool = False,
@ -1047,10 +1085,14 @@ if not utils.BITCOIN_ONLY:
RustLayout( RustLayout(
trezorui2.flow_confirm_output( trezorui2.flow_confirm_output(
title=TR.words__address, title=TR.words__address,
subtitle=TR.words__recipient, subtitle=(
message=recipient, TR.words__recipient
if not is_contract_interaction
else TR.ethereum__interaction_contract
),
message=(recipient or TR.ethereum__new_contract),
amount=None, amount=None,
chunkify=chunkify, chunkify=(chunkify if recipient else False),
text_mono=True, text_mono=True,
account=account, account=account,
account_path=account_path, account_path=account_path,
@ -1197,7 +1239,7 @@ def confirm_replacement(title: str, txid: str) -> Awaitable[None]:
title, title,
txid, txid,
TR.send__transaction_id, TR.send__transaction_id,
TR.buttons__continue, verb=TR.buttons__continue,
br_code=ButtonRequestType.SignTx, br_code=ButtonRequestType.SignTx,
) )
@ -1215,7 +1257,6 @@ async def confirm_modify_output(
verb=TR.buttons__continue, verb=TR.buttons__continue,
verb_cancel=None, verb_cancel=None,
description=f"{TR.words__address}:", description=f"{TR.words__address}:",
extra=None,
) )
) )
modify_layout = RustLayout( modify_layout = RustLayout(
@ -1381,7 +1422,6 @@ async def confirm_signverify(
title=TR.sign_message__confirm_message, title=TR.sign_message__confirm_message,
description=None, description=None,
data=message, data=message,
extra=None,
hold=not verify, hold=not verify,
verb=TR.buttons__confirm if verify else None, verb=TR.buttons__confirm if verify else None,
) )

View File

@ -725,6 +725,8 @@ def show_warning(
content: str, content: str,
subheader: str | None = None, subheader: str | None = None,
button: str | None = None, button: str | None = None,
default_cancel: bool = False, # NB: model R does not implement "default_cancel"-style warnings
verb_cancel: str | None = None,
br_code: ButtonRequestType = ButtonRequestType.Warning, br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> Awaitable[None]: ) -> Awaitable[None]:
from trezor import translations from trezor import translations
@ -901,18 +903,86 @@ async def should_show_more(
raise ActionCancelled raise ActionCancelled
async def confirm_blob_with_optional_pagination(
br_name: str,
title: str,
data: bytes | str,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
br_code: ButtonRequestType = BR_CODE_OTHER,
chunkify: bool = False,
):
while True:
# show first page first
layout = RustLayout(
trezorui2.confirm_blob(
title=title,
data=data,
description=None,
subtitle=subtitle,
verb=verb,
verb_cancel="",
verb_info=TR.buttons__view_all_data,
info=True,
hold=False,
chunkify=chunkify,
prompt_screen=False,
page_limit=1,
)
)
result = await interact(
layout,
br_name,
br_code,
)
if result is CONFIRMED:
break
elif result is INFO:
# user requested to view the whole blob
layout_whole = RustLayout(
trezorui2.confirm_blob(
title=title,
description=None,
data=data,
verb=verb,
verb_cancel="<",
hold=False,
chunkify=chunkify,
)
)
result_whole = await interact(
layout_whole,
br_name,
br_code,
)
if result_whole is CONFIRMED:
break
elif result_whole is CANCELLED:
# CANCELED seeing all pages => back to showing only the first page
continue
else:
raise ActionCancelled
else:
raise ActionCancelled
def confirm_blob( def confirm_blob(
br_name: str, br_name: str,
title: str, title: str,
data: bytes | str, data: bytes | str,
description: str | None = None, description: str | None = None,
text_mono: bool = True,
subtitle: str | None = None,
verb: str | None = None, verb: str | None = None,
verb_cancel: str | None = "", # icon verb_cancel: str | None = "", # icon
info: bool = True,
hold: bool = False, hold: bool = False,
br_code: ButtonRequestType = BR_CODE_OTHER, br_code: ButtonRequestType = BR_CODE_OTHER,
ask_pagination: bool = False,
chunkify: bool = False, chunkify: bool = False,
default_cancel: bool = False,
prompt_screen: bool = True, prompt_screen: bool = True,
ask_pagination: bool = False,
) -> Awaitable[None]: ) -> Awaitable[None]:
verb = verb or TR.buttons__confirm # def_arg verb = verb or TR.buttons__confirm # def_arg
layout = RustLayout( layout = RustLayout(
@ -920,7 +990,6 @@ def confirm_blob(
title=title, title=title,
description=description, description=description,
data=data, data=data,
extra=None,
verb=verb, verb=verb,
verb_cancel=verb_cancel, verb_cancel=verb_cancel,
hold=hold, hold=hold,
@ -991,13 +1060,16 @@ async def _confirm_ask_pagination(
def confirm_address( def confirm_address(
title: str, title: str,
address: str, address: str,
subtitle: str | None = None,
description: str | None = None, description: str | None = None,
verb: str | None = None,
chunkify: bool = True,
br_name: str = "confirm_address", br_name: str = "confirm_address",
br_code: ButtonRequestType = BR_CODE_OTHER, br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]: ) -> Awaitable[None]:
return confirm_blob( return confirm_blob(
br_name, br_name,
title, subtitle or title,
address, address,
description, description,
br_code=br_code, br_code=br_code,
@ -1139,7 +1211,6 @@ async def confirm_value(
title=info_title, title=info_title,
data=info_value, data=info_value,
description=description, description=description,
extra=None,
verb="", verb="",
verb_cancel="<", verb_cancel="<",
hold=False, hold=False,
@ -1298,12 +1369,13 @@ if not utils.BITCOIN_ONLY:
) )
async def confirm_ethereum_tx( async def confirm_ethereum_tx(
recipient: str, recipient: str | None,
total_amount: str, total_amount: str,
_account: str | None, _account: str | None,
_account_path: str | None, _account_path: str | None,
maximum_fee: str, maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]], fee_info_items: Iterable[tuple[str, str]],
is_contract_interaction: bool,
br_name: str = "confirm_ethereum_tx", br_name: str = "confirm_ethereum_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx, br_code: ButtonRequestType = ButtonRequestType.SignTx,
chunkify: bool = False, chunkify: bool = False,
@ -1319,14 +1391,19 @@ if not utils.BITCOIN_ONLY:
) )
) )
if not is_contract_interaction:
title = TR.words__recipient
else:
title = TR.ethereum__interaction_contract if recipient else ""
while True: while True:
# Allowing going back and forth between recipient and summary/details # Allowing going back and forth between recipient and summary/details
await confirm_blob( await confirm_blob(
br_name, br_name,
TR.words__recipient, title,
recipient, recipient or TR.ethereum__new_contract,
verb=TR.buttons__continue, verb=TR.buttons__continue,
chunkify=chunkify, chunkify=(chunkify if recipient else False),
) )
try: try:
@ -1397,9 +1474,7 @@ async def confirm_modify_output(
title=TR.modify_amount__title, title=TR.modify_amount__title,
data=address, data=address,
verb=TR.buttons__continue, verb=TR.buttons__continue,
verb_cancel=None,
description=f"{TR.words__address}:", description=f"{TR.words__address}:",
extra=None,
) )
) )
modify_layout = RustLayout( modify_layout = RustLayout(
@ -1510,7 +1585,6 @@ async def confirm_signverify(
verb=TR.buttons__continue, verb=TR.buttons__continue,
br_code=BR_CODE_OTHER, br_code=BR_CODE_OTHER,
) )
try: try:
await confirm_blob( await confirm_blob(
br_name, br_name,
@ -1658,7 +1732,7 @@ def pin_mismatch_popup(is_wipe_code: bool = False) -> Awaitable[None]:
description, description,
TR.pin__please_check_again, TR.pin__please_check_again,
TR.buttons__check_again, TR.buttons__check_again,
BR_CODE_OTHER, br_code=BR_CODE_OTHER,
) )

View File

@ -153,4 +153,4 @@ async def show_recovery_warning(
br_code: ButtonRequestType = ButtonRequestType.Warning, br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> None: ) -> None:
button = button or TR.buttons__try_again # def_arg button = button or TR.buttons__try_again # def_arg
await show_warning(br_name, content, subheader, button, br_code) await show_warning(br_name, content, subheader, button, br_code=br_code)

View File

@ -276,7 +276,7 @@ async def show_warning_backup() -> None:
TR.words__title_remember, TR.words__title_remember,
TR.reset__never_make_digital_copy, TR.reset__never_make_digital_copy,
TR.buttons__ok_i_understand, TR.buttons__ok_i_understand,
ButtonRequestType.ResetDevice, br_code=ButtonRequestType.ResetDevice,
) )

View File

@ -644,6 +644,8 @@ def show_warning(
content: str, content: str,
subheader: str | None = None, subheader: str | None = None,
button: str | None = None, button: str | None = None,
default_cancel: bool = False, # NB: model T does not implement "default_cancel"-style warnings
verb_cancel: str | None = None,
br_code: ButtonRequestType = ButtonRequestType.Warning, br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> Awaitable[None]: ) -> Awaitable[None]:
button = button or TR.buttons__continue # def_arg button = button or TR.buttons__continue # def_arg
@ -821,6 +823,42 @@ async def should_show_more(
raise ActionCancelled raise ActionCancelled
async def confirm_blob_with_optional_pagination(
br_name: str,
title: str,
data: bytes | str,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
br_code: ButtonRequestType = BR_CODE_OTHER,
chunkify: bool = False,
):
verb = verb or TR.buttons__confirm # def_arg
layout = RustLayout(
trezorui2.confirm_blob(
title=title,
description=None,
data=data,
verb=verb,
verb_cancel=verb_cancel,
chunkify=chunkify,
)
)
if layout.page_count() > 1:
return await _confirm_ask_pagination(
br_name, title, data, subtitle or "", br_code
)
else:
return raise_if_not_confirmed(
interact(
layout,
br_name,
br_code,
)
)
async def _confirm_ask_pagination( async def _confirm_ask_pagination(
br_name: str, br_name: str,
title: str, title: str,
@ -847,7 +885,8 @@ async def _confirm_ask_pagination(
paginated = RustLayout( paginated = RustLayout(
trezorui2.confirm_more( trezorui2.confirm_more(
title=title, title=title,
button=TR.buttons__close, button=TR.buttons__confirm,
button_style_confirm=True,
items=[(ui.MONO, data)], items=[(ui.MONO, data)],
) )
) )
@ -856,6 +895,8 @@ async def _confirm_ask_pagination(
result = await interact(paginated, br_name, br_code) result = await interact(paginated, br_name, br_code)
assert result in (CONFIRMED, CANCELLED) assert result in (CONFIRMED, CANCELLED)
if result is CONFIRMED:
return
assert False assert False
@ -865,12 +906,15 @@ def confirm_blob(
title: str, title: str,
data: bytes | str, data: bytes | str,
description: str | None = None, description: str | None = None,
text_mono: bool = True,
subtitle: str | None = None,
verb: str | None = None, verb: str | None = None,
verb_cancel: str | None = None, verb_cancel: str | None = None,
info: bool = True,
hold: bool = False, hold: bool = False,
br_code: ButtonRequestType = BR_CODE_OTHER, br_code: ButtonRequestType = BR_CODE_OTHER,
ask_pagination: bool = False,
chunkify: bool = False, chunkify: bool = False,
default_cancel: bool = False,
prompt_screen: bool = True, prompt_screen: bool = True,
) -> Awaitable[None]: ) -> Awaitable[None]:
verb = verb or TR.buttons__confirm # def_arg verb = verb or TR.buttons__confirm # def_arg
@ -878,8 +922,8 @@ def confirm_blob(
trezorui2.confirm_blob( trezorui2.confirm_blob(
title=title, title=title,
description=description, description=description,
text_mono=text_mono,
data=data, data=data,
extra=None,
hold=hold, hold=hold,
verb=verb, verb=verb,
verb_cancel=verb_cancel, verb_cancel=verb_cancel,
@ -887,11 +931,6 @@ def confirm_blob(
) )
) )
if ask_pagination and layout.page_count() > 1:
assert not hold
return _confirm_ask_pagination(br_name, title, data, description or "", br_code)
else:
return raise_if_not_confirmed( return raise_if_not_confirmed(
interact( interact(
layout, layout,
@ -904,17 +943,21 @@ def confirm_blob(
def confirm_address( def confirm_address(
title: str, title: str,
address: str, address: str,
subtitle: str | None = None,
description: str | None = None, description: str | None = None,
verb: str | None = None,
chunkify: bool = True,
br_name: str = "confirm_address", br_name: str = "confirm_address",
br_code: ButtonRequestType = BR_CODE_OTHER, br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]: ) -> Awaitable[None]:
return confirm_value( return confirm_value(
title, title,
address, address,
description or "", description or subtitle or "",
br_name, br_name,
br_code, br_code,
verb=TR.buttons__confirm, subtitle=None,
verb=(verb or TR.buttons__confirm),
) )
@ -1095,12 +1138,13 @@ def _confirm_summary(
if not utils.BITCOIN_ONLY: if not utils.BITCOIN_ONLY:
async def confirm_ethereum_tx( async def confirm_ethereum_tx(
recipient: str, recipient: str | None,
total_amount: str, total_amount: str,
_account: str | None, _account: str | None,
_account_path: str | None, _account_path: str | None,
maximum_fee: str, maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]], fee_info_items: Iterable[tuple[str, str]],
is_contract_interaction: bool,
br_name: str = "confirm_ethereum_tx", br_name: str = "confirm_ethereum_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx, br_code: ButtonRequestType = ButtonRequestType.SignTx,
chunkify: bool = False, chunkify: bool = False,
@ -1123,14 +1167,20 @@ if not utils.BITCOIN_ONLY:
) )
) )
if not is_contract_interaction:
description = TR.words__recipient
else:
description = TR.ethereum__interaction_contract if recipient else None
while True: while True:
# Allowing going back and forth between recipient and summary/details # Allowing going back and forth between recipient and summary/details
await confirm_blob( await confirm_blob(
br_name, br_name,
TR.words__recipient, TR.words__address,
recipient, recipient or TR.ethereum__new_contract,
description=description,
verb=TR.buttons__continue, verb=TR.buttons__continue,
chunkify=chunkify, chunkify=(chunkify if recipient else False),
) )
try: try:
@ -1269,7 +1319,7 @@ def confirm_replacement(title: str, txid: str) -> Awaitable[None]:
title, title,
txid, txid,
TR.send__transaction_id, TR.send__transaction_id,
TR.buttons__continue, verb=TR.buttons__continue,
br_code=ButtonRequestType.SignTx, br_code=ButtonRequestType.SignTx,
) )
@ -1287,7 +1337,6 @@ async def confirm_modify_output(
verb=TR.buttons__continue, verb=TR.buttons__continue,
verb_cancel=None, verb_cancel=None,
description=f"{TR.words__address}:", description=f"{TR.words__address}:",
extra=None,
) )
) )
modify_layout = RustLayout( modify_layout = RustLayout(
@ -1453,7 +1502,6 @@ async def confirm_signverify(
title=TR.sign_message__confirm_message, title=TR.sign_message__confirm_message,
description=None, description=None,
data=message, data=message,
extra=None,
hold=not verify, hold=not verify,
verb=TR.buttons__confirm if verify else None, verb=TR.buttons__confirm if verify else None,
) )

View File

@ -284,7 +284,7 @@
"ethereum__show_full_message": "text,1", "ethereum__show_full_message": "text,1",
"ethereum__show_full_struct": "text,1", "ethereum__show_full_struct": "text,1",
"ethereum__sign_eip712": "text,2", "ethereum__sign_eip712": "text,2",
"ethereum__title_confirm_data": "title,1", "ethereum__title_input_data": "title,1",
"ethereum__title_confirm_domain": "title,1", "ethereum__title_confirm_domain": "title,1",
"ethereum__title_confirm_message": "title,1", "ethereum__title_confirm_message": "title,1",
"ethereum__title_confirm_struct": "title,1", "ethereum__title_confirm_struct": "title,1",

View File

@ -344,7 +344,6 @@
"ethereum__gas_price": "Cena gasu", "ethereum__gas_price": "Cena gasu",
"ethereum__max_gas_price": "Maximální cena gasu", "ethereum__max_gas_price": "Maximální cena gasu",
"ethereum__name_and_version": "Název a verze", "ethereum__name_and_version": "Název a verze",
"ethereum__new_contract": "nový kontrakt?",
"ethereum__no_message_field": "Žádné pole zprávy", "ethereum__no_message_field": "Žádné pole zprávy",
"ethereum__priority_fee": "Přednostní poplatek", "ethereum__priority_fee": "Přednostní poplatek",
"ethereum__show_full_array": "Zobrazit celé pole", "ethereum__show_full_array": "Zobrazit celé pole",
@ -360,11 +359,11 @@
"ethereum__staking_stake_intro": "Stakovat ETH na Everstake?", "ethereum__staking_stake_intro": "Stakovat ETH na Everstake?",
"ethereum__staking_unstake": "Zrušit stakování", "ethereum__staking_unstake": "Zrušit stakování",
"ethereum__staking_unstake_intro": "Zrušit stakování ETH z Everstake?", "ethereum__staking_unstake_intro": "Zrušit stakování ETH z Everstake?",
"ethereum__title_confirm_data": "Potvrdit data",
"ethereum__title_confirm_domain": "Potvrdit doménu", "ethereum__title_confirm_domain": "Potvrdit doménu",
"ethereum__title_confirm_message": "Potvrdit zprávu", "ethereum__title_confirm_message": "Potvrdit zprávu",
"ethereum__title_confirm_struct": "Potvrdit strukturu", "ethereum__title_confirm_struct": "Potvrdit strukturu",
"ethereum__title_confirm_typed_data": "Potvrdit typ. data", "ethereum__title_confirm_typed_data": "Potvrdit typ. data",
"ethereum__title_input_data": "Vstup data",
"ethereum__title_signing_address": "Podepisování adresy", "ethereum__title_signing_address": "Podepisování adresy",
"ethereum__units_template": "Jednotky: {0}", "ethereum__units_template": "Jednotky: {0}",
"ethereum__unknown_token": "Neznámý token", "ethereum__unknown_token": "Neznámý token",

View File

@ -344,7 +344,6 @@
"ethereum__gas_price": "Gas-Preis", "ethereum__gas_price": "Gas-Preis",
"ethereum__max_gas_price": "Max. Gas-Preis", "ethereum__max_gas_price": "Max. Gas-Preis",
"ethereum__name_and_version": "Name und Version", "ethereum__name_and_version": "Name und Version",
"ethereum__new_contract": "neuer Kontrakt?",
"ethereum__no_message_field": "Kein Nachrichtenfeld", "ethereum__no_message_field": "Kein Nachrichtenfeld",
"ethereum__priority_fee": "Prioritätsgebühr", "ethereum__priority_fee": "Prioritätsgebühr",
"ethereum__show_full_array": "Ganzes anzeigen", "ethereum__show_full_array": "Ganzes anzeigen",
@ -360,11 +359,11 @@
"ethereum__staking_stake_intro": "ETH auf Everstake staken?", "ethereum__staking_stake_intro": "ETH auf Everstake staken?",
"ethereum__staking_unstake": "Entstaken", "ethereum__staking_unstake": "Entstaken",
"ethereum__staking_unstake_intro": "ETH von Everstake entstaken?", "ethereum__staking_unstake_intro": "ETH von Everstake entstaken?",
"ethereum__title_confirm_data": "Daten bestätigen",
"ethereum__title_confirm_domain": "Domain bestätigen", "ethereum__title_confirm_domain": "Domain bestätigen",
"ethereum__title_confirm_message": "Nachr. bestätigen", "ethereum__title_confirm_message": "Nachr. bestätigen",
"ethereum__title_confirm_struct": "Struktur bestätigen", "ethereum__title_confirm_struct": "Struktur bestätigen",
"ethereum__title_confirm_typed_data": "Daten bestätigen", "ethereum__title_confirm_typed_data": "Daten bestätigen",
"ethereum__title_input_data": "Eingabe-Daten",
"ethereum__title_signing_address": "Signieradresse", "ethereum__title_signing_address": "Signieradresse",
"ethereum__units_template": "{0} Einheiten", "ethereum__units_template": "{0} Einheiten",
"ethereum__unknown_token": "Ungültiger Token", "ethereum__unknown_token": "Ungültiger Token",
@ -966,8 +965,8 @@
"words__confirm": "Bestätigen", "words__confirm": "Bestätigen",
"words__confirm_fee": "Gebühr bestätigen", "words__confirm_fee": "Gebühr bestätigen",
"words__contains": "Enthält", "words__contains": "Enthält",
"words__continue_anyway": "trotzdem fortfahren", "words__continue_anyway": "Trotzdem fortfahren",
"words__continue_anyway_question": "trotzdem fortfahren?", "words__continue_anyway_question": "Trotzdem fortfahren?",
"words__continue_with": "Weiter mit", "words__continue_with": "Weiter mit",
"words__error": "Fehler", "words__error": "Fehler",
"words__fee": "Gebühr", "words__fee": "Gebühr",

View File

@ -123,6 +123,7 @@
"buttons__try_again": "Try again", "buttons__try_again": "Try again",
"buttons__turn_off": "Turn off", "buttons__turn_off": "Turn off",
"buttons__turn_on": "Turn on", "buttons__turn_on": "Turn on",
"buttons__view_all_data": "View all data",
"cardano__addr_base": "Base", "cardano__addr_base": "Base",
"cardano__addr_enterprise": "Enterprise", "cardano__addr_enterprise": "Enterprise",
"cardano__addr_legacy": "Legacy", "cardano__addr_legacy": "Legacy",
@ -300,9 +301,10 @@
"ethereum__data_size_template": "Size: {0} bytes", "ethereum__data_size_template": "Size: {0} bytes",
"ethereum__gas_limit": "Gas limit", "ethereum__gas_limit": "Gas limit",
"ethereum__gas_price": "Gas price", "ethereum__gas_price": "Gas price",
"ethereum__interaction_contract": "Interaction contract",
"ethereum__max_gas_price": "Max gas price", "ethereum__max_gas_price": "Max gas price",
"ethereum__name_and_version": "Name and version", "ethereum__name_and_version": "Name and version",
"ethereum__new_contract": "new contract?", "ethereum__new_contract": "New contract will be deployed",
"ethereum__no_message_field": "No message field", "ethereum__no_message_field": "No message field",
"ethereum__priority_fee": "Priority fee", "ethereum__priority_fee": "Priority fee",
"ethereum__show_full_array": "Show full array", "ethereum__show_full_array": "Show full array",
@ -318,13 +320,15 @@
"ethereum__staking_stake_intro": "Stake ETH on Everstake?", "ethereum__staking_stake_intro": "Stake ETH on Everstake?",
"ethereum__staking_unstake": "Unstake", "ethereum__staking_unstake": "Unstake",
"ethereum__staking_unstake_intro": "Unstake ETH from Everstake?", "ethereum__staking_unstake_intro": "Unstake ETH from Everstake?",
"ethereum__title_confirm_data": "Confirm data",
"ethereum__title_confirm_domain": "Confirm domain", "ethereum__title_confirm_domain": "Confirm domain",
"ethereum__title_confirm_message": "Confirm message", "ethereum__title_confirm_message": "Confirm message",
"ethereum__title_confirm_struct": "Confirm struct", "ethereum__title_confirm_struct": "Confirm struct",
"ethereum__title_confirm_typed_data": "Confirm typed data", "ethereum__title_confirm_typed_data": "Confirm typed data",
"ethereum__title_input_data": "Input data",
"ethereum__title_signing_address": "Signing address", "ethereum__title_signing_address": "Signing address",
"ethereum__token_contract": "Token contract",
"ethereum__units_template": "{0} units", "ethereum__units_template": "{0} units",
"ethereum__unknown_contract_address": "Unknown contract address. Continue only if you know what you are doing.",
"ethereum__unknown_token": "Unknown token", "ethereum__unknown_token": "Unknown token",
"ethereum__valid_signature": "The signature is valid.", "ethereum__valid_signature": "The signature is valid.",
"experimental_mode__enable": "Enable experimental features?", "experimental_mode__enable": "Enable experimental features?",
@ -398,6 +402,7 @@
"instructions__swipe_up": "Swipe up", "instructions__swipe_up": "Swipe up",
"instructions__tap_to_confirm": "Tap to confirm", "instructions__tap_to_confirm": "Tap to confirm",
"instructions__tap_to_start": "Tap to start", "instructions__tap_to_start": "Tap to start",
"instructions__view_all_data": "View all data in the menu.",
"joint__title": "Joint transaction", "joint__title": "Joint transaction",
"joint__to_the_total_amount": "To the total amount:", "joint__to_the_total_amount": "To the total amount:",
"joint__you_are_contributing": "You are contributing:", "joint__you_are_contributing": "You are contributing:",

View File

@ -344,7 +344,6 @@
"ethereum__gas_price": "Precio de gas", "ethereum__gas_price": "Precio de gas",
"ethereum__max_gas_price": "Precio máximo de gas", "ethereum__max_gas_price": "Precio máximo de gas",
"ethereum__name_and_version": "Nombre y versión", "ethereum__name_and_version": "Nombre y versión",
"ethereum__new_contract": "¿Nuevo contrato?",
"ethereum__no_message_field": "Sin campo de mensaje.", "ethereum__no_message_field": "Sin campo de mensaje.",
"ethereum__priority_fee": "Comisión de prioridad", "ethereum__priority_fee": "Comisión de prioridad",
"ethereum__show_full_array": "Ver matriz íntegra.", "ethereum__show_full_array": "Ver matriz íntegra.",
@ -360,11 +359,11 @@
"ethereum__staking_stake_intro": "¿Hacer stake de ETH en Everstake?", "ethereum__staking_stake_intro": "¿Hacer stake de ETH en Everstake?",
"ethereum__staking_unstake": "Retirar stake", "ethereum__staking_unstake": "Retirar stake",
"ethereum__staking_unstake_intro": "¿Retirar ETH de Everstake?", "ethereum__staking_unstake_intro": "¿Retirar ETH de Everstake?",
"ethereum__title_confirm_data": "Validar datos",
"ethereum__title_confirm_domain": "Validar dominio", "ethereum__title_confirm_domain": "Validar dominio",
"ethereum__title_confirm_message": "Validar mensaje", "ethereum__title_confirm_message": "Validar mensaje",
"ethereum__title_confirm_struct": "Validar estructura", "ethereum__title_confirm_struct": "Validar estructura",
"ethereum__title_confirm_typed_data": "Validar datos", "ethereum__title_confirm_typed_data": "Validar datos",
"ethereum__title_input_data": "Datos entrados",
"ethereum__title_signing_address": "Dirección firma", "ethereum__title_signing_address": "Dirección firma",
"ethereum__units_template": "{0} unidades", "ethereum__units_template": "{0} unidades",
"ethereum__unknown_token": "Token desconocido", "ethereum__unknown_token": "Token desconocido",

View File

@ -344,7 +344,6 @@
"ethereum__gas_price": "Prix du gaz", "ethereum__gas_price": "Prix du gaz",
"ethereum__max_gas_price": "Prix max du gaz", "ethereum__max_gas_price": "Prix max du gaz",
"ethereum__name_and_version": "Nom et version", "ethereum__name_and_version": "Nom et version",
"ethereum__new_contract": "nouveau contrat ?",
"ethereum__no_message_field": "0 champ de mess.", "ethereum__no_message_field": "0 champ de mess.",
"ethereum__priority_fee": "Frais de priorité", "ethereum__priority_fee": "Frais de priorité",
"ethereum__show_full_array": "Aff. liste complète", "ethereum__show_full_array": "Aff. liste complète",
@ -360,11 +359,11 @@
"ethereum__staking_stake_intro": "Staker de l'ETH sur Everstake ?", "ethereum__staking_stake_intro": "Staker de l'ETH sur Everstake ?",
"ethereum__staking_unstake": "Unstake", "ethereum__staking_unstake": "Unstake",
"ethereum__staking_unstake_intro": "Terminer le Staking de l'ETH sur Everstake ?", "ethereum__staking_unstake_intro": "Terminer le Staking de l'ETH sur Everstake ?",
"ethereum__title_confirm_data": "Conf. données",
"ethereum__title_confirm_domain": "Conf. domaine", "ethereum__title_confirm_domain": "Conf. domaine",
"ethereum__title_confirm_message": "Conf. message", "ethereum__title_confirm_message": "Conf. message",
"ethereum__title_confirm_struct": "Conf. structure", "ethereum__title_confirm_struct": "Conf. structure",
"ethereum__title_confirm_typed_data": "Conf. données", "ethereum__title_confirm_typed_data": "Conf. données",
"ethereum__title_input_data": "Données entrées",
"ethereum__title_signing_address": "Adr. de signature", "ethereum__title_signing_address": "Adr. de signature",
"ethereum__units_template": "{0} unités", "ethereum__units_template": "{0} unités",
"ethereum__unknown_token": "Jeton inconnu", "ethereum__unknown_token": "Jeton inconnu",

View File

@ -344,7 +344,6 @@
"ethereum__gas_price": "Prezzo gas:", "ethereum__gas_price": "Prezzo gas:",
"ethereum__max_gas_price": "Prezzo max gas:", "ethereum__max_gas_price": "Prezzo max gas:",
"ethereum__name_and_version": "Nome e versione", "ethereum__name_and_version": "Nome e versione",
"ethereum__new_contract": "nuovo contratto?",
"ethereum__no_message_field": "Nessun campo mess.", "ethereum__no_message_field": "Nessun campo mess.",
"ethereum__priority_fee": "Commiss. di priorità:", "ethereum__priority_fee": "Commiss. di priorità:",
"ethereum__show_full_array": "Mostra matrice compl.", "ethereum__show_full_array": "Mostra matrice compl.",
@ -360,11 +359,11 @@
"ethereum__staking_stake_intro": "Staking di ETH su Everstake?", "ethereum__staking_stake_intro": "Staking di ETH su Everstake?",
"ethereum__staking_unstake": "Unstaking", "ethereum__staking_unstake": "Unstaking",
"ethereum__staking_unstake_intro": "Unstaking di ETH da Everstake?", "ethereum__staking_unstake_intro": "Unstaking di ETH da Everstake?",
"ethereum__title_confirm_data": "Conferma dati",
"ethereum__title_confirm_domain": "Conferma dom.", "ethereum__title_confirm_domain": "Conferma dom.",
"ethereum__title_confirm_message": "Conferma mess.", "ethereum__title_confirm_message": "Conferma mess.",
"ethereum__title_confirm_struct": "Conferma strutt.", "ethereum__title_confirm_struct": "Conferma strutt.",
"ethereum__title_confirm_typed_data": "Conf. dati digitati", "ethereum__title_confirm_typed_data": "Conf. dati digitati",
"ethereum__title_input_data": "Dati input",
"ethereum__title_signing_address": "Indirizzo di firma", "ethereum__title_signing_address": "Indirizzo di firma",
"ethereum__units_template": "{0} unità", "ethereum__units_template": "{0} unità",
"ethereum__unknown_token": "Token sconosciuto", "ethereum__unknown_token": "Token sconosciuto",

View File

@ -285,7 +285,7 @@
"283": "ethereum__show_full_message", "283": "ethereum__show_full_message",
"284": "ethereum__show_full_struct", "284": "ethereum__show_full_struct",
"285": "ethereum__sign_eip712", "285": "ethereum__sign_eip712",
"286": "ethereum__title_confirm_data", "286": "ethereum__title_input_data",
"287": "ethereum__title_confirm_domain", "287": "ethereum__title_confirm_domain",
"288": "ethereum__title_confirm_message", "288": "ethereum__title_confirm_message",
"289": "ethereum__title_confirm_struct", "289": "ethereum__title_confirm_struct",
@ -966,5 +966,10 @@
"964": "instructions__swipe_down", "964": "instructions__swipe_down",
"965": "fido__title_credential_details", "965": "fido__title_credential_details",
"966": "address__public_key_confirmed", "966": "address__public_key_confirmed",
"967": "words__continue_anyway" "967": "words__continue_anyway",
"968": "ethereum__unknown_contract_address",
"970": "ethereum__token_contract",
"971": "buttons__view_all_data",
"972": "instructions__view_all_data",
"973": "ethereum__interaction_contract"
} }

View File

@ -344,7 +344,6 @@
"ethereum__gas_price": "Preço do gás:", "ethereum__gas_price": "Preço do gás:",
"ethereum__max_gas_price": "Preço máximo do gás:", "ethereum__max_gas_price": "Preço máximo do gás:",
"ethereum__name_and_version": "Nome e versão", "ethereum__name_and_version": "Nome e versão",
"ethereum__new_contract": "novo contrato?",
"ethereum__no_message_field": "Sem campo mens.", "ethereum__no_message_field": "Sem campo mens.",
"ethereum__priority_fee": "Taxa de prioridade:", "ethereum__priority_fee": "Taxa de prioridade:",
"ethereum__show_full_array": "Exibir array completo", "ethereum__show_full_array": "Exibir array completo",
@ -360,11 +359,11 @@
"ethereum__staking_stake_intro": "Fazer stake de ETH no Everstake?", "ethereum__staking_stake_intro": "Fazer stake de ETH no Everstake?",
"ethereum__staking_unstake": "Tirar do stake", "ethereum__staking_unstake": "Tirar do stake",
"ethereum__staking_unstake_intro": "Tirar ETH do stake no Everstake?", "ethereum__staking_unstake_intro": "Tirar ETH do stake no Everstake?",
"ethereum__title_confirm_data": "Confirmar dados",
"ethereum__title_confirm_domain": "Confirmar domínio", "ethereum__title_confirm_domain": "Confirmar domínio",
"ethereum__title_confirm_message": "Confirmar mensagem", "ethereum__title_confirm_message": "Confirmar mensagem",
"ethereum__title_confirm_struct": "Conf. estrutura", "ethereum__title_confirm_struct": "Conf. estrutura",
"ethereum__title_confirm_typed_data": "Conf. dados", "ethereum__title_confirm_typed_data": "Conf. dados",
"ethereum__title_input_data": "Dados entrados",
"ethereum__title_signing_address": "End. assinatura", "ethereum__title_signing_address": "End. assinatura",
"ethereum__units_template": "{0} unidades", "ethereum__units_template": "{0} unidades",
"ethereum__unknown_token": "Token desconhecido", "ethereum__unknown_token": "Token desconhecido",

View File

@ -1,8 +1,8 @@
{ {
"current": { "current": {
"merkle_root": "a49211266cbf110206e919cb605c8f0765c84aae5c89fd21d448226fc96e1686", "merkle_root": "fe166db67e7829b73a6af9368005a8dedc215de811adda1c403a8a68288a13ca",
"datetime": "2024-10-29T22:34:40.907782", "datetime": "2024-10-30T07:52:31.990241",
"commit": "13e0d61365a9c949023f3c3903dbd9faaf971193" "commit": "91c5edd60ef1d34436afc99a19b68617f31bf672"
}, },
"history": [ "history": [
{ {

View File

@ -332,7 +332,6 @@
"ethereum__gas_price": "Gaz fiyatı:", "ethereum__gas_price": "Gaz fiyatı:",
"ethereum__max_gas_price": "Maksimum gaz fiyatı:", "ethereum__max_gas_price": "Maksimum gaz fiyatı:",
"ethereum__name_and_version": "Ad ve sürüm", "ethereum__name_and_version": "Ad ve sürüm",
"ethereum__new_contract": "yeni sözleşme mi?",
"ethereum__no_message_field": "Mesaj alanı yok", "ethereum__no_message_field": "Mesaj alanı yok",
"ethereum__priority_fee": "Öncelik ücreti:", "ethereum__priority_fee": "Öncelik ücreti:",
"ethereum__show_full_array": "Tüm diziyi göster", "ethereum__show_full_array": "Tüm diziyi göster",
@ -348,11 +347,11 @@
"ethereum__staking_stake_intro": "Everstake'te ETH stake edilsin mi?", "ethereum__staking_stake_intro": "Everstake'te ETH stake edilsin mi?",
"ethereum__staking_unstake": "Unstake et", "ethereum__staking_unstake": "Unstake et",
"ethereum__staking_unstake_intro": "Everstake'ten ETH unstake edilsin mi?", "ethereum__staking_unstake_intro": "Everstake'ten ETH unstake edilsin mi?",
"ethereum__title_confirm_data": "Veri̇leri̇ onayla",
"ethereum__title_confirm_domain": "Etki̇ alanini onayla", "ethereum__title_confirm_domain": "Etki̇ alanini onayla",
"ethereum__title_confirm_message": "Mesaji onayla", "ethereum__title_confirm_message": "Mesaji onayla",
"ethereum__title_confirm_struct": "Yapiyi onayla", "ethereum__title_confirm_struct": "Yapiyi onayla",
"ethereum__title_confirm_typed_data": "Yzln veri̇yi̇ onayla", "ethereum__title_confirm_typed_data": "Yzln veri̇yi̇ onayla",
"ethereum__title_input_data": "Veri̇leri̇ girdiyi",
"ethereum__title_signing_address": "İmza adresi̇", "ethereum__title_signing_address": "İmza adresi̇",
"ethereum__units_template": "{0} birim", "ethereum__units_template": "{0} birim",
"ethereum__unknown_token": "Bilinmeyen token", "ethereum__unknown_token": "Bilinmeyen token",

View File

@ -9,6 +9,7 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path from trezorlib.tools import parse_path
from ...input_flows import InputFlowConfirmAllWarnings
from . import common from . import common
from .test_sign_typed_data import DATA as TYPED_DATA from .test_sign_typed_data import DATA as TYPED_DATA
@ -122,6 +123,9 @@ def test_external_token(client: Client) -> None:
def test_external_chain_without_token(client: Client) -> None: def test_external_chain_without_token(client: Client) -> None:
with client:
if not client.debug.legacy_debug:
client.set_input_flow(InputFlowConfirmAllWarnings(client).get())
# when using an external chains, unknown tokens are allowed # when using an external chains, unknown tokens are allowed
network = common.encode_network(chain_id=66666, slip44=60) network = common.encode_network(chain_id=66666, slip44=60)
params = DEFAULT_ERC20_PARAMS.copy() params = DEFAULT_ERC20_PARAMS.copy()
@ -141,10 +145,15 @@ def test_external_chain_token_ok(client: Client) -> None:
def test_external_chain_token_mismatch(client: Client) -> None: def test_external_chain_token_mismatch(client: Client) -> None:
with client:
if not client.debug.legacy_debug:
client.set_input_flow(InputFlowConfirmAllWarnings(client).get())
# when providing external defs, we explicitly allow, but not use, tokens # when providing external defs, we explicitly allow, but not use, tokens
# from other chains # from other chains
network = common.encode_network(chain_id=66666, slip44=60) network = common.encode_network(chain_id=66666, slip44=60)
token = common.encode_token(address=ERC20_FAKE_ADDRESS, chain_id=55555, decimals=8) token = common.encode_token(
address=ERC20_FAKE_ADDRESS, chain_id=55555, decimals=8
)
params = DEFAULT_ERC20_PARAMS.copy() params = DEFAULT_ERC20_PARAMS.copy()
params.update(to=ERC20_FAKE_ADDRESS, chain_id=66666) params.update(to=ERC20_FAKE_ADDRESS, chain_id=66666)
ethereum.sign_tx(client, **params, definitions=common.make_defs(network, token)) ethereum.sign_tx(client, **params, definitions=common.make_defs(network, token))

View File

@ -24,6 +24,7 @@ from trezorlib.tools import parse_path, unharden
from ...common import parametrize_using_common_fixtures from ...common import parametrize_using_common_fixtures
from ...input_flows import ( from ...input_flows import (
InputFlowConfirmAllWarnings,
InputFlowEthereumSignTxDataGoBack, InputFlowEthereumSignTxDataGoBack,
InputFlowEthereumSignTxDataScrollDown, InputFlowEthereumSignTxDataScrollDown,
InputFlowEthereumSignTxDataSkip, InputFlowEthereumSignTxDataSkip,
@ -56,7 +57,12 @@ def make_defs(parameters: dict) -> messages.EthereumDefinitions:
) )
@pytest.mark.parametrize("chunkify", (True, False)) @pytest.mark.parametrize("chunkify", (True, False))
def test_signtx(client: Client, chunkify: bool, parameters: dict, result: dict): def test_signtx(client: Client, chunkify: bool, parameters: dict, result: dict):
_do_test_signtx(client, parameters, result, chunkify=chunkify) input_flow = (
InputFlowConfirmAllWarnings(client).get()
if not client.debug.legacy_debug
else None
)
_do_test_signtx(client, parameters, result, input_flow, chunkify=chunkify)
def _do_test_signtx( def _do_test_signtx(
@ -143,6 +149,8 @@ def test_signtx_go_back_from_summary(client: Client):
@pytest.mark.parametrize("chunkify", (True, False)) @pytest.mark.parametrize("chunkify", (True, False))
def test_signtx_eip1559(client: Client, chunkify: bool, parameters: dict, result: dict): def test_signtx_eip1559(client: Client, chunkify: bool, parameters: dict, result: dict):
with client: with client:
if not client.debug.legacy_debug:
client.set_input_flow(InputFlowConfirmAllWarnings(client).get())
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559( sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
client, client,
n=parse_path(parameters["path"]), n=parse_path(parameters["path"]),
@ -455,6 +463,7 @@ def test_signtx_data_pagination(client: Client, flow):
client.set_input_flow(flow(client)) client.set_input_flow(flow(client))
_sign_tx_call() _sign_tx_call()
if flow is not input_flow_data_scroll_down:
with client, pytest.raises(exceptions.Cancelled): with client, pytest.raises(exceptions.Cancelled):
client.watch_layout() client.watch_layout()
client.set_input_flow(flow(client, cancel=True)) client.set_input_flow(flow(client, cancel=True))

View File

@ -1216,12 +1216,21 @@ class InputFlowEthereumSignTxDataScrollDown(InputFlowBase):
self.cancel = cancel self.cancel = cancel
def input_flow_common(self) -> BRGeneratorType: def input_flow_common(self) -> BRGeneratorType:
# this flow will not test for the cancel case,
# because once we enter the "view all data",
# the only way to cancel is by going back to the 1st page view
# but that case would be covered by InputFlowEthereumSignTxDataGoBack
assert not self.cancel
yield from self.ETH.confirm_data(info=True) yield from self.ETH.confirm_data(info=True)
yield from self.ETH.paginate_data() yield from self.ETH.paginate_data()
if self.cancel:
yield from self.ETH.confirm_data(cancel=True) self.debug.wait_layout()
if self.debug.layout_type is LayoutType.TR:
self.debug.press_right()
else: else:
yield from self.ETH.confirm_data() self.debug.click(buttons.OK)
yield from self.ETH.confirm_tx() yield from self.ETH.confirm_tx()
@ -2288,7 +2297,8 @@ class InputFlowConfirmAllWarnings(InputFlowBase):
text = layout.text_content().lower() text = layout.text_content().lower()
# hi priority warning # hi priority warning
hi_prio = ( hi_prio = (
TR.translate("addr_mismatch__wrong_derivation_path") TR.translate("ethereum__unknown_contract_address")
+ TR.translate("addr_mismatch__wrong_derivation_path")
+ TR.translate("send__receiving_to_multisig") + TR.translate("send__receiving_to_multisig")
+ [ + [
"witness path", "witness path",

View File

@ -343,9 +343,7 @@ class EthereumFlow:
def confirm_data(self, info: bool = False, cancel: bool = False) -> BRGeneratorType: def confirm_data(self, info: bool = False, cancel: bool = False) -> BRGeneratorType:
yield yield
TR.assert_equals( TR.assert_equals(self.debug.wait_layout().title(), "ethereum__title_input_data")
self.debug.wait_layout().title(), "ethereum__title_confirm_data"
)
if info: if info:
self.debug.press_info() self.debug.press_info()
elif cancel: elif cancel:
@ -355,21 +353,21 @@ class EthereumFlow:
def paginate_data(self) -> BRGeneratorType: def paginate_data(self) -> BRGeneratorType:
br = yield br = yield
TR.assert_equals( TR.assert_equals(self.debug.wait_layout().title(), "ethereum__title_input_data")
self.debug.wait_layout().title(), "ethereum__title_confirm_data"
)
assert br.pages is not None assert br.pages is not None
if self.client.layout_type is LayoutType.TR:
for i in range(br.pages):
if i < br.pages - 1:
self.debug.press_right()
else:
for i in range(br.pages): for i in range(br.pages):
self.debug.wait_layout() self.debug.wait_layout()
if i < br.pages - 1: if i < br.pages - 1:
self.debug.swipe_up() self.debug.click(buttons.OK)
self.debug.press_yes()
def paginate_data_go_back(self) -> BRGeneratorType: def paginate_data_go_back(self) -> BRGeneratorType:
br = yield br = yield
TR.assert_equals( TR.assert_equals(self.debug.wait_layout().title(), "ethereum__title_input_data")
self.debug.wait_layout().title(), "ethereum__title_confirm_data"
)
assert br.pages is not None assert br.pages is not None
assert br.pages > 2 assert br.pages > 2
if self.client.layout_type is LayoutType.TR: if self.client.layout_type is LayoutType.TR:
@ -395,7 +393,7 @@ class EthereumFlow:
yield yield
if self.client.layout_type is LayoutType.TT: if self.client.layout_type is LayoutType.TT:
TR.assert_equals(self.debug.wait_layout().title(), "words__recipient") TR.assert_equals(self.debug.wait_layout().title(), "words__address")
if cancel: if cancel:
self.debug.press_no() self.debug.press_no()
else: else:
@ -425,7 +423,10 @@ class EthereumFlow:
yield yield
elif self.client.layout_type is LayoutType.TR: elif self.client.layout_type is LayoutType.TR:
TR.assert_equals(self.debug.wait_layout().title(), "words__recipient") TR.assert_in_multiple(
self.debug.wait_layout().title(),
["ethereum__interaction_contract", "words__recipient"],
)
if cancel: if cancel:
self.debug.press_left() self.debug.press_left()
else: else:

File diff suppressed because it is too large Load Diff