1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-19 01:26:06 +00:00

feat(core/ui): T3T1 send flow

[no changelog]
This commit is contained in:
Martin Milata 2024-05-12 20:37:14 +02:00
parent 7bbfa64dbb
commit a212b325fe
37 changed files with 843 additions and 169 deletions

View File

@ -19,7 +19,9 @@ static void _librust_qstrs(void) {
MP_QSTR___dict__;
MP_QSTR___name__;
MP_QSTR_account;
MP_QSTR_account_items;
MP_QSTR_account_label;
MP_QSTR_account_path;
MP_QSTR_accounts;
MP_QSTR_action;
MP_QSTR_active;
@ -175,6 +177,7 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_reset_device;
MP_QSTR_confirm_total;
MP_QSTR_confirm_total__fee_rate;
MP_QSTR_confirm_total__fee_rate_colon;
MP_QSTR_confirm_total__sending_from_account;
MP_QSTR_confirm_total__title_fee;
MP_QSTR_confirm_total__title_sending_from;
@ -205,6 +208,7 @@ static void _librust_qstrs(void) {
MP_QSTR_experimental_mode__title;
MP_QSTR_extra;
MP_QSTR_fee_amount;
MP_QSTR_fee_items;
MP_QSTR_fee_label;
MP_QSTR_fee_rate_amount;
MP_QSTR_fee_title;
@ -212,9 +216,11 @@ static void _librust_qstrs(void) {
MP_QSTR_fingerprint;
MP_QSTR_firmware_update__title;
MP_QSTR_firmware_update__title_fingerprint;
MP_QSTR_flow_confirm_output;
MP_QSTR_flow_confirm_reset_create;
MP_QSTR_flow_confirm_reset_recover;
MP_QSTR_flow_confirm_set_new_pin;
MP_QSTR_flow_confirm_summary;
MP_QSTR_flow_get_address;
MP_QSTR_flow_prompt_backup;
MP_QSTR_flow_show_share_words;
@ -248,6 +254,7 @@ static void _librust_qstrs(void) {
MP_QSTR_inputs__space;
MP_QSTR_instructions__continue_in_app;
MP_QSTR_instructions__hold_to_confirm;
MP_QSTR_instructions__hold_to_sign;
MP_QSTR_instructions__swipe_up;
MP_QSTR_instructions__tap_to_confirm;
MP_QSTR_is_type_of;
@ -521,11 +528,15 @@ static void _librust_qstrs(void) {
MP_QSTR_select_word;
MP_QSTR_select_word_count;
MP_QSTR_send__address_path;
MP_QSTR_send__cancel_sign;
MP_QSTR_send__confirm_sending;
MP_QSTR_send__from_multiple_accounts;
MP_QSTR_send__incl_transaction_fee;
MP_QSTR_send__including_fee;
MP_QSTR_send__maximum_fee;
MP_QSTR_send__receiving_to_multisig;
MP_QSTR_send__send_from;
MP_QSTR_send__sign_transaction;
MP_QSTR_send__title_confirm_sending;
MP_QSTR_send__title_joint_transaction;
MP_QSTR_send__title_receiving_to;
@ -534,6 +545,7 @@ static void _librust_qstrs(void) {
MP_QSTR_send__title_sending_to;
MP_QSTR_send__to_the_total_amount;
MP_QSTR_send__total_amount;
MP_QSTR_send__total_amount_colon;
MP_QSTR_send__transaction_id;
MP_QSTR_send__you_are_contributing;
MP_QSTR_share_words;
@ -833,7 +845,6 @@ static void _librust_qstrs(void) {
MP_QSTR_eos__requirement;
MP_QSTR_eos__sell_ram;
MP_QSTR_eos__sender;
MP_QSTR_eos__sign_transaction;
MP_QSTR_eos__threshold;
MP_QSTR_eos__to;
MP_QSTR_eos__transfer;

View File

@ -351,9 +351,9 @@ pub enum TranslatedString {
coinjoin__title_do_not_disconnect = 217, // "Do not disconnect your trezor!"
coinjoin__title_progress = 218, // "Coinjoin in progress"
coinjoin__waiting_for_others = 219, // "Waiting for others"
confirm_total__fee_rate = 220, // "Fee rate:"
confirm_total__fee_rate_colon = 220, // "Fee rate:"
confirm_total__sending_from_account = 221, // "Sending from account:"
confirm_total__title_fee = 222, // "Fee information"
confirm_total__title_fee = 222, // "Fee info"
confirm_total__title_sending_from = 223, // "Sending from"
debug__loading_seed = 224, // "Loading seed"
debug__loading_seed_not_recommended = 225, // "Loading private seed is not recommended."
@ -420,8 +420,7 @@ pub enum TranslatedString {
eos__sell_ram = 258, // "Sell RAM"
#[cfg(feature = "universal_fw")]
eos__sender = 259, // "Sender:"
#[cfg(feature = "universal_fw")]
eos__sign_transaction = 260, // "Sign transaction"
send__sign_transaction = 260, // "Sign transaction"
#[cfg(feature = "universal_fw")]
eos__threshold = 261, // "Threshold:"
#[cfg(feature = "universal_fw")]
@ -940,7 +939,7 @@ pub enum TranslatedString {
send__title_sending_amount = 650, // "Sending amount"
send__title_sending_to = 651, // "Sending to"
send__to_the_total_amount = 652, // "To the total amount:"
send__total_amount = 653, // "Total amount:"
send__total_amount_colon = 653, // "Total amount:"
send__transaction_id = 654, // "Transaction ID:"
send__you_are_contributing = 655, // "You are contributing:"
share_words__words_in_order = 656, // " words in order."
@ -1271,6 +1270,12 @@ pub enum TranslatedString {
pin__cancel_description = 870, // "Continue without PIN"
pin__cancel_info = 871, // "Without a PIN, anyone can access this device."
pin__cancel_setup = 872, // "Cancel PIN setup"
send__cancel_sign = 873, // "Cancel sign"
send__send_from = 874, // "Send from"
instructions__hold_to_sign = 875, // "Hold to sign"
confirm_total__fee_rate = 876, // "Fee rate"
send__incl_transaction_fee = 877, // "incl. Transaction fee"
send__total_amount = 878, // "Total amount"
}
impl TranslatedString {
@ -1617,9 +1622,9 @@ impl TranslatedString {
Self::coinjoin__title_do_not_disconnect => "Do not disconnect your trezor!",
Self::coinjoin__title_progress => "Coinjoin in progress",
Self::coinjoin__waiting_for_others => "Waiting for others",
Self::confirm_total__fee_rate => "Fee rate:",
Self::confirm_total__fee_rate_colon => "Fee rate:",
Self::confirm_total__sending_from_account => "Sending from account:",
Self::confirm_total__title_fee => "Fee information",
Self::confirm_total__title_fee => "Fee info",
Self::confirm_total__title_sending_from => "Sending from",
Self::debug__loading_seed => "Loading seed",
Self::debug__loading_seed_not_recommended => "Loading private seed is not recommended.",
@ -1686,8 +1691,7 @@ impl TranslatedString {
Self::eos__sell_ram => "Sell RAM",
#[cfg(feature = "universal_fw")]
Self::eos__sender => "Sender:",
#[cfg(feature = "universal_fw")]
Self::eos__sign_transaction => "Sign transaction",
Self::send__sign_transaction => "Sign transaction",
#[cfg(feature = "universal_fw")]
Self::eos__threshold => "Threshold:",
#[cfg(feature = "universal_fw")]
@ -2206,7 +2210,7 @@ impl TranslatedString {
Self::send__title_sending_amount => "Sending amount",
Self::send__title_sending_to => "Sending to",
Self::send__to_the_total_amount => "To the total amount:",
Self::send__total_amount => "Total amount:",
Self::send__total_amount_colon => "Total amount:",
Self::send__transaction_id => "Transaction ID:",
Self::send__you_are_contributing => "You are contributing:",
Self::share_words__words_in_order => " words in order.",
@ -2537,6 +2541,12 @@ impl TranslatedString {
Self::pin__cancel_description => "Continue without PIN",
Self::pin__cancel_info => "Without a PIN, anyone can access this device.",
Self::pin__cancel_setup => "Cancel PIN setup",
Self::send__cancel_sign => "Cancel sign",
Self::send__send_from => "Send from",
Self::instructions__hold_to_sign => "Hold to sign",
Self::confirm_total__fee_rate => "Fee rate",
Self::send__incl_transaction_fee => "incl. Transaction fee",
Self::send__total_amount => "Total amount",
}
}
@ -2884,7 +2894,7 @@ impl TranslatedString {
Qstr::MP_QSTR_coinjoin__title_do_not_disconnect => Some(Self::coinjoin__title_do_not_disconnect),
Qstr::MP_QSTR_coinjoin__title_progress => Some(Self::coinjoin__title_progress),
Qstr::MP_QSTR_coinjoin__waiting_for_others => Some(Self::coinjoin__waiting_for_others),
Qstr::MP_QSTR_confirm_total__fee_rate => Some(Self::confirm_total__fee_rate),
Qstr::MP_QSTR_confirm_total__fee_rate_colon => Some(Self::confirm_total__fee_rate_colon),
Qstr::MP_QSTR_confirm_total__sending_from_account => Some(Self::confirm_total__sending_from_account),
Qstr::MP_QSTR_confirm_total__title_fee => Some(Self::confirm_total__title_fee),
Qstr::MP_QSTR_confirm_total__title_sending_from => Some(Self::confirm_total__title_sending_from),
@ -2953,8 +2963,7 @@ impl TranslatedString {
Qstr::MP_QSTR_eos__sell_ram => Some(Self::eos__sell_ram),
#[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_eos__sender => Some(Self::eos__sender),
#[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_eos__sign_transaction => Some(Self::eos__sign_transaction),
Qstr::MP_QSTR_send__sign_transaction => Some(Self::send__sign_transaction),
#[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_eos__threshold => Some(Self::eos__threshold),
#[cfg(feature = "universal_fw")]
@ -3473,7 +3482,7 @@ impl TranslatedString {
Qstr::MP_QSTR_send__title_sending_amount => Some(Self::send__title_sending_amount),
Qstr::MP_QSTR_send__title_sending_to => Some(Self::send__title_sending_to),
Qstr::MP_QSTR_send__to_the_total_amount => Some(Self::send__to_the_total_amount),
Qstr::MP_QSTR_send__total_amount => Some(Self::send__total_amount),
Qstr::MP_QSTR_send__total_amount_colon => Some(Self::send__total_amount_colon),
Qstr::MP_QSTR_send__transaction_id => Some(Self::send__transaction_id),
Qstr::MP_QSTR_send__you_are_contributing => Some(Self::send__you_are_contributing),
Qstr::MP_QSTR_share_words__words_in_order => Some(Self::share_words__words_in_order),
@ -3804,6 +3813,12 @@ impl TranslatedString {
Qstr::MP_QSTR_pin__cancel_description => Some(Self::pin__cancel_description),
Qstr::MP_QSTR_pin__cancel_info => Some(Self::pin__cancel_info),
Qstr::MP_QSTR_pin__cancel_setup => Some(Self::pin__cancel_setup),
Qstr::MP_QSTR_send__cancel_sign => Some(Self::send__cancel_sign),
Qstr::MP_QSTR_send__send_from => Some(Self::send__send_from),
Qstr::MP_QSTR_instructions__hold_to_sign => Some(Self::instructions__hold_to_sign),
Qstr::MP_QSTR_confirm_total__fee_rate => Some(Self::confirm_total__fee_rate),
Qstr::MP_QSTR_send__incl_transaction_fee => Some(Self::send__incl_transaction_fee),
Qstr::MP_QSTR_send__total_amount => Some(Self::send__total_amount),
_ => None,
}
}

View File

@ -104,6 +104,7 @@ where
pub fn with_warning_button(self) -> Self {
self.with_button(theme::ICON_WARNING, CancelInfoConfirmMsg::Info, false)
.button_styled(theme::button_danger())
}
pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self {

View File

@ -0,0 +1,158 @@
use crate::{
error,
micropython::qstr::Qstr,
strutil::TString,
translations::TR,
ui::{
component::{ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
},
};
use super::super::{
component::{
AddressDetails, Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg,
},
theme,
};
use super::util::ConfirmBlobParams;
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmOutput {
Address,
Amount,
// Tap,
Menu,
AccountInfo,
CancelTap,
}
impl FlowState for ConfirmOutput {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmOutput::Address | ConfirmOutput::Amount, SwipeDirection::Left) => {
Decision::Goto(ConfirmOutput::Menu, direction)
}
(ConfirmOutput::Address, SwipeDirection::Up) => {
Decision::Goto(ConfirmOutput::Amount, direction)
}
(ConfirmOutput::Amount, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
(ConfirmOutput::Amount, SwipeDirection::Down) => {
Decision::Goto(ConfirmOutput::Address, direction)
}
(ConfirmOutput::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmOutput::Address, direction)
}
(ConfirmOutput::Menu, SwipeDirection::Left) => {
Decision::Goto(ConfirmOutput::AccountInfo, direction)
}
(ConfirmOutput::AccountInfo | ConfirmOutput::CancelTap, SwipeDirection::Right) => {
Decision::Goto(ConfirmOutput::Menu, direction)
}
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(_, FlowMsg::Info) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Left),
(ConfirmOutput::Menu, FlowMsg::Choice(0)) => {
Decision::Goto(ConfirmOutput::AccountInfo, SwipeDirection::Left)
}
(ConfirmOutput::Menu, FlowMsg::Choice(1)) => {
Decision::Goto(ConfirmOutput::CancelTap, SwipeDirection::Left)
}
(ConfirmOutput::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmOutput::Address, SwipeDirection::Right)
}
(ConfirmOutput::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled),
(_, FlowMsg::Cancelled) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Right),
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::layout::obj::LayoutObj,
};
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmOutput::new_obj) }
}
impl ConfirmOutput {
const EXTRA_PADDING: i16 = 6;
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: Option<TString> = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?;
let account: Option<TString> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
let account_path: Option<TString> =
kwargs.get(Qstr::MP_QSTR_account_path)?.try_into_option()?;
let address: Obj = kwargs.get(Qstr::MP_QSTR_address)?;
let amount: Obj = kwargs.get(Qstr::MP_QSTR_amount)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?;
// Address
let content_address = ConfirmBlobParams::new(TR::words__address.into(), address, None)
.with_subtitle(title)
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.with_chunkify(chunkify)
.with_text_mono(text_mono)
.into_layout()?;
// .one_button_request(ButtonRequestCode::ConfirmOutput, br_type);
// Amount
let content_amount = ConfirmBlobParams::new(TR::words__amount.into(), amount, None)
.with_subtitle(title)
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.with_text_mono(text_mono)
.into_layout()?;
// .one_button_request(ButtonRequestCode::ConfirmOutput, br_type);
// Menu
let content_menu = Frame::left_aligned(
"".into(),
VerticalMenu::empty()
.item(theme::ICON_CHEVRON_RIGHT, "Account info".into())
.danger(theme::ICON_CANCEL, "Cancel sign".into()),
)
.with_cancel_button()
.map(|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
// AccountInfo
let ad = AddressDetails::new(TR::send__send_from.into(), account, account_path)?;
let content_account = SwipePage::horizontal(ad).map(|_| Some(FlowMsg::Cancelled));
// CancelTap
let content_cancel_tap = Frame::left_aligned(
TR::send__cancel_sign.into(),
PromptScreen::new_tap_to_cancel(),
)
.with_cancel_button()
.with_footer(TR::instructions__tap_to_confirm.into(), None)
.map(|msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
let store = flow_store()
.add(content_address)?
.add(content_amount)?
.add(content_menu)?
.add(content_account)?
.add(content_cancel_tap)?;
let res = SwipeFlow::new(ConfirmOutput::Address, store)?;
Ok(LayoutObj::new(res)?.into())
}
}

View File

@ -0,0 +1,169 @@
use crate::{
error,
micropython::{iter::IterBuf, qstr::Qstr},
strutil::TString,
translations::TR,
ui::{
component::{ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow},
},
};
use super::super::{
component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg},
theme,
};
use super::util::ShowInfoParams;
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmSummary {
Summary,
Hold,
Menu,
FeeInfo,
AccountInfo,
CancelTap,
}
impl FlowState for ConfirmSummary {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmSummary::Summary | ConfirmSummary::Hold, SwipeDirection::Left) => {
Decision::Goto(ConfirmSummary::Menu, direction)
}
(ConfirmSummary::Summary, SwipeDirection::Up) => {
Decision::Goto(ConfirmSummary::Hold, direction)
}
(ConfirmSummary::Hold, SwipeDirection::Down) => {
Decision::Goto(ConfirmSummary::Summary, direction)
}
(ConfirmSummary::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmSummary::Summary, direction)
}
(ConfirmSummary::Menu, SwipeDirection::Left) => {
Decision::Goto(ConfirmSummary::FeeInfo, direction)
}
(
ConfirmSummary::AccountInfo | ConfirmSummary::FeeInfo | ConfirmSummary::CancelTap,
SwipeDirection::Right,
) => Decision::Goto(ConfirmSummary::Menu, direction),
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(_, FlowMsg::Info) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Left),
(ConfirmSummary::Hold, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed),
(ConfirmSummary::Menu, FlowMsg::Choice(0)) => {
Decision::Goto(ConfirmSummary::FeeInfo, SwipeDirection::Left)
}
(ConfirmSummary::Menu, FlowMsg::Choice(1)) => {
Decision::Goto(ConfirmSummary::AccountInfo, SwipeDirection::Left)
}
(ConfirmSummary::Menu, FlowMsg::Choice(2)) => {
Decision::Goto(ConfirmSummary::CancelTap, SwipeDirection::Left)
}
(ConfirmSummary::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmSummary::Summary, SwipeDirection::Right)
}
(ConfirmSummary::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled),
(_, FlowMsg::Cancelled) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Right),
_ => Decision::Nothing,
}
}
}
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::layout::obj::LayoutObj,
};
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmSummary::new_obj) }
}
impl ConfirmSummary {
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let account_items: Obj = kwargs.get(Qstr::MP_QSTR_account_items)?;
let fee_items: Obj = kwargs.get(Qstr::MP_QSTR_fee_items)?;
// Summary
let mut summary = ShowInfoParams::new(title)
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None);
for pair in IterBuf::new().try_iterate(items)? {
let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
summary = unwrap!(summary.add(label, value));
}
let content_summary = summary.into_layout()?;
// Hold to confirm
let content_hold = Frame::left_aligned(
TR::send__sign_transaction.into(),
PromptScreen::new_hold_to_confirm(),
)
.with_menu_button()
.with_footer(TR::instructions__hold_to_sign.into(), None)
.map(|msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
FrameMsg::Button(_) => Some(FlowMsg::Info),
});
// Menu
let content_menu = Frame::left_aligned(
"".into(),
VerticalMenu::empty()
.item(theme::ICON_CHEVRON_RIGHT, "Fee info".into())
.item(theme::ICON_CHEVRON_RIGHT, "Account info".into())
.danger(theme::ICON_CANCEL, "Cancel sign".into()),
)
.with_cancel_button()
.map(|msg| match msg {
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
// FeeInfo
let mut fee = ShowInfoParams::new(TR::confirm_total__title_fee.into()).with_cancel_button();
for pair in IterBuf::new().try_iterate(fee_items)? {
let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
fee = unwrap!(fee.add(label, value));
}
let content_fee = fee.into_layout()?;
// AccountInfo
let mut account = ShowInfoParams::new(TR::send__send_from.into()).with_cancel_button();
for pair in IterBuf::new().try_iterate(account_items)? {
let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
account = unwrap!(account.add(label, value));
}
let content_account = account.into_layout()?;
// CancelTap
let content_cancel_tap = Frame::left_aligned(
TR::send__cancel_sign.into(),
PromptScreen::new_tap_to_cancel(),
)
.with_cancel_button()
.with_footer(TR::instructions__tap_to_confirm.into(), None)
.map(|msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
});
let store = flow_store()
.add(content_summary)?
.add(content_hold)?
.add(content_menu)?
.add(content_fee)?
.add(content_account)?
.add(content_cancel_tap)?;
let res = SwipeFlow::new(ConfirmSummary::Summary, store)?;
Ok(LayoutObj::new(res)?.into())
}
}

