1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-12 22:26:08 +00:00

feat(core): support optional chunkification of addresses in receive and send flows

This commit is contained in:
grdddj 2023-09-14 12:25:13 +02:00 committed by Jiří Musil
parent da3cab22fd
commit bcb353a4a1
34 changed files with 292 additions and 41 deletions

View File

@ -36,6 +36,7 @@ static void _librust_qstrs(void) {
MP_QSTR_button_event; MP_QSTR_button_event;
MP_QSTR_cancel_arrow; MP_QSTR_cancel_arrow;
MP_QSTR_case_sensitive; MP_QSTR_case_sensitive;
MP_QSTR_chunkify;
MP_QSTR_confirm_action; MP_QSTR_confirm_action;
MP_QSTR_confirm_address; MP_QSTR_confirm_address;
MP_QSTR_confirm_backup; MP_QSTR_confirm_backup;

View File

@ -52,6 +52,40 @@ pub struct TextLayout {
pub continues_from_prev_page: bool, pub continues_from_prev_page: bool,
} }
/// Configuration for chunkifying the text into smaller parts.
#[derive(Copy, Clone)]
pub struct Chunks {
/// How many characters will be grouped in one chunk.
pub chunk_size: usize,
/// How big will be the space between chunks (in pixels).
pub x_offset: i16,
/// Optional characters that are wider and should be accounted for
pub wider_chars: Option<&'static str>,
}
impl Chunks {
pub const fn new(chunk_size: usize, x_offset: i16) -> Self {
Chunks {
chunk_size,
x_offset,
wider_chars: None,
}
}
pub const fn with_wider_chars(mut self, wider_chars: &'static str) -> Self {
self.wider_chars = Some(wider_chars);
self
}
pub fn is_char_wider(self, ch: char) -> bool {
if let Some(wider_chars) = self.wider_chars {
wider_chars.contains(ch)
} else {
false
}
}
}
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct TextStyle { pub struct TextStyle {
/// Text font ID. /// Text font ID.
@ -76,6 +110,14 @@ pub struct TextStyle {
pub line_breaking: LineBreaking, pub line_breaking: LineBreaking,
/// Specifies what to do at the end of the page. /// Specifies what to do at the end of the page.
pub page_breaking: PageBreaking, pub page_breaking: PageBreaking,
/// Optionally chunkify all the text with a specified chunk
/// size and pixel offset for the next chunk.
pub chunks: Option<Chunks>,
/// Optionally increase the vertical space between text lines
/// (can be even negative, in which case it will decrease it).
pub line_spacing: i16,
} }
impl TextStyle { impl TextStyle {
@ -96,6 +138,8 @@ impl TextStyle {
prev_page_ellipsis_icon: None, prev_page_ellipsis_icon: None,
line_breaking: LineBreaking::BreakAtWhitespace, line_breaking: LineBreaking::BreakAtWhitespace,
page_breaking: PageBreaking::CutAndInsertEllipsis, page_breaking: PageBreaking::CutAndInsertEllipsis,
chunks: None,
line_spacing: 0,
} }
} }
@ -121,6 +165,18 @@ impl TextStyle {
self self
} }
/// Adding optional chunkification to the text.
pub const fn with_chunks(mut self, chunks: Chunks) -> Self {
self.chunks = Some(chunks);
self
}
/// Adding optional change of vertical line spacing.
pub const fn with_line_spacing(mut self, line_spacing: i16) -> Self {
self.line_spacing = line_spacing;
self
}
fn ellipsis_width(&self) -> i16 { fn ellipsis_width(&self) -> i16 {
if let Some((icon, margin)) = self.ellipsis_icon { if let Some((icon, margin)) = self.ellipsis_icon {
icon.toif.width() + margin icon.toif.width() + margin
@ -220,12 +276,13 @@ impl TextLayout {
}; };
let remaining_width = self.bounds.x1 - cursor.x; let remaining_width = self.bounds.x1 - cursor.x;
let span = Span::fit_horizontally( let mut span = Span::fit_horizontally(
remaining_text, remaining_text,
remaining_width, remaining_width,
self.style.text_font, self.style.text_font,
self.style.line_breaking, self.style.line_breaking,
line_ending_space, line_ending_space,
self.style.chunks,
); );
cursor.x += match self.align { cursor.x += match self.align {
@ -251,6 +308,9 @@ impl TextLayout {
if span.advance.y > 0 { if span.advance.y > 0 {
// We're advancing to the next line. // We're advancing to the next line.
// Possibly making a bigger/smaller vertical jump
span.advance.y += self.style.line_spacing;
// Check if we should be appending a hyphen at this point. // Check if we should be appending a hyphen at this point.
if span.insert_hyphen_before_line_break { if span.insert_hyphen_before_line_break {
sink.hyphen(*cursor, self); sink.hyphen(*cursor, self);
@ -488,6 +548,7 @@ impl Span {
text_font: impl GlyphMetrics, text_font: impl GlyphMetrics,
breaking: LineBreaking, breaking: LineBreaking,
line_ending_space: i16, line_ending_space: i16,
chunks: Option<Chunks>,
) -> Self { ) -> Self {
const ASCII_LF: char = '\n'; const ASCII_LF: char = '\n';
const ASCII_CR: char = '\r'; const ASCII_CR: char = '\r';
@ -537,6 +598,7 @@ impl Span {
let mut span_width = 0; let mut span_width = 0;
let mut found_any_whitespace = false; let mut found_any_whitespace = false;
let mut chunks_wider_chars = 0;
let mut char_indices_iter = text.char_indices().peekable(); let mut char_indices_iter = text.char_indices().peekable();
// Iterating manually because we need a reference to the iterator inside the // Iterating manually because we need a reference to the iterator inside the
@ -544,6 +606,22 @@ impl Span {
while let Some((i, ch)) = char_indices_iter.next() { while let Some((i, ch)) = char_indices_iter.next() {
let char_width = text_font.char_width(ch); let char_width = text_font.char_width(ch);
// When there is a set chunk size and we reach it,
// adjust the line advances and return the line.
if let Some(chunkify_config) = chunks {
if i == chunkify_config.chunk_size {
line.advance.y = 0;
// Decreasing the offset for each wider character in the chunk
line.advance.x += chunkify_config.x_offset - chunks_wider_chars;
return line;
} else {
// Counting all the wider characters in the chunk
if chunkify_config.is_char_wider(ch) {
chunks_wider_chars += 1;
}
}
}
// Consider if we could be breaking the line at this position. // Consider if we could be breaking the line at this position.
if is_whitespace(ch) && span_width + complete_word_end_width <= max_width { if is_whitespace(ch) && span_width + complete_word_end_width <= max_width {
// Break before the whitespace, without hyphen. // Break before the whitespace, without hyphen.
@ -679,6 +757,7 @@ mod tests {
FIXED_FONT, FIXED_FONT,
LineBreaking::BreakAtWhitespace, LineBreaking::BreakAtWhitespace,
0, 0,
None,
); );
spans.push(( spans.push((
&remaining_text[..span.length], &remaining_text[..span.length],

View File

@ -8,7 +8,7 @@ use crate::{
}; };
use super::{ use super::{
layout::{LayoutFit, LayoutSink, TextLayout}, layout::{Chunks, LayoutFit, LayoutSink, TextLayout},
LineBreaking, TextStyle, LineBreaking, TextStyle,
}; };
@ -90,6 +90,12 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
cursor.x += offset.x; cursor.x += offset.x;
cursor.y += offset.y; cursor.y += offset.y;
} }
Op::Chunkify(chunks) => {
self.layout.style.chunks = chunks;
}
Op::LineSpacing(line_spacing) => {
self.layout.style.line_spacing = line_spacing;
}
// Moving to the next page // Moving to the next page
Op::NextPage => { Op::NextPage => {
// Pretending that nothing more fits on current page to force // Pretending that nothing more fits on current page to force
@ -219,6 +225,14 @@ impl<T: StringType + Clone> OpTextLayout<T> {
pub fn line_breaking(self, line_breaking: LineBreaking) -> Self { pub fn line_breaking(self, line_breaking: LineBreaking) -> Self {
self.with_new_item(Op::LineBreaking(line_breaking)) self.with_new_item(Op::LineBreaking(line_breaking))
} }
pub fn chunks(self, chunks: Option<Chunks>) -> Self {
self.with_new_item(Op::Chunkify(chunks))
}
pub fn line_spacing(self, spacing: i16) -> Self {
self.with_new_item(Op::LineSpacing(spacing))
}
} }
// Op-adding aggregation operations // Op-adding aggregation operations
@ -238,6 +252,14 @@ impl<T: StringType + Clone> OpTextLayout<T> {
pub fn text_demibold(self, text: T) -> Self { pub fn text_demibold(self, text: T) -> Self {
self.font(Font::DEMIBOLD).text(text) self.font(Font::DEMIBOLD).text(text)
} }
pub fn chunkify_text(self, chunks: Option<(Chunks, i16)>) -> Self {
if let Some(chunks) = chunks {
self.chunks(Some(chunks.0)).line_spacing(chunks.1)
} else {
self.chunks(None).line_spacing(0)
}
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -258,4 +280,8 @@ pub enum Op<T: StringType> {
CursorOffset(Offset), CursorOffset(Offset),
/// Force continuing on the next page. /// Force continuing on the next page.
NextPage, NextPage,
/// Render the following text in a chunkified way. None will disable that.
Chunkify(Option<Chunks>),
/// Change the line vertical line spacing.
LineSpacing(i16),
} }

View File

@ -22,7 +22,7 @@ use crate::{
}, },
TextStyle, TextStyle,
}, },
ComponentExt, FormattedText, LineBreaking, Timeout, ComponentExt, FormattedText, Timeout,
}, },
display, geometry, display, geometry,
layout::{ layout::{
@ -557,6 +557,7 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
let amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?; let amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?;
let address_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_title)?.try_into()?; let address_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_title)?.try_into()?;
let amount_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_title)?.try_into()?; let amount_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_title)?.try_into()?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let get_page = move |page_index| { let get_page = move |page_index| {
// Showing two screens - the recipient address and summary confirmation // Showing two screens - the recipient address and summary confirmation
@ -567,11 +568,20 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
let btn_actions = ButtonActions::cancel_none_next(); let btn_actions = ButtonActions::cancel_none_next();
// Not putting hyphens in the address. // Not putting hyphens in the address.
// Potentially adding address label in different font. // Potentially adding address label in different font.
let mut ops = OpTextLayout::new(theme::TEXT_MONO) let mut ops = OpTextLayout::new(theme::TEXT_MONO_DATA);
.line_breaking(LineBreaking::BreakWordsNoHyphen);
if !address_label.is_empty() { if !address_label.is_empty() {
// NOTE: need to explicitly turn off the chunkification before rendering the
// address label (for some reason it does not help to turn it off after
// rendering the chunks)
if chunkify {
ops = ops.chunkify_text(None);
}
ops = ops.text_normal(address_label.clone()).newline(); ops = ops.text_normal(address_label.clone()).newline();
} }
if chunkify {
// Chunkifying the address into smaller pieces when requested
ops = ops.chunkify_text(Some((theme::MONO_CHUNKS, 2)));
}
ops = ops.text_mono(address.clone()); ops = ops.text_mono(address.clone());
let formatted = FormattedText::new(ops).vertically_centered(); let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(address_title.clone()) Page::new(btn_layout, btn_actions, formatted).with_title(address_title.clone())
@ -752,15 +762,20 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?; let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let get_page = move |page_index| { let get_page = move |page_index| {
assert!(page_index == 0); assert!(page_index == 0);
let btn_layout = ButtonLayout::cancel_armed_info("CONFIRM".into()); let btn_layout = ButtonLayout::cancel_armed_info("CONFIRM".into());
let btn_actions = ButtonActions::cancel_confirm_info(); let btn_actions = ButtonActions::cancel_confirm_info();
let ops = OpTextLayout::new(theme::TEXT_MONO) let style = if chunkify {
.line_breaking(LineBreaking::BreakWordsNoHyphen) // Chunkifying the address into smaller pieces when requested
.text_mono(address.clone()); theme::TEXT_MONO_ADDRESS_CHUNKS
} else {
theme::TEXT_MONO_DATA
};
let ops = OpTextLayout::new(style).text_mono(address.clone());
let formatted = FormattedText::new(ops).vertically_centered(); let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(title.clone()) Page::new(btn_layout, btn_actions, formatted).with_title(title.clone())
}; };
@ -1584,6 +1599,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// data: str, /// data: str,
/// description: str | None, # unused on TR /// description: str | None, # unused on TR
/// extra: str | None, # unused on TR /// extra: str | None, # unused on TR
/// chunkify: bool = False,
/// ) -> object: /// ) -> object:
/// """Confirm address.""" /// """Confirm address."""
Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(), Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(),
@ -1659,6 +1675,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// amount: str, /// amount: str,
/// address_title: str, /// address_title: str,
/// amount_title: str, /// amount_title: str,
/// chunkify: bool = False,
/// ) -> object: /// ) -> object:
/// """Confirm output.""" /// """Confirm output."""
Qstr::MP_QSTR_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(), Qstr::MP_QSTR_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(),

View File

@ -1,5 +1,8 @@
use crate::ui::{ use crate::ui::{
component::{text::TextStyle, LineBreaking, PageBreaking}, component::{
text::{layout::Chunks, TextStyle},
LineBreaking, PageBreaking,
},
display::{toif::Icon, Color, Font}, display::{toif::Icon, Color, Font},
geometry::Offset, geometry::Offset,
}; };
@ -35,6 +38,13 @@ pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, FG, BG, FG, FG)
/// Mono data text does not have hyphens /// Mono data text does not have hyphens
pub const TEXT_MONO_DATA: TextStyle = pub const TEXT_MONO_DATA: TextStyle =
TEXT_MONO.with_line_breaking(LineBreaking::BreakWordsNoHyphen); TEXT_MONO.with_line_breaking(LineBreaking::BreakWordsNoHyphen);
pub const TEXT_MONO_ADDRESS_CHUNKS: TextStyle = TEXT_MONO_DATA
.with_chunks(MONO_CHUNKS)
.with_line_spacing(2)
.with_ellipsis_icon(ICON_NEXT_PAGE, -2);
// Chunks for this model, with accounting for some wider characters in MONO font
pub const MONO_CHUNKS: Chunks = Chunks::new(4, 4).with_wider_chars("mMwW");
/// Convert Python-side numeric id to a `TextStyle`. /// Convert Python-side numeric id to a `TextStyle`.
pub fn textstyle_number(num: i32) -> &'static TextStyle { pub fn textstyle_number(num: i32) -> &'static TextStyle {

View File

@ -496,6 +496,7 @@ struct ConfirmBlobParams {
verb_cancel: Option<StrBuffer>, verb_cancel: Option<StrBuffer>,
info_button: bool, info_button: bool,
hold: bool, hold: bool,
chunkify: bool,
} }
impl ConfirmBlobParams { impl ConfirmBlobParams {
@ -517,6 +518,7 @@ impl ConfirmBlobParams {
verb_cancel, verb_cancel,
info_button: false, info_button: false,
hold, hold,
chunkify: false,
} }
} }
@ -535,6 +537,11 @@ impl ConfirmBlobParams {
self self
} }
fn with_chunkify(mut self, chunkify: bool) -> Self {
self.chunkify = chunkify;
self
}
fn into_layout(self) -> Result<Obj, Error> { fn into_layout(self) -> Result<Obj, Error> {
let paragraphs = ConfirmBlob { let paragraphs = ConfirmBlob {
description: self.description.unwrap_or_else(StrBuffer::empty), description: self.description.unwrap_or_else(StrBuffer::empty),
@ -542,7 +549,11 @@ impl ConfirmBlobParams {
data: self.data.try_into()?, data: self.data.try_into()?,
description_font: &theme::TEXT_NORMAL, description_font: &theme::TEXT_NORMAL,
extra_font: &theme::TEXT_DEMIBOLD, extra_font: &theme::TEXT_DEMIBOLD,
data_font: &theme::TEXT_MONO, data_font: if self.chunkify {
&theme::TEXT_MONO_ADDRESS_CHUNKS
} else {
&theme::TEXT_MONO
},
} }
.into_paragraphs(); .into_paragraphs();
@ -611,6 +622,21 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?; let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?; let data: Obj = kwargs.get(Qstr::MP_QSTR_data)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let data_style = if chunkify {
// Longer addresses have smaller x_offset so they fit even with scrollbar
// (as they will be shown on more than one page)
const FITS_ON_ONE_PAGE: usize = 16 * 4;
let address: StrBuffer = data.try_into()?;
if address.len() <= FITS_ON_ONE_PAGE {
&theme::TEXT_MONO_ADDRESS_CHUNKS
} else {
&theme::TEXT_MONO_ADDRESS_CHUNKS_SMALLER_X_OFFSET
}
} else {
&theme::TEXT_MONO
};
let paragraphs = ConfirmBlob { let paragraphs = ConfirmBlob {
description: description.unwrap_or_else(StrBuffer::empty), description: description.unwrap_or_else(StrBuffer::empty),
@ -618,7 +644,7 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
data: data.try_into()?, data: data.try_into()?,
description_font: &theme::TEXT_NORMAL, description_font: &theme::TEXT_NORMAL,
extra_font: &theme::TEXT_DEMIBOLD, extra_font: &theme::TEXT_DEMIBOLD,
data_font: &theme::TEXT_MONO, data_font: data_style,
} }
.into_paragraphs(); .into_paragraphs();
@ -805,10 +831,12 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
.unwrap_or_else(|_| Obj::const_none()) .unwrap_or_else(|_| Obj::const_none())
.try_into_option()?; .try_into_option()?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?; let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
ConfirmBlobParams::new(title, value, description, verb, verb_cancel, hold) ConfirmBlobParams::new(title, value, description, verb, verb_cancel, hold)
.with_subtitle(subtitle) .with_subtitle(subtitle)
.with_info_button(info_button) .with_info_button(info_button)
.with_chunkify(chunkify)
.into_layout() .into_layout()
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1680,6 +1708,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// data: str | bytes, /// data: str | bytes,
/// description: str | None, /// description: str | None,
/// extra: str | None, /// extra: str | None,
/// chunkify: bool = False,
/// ) -> object: /// ) -> object:
/// """Confirm address. Similar to `confirm_blob` but has corner info button /// """Confirm address. Similar to `confirm_blob` but has corner info button
/// and allows left swipe which does the same thing as the button.""" /// and allows left swipe which does the same thing as the button."""
@ -1734,6 +1763,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// verb_cancel: str | None = None, /// verb_cancel: str | None = None,
/// info_button: bool = False, /// info_button: bool = False,
/// hold: bool = False, /// hold: bool = False,
/// chunkify: bool = False,
/// ) -> object: /// ) -> object:
/// """Confirm value. Merge of confirm_total and confirm_output.""" /// """Confirm value. Merge of confirm_total and confirm_output."""
Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(), Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(),

View File

@ -2,7 +2,7 @@ use crate::{
time::Duration, time::Duration,
ui::{ ui::{
component::{ component::{
text::{LineBreaking, PageBreaking, TextStyle}, text::{layout::Chunks, LineBreaking, PageBreaking, TextStyle},
FixedHeightBar, FixedHeightBar,
}, },
display::{Color, Font, Icon}, display::{Color, Font, Icon},
@ -517,6 +517,18 @@ pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, FG, BG, GREY_LIGHT,
.with_page_breaking(PageBreaking::CutAndInsertEllipsisBoth) .with_page_breaking(PageBreaking::CutAndInsertEllipsisBoth)
.with_ellipsis_icon(ICON_PAGE_NEXT, 0) .with_ellipsis_icon(ICON_PAGE_NEXT, 0)
.with_prev_page_icon(ICON_PAGE_PREV, 0); .with_prev_page_icon(ICON_PAGE_PREV, 0);
/// Makes sure that the displayed text (usually address) will get divided into
/// smaller chunks.
pub const TEXT_MONO_ADDRESS_CHUNKS: TextStyle = TEXT_MONO
.with_chunks(Chunks::new(4, 9))
.with_line_spacing(5);
/// Smaller horizontal chunk offset, used e.g. for long Cardano addresses.
/// Also moving the next page ellipsis to the left (as there is a space on the
/// left).
pub const TEXT_MONO_ADDRESS_CHUNKS_SMALLER_X_OFFSET: TextStyle = TEXT_MONO
.with_chunks(Chunks::new(4, 7))
.with_line_spacing(5)
.with_ellipsis_icon(ICON_PAGE_NEXT, -12);
/// Convert Python-side numeric id to a `TextStyle`. /// Convert Python-side numeric id to a `TextStyle`.
pub fn textstyle_number(num: i32) -> &'static TextStyle { pub fn textstyle_number(num: i32) -> &'static TextStyle {

View File

@ -50,6 +50,7 @@ def confirm_address(
data: str, data: str,
description: str | None, # unused on TR description: str | None, # unused on TR
extra: str | None, # unused on TR extra: str | None, # unused on TR
chunkify: bool = False,
) -> object: ) -> object:
"""Confirm address.""" """Confirm address."""
@ -132,6 +133,7 @@ def confirm_output(
amount: str, amount: str,
address_title: str, address_title: str,
amount_title: str, amount_title: str,
chunkify: bool = False,
) -> object: ) -> object:
"""Confirm output.""" """Confirm output."""
@ -496,6 +498,7 @@ def confirm_address(
data: str | bytes, data: str | bytes,
description: str | None, description: str | None,
extra: str | None, extra: str | None,
chunkify: bool = False,
) -> object: ) -> object:
"""Confirm address. Similar to `confirm_blob` but has corner info button """Confirm address. Similar to `confirm_blob` but has corner info button
and allows left swipe which does the same thing as the button.""" and allows left swipe which does the same thing as the button."""
@ -555,6 +558,7 @@ def confirm_value(
verb_cancel: str | None = None, verb_cancel: str | None = None,
info_button: bool = False, info_button: bool = False,
hold: bool = False, hold: bool = False,
chunkify: bool = False,
) -> object: ) -> object:
"""Confirm value. Merge of confirm_total and confirm_output.""" """Confirm value. Merge of confirm_total and confirm_output."""

View File

@ -26,6 +26,8 @@ async def get_address(msg: BinanceGetAddress, keychain: Keychain) -> BinanceAddr
pubkey = node.public_key() pubkey = node.public_key()
address = address_from_public_key(pubkey, HRP) address = address_from_public_key(pubkey, HRP)
if msg.show_display: if msg.show_display:
await show_address(address, path=paths.address_n_to_str(address_n)) await show_address(
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
)
return BinanceAddress(address=address) return BinanceAddress(address=address)

View File

@ -34,21 +34,18 @@ async def require_confirm_transfer(msg: BinanceTransferMsg) -> None:
for txoutput in msg.outputs: for txoutput in msg.outputs:
make_input_output_pages(txoutput, "Confirm output") make_input_output_pages(txoutput, "Confirm output")
await _confirm_transfer(items) await _confirm_transfer(items, chunkify=bool(msg.chunkify))
async def _confirm_transfer(inputs_outputs: Sequence[tuple[str, str, str]]) -> None: async def _confirm_transfer(
inputs_outputs: Sequence[tuple[str, str, str]], chunkify: bool
) -> None:
from trezor.ui.layouts import confirm_output from trezor.ui.layouts import confirm_output
for index, (title, amount, address) in enumerate(inputs_outputs): for index, (title, amount, address) in enumerate(inputs_outputs):
# Having hold=True on the last item # Having hold=True on the last item
hold = index == len(inputs_outputs) - 1 hold = index == len(inputs_outputs) - 1
await confirm_output( await confirm_output(address, amount, title, hold=hold, chunkify=chunkify)
address,
amount,
title,
hold=hold,
)
async def require_confirm_cancel(msg: BinanceCancelMsg) -> None: async def require_confirm_cancel(msg: BinanceCancelMsg) -> None:

View File

@ -113,6 +113,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
multisig_index=multisig_index, multisig_index=multisig_index,
xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes), xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes),
account=f"Multisig {multisig.m} of {len(pubnodes)}", account=f"Multisig {multisig.m} of {len(pubnodes)}",
chunkify=bool(msg.chunkify),
) )
else: else:
account_name = address_n_to_name(coin, address_n, script_type) account_name = address_n_to_name(coin, address_n, script_type)
@ -128,6 +129,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
case_sensitive=address_case_sensitive, case_sensitive=address_case_sensitive,
path=path, path=path,
account=account, account=account,
chunkify=bool(msg.chunkify),
) )
return Address(address=address, mac=mac) return Address(address=address, mac=mac)

View File

@ -356,6 +356,7 @@ class AccountType:
coin: coininfo.CoinInfo, coin: coininfo.CoinInfo,
address_n: Bip32Path, address_n: Bip32Path,
script_type: InputScriptType | None, script_type: InputScriptType | None,
show_account_str: bool,
) -> str | None: ) -> str | None:
pattern = self.pattern pattern = self.pattern
if self.account_level: if self.account_level:
@ -373,6 +374,8 @@ class AccountType:
return None return None
name = self.account_name name = self.account_name
if show_account_str:
name = f"{self.account_name} account"
account_pos = pattern.find("/account'") account_pos = pattern.find("/account'")
if account_pos >= 0: if account_pos >= 0:
i = pattern.count("/", 0, account_pos) i = pattern.count("/", 0, account_pos)
@ -387,6 +390,7 @@ def address_n_to_name(
address_n: Bip32Path, address_n: Bip32Path,
script_type: InputScriptType | None = None, script_type: InputScriptType | None = None,
account_level: bool = False, account_level: bool = False,
show_account_str: bool = False,
) -> str | None: ) -> str | None:
ACCOUNT_TYPES = ( ACCOUNT_TYPES = (
AccountType( AccountType(
@ -446,7 +450,7 @@ def address_n_to_name(
) )
for account in ACCOUNT_TYPES: for account in ACCOUNT_TYPES:
name = account.get_name(coin, address_n, script_type) name = account.get_name(coin, address_n, script_type, show_account_str)
if name: if name:
return name return name

View File

@ -144,6 +144,7 @@ class BasicApprover(Approver):
super().__init__(tx, coin) super().__init__(tx, coin)
self.change_count = 0 # the number of change-outputs self.change_count = 0 # the number of change-outputs
self.foreign_address_confirmed = False self.foreign_address_confirmed = False
self.chunkify = bool(tx.chunkify)
async def add_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None: async def add_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None:
if not validate_path_against_script_type(self.coin, txi): if not validate_path_against_script_type(self.coin, txi):
@ -224,7 +225,11 @@ class BasicApprover(Approver):
# Ask user to confirm output, unless it is part of a payment # Ask user to confirm output, unless it is part of a payment
# request, which gets confirmed separately. # request, which gets confirmed separately.
await helpers.confirm_output( await helpers.confirm_output(
txo, self.coin, self.amount_unit, self.external_output_index txo,
self.coin,
self.amount_unit,
self.external_output_index,
self.chunkify,
) )
self.external_output_index += 1 self.external_output_index += 1

View File

@ -44,11 +44,13 @@ class UiConfirmOutput(UiConfirm):
coin: CoinInfo, coin: CoinInfo,
amount_unit: AmountUnit, amount_unit: AmountUnit,
output_index: int, output_index: int,
chunkify: bool,
): ):
self.output = output self.output = output
self.coin = coin self.coin = coin
self.amount_unit = amount_unit self.amount_unit = amount_unit
self.output_index = output_index self.output_index = output_index
self.chunkify = chunkify
def confirm_dialog(self) -> Awaitable[Any]: def confirm_dialog(self) -> Awaitable[Any]:
return layout.confirm_output( return layout.confirm_output(
@ -56,6 +58,7 @@ class UiConfirmOutput(UiConfirm):
self.coin, self.coin,
self.amount_unit, self.amount_unit,
self.output_index, self.output_index,
self.chunkify,
) )
@ -238,8 +241,8 @@ class UiConfirmMultipleAccounts(UiConfirm):
return layout.confirm_multiple_accounts() return layout.confirm_multiple_accounts()
def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int) -> Awaitable[None]: # type: ignore [awaitable-is-generator] def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit, output_index: int, chunkify: bool) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
return (yield UiConfirmOutput(output, coin, amount_unit, output_index)) return (yield UiConfirmOutput(output, coin, amount_unit, output_index, chunkify))
def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-is-generator] def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[None]: # type: ignore [awaitable-is-generator]

View File

@ -62,6 +62,7 @@ async def confirm_output(
coin: CoinInfo, coin: CoinInfo,
amount_unit: AmountUnit, amount_unit: AmountUnit,
output_index: int, output_index: int,
chunkify: bool,
) -> None: ) -> None:
from trezor.enums import OutputScriptType from trezor.enums import OutputScriptType
@ -97,9 +98,18 @@ async def confirm_output(
address_label = None address_label = None
if output.address_n and not output.multisig: if output.address_n and not output.multisig:
from trezor import utils
# Showing the account string only for T2B1 model
show_account_str = utils.INTERNAL_MODEL == "T2B1"
script_type = CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[output.script_type] script_type = CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[output.script_type]
address_label = ( address_label = (
address_n_to_name(coin, output.address_n, script_type) address_n_to_name(
coin,
output.address_n,
script_type,
show_account_str=show_account_str,
)
or f"address path {address_n_to_str(output.address_n)}" or f"address path {address_n_to_str(output.address_n)}"
) )
@ -109,6 +119,7 @@ async def confirm_output(
title=title, title=title,
address_label=address_label, address_label=address_label,
output_index=output_index, output_index=output_index,
chunkify=chunkify,
) )
await layout await layout
@ -147,7 +158,7 @@ async def confirm_payment_request(
) -> Any: ) -> Any:
from trezor import wire from trezor import wire
memo_texts = [] memo_texts: list[str] = []
for m in msg.memos: for m in msg.memos:
if m.text_memo is not None: if m.text_memo is not None:
memo_texts.append(m.text_memo.text) memo_texts.append(m.text_memo.text)

View File

@ -39,6 +39,8 @@ async def get_address(
Credential.payment_credential(address_parameters), Credential.payment_credential(address_parameters),
Credential.stake_credential(address_parameters), Credential.stake_credential(address_parameters),
) )
await show_cardano_address(address_parameters, address, msg.protocol_magic) await show_cardano_address(
address_parameters, address, msg.protocol_magic, chunkify=bool(msg.chunkify)
)
return CardanoAddress(address=address) return CardanoAddress(address=address)

View File

@ -210,6 +210,7 @@ async def confirm_sending(
to: str, to: str,
output_type: Literal["address", "change", "collateral-return"], output_type: Literal["address", "change", "collateral-return"],
network_id: int, network_id: int,
chunkify: bool,
) -> None: ) -> None:
if output_type == "address": if output_type == "address":
title = "Sending" title = "Sending"
@ -225,6 +226,7 @@ async def confirm_sending(
format_coin_amount(ada_amount, network_id), format_coin_amount(ada_amount, network_id),
title, title,
br_code=ButtonRequestType.Other, br_code=ButtonRequestType.Other,
chunkify=chunkify,
) )
@ -898,6 +900,7 @@ async def show_cardano_address(
address_parameters: messages.CardanoAddressParametersType, address_parameters: messages.CardanoAddressParametersType,
address: str, address: str,
protocol_magic: int, protocol_magic: int,
chunkify: bool,
) -> None: ) -> None:
CAT = CardanoAddressType # local_cache_global CAT = CardanoAddressType # local_cache_global
@ -925,4 +928,5 @@ async def show_cardano_address(
path=path, path=path,
account=account, account=account,
network=network_name, network=network_name,
chunkify=chunkify,
) )

