1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 04:18:10 +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_event;
MP_QSTR_button_request;
MP_QSTR_button_style_confirm;
MP_QSTR_buttons__abort;
MP_QSTR_buttons__access;
MP_QSTR_buttons__again;
@ -167,6 +168,7 @@ static void _librust_qstrs(void) {
MP_QSTR_buttons__try_again;
MP_QSTR_buttons__turn_off;
MP_QSTR_buttons__turn_on;
MP_QSTR_buttons__view_all_data;
MP_QSTR_can_go_back;
MP_QSTR_cancel_arrow;
MP_QSTR_cancel_cross;
@ -218,8 +220,10 @@ static void _librust_qstrs(void) {
MP_QSTR_debug__loading_seed;
MP_QSTR_debug__loading_seed_not_recommended;
MP_QSTR_decode;
MP_QSTR_default_cancel;
MP_QSTR_deinit;
MP_QSTR_description;
MP_QSTR_description_font_green;
MP_QSTR_details_title;
MP_QSTR_device_name__change_template;
MP_QSTR_device_name__title;
@ -307,6 +311,7 @@ static void _librust_qstrs(void) {
MP_QSTR_instructions__swipe_up;
MP_QSTR_instructions__tap_to_confirm;
MP_QSTR_instructions__tap_to_start;
MP_QSTR_instructions__view_all_data;
MP_QSTR_is_type_of;
MP_QSTR_items;
MP_QSTR_items_title;
@ -349,6 +354,7 @@ static void _librust_qstrs(void) {
MP_QSTR_notification;
MP_QSTR_notification_level;
MP_QSTR_page_count;
MP_QSTR_page_limit;
MP_QSTR_pages;
MP_QSTR_paint;
MP_QSTR_passphrase__access_wallet;
@ -730,6 +736,7 @@ static void _librust_qstrs(void) {
MP_QSTR_value;
MP_QSTR_verb;
MP_QSTR_verb_cancel;
MP_QSTR_verb_info;
MP_QSTR_verify;
MP_QSTR_version;
MP_QSTR_warning;
@ -980,6 +987,7 @@ static void _librust_qstrs(void) {
MP_QSTR_ethereum__data_size_template;
MP_QSTR_ethereum__gas_limit;
MP_QSTR_ethereum__gas_price;
MP_QSTR_ethereum__interaction_contract;
MP_QSTR_ethereum__max_gas_price;
MP_QSTR_ethereum__name_and_version;
MP_QSTR_ethereum__new_contract;
@ -998,13 +1006,15 @@ static void _librust_qstrs(void) {
MP_QSTR_ethereum__staking_stake_intro;
MP_QSTR_ethereum__staking_unstake;
MP_QSTR_ethereum__staking_unstake_intro;
MP_QSTR_ethereum__title_confirm_data;
MP_QSTR_ethereum__title_confirm_domain;
MP_QSTR_ethereum__title_confirm_message;
MP_QSTR_ethereum__title_confirm_struct;
MP_QSTR_ethereum__title_confirm_typed_data;
MP_QSTR_ethereum__title_input_data;
MP_QSTR_ethereum__title_signing_address;
MP_QSTR_ethereum__token_contract;
MP_QSTR_ethereum__units_template;
MP_QSTR_ethereum__unknown_contract_address;
MP_QSTR_ethereum__unknown_token;
MP_QSTR_ethereum__valid_signature;
MP_QSTR_fido__already_registered;

View File

@ -457,7 +457,7 @@ pub enum TranslatedString {
#[cfg(feature = "universal_fw")]
ethereum__name_and_version = 277, // "Name and version"
#[cfg(feature = "universal_fw")]
ethereum__new_contract = 278, // "new contract?"
ethereum__new_contract = 278, // "New contract will be deployed"
#[cfg(feature = "universal_fw")]
ethereum__no_message_field = 279, // "No message field"
#[cfg(feature = "universal_fw")]
@ -473,7 +473,7 @@ pub enum TranslatedString {
#[cfg(feature = "universal_fw")]
ethereum__sign_eip712 = 285, // "Really sign EIP-712 typed data?"
#[cfg(feature = "universal_fw")]
ethereum__title_confirm_data = 286, // "Confirm data"
ethereum__title_input_data = 286, // "Input data"
#[cfg(feature = "universal_fw")]
ethereum__title_confirm_domain = 287, // "Confirm domain"
#[cfg(feature = "universal_fw")]
@ -1371,6 +1371,14 @@ pub enum TranslatedString {
fido__title_credential_details = 965, // "Credential details"
address__public_key_confirmed = 966, // "Public key confirmed"
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 {
@ -1822,7 +1830,7 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")]
Self::ethereum__name_and_version => "Name and version",
#[cfg(feature = "universal_fw")]
Self::ethereum__new_contract => "new contract?",
Self::ethereum__new_contract => "New contract will be deployed",
#[cfg(feature = "universal_fw")]
Self::ethereum__no_message_field => "No message field",
#[cfg(feature = "universal_fw")]
@ -1838,7 +1846,7 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")]
Self::ethereum__sign_eip712 => "Really sign EIP-712 typed data?",
#[cfg(feature = "universal_fw")]
Self::ethereum__title_confirm_data => "Confirm data",
Self::ethereum__title_input_data => "Input data",
#[cfg(feature = "universal_fw")]
Self::ethereum__title_confirm_domain => "Confirm domain",
#[cfg(feature = "universal_fw")]
@ -2736,6 +2744,14 @@ impl TranslatedString {
Self::fido__title_credential_details => "Credential details",
Self::address__public_key_confirmed => "Public key confirmed",
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")]
Qstr::MP_QSTR_ethereum__sign_eip712 => Some(Self::ethereum__sign_eip712),
#[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")]
Qstr::MP_QSTR_ethereum__title_confirm_domain => Some(Self::ethereum__title_confirm_domain),
#[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_address__public_key_confirmed => Some(Self::address__public_key_confirmed),
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,
}
}

View File

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

View File

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

View File

@ -150,6 +150,11 @@ where
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 {
self.with_button(theme::ICON_WARNING, FlowMsg::Info, false)
.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)]
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) }
@ -141,35 +218,43 @@ fn new_confirm_action_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Err
new_confirm_action_simple(
paragraphs,
title,
subtitle,
verb_cancel,
prompt_screen.then_some(prompt_title),
ConfirmActionMenu::new(verb_cancel, false, None),
ConfirmActionStrings::new(title, subtitle, None, prompt_screen.then_some(prompt_title)),
hold,
false,
None,
)
}
#[inline(never)]
pub fn new_confirm_action_uni<T: Component + MaybeTrace + 'static>(
fn new_confirm_action_uni<T: Component + MaybeTrace + 'static>(
content: T,
title: TString<'static>,
subtitle: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>,
prompt_screen: Option<TString<'static>>,
menu: ConfirmActionMenu,
strings: ConfirmActionStrings,
hold: bool,
info: bool,
default_cancel: bool,
) -> 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)
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
let mut content_intro = Frame::left_aligned(strings.title, content)
.with_swipe(Direction::Up, SwipeSettings::default())
.with_swipe(Direction::Left, SwipeSettings::default())
.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);
}
@ -182,13 +267,18 @@ pub fn new_confirm_action_uni<T: Component + MaybeTrace + 'static>(
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(
title: TString<'static>,
prompt_screen: Option<TString<'static>>,
hold: bool,
default_cancel: bool,
) -> (
Option<TString<'static>>,
usize,
@ -198,52 +288,51 @@ fn create_flow(
let prompt_screen = prompt_screen.or_else(|| hold.then_some(title));
let prompt_pages: usize = prompt_screen.is_some().into();
let flow = if prompt_screen.is_some() {
SwipeFlow::new(&ConfirmAction::Intro)
let (flow, page): (Result<SwipeFlow, Error>, &dyn FlowController) = if prompt_screen.is_some() {
(SwipeFlow::new(&ConfirmAction::Intro), &ConfirmAction::Intro)
} else if default_cancel {
(
SwipeFlow::new(&ConfirmActionSimpleDefaultCancel::Intro),
&ConfirmActionSimpleDefaultCancel::Intro,
)
} else {
SwipeFlow::new(&ConfirmActionSimple::Intro)
};
let page: &dyn FlowController = if prompt_screen.is_some() {
&ConfirmAction::Intro
} else {
&ConfirmActionSimple::Intro
(
SwipeFlow::new(&ConfirmActionSimple::Intro),
&ConfirmActionSimple::Intro,
)
};
(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(
flow: SwipeFlow,
verb_cancel: Option<TString<'static>>,
info: bool,
menu: ConfirmActionMenu,
default_cancel: bool,
prompt_screen: Option<TString<'static>>,
) -> 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,
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(
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)
.with_cancel_button()
.with_swipe(Direction::Right, SwipeSettings::immediate());
@ -304,20 +393,33 @@ fn create_confirm(
#[inline(never)]
pub fn new_confirm_action_simple<T: Component + Paginate + MaybeTrace + 'static>(
content: T,
title: TString<'static>,
subtitle: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>,
prompt_screen: Option<TString<'static>>,
menu: ConfirmActionMenu,
strings: ConfirmActionStrings,
hold: bool,
info: bool,
page_limit: Option<usize>,
) -> Result<Obj, error::Error> {
new_confirm_action_uni(
SwipeContent::new(SwipePage::vertical(content)),
title,
subtitle,
verb_cancel,
prompt_screen,
SwipeContent::new(SwipePage::vertical(content).with_limit(page_limit)),
menu,
strings,
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;
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")]
pub use confirm_fido::new_confirm_fido;
pub use confirm_firmware_update::new_confirm_firmware_update;

View File

@ -50,7 +50,10 @@ use crate::{
},
model_mercury::{
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,
},
},
@ -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(),
title,
None,
None,
Some(title),
false,
ConfirmActionMenu::new(None, false, None),
ConfirmActionStrings::new(title, None, None, Some(title)),
false,
None,
)
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -256,14 +257,18 @@ struct ConfirmBlobParams {
subtitle: Option<TString<'static>>,
data: Obj,
description: Option<TString<'static>>,
description_font: &'static TextStyle,
extra: Option<TString<'static>>,
verb: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>,
verb_info: Option<TString<'static>>,
info_button: bool,
prompt: bool,
hold: bool,
chunkify: bool,
text_mono: bool,
page_limit: Option<usize>,
default_cancel: bool,
}
impl ConfirmBlobParams {
@ -272,7 +277,7 @@ impl ConfirmBlobParams {
data: Obj,
description: Option<TString<'static>>,
verb: Option<TString<'static>>,
verb_cancel: Option<TString<'static>>,
verb_info: Option<TString<'static>>,
prompt: bool,
hold: bool,
) -> Self {
@ -281,14 +286,18 @@ impl ConfirmBlobParams {
subtitle: None,
data,
description,
description_font: &theme::TEXT_NORMAL,
extra: None,
verb,
verb_cancel,
verb_cancel: None,
verb_info,
info_button: false,
prompt,
hold,
chunkify: false,
text_mono: true,
page_limit: None,
default_cancel: false,
}
}
@ -302,6 +311,11 @@ impl ConfirmBlobParams {
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 {
self.info_button = info_button;
self
@ -317,12 +331,27 @@ impl ConfirmBlobParams {
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> {
let paragraphs = ConfirmBlob {
description: self.description.unwrap_or("".into()),
extra: self.extra.unwrap_or("".into()),
data: self.data.try_into()?,
description_font: &theme::TEXT_NORMAL,
description_font: self.description_font,
extra_font: &theme::TEXT_DEMIBOLD,
data_font: if self.chunkify {
let data: TString = self.data.try_into()?;
@ -335,14 +364,23 @@ impl ConfirmBlobParams {
}
.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,
ConfirmActionMenu::new(self.verb_cancel, self.info_button, self.verb_info),
ConfirmActionStrings::new(
self.title,
self.subtitle,
self.verb_cancel,
self.verb,
self.prompt.then_some(self.title),
),
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 description: Option<TString> =
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
.get(Qstr::MP_QSTR_verb)
.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)
.unwrap_or_else(|_| Obj::const_none())
.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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
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(
title,
data,
description,
verb,
verb_cancel,
verb_info,
prompt_screen,
hold,
)
.with_description_font(description_font)
.with_text_mono(text_mono)
.with_subtitle(subtitle)
.with_verb_cancel(verb_cancel)
.with_extra(extra)
.with_info_button(info)
.with_chunkify(chunkify)
.with_default_cancel(default_cancel)
.with_page_limit(page_limit)
.into_flow()
};
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();
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) }
}
@ -426,14 +503,12 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
&theme::TEXT_MONO,
)?;
flow::new_confirm_action_simple(
new_confirm_action_simple(
paragraphs.into_paragraphs(),
title,
None,
None,
hold.then_some(title),
ConfirmActionMenu::new(None, false, None),
ConfirmActionStrings::new(title, None, None, hold.then_some(title)),
hold,
false,
None,
)
};
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(
paragraphs,
ConfirmActionMenu::new(None, false, None),
ConfirmActionStrings::new(
TR::homescreen__settings_title.into(),
Some(TR::homescreen__settings_subtitle.into()),
None,
Some(TR::homescreen__settings_title.into()),
),
false,
false,
None,
)
} else {
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 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_verb_cancel(verb_cancel)
.with_info_button(info_button)
.with_chunkify(chunkify)
.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));
}
flow::new_confirm_action_simple(
new_confirm_action_simple(
paragraphs.into_paragraphs(),
title,
None,
None,
Some(title),
true,
ConfirmActionMenu::new(None, true, None),
ConfirmActionStrings::new(title, None, None, Some(title)),
true,
None,
)
};
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();
flow::new_confirm_action_simple(
new_confirm_action_simple(
paragraphs,
ConfirmActionMenu::new(None, false, None),
ConfirmActionStrings::new(
TR::coinjoin__title.into(),
None,
None,
Some(TR::coinjoin__title.into()),
),
true,
false,
None,
)
};
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,
/// data: str | bytes,
/// 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_cancel: str | None = None,
/// verb_info: str | None = None,
/// info: bool = True,
/// hold: bool = False,
/// chunkify: bool = False,
/// prompt_screen: bool = False,
/// default_cancel: bool = False,
/// page_limit: int | None = None,
/// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data."""
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),

View File

@ -2,7 +2,7 @@ use crate::{
translations::TR,
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate},
display::Color,
display::{Color, Font},
geometry::{Insets, Rect},
shape::Renderer,
},
@ -13,21 +13,26 @@ use super::{
ButtonDetails, ButtonLayout, ButtonPos,
};
#[derive(PartialEq)]
enum LastPageLayout {
Confirm,
ArmedConfirmPlusInfo,
}
pub struct ButtonPage<T>
where
T: Component + Paginate,
{
page_count: usize,
active_page: usize,
page_limit: Option<usize>,
last_page_layout: LastPageLayout,
content: Child<T>,
pad: Pad,
/// Left button of the first screen
cancel_btn_details: Option<ButtonDetails>,
/// Right button of the last screen
confirm_btn_details: Option<ButtonDetails>,
/// Left button of every screen
info_btn_details: Option<ButtonDetails>,
back_btn_details: Option<ButtonDetails>,
/// Right button of every screen apart the last one
next_btn_details: Option<ButtonDetails>,
buttons: Child<ButtonController>,
}
@ -40,10 +45,17 @@ where
Self {
page_count: 0, // will be set in place()
active_page: 0,
page_limit: None,
last_page_layout: LastPageLayout::Confirm,
content: Child::new(content),
pad: Pad::with_background(background).with_clear(),
cancel_btn_details: Some(ButtonDetails::cancel_icon()),
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()),
next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()),
// 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 {
self.cancel_btn_details = btn_details;
self
@ -73,6 +91,11 @@ where
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 {
self.active_page < self.page_count - 1
}
@ -119,11 +142,11 @@ where
fn get_button_layout(&self, has_prev: bool, has_next: bool) -> ButtonLayout {
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);
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> {
if is_first {
self.cancel_btn_details.clone()
@ -132,13 +155,21 @@ where
}
}
/// Get the right button details, depending on whether there is a next
/// page.
fn get_middle_button_details(&self, has_next_page: bool) -> Option<ButtonDetails> {
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> {
if has_next_page {
self.next_btn_details.clone()
} else {
} else if self.last_page_layout == LastPageLayout::Confirm {
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
// and we can calculate the 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.buttons.place(button_area);
bounds
@ -191,17 +226,25 @@ where
return Some(PageMsg::Cancelled);
}
}
ButtonPos::Middle => {
return Some(PageMsg::Confirmed);
}
ButtonPos::Right => {
if self.has_next_page() {
// Clicked NEXT. Scroll down.
self.go_to_next_page();
self.change_page(ctx);
} else {
// Clicked CONFIRM. Send result.
match self.last_page_layout {
LastPageLayout::Confirm => {
return Some(PageMsg::Confirmed);
}
LastPageLayout::ArmedConfirmPlusInfo => {
return Some(PageMsg::Info);
}
}
}
}
_ => {}
}
}