View File

@ -1,16 +1,22 @@
pub mod confirm_action;
pub mod confirm_output;
pub mod confirm_reset_create;
pub mod confirm_reset_recover;
pub mod confirm_set_new_pin;
pub mod confirm_summary;
pub mod get_address;
pub mod prompt_backup;
pub mod show_share_words;
pub mod warning_hi_prio;
pub use confirm_action::new_confirm_action;
mod util;
pub use confirm_output::new_confirm_output;
pub use confirm_reset_create::ConfirmResetCreate;
pub use confirm_reset_recover::ConfirmResetRecover;
pub use confirm_set_new_pin::SetNewPin;
pub use confirm_summary::new_confirm_summary;
pub use get_address::GetAddress;
pub use prompt_backup::PromptBackup;
pub use show_share_words::ShowShareWords;

View File

@ -0,0 +1,236 @@
use super::super::{
component::{Frame, FrameMsg},
theme,
};
use crate::{
error::Error,
maybe_trace::MaybeTrace,
micropython::obj::Obj,
strutil::TString,
ui::{
component::{
base::ComponentExt,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt},
Component,
},
flow::{FlowMsg, Swipable, SwipePage},
layout::util::ConfirmBlob,
},
};
use heapless::Vec;
pub struct ConfirmBlobParams {
title: TString<'static>,
subtitle: Option<TString<'static>>,
footer_instruction: Option<TString<'static>>,
footer_description: Option<TString<'static>>,
data: Obj,
description: Option<TString<'static>>,
extra: Option<TString<'static>>,
menu_button: bool,
chunkify: bool,
text_mono: bool,
}
impl ConfirmBlobParams {
pub const fn new(
title: TString<'static>,
data: Obj,
description: Option<TString<'static>>,
) -> Self {
Self {
title,
subtitle: None,
footer_instruction: None,
footer_description: None,
data,
description,
extra: None,
menu_button: false,
chunkify: false,
text_mono: true,
}
}
pub const fn with_extra(mut self, extra: Option<TString<'static>>) -> Self {
self.extra = extra;
self
}
pub const fn with_subtitle(mut self, subtitle: Option<TString<'static>>) -> Self {
self.subtitle = subtitle;
self
}
pub const fn with_menu_button(mut self) -> Self {
self.menu_button = true;
self
}
pub const fn with_footer(
mut self,
instruction: TString<'static>,
description: Option<TString<'static>>,
) -> Self {
self.footer_instruction = Some(instruction);
self.footer_description = description;
self
}
pub const fn with_chunkify(mut self, chunkify: bool) -> Self {
self.chunkify = chunkify;
self
}
pub const fn with_text_mono(mut self, text_mono: bool) -> Self {
self.text_mono = text_mono;
self
}
pub fn into_layout(
self,
) -> Result<impl Component<Msg = FlowMsg> + Swipable + MaybeTrace, Error> {
let paragraphs = ConfirmBlob {
description: self.description.unwrap_or("".into()),
extra: self.extra.unwrap_or("".into()),
data: self.data.try_into()?,
description_font: &theme::TEXT_NORMAL,
extra_font: &theme::TEXT_DEMIBOLD,
data_font: if self.chunkify {
let data: TString = self.data.try_into()?;
theme::get_chunkified_text_style(data.len())
} else if self.text_mono {
&theme::TEXT_MONO
} else {
&theme::TEXT_NORMAL
},
}
.into_paragraphs();
let page = SwipePage::vertical(paragraphs);
let mut frame = Frame::left_aligned(self.title, page);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(subtitle);
}
if self.menu_button {
frame = frame.with_menu_button();
}
if let Some(instruction) = self.footer_instruction {
frame = frame.with_footer(instruction, self.footer_description);
}
Ok(frame.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)))
}
}
pub struct ShowInfoParams {
title: TString<'static>,
subtitle: Option<TString<'static>>,
menu_button: bool,
cancel_button: bool,
footer_instruction: Option<TString<'static>>,
footer_description: Option<TString<'static>>,
chunkify: bool,
items: Vec<(TString<'static>, TString<'static>), 4>,
}
impl ShowInfoParams {
pub const fn new(title: TString<'static>) -> Self {
Self {
title,
subtitle: None,
menu_button: false,
cancel_button: false,
footer_instruction: None,
footer_description: None,
chunkify: false,
items: Vec::new(),
}
}
pub fn add(mut self, key: TString<'static>, value: TString<'static>) -> Option<Self> {
if self.items.push((key, value)).is_ok() {
Some(self)
} else {
None
}
}
pub const fn with_subtitle(mut self, subtitle: Option<TString<'static>>) -> Self {
self.subtitle = subtitle;
self
}
pub const fn with_menu_button(mut self) -> Self {
self.menu_button = true;
self
}
pub const fn with_cancel_button(mut self) -> Self {
self.cancel_button = true;
self
}
pub const fn with_footer(
mut self,
instruction: TString<'static>,
description: Option<TString<'static>>,
) -> Self {
self.footer_instruction = Some(instruction);
self.footer_description = description;
self
}
pub const fn with_chunkify(mut self, chunkify: bool) -> Self {
self.chunkify = chunkify;
self
}
pub fn into_layout(
self,
) -> Result<impl Component<Msg = FlowMsg> + Swipable + MaybeTrace, Error> {
let mut paragraphs = ParagraphVecShort::new();
let mut first: bool = true;
for item in self.items {
// FIXME: padding:
if !first {
paragraphs.add(Paragraph::new::<TString<'static>>(
&theme::TEXT_SUB_GREY,
" ".into(),
));
}
first = false;
paragraphs.add(Paragraph::new(&theme::TEXT_SUB_GREY, item.0).no_break());
if self.chunkify {
paragraphs.add(Paragraph::new(
theme::get_chunkified_text_style(item.1.len()),
item.1,
));
} else {
paragraphs.add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, item.1));
}
}
let mut frame = Frame::left_aligned(
self.title,
SwipePage::vertical(paragraphs.into_paragraphs()),
);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(subtitle);
}
if self.cancel_button {
frame = frame.with_cancel_button();
} else if self.menu_button {
frame = frame.with_menu_button();
}
if let Some(instruction) = self.footer_instruction {
frame = frame.with_footer(instruction, self.footer_description);
}
Ok(frame.map(move |msg| {
matches!(msg, FrameMsg::Button(_)).then_some(if self.cancel_button {
FlowMsg::Cancelled
} else {
FlowMsg::Info
})
}))
}
}