View File

@ -366,6 +366,7 @@ class Signer:
address, address,
"change" if self._is_change_output(output) else "address", "change" if self._is_change_output(output) else "address",
self.msg.network_id, self.msg.network_id,
chunkify=bool(self.msg.chunkify),
) )
async def _show_output_credentials( async def _show_output_credentials(
@ -1043,6 +1044,7 @@ class Signer:
address, address,
"collateral-return", "collateral-return",
self.msg.network_id, self.msg.network_id,
chunkify=bool(self.msg.chunkify),
) )
def _should_show_collateral_return_init(self, output: CardanoTxOutput) -> bool: def _should_show_collateral_return_init(self, output: CardanoTxOutput) -> bool:

View File

@ -32,6 +32,8 @@ async def get_address(
address = address_from_bytes(node.ethereum_pubkeyhash(), defs.network) address = address_from_bytes(node.ethereum_pubkeyhash(), defs.network)
if msg.show_display: if msg.show_display:
await show_address(address, path=paths.address_n_to_str(address_n)) await show_address(
address, path=paths.address_n_to_str(address_n), chunkify=bool(msg.chunkify)
)
return EthereumAddress(address=address) return EthereumAddress(address=address)

View File

@ -69,6 +69,7 @@ async def get_address(msg: MoneroGetAddress, keychain: Keychain) -> MoneroAddres
addr, addr,
address_qr="monero:" + addr, address_qr="monero:" + addr,
path=paths.address_n_to_str(msg.address_n), path=paths.address_n_to_str(msg.address_n),
chunkify=bool(msg.chunkify),
) )
return MoneroAddress(address=addr.encode()) return MoneroAddress(address=addr.encode())

