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

pull/3256/head
grdddj 9 months ago committed by Jiří Musil
parent da3cab22fd
commit bcb353a4a1

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

@ -52,6 +52,40 @@ pub struct TextLayout {
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)]
pub struct TextStyle {
/// Text font ID.
@ -76,6 +110,14 @@ pub struct TextStyle {
pub line_breaking: LineBreaking,
/// Specifies what to do at the end of the page.
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 {
@ -96,6 +138,8 @@ impl TextStyle {
prev_page_ellipsis_icon: None,
line_breaking: LineBreaking::BreakAtWhitespace,
page_breaking: PageBreaking::CutAndInsertEllipsis,
chunks: None,
line_spacing: 0,
}
}
@ -121,6 +165,18 @@ impl TextStyle {
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 {
if let Some((icon, margin)) = self.ellipsis_icon {
icon.toif.width() + margin
@ -220,12 +276,13 @@ impl TextLayout {
};
let remaining_width = self.bounds.x1 - cursor.x;
let span = Span::fit_horizontally(
let mut span = Span::fit_horizontally(
remaining_text,
remaining_width,
self.style.text_font,
self.style.line_breaking,
line_ending_space,
self.style.chunks,
);
cursor.x += match self.align {
@ -251,6 +308,9 @@ impl TextLayout {
if span.advance.y > 0 {
// 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.
if span.insert_hyphen_before_line_break {
sink.hyphen(*cursor, self);
@ -488,6 +548,7 @@ impl Span {
text_font: impl GlyphMetrics,
breaking: LineBreaking,
line_ending_space: i16,
chunks: Option<Chunks>,
) -> Self {
const ASCII_LF: char = '\n';
const ASCII_CR: char = '\r';
@ -537,6 +598,7 @@ impl Span {
let mut span_width = 0;
let mut found_any_whitespace = false;
let mut chunks_wider_chars = 0;
let mut char_indices_iter = text.char_indices().peekable();
// 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() {
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.
if is_whitespace(ch) && span_width + complete_word_end_width <= max_width {
// Break before the whitespace, without hyphen.
@ -679,6 +757,7 @@ mod tests {
FIXED_FONT,
LineBreaking::BreakAtWhitespace,
0,
None,
);
spans.push((
&remaining_text[..span.length],

@ -8,7 +8,7 @@ use crate::{
};
use super::{
layout::{LayoutFit, LayoutSink, TextLayout},
layout::{Chunks, LayoutFit, LayoutSink, TextLayout},
LineBreaking, TextStyle,
};
@ -90,6 +90,12 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
cursor.x += offset.x;
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
Op::NextPage => {
// 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 {
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
@ -238,6 +252,14 @@ impl<T: StringType + Clone> OpTextLayout<T> {
pub fn text_demibold(self, text: T) -> Self {
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)]
@ -258,4 +280,8 @@ pub enum Op<T: StringType> {
CursorOffset(Offset),
/// Force continuing on the next page.
NextPage,
/// Render the following text in a chunkified way. None will disable that.
Chunkify(Option<Chunks>),
/// Change the line vertical line spacing.
LineSpacing(i16),
}

@ -22,7 +22,7 @@ use crate::{
},
TextStyle,
},
ComponentExt, FormattedText, LineBreaking, Timeout,
ComponentExt, FormattedText, Timeout,
},
display, geometry,
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 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 chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let get_page = move |page_index| {
// 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();
// Not putting hyphens in the address.
// Potentially adding address label in different font.
let mut ops = OpTextLayout::new(theme::TEXT_MONO)
.line_breaking(LineBreaking::BreakWordsNoHyphen);
let mut ops = OpTextLayout::new(theme::TEXT_MONO_DATA);
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();
}
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());
let formatted = FormattedText::new(ops).vertically_centered();
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 title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.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| {
assert!(page_index == 0);
let btn_layout = ButtonLayout::cancel_armed_info("CONFIRM".into());
let btn_actions = ButtonActions::cancel_confirm_info();
let ops = OpTextLayout::new(theme::TEXT_MONO)
.line_breaking(LineBreaking::BreakWordsNoHyphen)
.text_mono(address.clone());
let style = if chunkify {
// Chunkifying the address into smaller pieces when requested
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();
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,
/// description: str | None, # unused on TR
/// extra: str | None, # unused on TR
/// chunkify: bool = False,
/// ) -> object:
/// """Confirm address."""
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,
/// address_title: str,
/// amount_title: str,
/// chunkify: bool = False,
/// ) -> object:
/// """Confirm output."""
Qstr::MP_QSTR_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(),

@ -1,5 +1,8 @@
use crate::ui::{
component::{text::TextStyle, LineBreaking, PageBreaking},
component::{
text::{layout::Chunks, TextStyle},
LineBreaking, PageBreaking,
},
display::{toif::Icon, Color, Font},
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
pub const TEXT_MONO_DATA: TextStyle =
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`.
pub fn textstyle_number(num: i32) -> &'static TextStyle {

@ -496,6 +496,7 @@ struct ConfirmBlobParams {
verb_cancel: Option<StrBuffer>,
info_button: bool,
hold: bool,
chunkify: bool,
}
impl ConfirmBlobParams {
@ -517,6 +518,7 @@ impl ConfirmBlobParams {
verb_cancel,
info_button: false,
hold,
chunkify: false,
}
}
@ -535,6 +537,11 @@ impl ConfirmBlobParams {
self
}
fn with_chunkify(mut self, chunkify: bool) -> Self {
self.chunkify = chunkify;
self
}
fn into_layout(self) -> Result<Obj, Error> {
let paragraphs = ConfirmBlob {
description: self.description.unwrap_or_else(StrBuffer::empty),
@ -542,7 +549,11 @@ impl ConfirmBlobParams {
data: self.data.try_into()?,
description_font: &theme::TEXT_NORMAL,
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();
@ -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()?;
let extra: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_extra)?.try_into_option()?;
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 {
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()?,
description_font: &theme::TEXT_NORMAL,
extra_font: &theme::TEXT_DEMIBOLD,
data_font: &theme::TEXT_MONO,
data_font: data_style,
}
.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())
.try_into_option()?;
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)
.with_subtitle(subtitle)
.with_info_button(info_button)
.with_chunkify(chunkify)
.into_layout()
};
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,
/// description: str | None,
/// extra: str | None,
/// chunkify: bool = False,
/// ) -> object:
/// """Confirm address. Similar to `confirm_blob` but has corner info 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,
/// info_button: bool = False,
/// hold: bool = False,
/// chunkify: bool = False,
/// ) -> object:
/// """Confirm value. Merge of confirm_total and confirm_output."""
Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(),

@ -2,7 +2,7 @@ use crate::{
time::Duration,
ui::{
component::{
text::{LineBreaking, PageBreaking, TextStyle},
text::{layout::Chunks, LineBreaking, PageBreaking, TextStyle},
FixedHeightBar,
},
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_ellipsis_icon(ICON_PAGE_NEXT, 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`.
pub fn textstyle_number(num: i32) -> &'static TextStyle {

@ -50,6 +50,7 @@ def confirm_address(
data: str,
description: str | None, # unused on TR
extra: str | None, # unused on TR
chunkify: bool = False,
) -> object:
"""Confirm address."""
@ -132,6 +133,7 @@ def confirm_output(
amount: str,
address_title: str,
amount_title: str,
chunkify: bool = False,
) -> object:
"""Confirm output."""
@ -496,6 +498,7 @@ def confirm_address(
data: str | bytes,
description: str | None,
extra: str | None,
chunkify: bool = False,
) -> object:
"""Confirm address. Similar to `confirm_blob` but has corner info 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,
info_button: bool = False,
hold: bool = False,
chunkify: bool = False,
) -> object:
"""Confirm value. Merge of confirm_total and confirm_output."""

@ -26,6 +26,8 @@ async def get_address(msg: BinanceGetAddress, keychain: Keychain) -> BinanceAddr
pubkey = node.public_key()
address = address_from_public_key(pubkey, HRP)
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)

@ -34,21 +34,18 @@ async def require_confirm_transfer(msg: BinanceTransferMsg) -> None:
for txoutput in msg.outputs:
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
for index, (title, amount, address) in enumerate(inputs_outputs):
# Having hold=True on the last item
hold = index == len(inputs_outputs) - 1
await confirm_output(
address,
amount,
title,
hold=hold,
)
await confirm_output(address, amount, title, hold=hold, chunkify=chunkify)
async def require_confirm_cancel(msg: BinanceCancelMsg) -> None:

@ -113,6 +113,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad
multisig_index=multisig_index,
xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes),
account=f"Multisig {multisig.m} of {len(pubnodes)}",
chunkify=bool(msg.chunkify),
)
else:
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,
path=path,
account=account,
chunkify=bool(msg.chunkify),
)
return Address(address=address, mac=mac)

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

@ -144,6 +144,7 @@ class BasicApprover(Approver):
super().__init__(tx, coin)
self.change_count = 0 # the number of change-outputs
self.foreign_address_confirmed = False
self.chunkify = bool(tx.chunkify)
async def add_internal_input(self, txi: TxInput, node: bip32.HDNode) -> None:
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
# request, which gets confirmed separately.
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

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

@ -62,6 +62,7 @@ async def confirm_output(
coin: CoinInfo,
amount_unit: AmountUnit,
output_index: int,
chunkify: bool,
) -> None:
from trezor.enums import OutputScriptType
@ -97,9 +98,18 @@ async def confirm_output(
address_label = None
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]
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)}"
)
@ -109,6 +119,7 @@ async def confirm_output(
title=title,
address_label=address_label,
output_index=output_index,
chunkify=chunkify,
)
await layout
@ -147,7 +158,7 @@ async def confirm_payment_request(
) -> Any:
from trezor import wire
memo_texts = []
memo_texts: list[str] = []
for m in msg.memos:
if m.text_memo is not None:
memo_texts.append(m.text_memo.text)

@ -39,6 +39,8 @@ async def get_address(
Credential.payment_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)

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

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

@ -32,6 +32,8 @@ async def get_address(
address = address_from_bytes(node.ethereum_pubkeyhash(), defs.network)
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)

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

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

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

@ -46,7 +46,9 @@ async def sign_tx(msg: NEMSignTx, keychain: Keychain) -> NEMSignedTx:
common = transaction
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:
tx = await namespace.namespace(public_key, common, msg.provision_namespace)
elif msg.mosaic_creation:

@ -12,11 +12,12 @@ async def transfer(
common: NEMTransactionCommon,
transfer: NEMTransfer,
node: bip32.HDNode,
chunkify: bool,
) -> bytes:
transfer.mosaics = serialize.canonicalize_mosaics(transfer.mosaics)
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)
for mosaic in transfer.mosaics:

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

@ -25,6 +25,10 @@ async def get_address(msg: RippleGetAddress, keychain: Keychain) -> RippleAddres
address = address_from_public_key(pubkey)
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)

@ -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
await confirm_output(to, format_amount(value, DECIMALS) + " XRP")
await confirm_output(to, format_amount(value, DECIMALS) + " XRP", chunkify=chunkify)

@ -47,7 +47,9 @@ async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx:
if payment.destination_tag is not None:
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)
# Signs and encodes signature into DER format

@ -25,6 +25,8 @@ async def get_address(msg: StellarGetAddress, keychain: Keychain) -> StellarAddr
if msg.show_display:
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)

@ -29,6 +29,10 @@ async def get_address(msg: TezosGetAddress, keychain: Keychain) -> TezosAddress:
address = helpers.base58_encode_check(pkh, helpers.TEZOS_ED25519_ADDRESS_PREFIX)
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)

@ -4,13 +4,14 @@ from trezor.ui.layouts import confirm_address, confirm_metadata, confirm_propert
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
await confirm_output(
to,
format_tezos_amount(value),
br_code=BR_SIGN_TX,
chunkify=chunkify,
)

@ -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
elif transfer is not None:
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)
else:
# transactions from an implicit account
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)
elif origination is not None:

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

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

Loading…
Cancel
Save