View File

@ -893,6 +893,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, "".into())?;
let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?;
let action: Option<TString> = kwargs.get(Qstr::MP_QSTR_button)?.try_into_option()?;
let content = SwipeUpScreen::new(Paragraphs::new([
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description),
@ -901,8 +902,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map
let obj = LayoutObj::new(
Frame::left_aligned(title, content)
.with_warning_button()
.button_styled(theme::button_warning_low())
.with_footer(TR::instructions__swipe_up.into(), None),
.with_footer(TR::instructions__swipe_up.into(), action),
)?;
Ok(obj.into())
};
@ -1992,6 +1992,28 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// ) -> LayoutObj[UiResult]:
/// """Warning modal with multiple steps to confirm."""
Qstr::MP_QSTR_flow_warning_hi_prio => obj_fn_kw!(0, flow::warning_hi_prio::new_warning_hi_prio).as_obj(),
/// def flow_confirm_output(
/// *,
/// title: str | None,
/// address: str,
/// amount: str,
/// chunkify: bool,
/// account: str | None,
/// account_path: str | None,
/// ) -> LayoutObj[UiResult]:
/// """Confirm recipient."""
Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, flow::new_confirm_output).as_obj(),
/// def flow_confirm_summary(
/// *,
/// title: str,
/// items: Iterable[tuple[str, str]],
/// account_items: Iterable[tuple[str, str]],
/// fee_items: Iterable[tuple[str, str]],
/// ) -> LayoutObj[UiResult]:
/// """Total summary and hold to confirm."""
Qstr::MP_QSTR_flow_confirm_summary => obj_fn_kw!(0, flow::new_confirm_summary).as_obj(),
};
#[cfg(test)]

