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

feat(cardano): use nicer summary for send tx

Also show Recipient {i} for simple tx
This commit is contained in:
obrusvit 2024-10-04 17:17:14 +02:00 committed by Vít Obrusník
parent a3aacb7f13
commit 2c6a13064b
13 changed files with 147 additions and 33 deletions

View File

@ -0,0 +1 @@
Simplified UI of Cardano transactions initiated by Trezor Suite.

View File

@ -34,6 +34,7 @@ static void _librust_qstrs(void) {
MP_QSTR___name__; MP_QSTR___name__;
MP_QSTR_account; MP_QSTR_account;
MP_QSTR_account_items; MP_QSTR_account_items;
MP_QSTR_account_items_title;
MP_QSTR_account_label; MP_QSTR_account_label;
MP_QSTR_account_path; MP_QSTR_account_path;
MP_QSTR_accounts; MP_QSTR_accounts;

View File

@ -86,6 +86,10 @@ impl ConfirmSummary {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let account_items: Obj = kwargs.get(Qstr::MP_QSTR_account_items)?; let account_items: Obj = kwargs.get(Qstr::MP_QSTR_account_items)?;
let account_items_title: Option<TString> = kwargs
.get(Qstr::MP_QSTR_account_items_title)
.unwrap_or(Obj::const_none())
.try_into_option()?;
let fee_items: Obj = kwargs.get(Qstr::MP_QSTR_fee_items)?; let fee_items: Obj = kwargs.get(Qstr::MP_QSTR_fee_items)?;
let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?; let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?;
let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?; let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?;
@ -134,7 +138,9 @@ impl ConfirmSummary {
// AccountInfo // AccountInfo
let mut has_account_info = false; let mut has_account_info = false;
let mut account = ShowInfoParams::new(TR::send__send_from.into()).with_cancel_button(); let mut account =
ShowInfoParams::new(account_items_title.unwrap_or(TR::send__send_from.into()))
.with_cancel_button();
for pair in IterBuf::new().try_iterate(account_items)? { for pair in IterBuf::new().try_iterate(account_items)? {
let [label, value]: [TString; 2] = util::iter_into_array(pair)?; let [label, value]: [TString; 2] = util::iter_into_array(pair)?;
account = unwrap!(account.add(label, value)); account = unwrap!(account.add(label, value));
@ -155,7 +161,7 @@ impl ConfirmSummary {
if has_account_info { if has_account_info {
menu = menu.item( menu = menu.item(
theme::ICON_CHEVRON_RIGHT, theme::ICON_CHEVRON_RIGHT,
TR::address_details__account_info.into(), account_items_title.unwrap_or(TR::address_details__account_info.into()),
); );
unwrap!(menu_items.push(MENU_ITEM_ACCOUNT_INFO)); unwrap!(menu_items.push(MENU_ITEM_ACCOUNT_INFO));
} }

View File

@ -1702,6 +1702,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str, /// title: str,
/// items: Iterable[tuple[str, str]], /// items: Iterable[tuple[str, str]],
/// account_items: Iterable[tuple[str, str]], /// account_items: Iterable[tuple[str, str]],
/// account_items_title: str | None,
/// fee_items: Iterable[tuple[str, str]], /// fee_items: Iterable[tuple[str, str]],
/// br_code: ButtonRequestType, /// br_code: ButtonRequestType,
/// br_name: str, /// br_name: str,

View File

@ -600,6 +600,7 @@ def flow_confirm_summary(
title: str, title: str,
items: Iterable[tuple[str, str]], items: Iterable[tuple[str, str]],
account_items: Iterable[tuple[str, str]], account_items: Iterable[tuple[str, str]],
account_items_title: str | None,
fee_items: Iterable[tuple[str, str]], fee_items: Iterable[tuple[str, str]],
br_code: ButtonRequestType, br_code: ButtonRequestType,
br_name: str, br_name: str,

View File

@ -218,11 +218,17 @@ async def confirm_sending(
ada_amount: int, ada_amount: int,
to: str, to: str,
output_type: Literal["address", "change", "collateral-return"], output_type: Literal["address", "change", "collateral-return"],
output_index: int | None,
network_id: int, network_id: int,
chunkify: bool, chunkify: bool,
) -> None: ) -> None:
output_index_shown = None
if output_type == "address": if output_type == "address":
if output_index is None:
title = TR.cardano__sending title = TR.cardano__sending
else:
title = None
output_index_shown = output_index
elif output_type == "change": elif output_type == "change":
title = TR.cardano__change_output title = TR.cardano__change_output
elif output_type == "collateral-return": elif output_type == "collateral-return":
@ -236,6 +242,7 @@ async def confirm_sending(
title, title,
br_code=ButtonRequestType.Other, br_code=ButtonRequestType.Other,
chunkify=chunkify, chunkify=chunkify,
output_index=output_index_shown,
) )
@ -506,20 +513,49 @@ async def confirm_witness_request(
async def confirm_tx( async def confirm_tx(
spending: int,
fee: int, fee: int,
network_id: int, network_id: int,
protocol_magic: int, protocol_magic: int,
ttl: int | None, ttl: int | None,
validity_interval_start: int | None, validity_interval_start: int | None,
) -> None:
total_amount = format_coin_amount(spending, network_id)
fee_amount = format_coin_amount(fee, network_id)
items = (
(TR.cardano__network, f"{protocol_magics.to_ui_string(protocol_magic)}"),
(TR.cardano__valid_since, f"{format_optional_int(validity_interval_start)}"),
(TR.cardano__ttl, f"{format_optional_int(ttl)}"),
)
await layouts.confirm_cardano_tx(
total_amount,
fee_amount,
items=items,
)
async def confirm_tx_details(
network_id: int,
protocol_magic: int,
ttl: int | None,
fee: int | None,
validity_interval_start: int | None,
total_collateral: int | None, total_collateral: int | None,
is_network_id_verifiable: bool, is_network_id_verifiable: bool,
tx_hash: bytes | None, tx_hash: bytes | None,
) -> None: ) -> None:
props: list[PropertyType] = [ props: list[PropertyType] = []
(TR.cardano__transaction_fee, format_coin_amount(fee, network_id)),
]
append = props.append # local_cache_attribute append = props.append # local_cache_attribute
if fee is not None:
append(
(
TR.cardano__transaction_fee,
format_coin_amount(fee, network_id),
)
)
if total_collateral is not None: if total_collateral is not None:
append( append(
( (
@ -547,6 +583,7 @@ async def confirm_tx(
if tx_hash: if tx_hash:
append((TR.cardano__transaction_id, tx_hash)) append((TR.cardano__transaction_id, tx_hash))
if props:
await confirm_properties( await confirm_properties(
"confirm_total", "confirm_total",
TR.cardano__confirm_transaction, TR.cardano__confirm_transaction,
@ -590,6 +627,7 @@ async def confirm_certificate(
"confirm_certificate", "confirm_certificate",
TR.cardano__confirm_transaction, TR.cardano__confirm_transaction,
props, props,
hold=False,
br_code=BRT_Other, br_code=BRT_Other,
) )

View File

@ -33,11 +33,11 @@ class MultisigSigner(Signer):
# super() omitted intentionally # super() omitted intentionally
is_network_id_verifiable = self._is_network_id_verifiable() is_network_id_verifiable = self._is_network_id_verifiable()
await layout.confirm_tx( await layout.confirm_tx_details(
msg.fee,
msg.network_id, msg.network_id,
msg.protocol_magic, msg.protocol_magic,
msg.ttl, msg.ttl,
msg.fee,
msg.validity_interval_start, msg.validity_interval_start,
msg.total_collateral, msg.total_collateral,
is_network_id_verifiable, is_network_id_verifiable,

View File

@ -92,8 +92,9 @@ class OrdinarySigner(Signer):
# super() omitted intentionally # super() omitted intentionally
msg = self.msg # local_cache_attribute msg = self.msg # local_cache_attribute
if self.suite_tx_type is SuiteTxType.SIMPLE_SEND: if self.suite_tx_type is SuiteTxType.SIMPLE_SEND:
spending = self.total_out + msg.fee - self.change_out
await layout.confirm_tx( await layout.confirm_tx(
self.total_amount, spending,
msg.fee, msg.fee,
msg.network_id, msg.network_id,
msg.protocol_magic, msg.protocol_magic,

View File

@ -38,11 +38,11 @@ class PlutusSigner(Signer):
# computed by a trusted device (in case the tx contains many items which are # computed by a trusted device (in case the tx contains many items which are
# tedious to check one by one on the Trezor screen). # tedious to check one by one on the Trezor screen).
is_network_id_verifiable = self._is_network_id_verifiable() is_network_id_verifiable = self._is_network_id_verifiable()
await layout.confirm_tx( await layout.confirm_tx_details(
msg.fee,
msg.network_id, msg.network_id,
msg.protocol_magic, msg.protocol_magic,
msg.ttl, msg.ttl,
msg.fee,
msg.validity_interval_start, msg.validity_interval_start,
msg.total_collateral, msg.total_collateral,
is_network_id_verifiable, is_network_id_verifiable,

View File

@ -109,6 +109,8 @@ class Signer:
self.msg = msg self.msg = msg
self.keychain = keychain self.keychain = keychain
self.total_out = 0 # sum of output amounts
self.change_out = 0 # sum of change amounts
self.account_path_checker = AccountPathChecker() self.account_path_checker = AccountPathChecker()
@ -257,7 +259,6 @@ class Signer:
raise ProcessError("Total collateral is out of range!") raise ProcessError("Total collateral is out of range!")
validate_network_info(msg.network_id, msg.protocol_magic) validate_network_info(msg.network_id, msg.protocol_magic)
async def _show_tx_init(self) -> None: async def _show_tx_init(self) -> None:
self.should_show_details = await layout.show_tx_init(self.SIGNING_MODE_TITLE) self.should_show_details = await layout.show_tx_init(self.SIGNING_MODE_TITLE)
@ -292,25 +293,25 @@ class Signer:
# outputs # outputs
async def _process_outputs(self, outputs_list: HashBuilderList) -> None: async def _process_outputs(self, outputs_list: HashBuilderList) -> None:
total_amount = 0 for output_index in range(self.msg.outputs_count):
for _ in range(self.msg.outputs_count):
output: CardanoTxOutput = await ctx_call( output: CardanoTxOutput = await ctx_call(
CardanoTxItemAck(), CardanoTxOutput CardanoTxItemAck(), CardanoTxOutput
) )
await self._process_output(outputs_list, output) await self._process_output(outputs_list, output, output_index)
self.total_out += output.amount
if self._is_change_output(output):
self.change_out += output.amount
total_amount += output.amount if self.total_out > LOVELACE_MAX_SUPPLY:
if total_amount > LOVELACE_MAX_SUPPLY:
raise ProcessError("Total transaction amount is out of range!") raise ProcessError("Total transaction amount is out of range!")
async def _process_output( async def _process_output(
self, outputs_list: HashBuilderList, output: CardanoTxOutput self, outputs_list: HashBuilderList, output: CardanoTxOutput, output_index: int
) -> None: ) -> None:
self._validate_output(output) self._validate_output(output)
should_show = self._should_show_output(output) should_show = self._should_show_output(output)
if should_show: if should_show:
await self._show_output_init(output) await self._show_output_init(output, output_index)
output_items_count = 2 + sum( output_items_count = 2 + sum(
( (
@ -371,7 +372,9 @@ class Signer:
self.account_path_checker.add_output(output) self.account_path_checker.add_output(output)
async def _show_output_init(self, output: CardanoTxOutput) -> None: async def _show_output_init(
self, output: CardanoTxOutput, output_index: int
) -> None:
address_type = self._get_output_address_type(output) address_type = self._get_output_address_type(output)
if ( if (
output.datum_hash is None output.datum_hash is None
@ -395,14 +398,11 @@ class Signer:
assert output.address is not None # _validate_output assert output.address is not None # _validate_output
address = output.address address = output.address
if self.suite_tx_type == SuiteTxType.SIMPLE_SEND:
output_type = n
else:
output_type = "change" if self._is_change_output(output) else "address"
await layout.confirm_sending( await layout.confirm_sending(
output.amount, output.amount,
address, address,
"change" if self._is_change_output(output) else "address", "change" if self._is_change_output(output) else "address",
output_index if self.suite_tx_type is SuiteTxType.SIMPLE_SEND else None,
self.msg.network_id, self.msg.network_id,
chunkify=bool(self.msg.chunkify), chunkify=bool(self.msg.chunkify),
) )
@ -1078,6 +1078,7 @@ class Signer:
output.amount, output.amount,
address, address,
"collateral-return", "collateral-return",
None,
self.msg.network_id, self.msg.network_id,
chunkify=bool(self.msg.chunkify), chunkify=bool(self.msg.chunkify),
) )

View File

@ -993,6 +993,7 @@ def confirm_total(
items=items, items=items,
fee_items=fee_items, fee_items=fee_items,
account_items=account_items, account_items=account_items,
account_items_title=None,
br_name=br_name, br_name=br_name,
br_code=br_code, br_code=br_code,
cancel_text=TR.send__cancel_sign, cancel_text=TR.send__cancel_sign,
@ -1011,7 +1012,6 @@ def _confirm_summary(
br_code: ButtonRequestType = ButtonRequestType.SignTx, br_code: ButtonRequestType = ButtonRequestType.SignTx,
cancel_text: str | None = None, cancel_text: str | None = None,
) -> Awaitable[None]: ) -> Awaitable[None]:
# TODO: info_title
title = title or TR.words__title_summary # def_arg title = title or TR.words__title_summary # def_arg
return raise_if_not_confirmed( return raise_if_not_confirmed(
@ -1021,6 +1021,7 @@ def _confirm_summary(
items=items or (), items=items or (),
fee_items=fee_items or (), fee_items=fee_items or (),
account_items=info_items or (), account_items=info_items or (),
account_items_title=info_title,
br_name=br_name, br_name=br_name,
br_code=br_code, br_code=br_code,
cancel_text=cancel_text, cancel_text=cancel_text,
@ -1137,6 +1138,24 @@ if not utils.BITCOIN_ONLY:
br_code=br_code, br_code=br_code,
) )
def confirm_cardano_tx(
amount: str,
fee: str,
items: Iterable[tuple[str, str]],
) -> Awaitable[None]:
amount_title = TR.send__total_amount
fee_title = TR.send__incl_transaction_fee
more_info_title = TR.buttons__more_info
return _confirm_summary(
items=((amount_title, amount), (fee_title, fee)),
info_items=items,
info_title=more_info_title,
fee_items=None,
br_name="confirm_cardano_tx",
br_code=ButtonRequestType.SignTx,
)
def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]: def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]:
return _confirm_summary( return _confirm_summary(

View File

@ -1228,6 +1228,7 @@ if not utils.BITCOIN_ONLY:
amount_value=amount_value, amount_value=amount_value,
fee_title=f"{TR.send__maximum_fee}:", fee_title=f"{TR.send__maximum_fee}:",
fee_value=maximum_fee, fee_value=maximum_fee,
items_title=TR.confirm_total__title_fee,
items=[(f"{k}:", v) for (k, v) in info_items], items=[(f"{k}:", v) for (k, v) in info_items],
cancel_cross=True, cancel_cross=True,
) )
@ -1258,6 +1259,7 @@ if not utils.BITCOIN_ONLY:
amount_value=amount, amount_value=amount,
fee_title=fee_title, fee_title=fee_title,
fee_value=fee, fee_value=fee,
items_title=TR.confirm_total__title_fee,
items=items, items=items,
cancel_cross=True, cancel_cross=True,
) )
@ -1267,6 +1269,34 @@ if not utils.BITCOIN_ONLY:
) )
) )
def confirm_cardano_tx(
amount: str,
fee: str,
items: Iterable[tuple[str, str]],
amount_title: str | None = None,
fee_title: str | None = None,
) -> Awaitable[None]:
amount_title = f"{TR.send__total_amount}:"
fee_title = TR.send__including_fee
return raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.altcoin_tx_summary(
amount_title=amount_title,
amount_value=amount,
fee_title=fee_title,
fee_value=fee,
items_title=TR.words__title_information,
items=items,
cancel_cross=True,
)
),
br_name="confirm_cardano_tx",
br_code=ButtonRequestType.SignTx,
)
)
async def confirm_ethereum_tx( async def confirm_ethereum_tx(
recipient: str, recipient: str,
total_amount: str, total_amount: str,
@ -1284,6 +1314,7 @@ if not utils.BITCOIN_ONLY:
amount_value=total_amount, amount_value=total_amount,
fee_title=f"{TR.send__maximum_fee}:", fee_title=f"{TR.send__maximum_fee}:",
fee_value=maximum_fee, fee_value=maximum_fee,
items_title=TR.confirm_total__title_fee,
items=[(f"{k}:", v) for (k, v) in fee_info_items], items=[(f"{k}:", v) for (k, v) in fee_info_items],
) )
) )

View File

@ -1208,6 +1208,20 @@ if not utils.BITCOIN_ONLY:
br_code=br_code, br_code=br_code,
) )
def confirm_cardano_tx(
amount: str,
fee: str,
items: Iterable[tuple[str, str]],
) -> Awaitable[None]:
amount_title = f"{TR.send__total_amount}:"
fee_title = TR.send__including_fee
return _confirm_summary(
((amount_title, amount), (fee_title, fee)),
info_items=items,
br_name="confirm_cardano_tx",
br_code=ButtonRequestType.SignTx,
)
def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]: def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]:
return raise_if_not_confirmed( return raise_if_not_confirmed(