View File

@ -126,7 +126,9 @@ async def require_confirm_transaction(
cur_payment = payment_id cur_payment = payment_id
else: else:
cur_payment = None cur_payment = None
await _require_confirm_output(dst, network_type, cur_payment) await _require_confirm_output(
dst, network_type, cur_payment, chunkify=bool(tsx_data.chunkify)
)
if ( if (
payment_id payment_id
@ -143,6 +145,7 @@ async def _require_confirm_output(
dst: MoneroTransactionDestinationEntry, dst: MoneroTransactionDestinationEntry,
network_type: MoneroNetworkType, network_type: MoneroNetworkType,
payment_id: bytes | None, payment_id: bytes | None,
chunkify: bool,
) -> None: ) -> None:
""" """
Single transaction destination confirmation Single transaction destination confirmation
@ -161,6 +164,7 @@ async def _require_confirm_output(
addr, addr,
_format_amount(dst.amount), _format_amount(dst.amount),
br_code=BRT_SignTx, br_code=BRT_SignTx,
chunkify=chunkify,
) )

View File

@ -35,6 +35,7 @@ async def get_address(msg: NEMGetAddress, keychain: Keychain) -> NEMAddress:
case_sensitive=False, case_sensitive=False,
path=address_n_to_str(address_n), path=address_n_to_str(address_n),
network=get_network_str(network), network=get_network_str(network),
chunkify=bool(msg.chunkify),
) )
return NEMAddress(address=address) return NEMAddress(address=address)