View File

@ -94,6 +94,7 @@ where
match msg {
PageMsg::Confirmed => Ok(CONFIRMED.as_obj()),
PageMsg::Cancelled => Ok(CANCELLED.as_obj()),
PageMsg::Info => Ok(INFO.as_obj()),
_ => Err(Error::TypeError),
}
}
@ -243,7 +244,9 @@ fn content_in_button_page<T: Component + Paginate + MaybeTrace + 'static>(
content: T,
verb: TString<'static>,
verb_cancel: Option<TString<'static>>,
info: bool,
hold: bool,
page_limit: Option<usize>,
) -> Result<Obj, Error> {
// Left button - icon, text or nothing.
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());
}
let content = ButtonPage::new(content, theme::BG)
let mut content = ButtonPage::new(content, theme::BG)
.with_cancel_btn(cancel_btn)
.with_page_limit(page_limit)
.with_confirm_btn(confirm_btn);
if info {
content = content.with_armed_confirm_plus_info();
}
let mut frame = ScrollableFrame::new(content);
if !title.is_empty() {
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()
};
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) }
}
@ -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 description: Option<TString> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let extra: Option<TString> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
let verb: TString<'static> =
kwargs.get_or(Qstr::MP_QSTR_verb, TR::buttons__confirm.into())?;
let verb_cancel: Option<TString<'static>> = kwargs
let extra: Option<TString> = kwargs
.get(Qstr::MP_QSTR_extra)
.unwrap_or_else(|_| Obj::const_none())
.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)
.unwrap_or_else(|_| Obj::const_none())
.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 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 {
// 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();
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) }
}
@ -393,7 +419,9 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
paragraphs.into_paragraphs(),
button_text,
Some("".into()),
false,
hold,
None,
)
};
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);
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) }
}
@ -505,7 +541,9 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
paragraphs,
verb.unwrap_or(TR::buttons__confirm.into()),
Some("".into()),
false,
hold,
None,
)
};
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,
TR::buttons__hold_to_confirm.into(),
Some("".into()),
false,
true,
None,
)
};
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(),
Some("".into()),
false,
false,
None,
)
};
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(),
Some("".into()),
false,
false,
None,
)
};
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,
Some("<".into()),
false,
false,
None,
)
};
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,
TR::buttons__hold_to_confirm.into(),
None,
false,
true,
None,
)
};
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,
Some("".into()),
false,
false,
None,
)
};
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(),
None,
false,
false,
None,
)
};
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,
/// data: str | bytes,
/// 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_cancel: str | None = None,
/// verb_info: str | None = None,
/// info: bool = True,
/// hold: bool = False,
/// chunkify: bool = False,
/// prompt_screen: bool = False,
/// default_cancel: bool = False,
/// page_limit: int | None = None,
/// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data."""
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: Swipe,
scrollbar: ScrollBar,
page_limit: Option<usize>,
/// Hold-to-confirm mode whenever this is `Some(loader)`.
loader: Option<Loader>,
button_cancel: Option<Button>,
@ -71,6 +72,7 @@ where
pad: Pad::with_background(background),
swipe: Swipe::new(),
scrollbar: ScrollBar::vertical(),
page_limit: None,
loader: None,
button_cancel: Some(Button::with_icon(theme::ICON_CANCEL)),
button_confirm: Button::with_icon(theme::ICON_CONFIRM).styled(theme::button_confirm()),
@ -110,6 +112,11 @@ where
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 {
self.cancel_from_any_page = true;
self.button_prev = Button::with_icon(theme::ICON_BACK).initially_enabled(false);
@ -328,6 +335,11 @@ where
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() {
self.button_confirm.place(layout.button_both);

View File

@ -198,6 +198,7 @@ where
PageMsg::Content(_) => Err(Error::TypeError),
PageMsg::Confirmed => Ok(CONFIRMED.as_obj()),
PageMsg::Cancelled => Ok(CANCELLED.as_obj()),
PageMsg::Info => Ok(INFO.as_obj()),
PageMsg::SwipeLeft => Ok(INFO.as_obj()),
PageMsg::SwipeRight => Ok(CANCELLED.as_obj()),
}
@ -412,6 +413,7 @@ struct ConfirmBlobParams {
hold: bool,
chunkify: bool,
text_mono: bool,
page_limit: Option<usize>,
}
impl ConfirmBlobParams {
@ -435,6 +437,7 @@ impl ConfirmBlobParams {
hold,
chunkify: false,
text_mono: true,
page_limit: None,
}
}
@ -463,6 +466,11 @@ impl ConfirmBlobParams {
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> {
let paragraphs = ConfirmBlob {
description: self.description.unwrap_or("".into()),
@ -488,10 +496,12 @@ impl ConfirmBlobParams {
if self.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);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(theme::label_subtitle(), subtitle);
}
if self.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 description: Option<TString> =
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
.get(Qstr::MP_QSTR_verb)
.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)
.unwrap_or_else(|_| Obj::const_none())
.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 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)
.with_text_mono(text_mono)
.with_extra(extra)
.with_chunkify(chunkify)
.with_info_button(info)
.with_page_limit(page_limit)
.into_layout()
};
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 title: TString = kwargs.get(Qstr::MP_QSTR_title)?.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 mut paragraphs = ParagraphVecLong::new();
@ -1155,7 +1179,11 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
title,
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
.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(),
))?;
Ok(obj.into())
@ -1762,12 +1790,19 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str,
/// data: str | bytes,
/// 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_cancel: str | None = None,
/// verb_info: str | None = None,
/// info: bool = True,
/// hold: bool = False,
/// chunkify: bool = False,
/// prompt_screen: bool = False,
/// default_cancel: bool = False,
/// page_limit: int | None = None,
/// ) -> LayoutObj[UiResult]:
/// """Confirm byte sequence data."""
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,
/// button: str,
/// button_style_confirm: bool = False,
/// items: Iterable[tuple[int, str | bytes]],
/// ) -> LayoutObj[UiResult]:
/// """Confirm long content with the possibility to go back from any page.