View File

@ -658,7 +658,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
.newline()
.newline()
.newline_half()
.text_bold(TR::confirm_total__fee_rate)
.text_bold(TR::confirm_total__fee_rate_colon)
.newline()
.text_mono(fee_rate_amount);

View File

@ -549,6 +549,30 @@ def flow_warning_hi_prio(
value: str = "",
) -> LayoutObj[UiResult]:
"""Warning modal with multiple steps to confirm."""
# rust/src/ui/model_mercury/layout.rs
def flow_confirm_output(
*,
title: str | None,
address: str,
amount: str,
chunkify: bool,
account: str | None,
account_path: str | None,
) -> LayoutObj[UiResult]:
"""Confirm recipient."""
# rust/src/ui/model_mercury/layout.rs
def flow_confirm_summary(
*,
title: str,
items: Iterable[tuple[str, str]],
account_items: Iterable[tuple[str, str]],
fee_items: Iterable[tuple[str, str]],
) -> LayoutObj[UiResult]:
"""Total summary and hold to confirm."""
CONFIRMED: UiResult
CANCELLED: UiResult
INFO: UiResult

View File

@ -235,9 +235,10 @@ class TR:
coinjoin__title_do_not_disconnect: str = "Do not disconnect your trezor!"
coinjoin__title_progress: str = "Coinjoin in progress"
coinjoin__waiting_for_others: str = "Waiting for others"
confirm_total__fee_rate: str = "Fee rate:"
confirm_total__fee_rate: str = "Fee rate"
confirm_total__fee_rate_colon: str = "Fee rate:"
confirm_total__sending_from_account: str = "Sending from account:"
confirm_total__title_fee: str = "Fee information"
confirm_total__title_fee: str = "Fee info"
confirm_total__title_sending_from: str = "Sending from"
debug__loading_seed: str = "Loading seed"
debug__loading_seed_not_recommended: str = "Loading private seed is not recommended."
@ -275,7 +276,6 @@ class TR:
eos__requirement: str = "Requirement:"
eos__sell_ram: str = "Sell RAM"
eos__sender: str = "Sender:"
eos__sign_transaction: str = "Sign transaction"
eos__threshold: str = "Threshold:"
eos__to: str = "To:"
eos__transfer: str = "Transfer:"
@ -364,6 +364,7 @@ class TR:
inputs__space: str = "SPACE"
instructions__continue_in_app: str = "Continue in the app"
instructions__hold_to_confirm: str = "Hold to confirm"
instructions__hold_to_sign: str = "Hold to sign"
instructions__swipe_up: str = "Swipe up"
instructions__tap_to_confirm: str = "Tap to confirm"
joint__title: str = "Joint transaction"
@ -675,11 +676,15 @@ class TR:
sd_card__wanna_format: str = "Do you really want to format the SD card?"
sd_card__wrong_sd_card: str = "Wrong SD card."
send__address_path: str = "address path"
send__cancel_sign: str = "Cancel sign"
send__confirm_sending: str = "Sending amount"
send__from_multiple_accounts: str = "Sending from multiple accounts."
send__incl_transaction_fee: str = "incl. Transaction fee"
send__including_fee: str = "Including fee:"
send__maximum_fee: str = "Maximum fee:"
send__receiving_to_multisig: str = "Receiving to a multisig address."
send__send_from: str = "Send from"
send__sign_transaction: str = "Sign transaction"
send__title_confirm_sending: str = "Confirm sending"
send__title_joint_transaction: str = "Joint transaction"
send__title_receiving_to: str = "Receiving to"
@ -687,7 +692,8 @@ class TR:
send__title_sending_amount: str = "Sending amount"
send__title_sending_to: str = "Sending to"
send__to_the_total_amount: str = "To the total amount:"
send__total_amount: str = "Total amount:"
send__total_amount: str = "Total amount"
send__total_amount_colon: str = "Total amount:"
send__transaction_id: str = "Transaction ID:"
send__you_are_contributing: str = "You are contributing:"
share_words__words_in_order: str = " words in order."