View File

@ -46,7 +46,9 @@ async def sign_tx(msg: NEMSignTx, keychain: Keychain) -> NEMSignedTx:
common = transaction common = transaction
if msg.transfer: if msg.transfer:
tx = await transfer.transfer(public_key, common, msg.transfer, node) tx = await transfer.transfer(
public_key, common, msg.transfer, node, chunkify=bool(msg.chunkify)
)
elif msg.provision_namespace: elif msg.provision_namespace:
tx = await namespace.namespace(public_key, common, msg.provision_namespace) tx = await namespace.namespace(public_key, common, msg.provision_namespace)
elif msg.mosaic_creation: elif msg.mosaic_creation:

View File

@ -12,11 +12,12 @@ async def transfer(
common: NEMTransactionCommon, common: NEMTransactionCommon,
transfer: NEMTransfer, transfer: NEMTransfer,
node: bip32.HDNode, node: bip32.HDNode,
chunkify: bool,
) -> bytes: ) -> bytes:
transfer.mosaics = serialize.canonicalize_mosaics(transfer.mosaics) transfer.mosaics = serialize.canonicalize_mosaics(transfer.mosaics)
payload, encrypted = serialize.get_transfer_payload(transfer, node) payload, encrypted = serialize.get_transfer_payload(transfer, node)
await layout.ask_transfer(common, transfer, encrypted) await layout.ask_transfer(common, transfer, encrypted, chunkify)
w = serialize.serialize_transfer(common, transfer, public_key, payload, encrypted) w = serialize.serialize_transfer(common, transfer, public_key, payload, encrypted)
for mosaic in transfer.mosaics: for mosaic in transfer.mosaics:

View File

@ -20,6 +20,7 @@ async def ask_transfer(
common: NEMTransactionCommon, common: NEMTransactionCommon,
transfer: NEMTransfer, transfer: NEMTransfer,
encrypted: bool, encrypted: bool,
chunkify: bool,
) -> None: ) -> None:
from trezor.ui.layouts import confirm_output, confirm_text from trezor.ui.layouts import confirm_output, confirm_text
@ -42,6 +43,7 @@ async def ask_transfer(
await confirm_output( await confirm_output(
transfer.recipient, transfer.recipient,
f"{format_amount(_get_xem_amount(transfer), NEM_MAX_DIVISIBILITY)} XEM", f"{format_amount(_get_xem_amount(transfer), NEM_MAX_DIVISIBILITY)} XEM",
chunkify=chunkify,
) )
await require_confirm_final(common.fee) await require_confirm_final(common.fee)

View File

@ -25,6 +25,10 @@ async def get_address(msg: RippleGetAddress, keychain: Keychain) -> RippleAddres
address = address_from_public_key(pubkey) address = address_from_public_key(pubkey)
if msg.show_display: if msg.show_display:
await show_address(address, path=paths.address_n_to_str(msg.address_n)) await show_address(
address,
path=paths.address_n_to_str(msg.address_n),
chunkify=bool(msg.chunkify),
)
return RippleAddress(address=address) return RippleAddress(address=address)

View File

@ -22,7 +22,7 @@ async def require_confirm_destination_tag(tag: int) -> None:
) )
async def require_confirm_tx(to: str, value: int) -> None: async def require_confirm_tx(to: str, value: int, chunkify: bool = False) -> None:
from trezor.ui.layouts import confirm_output from trezor.ui.layouts import confirm_output
await confirm_output(to, format_amount(value, DECIMALS) + " XRP") await confirm_output(to, format_amount(value, DECIMALS) + " XRP", chunkify=chunkify)

