from micropython import const from trezor import ui, wire from trezor.messages import ButtonRequestType from trezor.ui.container import Container from trezor.ui.loader import LoaderDanger from trezor.ui.qr import Qr from trezor.utils import chunks from ..components.common import break_path_to_lines from ..components.common.confirm import is_confirmed, raise_if_cancelled from ..components.tt.button import ButtonCancel, ButtonDefault from ..components.tt.confirm import Confirm, HoldToConfirm from ..components.tt.scroll import Paginated, paginate_text from ..components.tt.text import Span, Text from ..constants.tt import ( MONO_CHARS_PER_LINE, MONO_HEX_PER_LINE, QR_SIZE_THRESHOLD, QR_X, QR_Y, TEXT_MAX_LINES, ) from .common import interact if False: from typing import ( Iterator, List, Sequence, Type, Union, Optional, Awaitable, NoReturn, ) from trezor.messages.ButtonRequest import EnumTypeButtonRequestType from ..components.common.text import TextContent ExceptionType = Union[BaseException, Type[BaseException]] __all__ = ( "confirm_action", "confirm_reset_device", "confirm_backup", "confirm_path_warning", "confirm_sign_identity", "confirm_signverify", "show_address", "show_error_and_raise", "show_pubkey", "show_success", "show_xpub", "show_warning", "confirm_output", "confirm_decred_sstx_submission", "confirm_hex", "confirm_total", "confirm_joint_total", "confirm_metadata", "confirm_replacement", "confirm_modify_output", "confirm_modify_fee", "confirm_coinjoin", ) async def confirm_action( ctx: wire.GenericContext, br_type: str, title: str, action: Optional[str] = None, description: Optional[str] = None, description_param: Optional[str] = None, description_param_font: int = ui.BOLD, verb: Union[str, bytes, None] = Confirm.DEFAULT_CONFIRM, verb_cancel: Union[str, bytes, None] = Confirm.DEFAULT_CANCEL, hold: bool = False, hold_danger: bool = False, icon: Optional[str] = None, # TODO cleanup @ redesign icon_color: Optional[int] = None, # TODO cleanup @ redesign reverse: bool = False, # TODO cleanup @ redesign larger_vspace: bool = False, # TODO cleanup @ redesign exc: ExceptionType = wire.ActionCancelled, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, ) -> None: text = Text( title, icon if icon is not None else ui.ICON_DEFAULT, icon_color if icon_color is not None else ui.ORANGE_ICON, new_lines=False, ) if reverse and description is not None: text.format_parametrized( description, description_param if description_param is not None else "", param_font=description_param_font, ) elif action is not None: text.bold(action) if action is not None and description is not None: text.br() if larger_vspace: text.br_half() if reverse and action is not None: text.bold(action) elif description is not None: text.format_parametrized( description, description_param if description_param is not None else "", param_font=description_param_font, ) cls = HoldToConfirm if hold else Confirm kwargs = {} if hold_danger: kwargs = {"loader_style": LoaderDanger, "confirm_style": ButtonCancel} await raise_if_cancelled( interact( ctx, cls(text, confirm=verb, cancel=verb_cancel, **kwargs), br_type, br_code, ), exc, ) async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> None: text = Text("Create new wallet", ui.ICON_RESET, new_lines=False) text.bold(prompt) text.br() text.br_half() text.normal("By continuing you agree") text.br() text.normal("to ") text.bold("https://trezor.io/tos") await raise_if_cancelled( interact( ctx, Confirm(text, major_confirm=True), "setup_device", ButtonRequestType.ResetDevice, ) ) # TODO cleanup @ redesign async def confirm_backup(ctx: wire.GenericContext) -> bool: text1 = Text("Success", ui.ICON_CONFIRM, ui.GREEN) text1.bold("New wallet created", "successfully!") text1.br_half() text1.normal("You should back up your", "new wallet right now.") text2 = Text("Warning", ui.ICON_WRONG, ui.RED) text2.bold("Are you sure you want", "to skip the backup?") text2.br_half() text2.normal("You can back up your", "Trezor once, at any time.") if is_confirmed( await interact( ctx, Confirm(text1, cancel="Skip", confirm="Back up", major_confirm=True), "backup_device", ButtonRequestType.ResetDevice, ) ): return True confirmed = is_confirmed( await interact( ctx, Confirm(text2, cancel="Skip", confirm="Back up", major_confirm=True), "backup_device", ButtonRequestType.ResetDevice, ) ) return confirmed async def confirm_path_warning(ctx: wire.GenericContext, path: str) -> None: text = Text("Confirm path", ui.ICON_WRONG, ui.RED) text.normal("Path") text.mono(*break_path_to_lines(path, MONO_CHARS_PER_LINE)) text.normal("is unknown.", "Are you sure?") await raise_if_cancelled( interact( ctx, Confirm(text), "path_warning", ButtonRequestType.UnknownDerivationPath, ) ) def _show_qr( address: str, desc: str, cancel: str = "Address", ) -> Confirm: QR_COEF = const(4) if len(address) < QR_SIZE_THRESHOLD else const(3) qr = Qr(address, QR_X, QR_Y, QR_COEF) text = Text(desc, ui.ICON_RECEIVE, ui.GREEN) return Confirm(Container(qr, text), cancel=cancel, cancel_style=ButtonDefault) def _split_address(address: str) -> Iterator[str]: return chunks(address, MONO_CHARS_PER_LINE) def _truncate_hex( hex_data: str, lines: int = TEXT_MAX_LINES, width: int = MONO_HEX_PER_LINE, middle: bool = False, ) -> Iterator[str]: if len(hex_data) >= width * lines: if middle: hex_data = ( hex_data[: lines * width // 2 - 1] + "..." + hex_data[-lines * width // 2 + 2 :] ) else: hex_data = hex_data[: (width * lines - 3)] + "..." return chunks(hex_data, width) def _show_address( address: str, desc: str, network: Optional[str] = None, ) -> Confirm: text = Text(desc, ui.ICON_RECEIVE, ui.GREEN) if network is not None: text.normal("%s network" % network) text.mono(*_split_address(address)) return Confirm(text, cancel="QR", cancel_style=ButtonDefault) def _show_xpub(xpub: str, desc: str, cancel: str) -> Paginated: pages: List[ui.Component] = [] for lines in chunks(list(chunks(xpub, 16)), 5): text = Text(desc, ui.ICON_RECEIVE, ui.GREEN) text.mono(*lines) pages.append(text) content = Paginated(pages) content.pages[-1] = Confirm( content.pages[-1], cancel=cancel, cancel_style=ButtonDefault, ) return content async def show_xpub( ctx: wire.GenericContext, xpub: str, desc: str, cancel: str ) -> None: await raise_if_cancelled( interact( ctx, _show_xpub(xpub, desc, cancel), "show_xpub", ButtonRequestType.PublicKey, ) ) async def show_address( ctx: wire.GenericContext, address: str, address_qr: Optional[str] = None, desc: str = "Confirm address", network: Optional[str] = None, multisig_index: Optional[int] = None, xpubs: Sequence[str] = [], ) -> None: is_multisig = len(xpubs) > 0 while True: if is_confirmed( await interact( ctx, _show_address(address, desc, network), "show_address", ButtonRequestType.Address, ) ): break if is_confirmed( await interact( ctx, _show_qr( address if address_qr is None else address_qr, desc, cancel="XPUBs" if is_multisig else "Address", ), "show_qr", ButtonRequestType.Address, ) ): break if is_multisig: for i, xpub in enumerate(xpubs): cancel = "Next" if i < len(xpubs) - 1 else "Address" desc_xpub = "XPUB #%d" % (i + 1) desc_xpub += " (yours)" if i == multisig_index else " (cosigner)" if is_confirmed( await interact( ctx, _show_xpub(xpub, desc=desc_xpub, cancel=cancel), "show_xpub", ButtonRequestType.PublicKey, ) ): return def show_pubkey( ctx: wire.Context, pubkey: str, title: str = "Confirm public key" ) -> Awaitable[None]: return confirm_hex( ctx, br_type="show_pubkey", title="Confirm public key", data=pubkey, br_code=ButtonRequestType.PublicKey, icon=ui.ICON_RECEIVE, ) async def _show_modal( ctx: wire.GenericContext, br_type: str, br_code: EnumTypeButtonRequestType, header: str, subheader: Optional[str], content: str, button_confirm: Optional[str], button_cancel: Optional[str], icon: str, icon_color: int, exc: ExceptionType = wire.ActionCancelled, ) -> None: text = Text(header, icon, icon_color, new_lines=False) if subheader: text.bold(subheader) text.br() text.br_half() text.normal(content) await raise_if_cancelled( interact( ctx, Confirm(text, confirm=button_confirm, cancel=button_cancel), br_type, br_code, ), exc, ) async def show_error_and_raise( ctx: wire.GenericContext, br_type: str, content: str, header: str = "Error", subheader: Optional[str] = None, button: str = "Close", red: bool = False, exc: ExceptionType = wire.ActionCancelled, ) -> NoReturn: await _show_modal( ctx, br_type=br_type, br_code=ButtonRequestType.Other, header=header, subheader=subheader, content=content, button_confirm=None, button_cancel=button, icon=ui.ICON_WRONG, icon_color=ui.RED if red else ui.ORANGE_ICON, exc=exc, ) raise exc def show_warning( ctx: wire.GenericContext, br_type: str, content: str, header: str = "Warning", subheader: Optional[str] = None, button: str = "Try again", br_code: EnumTypeButtonRequestType = ButtonRequestType.Warning, ) -> Awaitable[None]: return _show_modal( ctx, br_type=br_type, br_code=br_code, header=header, subheader=subheader, content=content, button_confirm=button, button_cancel=None, icon=ui.ICON_WRONG, icon_color=ui.RED, ) def show_success( ctx: wire.GenericContext, br_type: str, content: str, subheader: Optional[str] = None, button: str = "Continue", ) -> Awaitable[None]: return _show_modal( ctx, br_type=br_type, br_code=ButtonRequestType.Success, header="Success", subheader=subheader, content=content, button_confirm=button, button_cancel=None, icon=ui.ICON_CONFIRM, icon_color=ui.GREEN, ) async def confirm_output( ctx: wire.GenericContext, address: str, amount: str, ) -> None: text = Text("Confirm sending", ui.ICON_SEND, ui.GREEN) text.normal(amount + " to") text.mono(*_split_address(address)) await raise_if_cancelled( interact(ctx, Confirm(text), "confirm_output", ButtonRequestType.ConfirmOutput) ) async def confirm_decred_sstx_submission( ctx: wire.GenericContext, address: str, amount: str, ) -> None: text = Text("Purchase ticket", ui.ICON_SEND, ui.GREEN) text.normal(amount) text.normal("with voting rights to") text.mono(*_split_address(address)) await raise_if_cancelled( interact( ctx, Confirm(text), "confirm_decred_sstx_submission", ButtonRequestType.ConfirmOutput, ) ) async def confirm_hex( ctx: wire.GenericContext, br_type: str, title: str, data: str, description: Optional[str] = None, br_code: EnumTypeButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign width: int = MONO_HEX_PER_LINE, truncate_middle: bool = False, ) -> None: text = Text(title, icon, icon_color, new_lines=False) description_lines = 0 if description is not None: description_lines = Span(description, 0, ui.NORMAL).count_lines() text.normal(description) text.br() text.mono( *_truncate_hex( data, lines=TEXT_MAX_LINES - description_lines, width=width, middle=truncate_middle, ) ) content: ui.Layout = Confirm(text) await raise_if_cancelled(interact(ctx, content, br_type, br_code)) async def confirm_total( ctx: wire.GenericContext, total_amount: str, fee_amount: str ) -> None: text = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) text.normal("Total amount:") text.bold(total_amount) text.normal("including fee:") text.bold(fee_amount) await raise_if_cancelled( interact(ctx, HoldToConfirm(text), "confirm_total", ButtonRequestType.SignTx) ) async def confirm_joint_total( ctx: wire.GenericContext, spending_amount: str, total_amount: str ) -> None: text = Text("Joint transaction", ui.ICON_SEND, ui.GREEN) text.normal("You are contributing:") text.bold(spending_amount) text.normal("to the total amount:") text.bold(total_amount) await raise_if_cancelled( interact( ctx, HoldToConfirm(text), "confirm_joint_total", ButtonRequestType.SignTx ) ) async def confirm_metadata( ctx: wire.GenericContext, br_type: str, title: str, content: str, param: Optional[str] = None, br_code: EnumTypeButtonRequestType = ButtonRequestType.SignTx, ) -> None: text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) text.format_parametrized(content, param if param is not None else "") text.br() text.normal("Continue?") await raise_if_cancelled(interact(ctx, Confirm(text), br_type, br_code)) async def confirm_replacement( ctx: wire.GenericContext, description: str, txid: str ) -> None: text = Text(description, ui.ICON_SEND, ui.GREEN) text.normal("Confirm transaction ID:") text.mono(*_truncate_hex(txid, TEXT_MAX_LINES - 1)) await raise_if_cancelled( interact(ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx) ) async def confirm_modify_output( ctx: wire.GenericContext, address: str, sign: int, amount_change: str, amount_new: str, ) -> None: page1 = Text("Modify amount", ui.ICON_SEND, ui.GREEN) page1.normal("Address:") page1.br_half() page1.mono(*_split_address(address)) page2 = Text("Modify amount", ui.ICON_SEND, ui.GREEN) if sign < 0: page2.normal("Decrease amount by:") else: page2.normal("Increase amount by:") page2.bold(amount_change) page2.br_half() page2.normal("New amount:") page2.bold(amount_new) await raise_if_cancelled( interact( ctx, Paginated([page1, Confirm(page2)]), "modify_output", ButtonRequestType.ConfirmOutput, ) ) async def confirm_modify_fee( ctx: wire.GenericContext, sign: int, user_fee_change: str, total_fee_new: str, ) -> None: text = Text("Modify fee", ui.ICON_SEND, ui.GREEN) if sign == 0: text.normal("Your fee did not change.") else: if sign < 0: text.normal("Decrease your fee by:") else: text.normal("Increase your fee by:") text.bold(user_fee_change) text.br_half() text.normal("Transaction fee:") text.bold(total_fee_new) await raise_if_cancelled( interact(ctx, HoldToConfirm(text), "modify_fee", ButtonRequestType.SignTx) ) async def confirm_coinjoin( ctx: wire.GenericContext, fee_per_anonymity: Optional[str], total_fee: str ) -> None: text = Text("Authorize CoinJoin", ui.ICON_RECOVERY, new_lines=False) if fee_per_anonymity is not None: text.normal("Fee per anonymity set:\n") text.bold("{} %\n".format(fee_per_anonymity)) text.normal("Maximum total fees:\n") text.bold(total_fee) await raise_if_cancelled( interact(ctx, HoldToConfirm(text), "coinjoin_final", ButtonRequestType.Other) ) # TODO cleanup @ redesign async def confirm_sign_identity( ctx: wire.GenericContext, proto: str, identity: str, challenge_visual: Optional[str] ) -> None: lines: List[TextContent] = [] if challenge_visual: lines.append(challenge_visual) lines.append(ui.MONO) lines.extend(chunks(identity, 18)) text = Text("Sign %s" % proto) text.normal(*lines) await raise_if_cancelled( interact(ctx, Confirm(text), "sign_identity", ButtonRequestType.Other) ) async def confirm_signverify( ctx: wire.GenericContext, coin: str, message: str, address: Optional[str] = None ) -> None: if address: header = "Verify {} message".format(coin) font = ui.MONO br_type = "verify_message" text = Text(header) text.bold("Confirm address:") text.mono(*_split_address(address)) await raise_if_cancelled( interact(ctx, Confirm(text), br_type, ButtonRequestType.Other) ) else: header = "Sign {} message".format(coin) font = ui.NORMAL br_type = "sign_message" await raise_if_cancelled( interact( ctx, paginate_text(message, header, font=font), br_type, ButtonRequestType.Other, ) )