View File

@ -121,6 +121,7 @@ class Approver:
self,
txo: TxOutput,
script_pubkey: bytes,
tx_info: TxInfo | None,
orig_txo: TxOutput | None = None,
) -> None:
await self._add_output(txo, script_pubkey)
@ -193,11 +194,12 @@ class BasicApprover(Approver):
self,
txo: TxOutput,
script_pubkey: bytes,
tx_info: TxInfo | None,
orig_txo: TxOutput | None = None,
) -> None:
from trezor.enums import OutputScriptType
await super().add_external_output(txo, script_pubkey, orig_txo)
await super().add_external_output(txo, script_pubkey, tx_info, orig_txo)
if orig_txo:
if txo.amount < orig_txo.amount:
@ -230,6 +232,7 @@ class BasicApprover(Approver):
"Adding new OP_RETURN outputs in replacement transactions is not supported."
)
elif txo.payment_req_index is None or self.show_payment_req_details:
source_path = tx_info.wallet_path.get_path() if tx_info else None
# Ask user to confirm output, unless it is part of a payment
# request, which gets confirmed separately.
await helpers.confirm_output(
@ -238,6 +241,7 @@ class BasicApprover(Approver):
self.amount_unit,
self.external_output_index,
self.chunkify,
source_path,
)
self.external_output_index += 1

View File

@ -530,7 +530,9 @@ class Bitcoin:
# Output is change and does not need approval.
await approver.add_change_output(txo, script_pubkey)
else:
await approver.add_external_output(txo, script_pubkey, orig_txo)
await approver.add_external_output(
txo, script_pubkey, self.tx_info, orig_txo
)
self.tx_info.add_output(txo, script_pubkey)

View File

@ -92,7 +92,9 @@ class DecredApprover(BasicApprover):
) -> None:
# NOTE: The following calls Approver.add_external_output(), not BasicApprover.add_external_output().
# This is needed to skip calling helpers.confirm_output(), which is what BasicApprover would do.
await super(BasicApprover, self).add_external_output(txo, script_pubkey, None)
await super(BasicApprover, self).add_external_output(
txo, script_pubkey, None, None
)
await helpers.confirm_decred_sstx_submission(txo, self.coin, self.amount_unit)

View File

@ -45,12 +45,14 @@ class UiConfirmOutput(UiConfirm):
amount_unit: AmountUnit,
output_index: int,
chunkify: bool,
address_n: Bip32Path | None,
):
self.output = output
self.coin = coin
self.amount_unit = amount_unit
self.output_index = output_index
self.chunkify = chunkify
self.address_n = address_n
def confirm_dialog(self) -> Awaitable[Any]:
return layout.confirm_output(
@ -59,6 +61,7 @@ class UiConfirmOutput(UiConfirm):
self.amount_unit,
self.output_index,
self.chunkify,
self.address_n,
)
@ -241,8 +244,12 @@ class UiConfirmMultipleAccounts(UiConfirm):
return layout.confirm_multiple_accounts()
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool) -> Awaitable[None]: # type: ignore [awaitable-return-type]
return (yield UiConfirmOutput(output, coin, amount_unit, output_index, chunkify)) # type: ignore [awaitable-return-type]
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool, address_n: Bip32Path | None) -> Awaitable[None]: # type: ignore [awaitable-return-type]
return (
yield UiConfirmOutput( # type: ignore [awaitable-return-type]
output, coin, amount_unit, output_index, chunkify, address_n
)
)
def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-return-type]

View File

@ -62,6 +62,7 @@ async def confirm_output(
amount_unit: AmountUnit,
output_index: int,
chunkify: bool,
address_n: Bip32Path | None,
) -> None:
from trezor.enums import OutputScriptType
@ -119,6 +120,8 @@ async def confirm_output(
address_label=address_label,
output_index=output_index,
chunkify=chunkify,
source_account=account_label(coin, address_n),
source_account_path=address_n_to_str(address_n) if address_n else None,
)
await layout
@ -245,7 +248,8 @@ async def confirm_total(
format_coin_amount(spending, coin, amount_unit),
format_coin_amount(fee, coin, amount_unit),
fee_rate_amount=format_fee_rate(fee_rate, coin) if fee_rate >= 0 else None,
account_label=account_label(coin, address_n),
source_account=account_label(coin, address_n),
source_account_path=address_n_to_str(address_n) if address_n else None,
)

View File