View File

@ -47,7 +47,9 @@ async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx:
if payment.destination_tag is not None: if payment.destination_tag is not None:
await layout.require_confirm_destination_tag(payment.destination_tag) await layout.require_confirm_destination_tag(payment.destination_tag)
await layout.require_confirm_tx(payment.destination, payment.amount) await layout.require_confirm_tx(
payment.destination, payment.amount, chunkify=bool(msg.chunkify)
)
await layout.require_confirm_total(payment.amount + msg.fee, msg.fee) await layout.require_confirm_total(payment.amount + msg.fee, msg.fee)
# Signs and encodes signature into DER format # Signs and encodes signature into DER format

View File

@ -25,6 +25,8 @@ async def get_address(msg: StellarGetAddress, keychain: Keychain) -> StellarAddr
if msg.show_display: if msg.show_display:
path = paths.address_n_to_str(msg.address_n) path = paths.address_n_to_str(msg.address_n)
await show_address(address, case_sensitive=False, path=path) await show_address(
address, case_sensitive=False, path=path, chunkify=bool(msg.chunkify)
)
return StellarAddress(address=address) return StellarAddress(address=address)

View File

@ -29,6 +29,10 @@ async def get_address(msg: TezosGetAddress, keychain: Keychain) -> TezosAddress:
address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX) address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX)
if msg.show_display: if msg.show_display:
await show_address(address, path=paths.address_n_to_str(msg.address_n)) await show_address(
address,
path=paths.address_n_to_str(msg.address_n),
chunkify=bool(msg.chunkify),
)
return TezosAddress(address=address) return TezosAddress(address=address)