View File

@ -118,12 +118,19 @@ def confirm_blob(
title: str,
data: str | bytes,
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_cancel: str | None = None,
verb_info: str | None = None,
info: bool = True,
hold: bool = False,
chunkify: bool = False,
prompt_screen: bool = False,
default_cancel: bool = False,
page_limit: int | None = None,
) -> LayoutObj[UiResult]:
"""Confirm byte sequence data."""
@ -675,12 +682,19 @@ def confirm_blob(
title: str,
data: str | bytes,
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_cancel: str | None = None,
verb_info: str | None = None,
info: bool = True,
hold: bool = False,
chunkify: bool = False,
prompt_screen: bool = False,
default_cancel: bool = False,
page_limit: int | None = None,
) -> LayoutObj[UiResult]:
"""Confirm byte sequence data."""
@ -1230,12 +1244,19 @@ def confirm_blob(
title: str,
data: str | bytes,
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_cancel: str | None = None,
verb_info: str | None = None,
info: bool = True,
hold: bool = False,
chunkify: bool = False,
prompt_screen: bool = False,
default_cancel: bool = False,
page_limit: int | None = None,
) -> LayoutObj[UiResult]:
"""Confirm byte sequence data."""
@ -1444,6 +1465,7 @@ def confirm_more(
*,
title: str,
button: str,
button_style_confirm: bool = False,
items: Iterable[tuple[int, str | bytes]],
) -> LayoutObj[UiResult]:
"""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__turn_off: str = "Turn off"
buttons__turn_on: str = "Turn on"
buttons__view_all_data: str = "View all data"
cardano__addr_base: str = "Base"
cardano__addr_enterprise: str = "Enterprise"
cardano__addr_legacy: str = "Legacy"
@ -298,9 +299,10 @@ class TR:
ethereum__data_size_template: str = "Size: {0} bytes"
ethereum__gas_limit: str = "Gas limit"
ethereum__gas_price: str = "Gas price"
ethereum__interaction_contract: str = "Interaction contract"
ethereum__max_gas_price: str = "Max gas price"
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__priority_fee: str = "Priority fee"
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_unstake: str = "Unstake"
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_message: str = "Confirm message"
ethereum__title_confirm_struct: str = "Confirm struct"
ethereum__title_confirm_typed_data: str = "Confirm typed data"
ethereum__title_input_data: str = "Input data"
ethereum__title_signing_address: str = "Signing address"
ethereum__token_contract: str = "Token contract"
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__valid_signature: str = "The signature is valid."
experimental_mode__enable: str = "Enable experimental features?"
@ -396,6 +400,7 @@ class TR:
instructions__swipe_up: str = "Swipe up"
instructions__tap_to_confirm: str = "Tap to confirm"
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__to_the_total_amount: str = "To the total amount:"
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.ui.layouts import (
confirm_blob,
confirm_blob_with_optional_pagination,
confirm_ethereum_staking_tx,
confirm_text,
should_show_more,
@ -35,18 +36,13 @@ async def require_confirm_tx(
fee_info_items: Iterable[tuple[str, str]],
network: EthereumNetworkInfo,
token: EthereumTokenInfo | None,
is_contract_interaction: bool,
chunkify: bool,
) -> None:
from trezor.ui.layouts import confirm_ethereum_tx
if to_bytes:
to_str = address_from_bytes(to_bytes, network)
else:
to_str = TR.ethereum__new_contract
chunkify = False
to_str = address_from_bytes(to_bytes, network) if to_bytes else None
total_amount = format_ethereum_amount(value, token, network)
account, account_path = get_account_and_path(address_n)
await confirm_ethereum_tx(
@ -56,6 +52,7 @@ async def require_confirm_tx(
account_path,
maximum_fee,
fee_info_items,
is_contract_interaction,
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 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()
return confirm_address(
TR.ethereum__unknown_token,
await confirm_address(
TR.words__address,
contract_address_hex,
TR.ethereum__contract,
"unknown_token",
subtitle=TR.ethereum__token_contract,
verb=TR.buttons__continue,
br_name="unknown_token",
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]:
return confirm_blob(
return confirm_blob_with_optional_pagination(
"confirm_data",
TR.ethereum__title_confirm_data,
TR.ethereum__title_input_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,
ask_pagination=True,
)
@ -301,7 +308,6 @@ async def confirm_typed_value(
title,
data,
description,
ask_pagination=True,
)
else:
await confirm_text(

View File

@ -56,7 +56,7 @@ async def sign_tx(
raise DataError("Fee overflow")
check_common_fields(msg)
# have a user confirm signing
# have the user confirm signing
await paths.validate_path(keychain, msg.address_n)
address_bytes = bytes_from_address(msg.to)
gas_price = int.from_bytes(msg.gas_price, "big")
@ -130,9 +130,11 @@ async def confirm_tx_data(
return
# 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_tx(
@ -143,7 +145,8 @@ async def confirm_tx_data(
fee_items,
defs.network,
token,
bool(msg.chunkify),
is_contract_interaction=is_contract_interaction,
chunkify=bool(msg.chunkify),
)
@ -189,7 +192,7 @@ async def handle_staking(
return False
async def handle_erc20_transfer(
async def _handle_erc20_transfer(
msg: MsgInSignTx,
definitions: Definitions,
address_bytes: bytes,

View File

@ -31,10 +31,9 @@ async def get_ecdh_session_key(msg: GetECDHSessionKey) -> ECDHSessionKey:
# require_confirm_ecdh_session_key
proto = msg_identity.proto.upper() if msg_identity.proto else "identity"
await confirm_address(
# TODO: translate?
f"Decrypt {proto}",
f"Decrypt {proto}", # TODO: translate?
serialize_identity_without_proto(msg_identity),
"",
chunkify=False,
)
# 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(
TR.nem__confirm_address,
address,
action,
"confirm_multisig",
ButtonRequestType.ConfirmOutput,
description=action,
br_name="confirm_multisig",
br_code=ButtonRequestType.ConfirmOutput,
)

View File

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

View File

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

View File

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

View File

@ -592,9 +592,26 @@ def show_warning(
content: str,
subheader: str | None = None,
button: str | None = None,
default_cancel: bool = False,
verb_cancel: str | None = None,
br_code: ButtonRequestType = ButtonRequestType.Warning,
) -> Awaitable[None]:
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(
interact(
RustLayout(
@ -750,43 +767,56 @@ async def should_show_more(
raise ActionCancelled
async def _confirm_ask_pagination(
async def confirm_blob_with_optional_pagination(
br_name: str,
title: str,
data: bytes | str,
description: str,
br_code: ButtonRequestType,
) -> None:
paginated: ui.Layout | None = None
# TODO: make should_show_more/confirm_more accept bytes directly
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,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
br_code: ButtonRequestType = BR_CODE_OTHER,
chunkify: bool = False,
):
return
if paginated is None:
paginated = RustLayout(
trezorui2.confirm_more(
# show first page first
layout = RustLayout(
trezorui2.confirm_blob(
title=title,
button=TR.buttons__close,
items=[(ui.MONO, data)],
data=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:
paginated.request_complete_repaint()
result = await interact(paginated, br_name, br_code)
assert result in (CONFIRMED, CANCELLED)
assert False
result = await interact(
layout,
br_name,
br_code,
)
if result is INFO:
# 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(
@ -794,33 +824,33 @@ def confirm_blob(
title: str,
data: bytes | str,
description: str | None = None,
text_mono: bool = True,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = None,
info: bool = True,
hold: bool = False,
br_code: ButtonRequestType = BR_CODE_OTHER,
ask_pagination: bool = False,
chunkify: bool = False,
default_cancel: bool = False,
prompt_screen: bool = True,
) -> Awaitable[None]:
layout = RustLayout(
trezorui2.confirm_blob(
title=title,
description=description,
data=data,
extra=None,
hold=hold,
description=description,
text_mono=text_mono,
subtitle=subtitle,
verb=verb,
verb_cancel=verb_cancel,
info=info,
hold=hold,
chunkify=chunkify,
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(
interact(
layout,
@ -833,7 +863,10 @@ def confirm_blob(
def confirm_address(
title: str,
address: str,
subtitle: str | None = None,
description: str | None = None,
verb: str | None = None,
chunkify: bool = True,
br_name: str = "confirm_address",
br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]:
@ -843,7 +876,9 @@ def confirm_address(
description or "",
br_name,
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,
hold: bool = False,
value_text_mono: bool = True,
chunkify: bool = False,
info_items: Iterable[tuple[str, str]] | None = None,
info_title: str | None = None,
chunkify_info: bool = False,
@ -922,6 +958,7 @@ def confirm_value(
verb=verb,
hold=hold,
info_button=bool(info_items),
chunkify=chunkify,
text_mono=value_text_mono,
)
),
@ -1033,12 +1070,13 @@ def _confirm_summary(
if not utils.BITCOIN_ONLY:
async def confirm_ethereum_tx(
recipient: str,
recipient: str | None,
total_amount: str,
account: str | None,
account_path: str | None,
maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]],
is_contract_interaction: bool,
br_name: str = "confirm_ethereum_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
chunkify: bool = False,
@ -1047,10 +1085,14 @@ if not utils.BITCOIN_ONLY:
RustLayout(
trezorui2.flow_confirm_output(
title=TR.words__address,
subtitle=TR.words__recipient,
message=recipient,
subtitle=(
TR.words__recipient
if not is_contract_interaction
else TR.ethereum__interaction_contract
),
message=(recipient or TR.ethereum__new_contract),
amount=None,
chunkify=chunkify,
chunkify=(chunkify if recipient else False),
text_mono=True,
account=account,
account_path=account_path,
@ -1197,7 +1239,7 @@ def confirm_replacement(title: str, txid: str) -> Awaitable[None]:
title,
txid,
TR.send__transaction_id,
TR.buttons__continue,
verb=TR.buttons__continue,
br_code=ButtonRequestType.SignTx,
)
@ -1215,7 +1257,6 @@ async def confirm_modify_output(
verb=TR.buttons__continue,
verb_cancel=None,
description=f"{TR.words__address}:",
extra=None,
)
)
modify_layout = RustLayout(
@ -1381,7 +1422,6 @@ async def confirm_signverify(
title=TR.sign_message__confirm_message,
description=None,
data=message,
extra=None,
hold=not verify,
verb=TR.buttons__confirm if verify else None,
)

View File

@ -725,6 +725,8 @@ def show_warning(
content: str,
subheader: 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,
) -> Awaitable[None]:
from trezor import translations
@ -901,18 +903,86 @@ async def should_show_more(
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(
br_name: str,
title: str,
data: bytes | str,
description: str | None = None,
text_mono: bool = True,
subtitle: str | None = None,
verb: str | None = None,
verb_cancel: str | None = "", # icon
info: bool = True,
hold: bool = False,
br_code: ButtonRequestType = BR_CODE_OTHER,
ask_pagination: bool = False,
chunkify: bool = False,
default_cancel: bool = False,
prompt_screen: bool = True,
ask_pagination: bool = False,
) -> Awaitable[None]:
verb = verb or TR.buttons__confirm # def_arg
layout = RustLayout(
@ -920,7 +990,6 @@ def confirm_blob(
title=title,
description=description,
data=data,
extra=None,
verb=verb,
verb_cancel=verb_cancel,
hold=hold,
@ -991,13 +1060,16 @@ async def _confirm_ask_pagination(
def confirm_address(
title: str,
address: str,
subtitle: str | None = None,
description: str | None = None,
verb: str | None = None,
chunkify: bool = True,
br_name: str = "confirm_address",
br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]:
return confirm_blob(
br_name,
title,
subtitle or title,
address,
description,
br_code=br_code,
@ -1139,7 +1211,6 @@ async def confirm_value(
title=info_title,
data=info_value,
description=description,
extra=None,
verb="",
verb_cancel="<",
hold=False,
@ -1298,12 +1369,13 @@ if not utils.BITCOIN_ONLY:
)
async def confirm_ethereum_tx(
recipient: str,
recipient: str | None,
total_amount: str,
_account: str | None,
_account_path: str | None,
maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]],
is_contract_interaction: bool,
br_name: str = "confirm_ethereum_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
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:
# Allowing going back and forth between recipient and summary/details
await confirm_blob(
br_name,
TR.words__recipient,
recipient,
title,
recipient or TR.ethereum__new_contract,
verb=TR.buttons__continue,
chunkify=chunkify,
chunkify=(chunkify if recipient else False),
)
try:
@ -1397,9 +1474,7 @@ async def confirm_modify_output(
title=TR.modify_amount__title,
data=address,
verb=TR.buttons__continue,
verb_cancel=None,
description=f"{TR.words__address}:",
extra=None,
)
)
modify_layout = RustLayout(
@ -1510,7 +1585,6 @@ async def confirm_signverify(
verb=TR.buttons__continue,
br_code=BR_CODE_OTHER,
)
try:
await confirm_blob(
br_name,
@ -1658,7 +1732,7 @@ def pin_mismatch_popup(is_wipe_code: bool = False) -> Awaitable[None]:
description,
TR.pin__please_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,
) -> None:
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.reset__never_make_digital_copy,
TR.buttons__ok_i_understand,
ButtonRequestType.ResetDevice,
br_code=ButtonRequestType.ResetDevice,
)

View File

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

View File

@ -284,7 +284,7 @@
"ethereum__show_full_message": "text,1",
"ethereum__show_full_struct": "text,1",
"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_message": "title,1",
"ethereum__title_confirm_struct": "title,1",

View File

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

View File

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

View File

@ -123,6 +123,7 @@
"buttons__try_again": "Try again",
"buttons__turn_off": "Turn off",
"buttons__turn_on": "Turn on",
"buttons__view_all_data": "View all data",
"cardano__addr_base": "Base",
"cardano__addr_enterprise": "Enterprise",
"cardano__addr_legacy": "Legacy",
@ -300,9 +301,10 @@
"ethereum__data_size_template": "Size: {0} bytes",
"ethereum__gas_limit": "Gas limit",
"ethereum__gas_price": "Gas price",
"ethereum__interaction_contract": "Interaction contract",
"ethereum__max_gas_price": "Max gas price",
"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__priority_fee": "Priority fee",
"ethereum__show_full_array": "Show full array",
@ -318,13 +320,15 @@
"ethereum__staking_stake_intro": "Stake ETH on Everstake?",
"ethereum__staking_unstake": "Unstake",
"ethereum__staking_unstake_intro": "Unstake ETH from Everstake?",
"ethereum__title_confirm_data": "Confirm data",
"ethereum__title_confirm_domain": "Confirm domain",
"ethereum__title_confirm_message": "Confirm message",
"ethereum__title_confirm_struct": "Confirm struct",
"ethereum__title_confirm_typed_data": "Confirm typed data",
"ethereum__title_input_data": "Input data",
"ethereum__title_signing_address": "Signing address",
"ethereum__token_contract": "Token contract",
"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__valid_signature": "The signature is valid.",
"experimental_mode__enable": "Enable experimental features?",
@ -398,6 +402,7 @@
"instructions__swipe_up": "Swipe up",
"instructions__tap_to_confirm": "Tap to confirm",
"instructions__tap_to_start": "Tap to start",
"instructions__view_all_data": "View all data in the menu.",
"joint__title": "Joint transaction",
"joint__to_the_total_amount": "To the total amount:",
"joint__you_are_contributing": "You are contributing:",

View File

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

View File

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

View File

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

View File

@ -285,7 +285,7 @@
"283": "ethereum__show_full_message",
"284": "ethereum__show_full_struct",
"285": "ethereum__sign_eip712",
"286": "ethereum__title_confirm_data",
"286": "ethereum__title_input_data",
"287": "ethereum__title_confirm_domain",
"288": "ethereum__title_confirm_message",
"289": "ethereum__title_confirm_struct",
@ -966,5 +966,10 @@
"964": "instructions__swipe_down",
"965": "fido__title_credential_details",
"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__max_gas_price": "Preço máximo do gás:",
"ethereum__name_and_version": "Nome e versão",
"ethereum__new_contract": "novo contrato?",
"ethereum__no_message_field": "Sem campo mens.",
"ethereum__priority_fee": "Taxa de prioridade:",
"ethereum__show_full_array": "Exibir array completo",
@ -360,11 +359,11 @@
"ethereum__staking_stake_intro": "Fazer stake de ETH no Everstake?",
"ethereum__staking_unstake": "Tirar do stake",
"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_message": "Confirmar mensagem",
"ethereum__title_confirm_struct": "Conf. estrutura",
"ethereum__title_confirm_typed_data": "Conf. dados",
"ethereum__title_input_data": "Dados entrados",
"ethereum__title_signing_address": "End. assinatura",
"ethereum__units_template": "{0} unidades",
"ethereum__unknown_token": "Token desconhecido",

View File

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

View File

@ -332,7 +332,6 @@
"ethereum__gas_price": "Gaz fiyatı:",
"ethereum__max_gas_price": "Maksimum gaz fiyatı:",
"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__priority_fee": "Öncelik ücreti:",
"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_unstake": "Unstake et",
"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_message": "Mesaji onayla",
"ethereum__title_confirm_struct": "Yapiyi onayla",
"ethereum__title_confirm_typed_data": "Yzln veri̇yi̇ onayla",
"ethereum__title_input_data": "Veri̇leri̇ girdiyi",
"ethereum__title_signing_address": "İmza adresi̇",
"ethereum__units_template": "{0} birim",
"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.tools import parse_path
from ...input_flows import InputFlowConfirmAllWarnings
from . import common
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:
with client:
if not client.debug.legacy_debug:
client.set_input_flow(InputFlowConfirmAllWarnings(client).get())
# when using an external chains, unknown tokens are allowed
network = common.encode_network(chain_id=66666, slip44=60)
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:
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
# from other chains
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.update(to=ERC20_FAKE_ADDRESS, chain_id=66666)
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 ...input_flows import (
InputFlowConfirmAllWarnings,
InputFlowEthereumSignTxDataGoBack,
InputFlowEthereumSignTxDataScrollDown,
InputFlowEthereumSignTxDataSkip,
@ -56,7 +57,12 @@ def make_defs(parameters: dict) -> messages.EthereumDefinitions:
)
@pytest.mark.parametrize("chunkify", (True, False))
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(
@ -143,6 +149,8 @@ def test_signtx_go_back_from_summary(client: Client):
@pytest.mark.parametrize("chunkify", (True, False))
def test_signtx_eip1559(client: Client, chunkify: bool, parameters: dict, result: dict):
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(
client,
n=parse_path(parameters["path"]),
@ -455,6 +463,7 @@ def test_signtx_data_pagination(client: Client, flow):
client.set_input_flow(flow(client))
_sign_tx_call()
if flow is not input_flow_data_scroll_down:
with client, pytest.raises(exceptions.Cancelled):
client.watch_layout()
client.set_input_flow(flow(client, cancel=True))

View File

@ -1216,12 +1216,21 @@ class InputFlowEthereumSignTxDataScrollDown(InputFlowBase):
self.cancel = cancel
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.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:
yield from self.ETH.confirm_data()
self.debug.click(buttons.OK)
yield from self.ETH.confirm_tx()
@ -2288,7 +2297,8 @@ class InputFlowConfirmAllWarnings(InputFlowBase):
text = layout.text_content().lower()
# hi priority warning
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")
+ [
"witness path",

View File

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

File diff suppressed because it is too large Load Diff