@ -14,7 +14,7 @@ async def require_sign_tx(num_actions: int) -> None:
await confirm_action(
"confirm_tx",
TR.eos__sign_transaction,
TR.send__sign_transaction,
description=TR.eos__about_to_sign_template,
description_param=format_plural(
"{count} {plural}", num_actions, TR.plurals__sign_x_actions

View File

@ -187,7 +187,7 @@ class RustLayout(ui.Layout):
# Turn the brightness on again.
ui.backlight_fade(self.BACKLIGHT_LEVEL)
def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator]
def handle_input_and_rendering(self) -> loop.Task:
from trezor import workflow
touch = loop.wait(io.TOUCH)
@ -203,7 +203,7 @@ class RustLayout(ui.Layout):
raise ui.Result(msg)
self._paint()
def handle_timers(self) -> loop.Task: # type: ignore [awaitable-is-generator]
def handle_timers(self) -> loop.Task:
while True:
# Using `yield` instead of `await` to avoid allocations.
token = yield self.timer
@ -499,9 +499,9 @@ async def show_warning(
interact(
RustLayout(
trezorui2.show_warning(
title=content,
description=subheader or "",
button=button,
title=TR.words__important,
value=content,
button=subheader or TR.words__continue_anyway,
)
),
br_type,
@ -539,58 +539,35 @@ async def confirm_output(
address_label: str | None = None,
output_index: int | None = None,
chunkify: bool = False,
source_account: str | None = None,
source_account_path: str | None = None,
) -> None:
if title is not None:
# TODO: handle translation
if title.upper().startswith("CONFIRM "):
title = title[len("CONFIRM ") :]
amount_title = title
recipient_title = title
if address_label is not None:
title = address_label
elif title is not None:
pass
elif output_index is not None:
amount_title = f"{TR.words__amount} #{output_index + 1}"
recipient_title = f"{TR.words__recipient} #{output_index + 1}"
title = f"{TR.words__recipient} #{output_index + 1}"
else:
amount_title = TR.send__confirm_sending
recipient_title = TR.send__title_sending_to
title = TR.send__title_sending_to
while True:
result = await interact(
# TODO: this should send 2x ButtonRequest
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.confirm_value(
title=recipient_title,
subtitle=address_label,
description=None,
value=address,
verb=TR.buttons__continue,
hold=False,
info_button=False,
trezorui2.flow_confirm_output(
address=address,
amount=amount,
title=title,
chunkify=chunkify,
account=source_account,
account_path=source_account_path,
)
),
"confirm_output",
br_code,
)
if result is not CONFIRMED:
raise ActionCancelled
result = await interact(
RustLayout(
trezorui2.confirm_value(
title=amount_title,
subtitle=None,
description=None,
value=amount,
verb=None if hold else TR.buttons__confirm,
verb_cancel="^",
hold=hold,
info_button=False,
)
),
"confirm_output",
br_code,
)
if result is CONFIRMED:
return
)
async def should_show_payment_request_details(
@ -627,7 +604,7 @@ async def should_show_payment_request_details(
async def should_show_more(
title: str,
para: Iterable[tuple[int, str]],
para: Iterable[tuple[int, str | bytes]],
button_text: str | None = None,
br_type: str = "should_show_more",
br_code: ButtonRequestType = BR_TYPE_OTHER,
@ -876,31 +853,42 @@ async def confirm_total(
title: str | None = None,
total_label: str | None = None,
fee_label: str | None = None,
account_label: str | None = None,
source_account: str | None = None,
source_account_path: str | None = None,
fee_rate_amount: str | None = None,
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
title = title or TR.words__title_summary # def_arg
total_label = total_label or TR.send__total_amount # def_arg
fee_label = fee_label or TR.send__including_fee # def_arg
fee_label = fee_label or TR.send__incl_transaction_fee # def_arg
items = [
(total_label, total_amount),
(fee_label, fee_amount),
]
info_items = []
if account_label:
info_items.append((TR.confirm_total__sending_from_account, account_label))
fee_items = []
account_items = []
if source_account:
account_items.append((TR.confirm_total__sending_from_account, source_account))
if source_account_path:
account_items.append((TR.address_details__derivation_path, source_account_path))
if fee_rate_amount:
info_items.append((TR.confirm_total__fee_rate, fee_rate_amount))
fee_items.append((TR.confirm_total__fee_rate, fee_rate_amount))
await confirm_summary(
items,
TR.words__title_summary,
info_items=info_items,
br_type=br_type,
br_code=br_code,
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.flow_confirm_summary(
title=title,
items=items,
fee_items=fee_items,
account_items=account_items,
)
),
br_type,
br_code,
)
)
@ -912,23 +900,21 @@ async def confirm_summary(
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
# TODO: info_title
title = title or TR.words__title_summary # def_arg
total_layout = RustLayout(
trezorui2.confirm_total(
title=title,
items=items,
info_button=bool(info_items),
await raise_if_not_confirmed(
RustLayout(
trezorui2.flow_confirm_summary(
title=title,
items=items or (),
fee_items=(),
account_items=info_items or (),
)
)
# TODO br_type,
# TODO br_code,
)
info_items = info_items or []
info_layout = RustLayout(
trezorui2.show_info_with_cancel(
title=info_title if info_title else TR.words__title_information,
items=info_items,
)
)
await raise_if_not_confirmed(with_info(total_layout, info_layout, br_type, br_code))
if not utils.BITCOIN_ONLY:
@ -1046,20 +1032,14 @@ if not utils.BITCOIN_ONLY:
async def confirm_joint_total(spending_amount: str, total_amount: str) -> None:
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.confirm_total(
title=TR.send__title_joint_transaction,
items=[
(TR.send__you_are_contributing, spending_amount),
(TR.send__to_the_total_amount, total_amount),
],
)
),
"confirm_joint_total",
ButtonRequestType.SignTx,
)
await confirm_summary(
items=[
(TR.send__you_are_contributing, spending_amount),
(TR.send__to_the_total_amount, total_amount),
],
title=TR.send__title_joint_transaction,
br_type="confirm_joint_total",
br_code=ButtonRequestType.SignTx,
)

View File

@ -734,6 +734,8 @@ async def confirm_output(
address_label: str | None = None,
output_index: int | None = None,
chunkify: bool = False,
source_account: str | None = None, # ignored on safe 3
source_account_path: str | None = None, # ignored on safe 3
) -> None:
title = title or TR.send__confirm_sending # def_arg
address_title = TR.words__recipient
@ -1096,11 +1098,12 @@ def confirm_total(
title: str | None = None,
total_label: str | None = None,
fee_label: str | None = None,
account_label: str | None = None,
source_account: str | None = None,
source_account_path: str | None = None,
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> Awaitable[None]:
total_label = total_label or TR.send__total_amount # def_arg
total_label = total_label or TR.send__total_amount_colon # def_arg
fee_label = fee_label or TR.send__including_fee # def_arg
return raise_if_not_confirmed(
interact(
@ -1110,7 +1113,7 @@ def confirm_total(
total_amount=total_amount, # type: ignore [No parameter named]
fee_amount=fee_amount, # type: ignore [No parameter named]
fee_rate_amount=fee_rate_amount, # type: ignore [No parameter named]
account_label=account_label, # type: ignore [No parameter named]
account_label=source_account, # type: ignore [No parameter named]
total_label=total_label, # type: ignore [No parameter named]
fee_label=fee_label, # type: ignore [No parameter named]
)

View File

@ -641,6 +641,8 @@ async def confirm_output(
address_label: str | None = None,
output_index: int | None = None,
chunkify: bool = False,
source_account: str | None = None, # ignored on model t
source_account_path: str | None = None, # ignored on model t
) -> None:
if title is not None:
# TODO: handle translation:
@ -978,13 +980,14 @@ def confirm_total(
title: str | None = None,
total_label: str | None = None,
fee_label: str | None = None,
account_label: str | None = None,
source_account: str | None = None,
source_account_path: str | None = None,
fee_rate_amount: str | None = None,
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> Awaitable[None]:
title = title or TR.words__title_summary # def_arg
total_label = total_label or TR.send__total_amount # def_arg
total_label = total_label or TR.send__total_amount_colon # def_arg
fee_label = fee_label or TR.send__including_fee # def_arg
items = [
@ -992,10 +995,10 @@ def confirm_total(
(fee_label, fee_amount),
]
info_items = []
if account_label:
info_items.append((TR.confirm_total__sending_from_account, account_label))
if source_account:
info_items.append((TR.confirm_total__sending_from_account, source_account))
if fee_rate_amount:
info_items.append((TR.confirm_total__fee_rate, fee_rate_amount))
info_items.append((TR.confirm_total__fee_rate_colon, fee_rate_amount))
return confirm_summary(
items,

View File

@ -175,6 +175,7 @@ class TestApprover(unittest.TestCase):
authorization = CoinJoinAuthorization(self.msg_auth)
approver = CoinJoinApprover(tx, self.coin, authorization)
signer = Bitcoin(tx, None, self.coin, approver)
tx_info = TxInfo(signer, tx)
for txi in inputs:
if txi.script_type == InputScriptType.EXTERNAL:
@ -186,9 +187,9 @@ class TestApprover(unittest.TestCase):
if txo.address_n:
await_result(approver.add_change_output(txo, script_pubkey=bytes(22)))
else:
await_result(approver.add_external_output(txo, script_pubkey=bytes(22)))
await_result(approver.add_external_output(txo, script_pubkey=bytes(22), tx_info=tx_info))
await_result(approver.approve_tx(TxInfo(signer, tx), [], None))
await_result(approver.approve_tx(tx_info, [], None))
def test_coinjoin_input_account_depth_mismatch(self):
txi = TxInput(

View File

@ -112,7 +112,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,
@ -120,7 +120,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out2)),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]),
True,
helpers.UiConfirmTotal(
12300000, 11000, fee_rate, coin, AmountUnit.BITCOIN, inp1.address_n[:3]
@ -309,7 +309,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,

View File

@ -123,7 +123,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(84), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,
@ -131,7 +131,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out2)),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(84), H_(1), H_(0)]),
True,
helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False),
True,
@ -332,7 +332,7 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(84), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,

View File

@ -114,7 +114,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,
@ -122,7 +122,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out2)),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]),
True,
helpers.UiConfirmTotal(
123445789 + 11000,
@ -317,7 +317,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,
@ -531,7 +531,7 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,

View File

@ -123,7 +123,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,
@ -131,7 +131,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out2)),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False),
helpers.UiConfirmOutput(out2, coin, AmountUnit.BITCOIN, 1, False, [H_(49), H_(1), H_(0)]),
True,
helpers.UiConfirmNonDefaultLocktime(tx.lock_time, lock_time_disabled=False),
True,
@ -336,7 +336,7 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(49), H_(1), H_(0)]),
True,
TxRequest(
request_type=TXOUTPUT,

View File

@ -180,7 +180,7 @@ class TestSignTxFeeThreshold(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False, None),
True,
helpers.UiConfirmMultipleAccounts(),
True,

View File

@ -111,7 +111,7 @@ class TestSignTx(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin_bitcoin, AmountUnit.BITCOIN, 0, False, [H_(44), H_(0), H_(0)]),
True,
helpers.UiConfirmTotal(
3_801_747,

View File

@ -112,7 +112,7 @@ class TestSignTxDecred(unittest.TestCase):
),
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN, 0, False, [H_(44), H_(1), H_(0)]),
True,
helpers.UiConfirmTotal(
200_000_000,

View File

@ -112,7 +112,7 @@ class TestSignTx_GRS(unittest.TestCase):
serialized=EMPTY_SERIALIZED,
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False),
helpers.UiConfirmOutput(out1, coin, AmountUnit.BITCOIN, 0, False, [H_(44), H_(17), H_(0)]),
True,
helpers.UiConfirmTotal(
210016, 192, fee_rate, coin, AmountUnit.BITCOIN, inp1.address_n[:3]

View File

@ -219,7 +219,7 @@
"coinjoin__title_do_not_disconnect": "title,2",
"coinjoin__title_progress": "title,1",
"coinjoin__waiting_for_others": "text,1",
"confirm_total__fee_rate": "text,1",
"confirm_total__fee_rate_colon": "text,1",
"confirm_total__sending_from_account": "text,1",
"confirm_total__title_fee": "title,1",
"confirm_total__title_sending_from": "title,1",
@ -259,7 +259,7 @@
"eos__requirement": "text,1",
"eos__sell_ram": "text,1",
"eos__sender": "text,1",
"eos__sign_transaction": "text,1",
"send__sign_transaction": "text,1",
"eos__threshold": "text,1",
"eos__to": "text,1",
"eos__transfer": "text,1",
@ -654,7 +654,7 @@
"send__title_sending_amount": "title,1",
"send__title_sending_to": "title,1",
"send__to_the_total_amount": "text,1",
"send__total_amount": "text,1",
"send__total_amount_colon": "text,1",
"send__transaction_id": "text,1",
"send__you_are_contributing": "text,1",
"share_words__words_in_order": "text,2",

View File

@ -257,7 +257,8 @@
"coinjoin__title_do_not_disconnect": "Neodpojujte trezor!",
"coinjoin__title_progress": "Probíhá coinjoin",
"coinjoin__waiting_for_others": "Čeká se na ostatní",
"confirm_total__fee_rate": "Výše poplatku:",
"confirm_total__fee_rate": "Výše poplatku",
"confirm_total__fee_rate_colon": "Výše poplatku:",
"confirm_total__sending_from_account": "Odeslání z účtu:",
"confirm_total__title_fee": "Info o poplatcích",
"confirm_total__title_sending_from": "Odeslání z",
@ -297,7 +298,7 @@
"eos__requirement": "Požadavek:",
"eos__sell_ram": "Prodat RAM",
"eos__sender": "Odesílatel:",
"eos__sign_transaction": "Podepsat transakci",
"send__sign_transaction": "Podepsat transakci",
"eos__threshold": "Počet částí zálohy pro obnovu:",
"eos__to": "Komu:",
"eos__transfer": "Převod:",
@ -699,7 +700,8 @@
"send__title_sending_amount": "Odesílání částky",
"send__title_sending_to": "Odesílání",
"send__to_the_total_amount": "Do celkové částky:",
"send__total_amount": "Celková částka:",
"send__total_amount": "Celková částka",
"send__total_amount_colon": "Celková částka:",
"send__transaction_id": "ID transakce:",
"send__you_are_contributing": "Přispíváte:",
"share_words__words_in_order": " slova v pořadí.",

View File

@ -257,7 +257,8 @@
"coinjoin__title_do_not_disconnect": "Trenne deinen trezor nicht!",
"coinjoin__title_progress": "Coinjoin läuft",
"coinjoin__waiting_for_others": "Auf andere warten",
"confirm_total__fee_rate": "Gebührensatz:",
"confirm_total__fee_rate": "Gebührensatz",
"confirm_total__fee_rate_colon": "Gebührensatz:",
"confirm_total__sending_from_account": "Gesendet von Konto:",
"confirm_total__title_fee": "Gebühren-info",
"confirm_total__title_sending_from": "Gesendet von",
@ -297,7 +298,7 @@
"eos__requirement": "Anforderung:",
"eos__sell_ram": "RAM verkaufen",
"eos__sender": "Sender:",
"eos__sign_transaction": "Transaktion signieren",
"send__sign_transaction": "Transaktion signieren",
"eos__threshold": "Schwelle:",
"eos__to": "An:",
"eos__transfer": "Überweisung:",
@ -699,7 +700,8 @@
"send__title_sending_amount": "Betrag senden",
"send__title_sending_to": "Senden an",
"send__to_the_total_amount": "Gesamtbetrag:",
"send__total_amount": "Gesamtbetrag:",
"send__total_amount": "Gesamtbetrag",
"send__total_amount_colon": "Gesamtbetrag:",
"send__transaction_id": "Transaktions-ID:",
"send__you_are_contributing": "Dein Anteil:",
"share_words__words_in_order": " Wörter der Reihe nach notiert.",

View File

@ -237,9 +237,10 @@
"coinjoin__title_do_not_disconnect": "Do not disconnect your trezor!",
"coinjoin__title_progress": "Coinjoin in progress",
"coinjoin__waiting_for_others": "Waiting for others",
"confirm_total__fee_rate": "Fee rate:",
"confirm_total__fee_rate": "Fee rate",
"confirm_total__fee_rate_colon": "Fee rate:",
"confirm_total__sending_from_account": "Sending from account:",
"confirm_total__title_fee": "Fee information",
"confirm_total__title_fee": "Fee info",
"confirm_total__title_sending_from": "Sending from",
"debug__loading_seed": "Loading seed",
"debug__loading_seed_not_recommended": "Loading private seed is not recommended.",
@ -277,7 +278,6 @@
"eos__requirement": "Requirement:",
"eos__sell_ram": "Sell RAM",
"eos__sender": "Sender:",
"eos__sign_transaction": "Sign transaction",
"eos__threshold": "Threshold:",
"eos__to": "To:",
"eos__transfer": "Transfer:",
@ -365,9 +365,10 @@
"inputs__show": "SHOW",
"inputs__space": "SPACE",
"instructions__continue_in_app": "Continue in the app",
"instructions__hold_to_confirm": "Hold to confirm",
"instructions__hold_to_sign": "Hold to sign",
"instructions__swipe_up": "Swipe up",
"instructions__tap_to_confirm": "Tap to confirm",
"instructions__hold_to_confirm": "Hold to confirm",
"joint__title": "Joint transaction",
"joint__to_the_total_amount": "To the total amount:",
"joint__you_are_contributing": "You are contributing:",
@ -677,11 +678,15 @@
"sd_card__wanna_format": "Do you really want to format the SD card?",
"sd_card__wrong_sd_card": "Wrong SD card.",
"send__address_path": "address path",
"send__cancel_sign": "Cancel sign",
"send__confirm_sending": "Sending amount",
"send__from_multiple_accounts": "Sending from multiple accounts.",
"send__incl_transaction_fee": "incl. Transaction fee",
"send__including_fee": "Including fee:",
"send__maximum_fee": "Maximum fee:",
"send__receiving_to_multisig": "Receiving to a multisig address.",
"send__send_from": "Send from",
"send__sign_transaction": "Sign transaction",
"send__title_confirm_sending": "Confirm sending",
"send__title_joint_transaction": "Joint transaction",
"send__title_receiving_to": "Receiving to",
@ -689,7 +694,8 @@
"send__title_sending_amount": "Sending amount",
"send__title_sending_to": "Sending to",
"send__to_the_total_amount": "To the total amount:",
"send__total_amount": "Total amount:",
"send__total_amount": "Total amount",
"send__total_amount_colon": "Total amount:",
"send__transaction_id": "Transaction ID:",
"send__you_are_contributing": "You are contributing:",
"share_words__words_in_order": " words in order.",

View File

@ -257,7 +257,8 @@
"coinjoin__title_do_not_disconnect": "¡No desconectes el trezor!",
"coinjoin__title_progress": "Coinjoin en curso",
"coinjoin__waiting_for_others": "Esperando a los demás",
"confirm_total__fee_rate": "Comisión:",
"confirm_total__fee_rate": "Comisión",
"confirm_total__fee_rate_colon": "Comisión:",
"confirm_total__sending_from_account": "Envío desde cuenta:",
"confirm_total__title_fee": "Info. comisión",
"confirm_total__title_sending_from": "Envío desde",
@ -297,7 +298,7 @@
"eos__requirement": "Requisito:",
"eos__sell_ram": "Vender RAM",
"eos__sender": "Remitente:",
"eos__sign_transaction": "Firmar transacción",
"send__sign_transaction": "Firmar transacción",
"eos__threshold": "Umbral:",
"eos__to": "Para:",
"eos__transfer": "Transferencia:",
@ -699,7 +700,8 @@
"send__title_sending_amount": "Importe envío",
"send__title_sending_to": "Envío a",
"send__to_the_total_amount": "Al importe total:",
"send__total_amount": "Importe total:",
"send__total_amount": "Importe total",
"send__total_amount_colon": "Importe total:",
"send__transaction_id": "ID de la transacción:",
"send__you_are_contributing": "Estás aportando:",
"share_words__words_in_order": " palabras en orden.",

View File

@ -257,7 +257,8 @@
"coinjoin__title_do_not_disconnect": "Ne déconnectez pas votre trezor !",
"coinjoin__title_progress": "Coinjoin en cours",
"coinjoin__waiting_for_others": "En attente des autres",
"confirm_total__fee_rate": "Taux des frais :",
"confirm_total__fee_rate": "Taux des frais",
"confirm_total__fee_rate_colon": "Taux des frais :",
"confirm_total__sending_from_account": "Compte d'envoi :",
"confirm_total__title_fee": "Infos sur les frais",
"confirm_total__title_sending_from": "Envoi depuis",
@ -297,7 +298,7 @@
"eos__requirement": "Exigence :",
"eos__sell_ram": "Vendre de la RAM",
"eos__sender": "Expéditeur :",
"eos__sign_transaction": "Signer la transaction",
"send__sign_transaction": "Signer la transaction",
"eos__threshold": "Seuil :",
"eos__to": "À :",
"eos__transfer": "Transfert :",
@ -699,7 +700,8 @@
"send__title_sending_amount": "Montant de l'envoi",
"send__title_sending_to": "Envoi à",
"send__to_the_total_amount": "Au montant total :",
"send__total_amount": "Montant total :",
"send__total_amount": "Montant total",
"send__total_amount_colon": "Montant total :",
"send__transaction_id": "ID de transaction :",
"send__you_are_contributing": "Votre contribution :",
"share_words__words_in_order": " mots dans l'ordre.",

View File

@ -219,7 +219,7 @@
"217": "coinjoin__title_do_not_disconnect",
"218": "coinjoin__title_progress",
"219": "coinjoin__waiting_for_others",
"220": "confirm_total__fee_rate",
"220": "confirm_total__fee_rate_colon",
"221": "confirm_total__sending_from_account",
"222": "confirm_total__title_fee",
"223": "confirm_total__title_sending_from",
@ -259,7 +259,7 @@
"257": "eos__requirement",
"258": "eos__sell_ram",
"259": "eos__sender",
"260": "eos__sign_transaction",
"260": "send__sign_transaction",
"261": "eos__threshold",
"262": "eos__to",
"263": "eos__transfer",
@ -652,7 +652,7 @@
"650": "send__title_sending_amount",
"651": "send__title_sending_to",
"652": "send__to_the_total_amount",
"653": "send__total_amount",
"653": "send__total_amount_colon",
"654": "send__transaction_id",
"655": "send__you_are_contributing",
"656": "share_words__words_in_order",
@ -871,5 +871,11 @@
"869": "address__confirmed",
"870": "pin__cancel_description",
"871": "pin__cancel_info",
"872": "pin__cancel_setup"
"872": "pin__cancel_setup",
"873": "send__cancel_sign",
"874": "send__send_from",
"875": "instructions__hold_to_sign",
"876": "confirm_total__fee_rate",
"877": "send__incl_transaction_fee",
"878": "send__total_amount"
}

View File

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "928438526b993d433d52359d86099848d570c13fbe6aac72a2f5a29a4e8e94c5",
"datetime": "2024-05-17T10:55:04.124405",
"commit": "1409ed27df07827a2a3e3756420db1b41fe108e5"
"merkle_root": "329b06dbf2564bf17ba46d2b3304f91df15abc42794763a55ea33bc04281ac42",
"datetime": "2024-05-20T15:37:23.831427",
"commit": "2a5dc6f8d54701e86c32451fb154d3e40a778ca9"
},
"history": [
{