View File

@ -4,13 +4,14 @@ from trezor.ui.layouts import confirm_address, confirm_metadata, confirm_propert
BR_SIGN_TX = ButtonRequestType.SignTx # global_import_cache BR_SIGN_TX = ButtonRequestType.SignTx # global_import_cache
async def require_confirm_tx(to: str, value: int) -> None: async def require_confirm_tx(to: str, value: int, chunkify: bool = False) -> None:
from trezor.ui.layouts import confirm_output from trezor.ui.layouts import confirm_output
await confirm_output( await confirm_output(
to, to,
format_tezos_amount(value), format_tezos_amount(value),
br_code=BR_SIGN_TX, br_code=BR_SIGN_TX,
chunkify=chunkify,
) )

View File

@ -71,12 +71,16 @@ async def sign_tx(msg: TezosSignTx, keychain: Keychain) -> TezosSignedTx:
# operation to transfer tokens from a smart contract to an implicit account or a smart contract # operation to transfer tokens from a smart contract to an implicit account or a smart contract
elif transfer is not None: elif transfer is not None:
to = _get_address_from_contract(transfer.destination) to = _get_address_from_contract(transfer.destination)
await layout.require_confirm_tx(to, transfer.amount) await layout.require_confirm_tx(
to, transfer.amount, chunkify=bool(msg.chunkify)
)
await layout.require_confirm_fee(transfer.amount, fee) await layout.require_confirm_fee(transfer.amount, fee)
else: else:
# transactions from an implicit account # transactions from an implicit account
to = _get_address_from_contract(transaction.destination) to = _get_address_from_contract(transaction.destination)
await layout.require_confirm_tx(to, transaction.amount) await layout.require_confirm_tx(
to, transaction.amount, chunkify=bool(msg.chunkify)
)
await layout.require_confirm_fee(transaction.amount, fee) await layout.require_confirm_fee(transaction.amount, fee)
elif origination is not None: elif origination is not None:

View File

@ -466,6 +466,7 @@ async def show_address(
mismatch_title: str = "ADDRESS MISMATCH?", mismatch_title: str = "ADDRESS MISMATCH?",
br_type: str = "show_address", br_type: str = "show_address",
br_code: ButtonRequestType = ButtonRequestType.Address, br_code: ButtonRequestType = ButtonRequestType.Address,
chunkify: bool = False,
) -> None: ) -> None:
send_button_request = True send_button_request = True
if title is None: if title is None:
@ -482,6 +483,7 @@ async def show_address(
data=address, data=address,
description="", # unused on TR description="", # unused on TR
extra=None, # unused on TR extra=None, # unused on TR
chunkify=chunkify,
) )
) )
if send_button_request: if send_button_request:
@ -550,6 +552,7 @@ def show_pubkey(
br_type=br_type, br_type=br_type,
br_code=ButtonRequestType.PublicKey, br_code=ButtonRequestType.PublicKey,
mismatch_title=mismatch_title, mismatch_title=mismatch_title,
chunkify=False,
) )
@ -652,6 +655,7 @@ async def confirm_output(
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
address_label: str | None = None, address_label: str | None = None,
output_index: int | None = None, output_index: int | None = None,
chunkify: bool = False,
) -> None: ) -> None:
address_title = ( address_title = (
"RECIPIENT" if output_index is None else f"RECIPIENT #{output_index + 1}" "RECIPIENT" if output_index is None else f"RECIPIENT #{output_index + 1}"
@ -667,6 +671,7 @@ async def confirm_output(
address_title=address_title, address_title=address_title,
amount_title=amount_title, amount_title=amount_title,
amount=amount, amount=amount,
chunkify=chunkify,
) )
), ),
"confirm_output", "confirm_output",

View File

@ -417,6 +417,7 @@ async def show_address(
mismatch_title: str = "Address mismatch?", mismatch_title: str = "Address mismatch?",
br_type: str = "show_address", br_type: str = "show_address",
br_code: ButtonRequestType = ButtonRequestType.Address, br_code: ButtonRequestType = ButtonRequestType.Address,
chunkify: bool = False,
) -> None: ) -> None:
send_button_request = True send_button_request = True
if title is None: if title is None:
@ -435,6 +436,7 @@ async def show_address(
data=address, data=address,
description=network or "", description=network or "",
extra=None, extra=None,
chunkify=chunkify,
) )
) )
if send_button_request: if send_button_request:
@ -500,6 +502,7 @@ def show_pubkey(
br_type=br_type, br_type=br_type,
br_code=ButtonRequestType.PublicKey, br_code=ButtonRequestType.PublicKey,
mismatch_title=mismatch_title, mismatch_title=mismatch_title,
chunkify=False,
) )
@ -577,6 +580,7 @@ async def confirm_output(
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput, br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
address_label: str | None = None, address_label: str | None = None,
output_index: int | None = None, output_index: int | None = None,
chunkify: bool = False,
) -> None: ) -> None:
if title is not None: if title is not None:
if title.upper().startswith("CONFIRM "): if title.upper().startswith("CONFIRM "):
@ -601,6 +605,7 @@ async def confirm_output(
verb="CONTINUE", verb="CONTINUE",
hold=False, hold=False,
info_button=False, info_button=False,
chunkify=chunkify,
) )
), ),
"confirm_output", "confirm_output",