You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1106 lines
32 KiB
1106 lines
32 KiB
4 years ago
|
from micropython import const
|
||
3 years ago
|
from ubinascii import hexlify
|
||
|
|
||
3 years ago
|
from trezor import ui, wire
|
||
3 years ago
|
from trezor.enums import ButtonRequestType
|
||
4 years ago
|
from trezor.ui.container import Container
|
||
|
from trezor.ui.loader import LoaderDanger
|
||
3 years ago
|
from trezor.ui.popup import Popup
|
||
4 years ago
|
from trezor.ui.qr import Qr
|
||
3 years ago
|
from trezor.utils import chunks, chunks_intersperse
|
||
4 years ago
|
|
||
3 years ago
|
from ...components.common import break_path_to_lines
|
||
|
from ...components.common.confirm import is_confirmed, raise_if_cancelled
|
||
|
from ...components.common.webauthn import ConfirmInfo
|
||
|
from ...components.tt import passphrase, pin
|
||
|
from ...components.tt.button import ButtonCancel, ButtonDefault
|
||
|
from ...components.tt.confirm import Confirm, ConfirmPageable, HoldToConfirm, Pageable
|
||
|
from ...components.tt.scroll import (
|
||
3 years ago
|
PAGEBREAK,
|
||
3 years ago
|
Paginated,
|
||
|
paginate_paragraphs,
|
||
|
paginate_text,
|
||
|
)
|
||
3 years ago
|
from ...components.tt.text import LINE_WIDTH_PAGINATED, Span, Text
|
||
|
from ...components.tt.webauthn import ConfirmContent
|
||
|
from ...constants.tt import (
|
||
3 years ago
|
MONO_ADDR_PER_LINE,
|
||
4 years ago
|
MONO_HEX_PER_LINE,
|
||
|
QR_SIZE_THRESHOLD,
|
||
|
QR_X,
|
||
|
QR_Y,
|
||
|
TEXT_MAX_LINES,
|
||
|
)
|
||
3 years ago
|
from ..common import button_request, interact
|
||
4 years ago
|
|
||
|
if False:
|
||
3 years ago
|
from typing import (
|
||
3 years ago
|
Awaitable,
|
||
3 years ago
|
Iterator,
|
||
3 years ago
|
NoReturn,
|
||
3 years ago
|
Sequence,
|
||
3 years ago
|
Tuple,
|
||
3 years ago
|
)
|
||
4 years ago
|
|
||
3 years ago
|
from .common import PropertyType, ExceptionType
|
||
3 years ago
|
|
||
3 years ago
|
|
||
4 years ago
|
__all__ = (
|
||
|
"confirm_action",
|
||
3 years ago
|
"confirm_address",
|
||
|
"confirm_text",
|
||
|
"confirm_amount",
|
||
4 years ago
|
"confirm_reset_device",
|
||
|
"confirm_backup",
|
||
|
"confirm_path_warning",
|
||
3 years ago
|
"confirm_sign_identity",
|
||
3 years ago
|
"confirm_signverify",
|
||
4 years ago
|
"show_address",
|
||
3 years ago
|
"show_error_and_raise",
|
||
3 years ago
|
"show_pubkey",
|
||
3 years ago
|
"show_success",
|
||
3 years ago
|
"show_xpub",
|
||
3 years ago
|
"show_warning",
|
||
4 years ago
|
"confirm_output",
|
||
4 years ago
|
"confirm_decred_sstx_submission",
|
||
3 years ago
|
"confirm_blob",
|
||
3 years ago
|
"confirm_properties",
|
||
4 years ago
|
"confirm_total",
|
||
3 years ago
|
"confirm_total_ethereum",
|
||
3 years ago
|
"confirm_total_ripple",
|
||
4 years ago
|
"confirm_joint_total",
|
||
|
"confirm_metadata",
|
||
|
"confirm_replacement",
|
||
3 years ago
|
"confirm_modify_output",
|
||
4 years ago
|
"confirm_modify_fee",
|
||
3 years ago
|
"confirm_coinjoin",
|
||
3 years ago
|
"confirm_timebounds_stellar",
|
||
3 years ago
|
"confirm_transfer_binance",
|
||
3 years ago
|
"show_popup",
|
||
|
"confirm_webauthn",
|
||
|
"confirm_webauthn_reset",
|
||
3 years ago
|
"draw_simple_text",
|
||
|
"request_passphrase_on_device",
|
||
|
"request_pin_on_device",
|
||
4 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_action(
|
||
4 years ago
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
title: str,
|
||
3 years ago
|
action: str | None = None,
|
||
|
description: str | None = None,
|
||
|
description_param: str | None = None,
|
||
3 years ago
|
description_param_font: int = ui.BOLD,
|
||
3 years ago
|
verb: str | bytes | None = Confirm.DEFAULT_CONFIRM,
|
||
|
verb_cancel: str | bytes | None = Confirm.DEFAULT_CANCEL,
|
||
3 years ago
|
hold: bool = False,
|
||
3 years ago
|
hold_danger: bool = False,
|
||
3 years ago
|
icon: str | None = None, # TODO cleanup @ redesign
|
||
|
icon_color: int | None = None, # TODO cleanup @ redesign
|
||
3 years ago
|
reverse: bool = False, # TODO cleanup @ redesign
|
||
|
larger_vspace: bool = False, # TODO cleanup @ redesign
|
||
3 years ago
|
exc: ExceptionType = wire.ActionCancelled,
|
||
3 years ago
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
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:
|
||
3 years ago
|
text.format_parametrized(
|
||
3 years ago
|
description,
|
||
|
description_param if description_param is not None else "",
|
||
|
param_font=description_param_font,
|
||
3 years ago
|
)
|
||
3 years ago
|
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()
|
||
4 years ago
|
|
||
3 years ago
|
if reverse and action is not None:
|
||
|
text.bold(action)
|
||
|
elif description is not None:
|
||
3 years ago
|
text.format_parametrized(
|
||
3 years ago
|
description,
|
||
|
description_param if description_param is not None else "",
|
||
|
param_font=description_param_font,
|
||
3 years ago
|
)
|
||
3 years ago
|
|
||
|
cls = HoldToConfirm if hold else Confirm
|
||
3 years ago
|
kwargs = {}
|
||
|
if hold_danger:
|
||
|
kwargs = {"loader_style": LoaderDanger, "confirm_style": ButtonCancel}
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx,
|
||
3 years ago
|
cls(text, confirm=verb, cancel=verb_cancel, **kwargs),
|
||
3 years ago
|
br_type,
|
||
|
br_code,
|
||
3 years ago
|
),
|
||
|
exc,
|
||
3 years ago
|
)
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
async def confirm_reset_device(ctx: wire.GenericContext, prompt: str) -> None:
|
||
4 years ago
|
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()
|
||
3 years ago
|
text.normal("to ")
|
||
4 years ago
|
text.bold("https://trezor.io/tos")
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx,
|
||
|
Confirm(text, major_confirm=True),
|
||
|
"setup_device",
|
||
|
ButtonRequestType.ResetDevice,
|
||
|
)
|
||
4 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
# TODO cleanup @ redesign
|
||
4 years ago
|
async def confirm_backup(ctx: wire.GenericContext) -> bool:
|
||
3 years ago
|
text1 = Text("Success", ui.ICON_CONFIRM, ui.GREEN, new_lines=False)
|
||
|
text1.bold("New wallet created successfully!\n")
|
||
4 years ago
|
text1.br_half()
|
||
3 years ago
|
text1.normal("You should back up your new wallet right now.")
|
||
4 years ago
|
|
||
3 years ago
|
text2 = Text("Warning", ui.ICON_WRONG, ui.RED, new_lines=False)
|
||
|
text2.bold("Are you sure you want to skip the backup?\n")
|
||
4 years ago
|
text2.br_half()
|
||
3 years ago
|
text2.normal("You can back up your Trezor once, at any time.")
|
||
4 years ago
|
|
||
3 years ago
|
if is_confirmed(
|
||
4 years ago
|
await interact(
|
||
|
ctx,
|
||
|
Confirm(text1, cancel="Skip", confirm="Back up", major_confirm=True),
|
||
|
"backup_device",
|
||
|
ButtonRequestType.ResetDevice,
|
||
|
)
|
||
|
):
|
||
|
return True
|
||
|
|
||
3 years ago
|
confirmed = is_confirmed(
|
||
4 years ago
|
await interact(
|
||
|
ctx,
|
||
|
Confirm(text2, cancel="Skip", confirm="Back up", major_confirm=True),
|
||
|
"backup_device",
|
||
|
ButtonRequestType.ResetDevice,
|
||
|
)
|
||
3 years ago
|
)
|
||
4 years ago
|
return confirmed
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_path_warning(
|
||
|
ctx: wire.GenericContext, path: str, path_type: str = "Path"
|
||
|
) -> None:
|
||
4 years ago
|
text = Text("Confirm path", ui.ICON_WRONG, ui.RED)
|
||
3 years ago
|
text.normal(path_type)
|
||
3 years ago
|
text.mono(*break_path_to_lines(path, MONO_ADDR_PER_LINE))
|
||
4 years ago
|
text.normal("is unknown.", "Are you sure?")
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx,
|
||
|
Confirm(text),
|
||
|
"path_warning",
|
||
|
ButtonRequestType.UnknownDerivationPath,
|
||
|
)
|
||
4 years ago
|
)
|
||
|
|
||
|
|
||
|
def _show_qr(
|
||
|
address: str,
|
||
3 years ago
|
title: str,
|
||
4 years ago
|
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)
|
||
3 years ago
|
text = Text(title, ui.ICON_RECEIVE, ui.GREEN)
|
||
4 years ago
|
|
||
|
return Confirm(Container(qr, text), cancel=cancel, cancel_style=ButtonDefault)
|
||
|
|
||
|
|
||
|
def _split_address(address: str) -> Iterator[str]:
|
||
3 years ago
|
return chunks_intersperse(address, MONO_ADDR_PER_LINE)
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
def _truncate_hex(
|
||
|
hex_data: str,
|
||
|
lines: int = TEXT_MAX_LINES,
|
||
|
width: int = MONO_HEX_PER_LINE,
|
||
|
middle: bool = False,
|
||
3 years ago
|
ellipsis: str = "...", # TODO: cleanup @ redesign
|
||
3 years ago
|
) -> Iterator[str]:
|
||
3 years ago
|
ell_len = len(ellipsis)
|
||
3 years ago
|
if len(hex_data) > width * lines:
|
||
3 years ago
|
if middle:
|
||
|
hex_data = (
|
||
3 years ago
|
hex_data[: lines * width // 2 - (ell_len // 2)]
|
||
|
+ ellipsis
|
||
|
+ hex_data[-lines * width // 2 + (ell_len - ell_len // 2) :]
|
||
3 years ago
|
)
|
||
|
else:
|
||
3 years ago
|
hex_data = hex_data[: (width * lines - ell_len)] + ellipsis
|
||
3 years ago
|
return chunks_intersperse(hex_data, width)
|
||
4 years ago
|
|
||
|
|
||
|
def _show_address(
|
||
|
address: str,
|
||
3 years ago
|
title: str,
|
||
3 years ago
|
network: str | None = None,
|
||
3 years ago
|
extra: str | None = None,
|
||
|
) -> ui.Layout:
|
||
3 years ago
|
para = [(ui.NORMAL, "%s network" % network)] if network is not None else []
|
||
3 years ago
|
if extra is not None:
|
||
|
para.append((ui.BOLD, extra))
|
||
3 years ago
|
para.extend(
|
||
|
(ui.MONO, address_line) for address_line in chunks(address, MONO_ADDR_PER_LINE)
|
||
|
)
|
||
|
return paginate_paragraphs(
|
||
|
para,
|
||
3 years ago
|
header=title,
|
||
3 years ago
|
header_icon=ui.ICON_RECEIVE,
|
||
|
icon_color=ui.GREEN,
|
||
3 years ago
|
confirm=lambda content: Confirm(
|
||
|
content, cancel="QR", cancel_style=ButtonDefault
|
||
|
),
|
||
3 years ago
|
)
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
def _show_xpub(xpub: str, title: str, cancel: str) -> Paginated:
|
||
3 years ago
|
pages: list[ui.Component] = []
|
||
3 years ago
|
for lines in chunks(list(chunks_intersperse(xpub, 16)), TEXT_MAX_LINES * 2):
|
||
3 years ago
|
text = Text(title, ui.ICON_RECEIVE, ui.GREEN, new_lines=False)
|
||
4 years ago
|
text.mono(*lines)
|
||
|
pages.append(text)
|
||
|
|
||
|
content = Paginated(pages)
|
||
|
|
||
|
content.pages[-1] = Confirm(
|
||
|
content.pages[-1],
|
||
|
cancel=cancel,
|
||
|
cancel_style=ButtonDefault,
|
||
|
)
|
||
|
|
||
|
return content
|
||
|
|
||
|
|
||
3 years ago
|
async def show_xpub(
|
||
3 years ago
|
ctx: wire.GenericContext, xpub: str, title: str, cancel: str
|
||
3 years ago
|
) -> None:
|
||
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx,
|
||
3 years ago
|
_show_xpub(xpub, title, cancel),
|
||
3 years ago
|
"show_xpub",
|
||
|
ButtonRequestType.PublicKey,
|
||
|
)
|
||
3 years ago
|
)
|
||
|
|
||
|
|
||
4 years ago
|
async def show_address(
|
||
|
ctx: wire.GenericContext,
|
||
|
address: str,
|
||
3 years ago
|
address_qr: str | None = None,
|
||
3 years ago
|
title: str = "Confirm address",
|
||
3 years ago
|
network: str | None = None,
|
||
|
multisig_index: int | None = None,
|
||
4 years ago
|
xpubs: Sequence[str] = [],
|
||
3 years ago
|
address_extra: str | None = None,
|
||
|
title_qr: str | None = None,
|
||
4 years ago
|
) -> None:
|
||
|
is_multisig = len(xpubs) > 0
|
||
|
while True:
|
||
3 years ago
|
if is_confirmed(
|
||
4 years ago
|
await interact(
|
||
|
ctx,
|
||
3 years ago
|
_show_address(
|
||
|
address,
|
||
|
title,
|
||
|
network,
|
||
3 years ago
|
extra=address_extra,
|
||
3 years ago
|
),
|
||
4 years ago
|
"show_address",
|
||
|
ButtonRequestType.Address,
|
||
|
)
|
||
|
):
|
||
|
break
|
||
3 years ago
|
if is_confirmed(
|
||
4 years ago
|
await interact(
|
||
|
ctx,
|
||
|
_show_qr(
|
||
|
address if address_qr is None else address_qr,
|
||
3 years ago
|
title if title_qr is None else title_qr,
|
||
4 years ago
|
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"
|
||
3 years ago
|
title_xpub = "XPUB #%d" % (i + 1)
|
||
|
title_xpub += " (yours)" if i == multisig_index else " (cosigner)"
|
||
3 years ago
|
if is_confirmed(
|
||
4 years ago
|
await interact(
|
||
|
ctx,
|
||
3 years ago
|
_show_xpub(xpub, title=title_xpub, cancel=cancel),
|
||
4 years ago
|
"show_xpub",
|
||
|
ButtonRequestType.PublicKey,
|
||
|
)
|
||
|
):
|
||
|
return
|
||
|
|
||
|
|
||
3 years ago
|
def show_pubkey(
|
||
3 years ago
|
ctx: wire.Context, pubkey: str, title: str = "Confirm public key"
|
||
3 years ago
|
) -> Awaitable[None]:
|
||
3 years ago
|
return confirm_blob(
|
||
3 years ago
|
ctx,
|
||
|
br_type="show_pubkey",
|
||
|
title="Confirm public key",
|
||
|
data=pubkey,
|
||
|
br_code=ButtonRequestType.PublicKey,
|
||
|
icon=ui.ICON_RECEIVE,
|
||
3 years ago
|
)
|
||
3 years ago
|
|
||
|
|
||
3 years ago
|
async def _show_modal(
|
||
3 years ago
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
3 years ago
|
br_code: ButtonRequestType,
|
||
3 years ago
|
header: str,
|
||
3 years ago
|
subheader: str | None,
|
||
3 years ago
|
content: str,
|
||
3 years ago
|
button_confirm: str | None,
|
||
|
button_cancel: str | None,
|
||
3 years ago
|
icon: str,
|
||
|
icon_color: int,
|
||
3 years ago
|
exc: ExceptionType = wire.ActionCancelled,
|
||
|
) -> None:
|
||
3 years ago
|
text = Text(header, icon, icon_color, new_lines=False)
|
||
3 years ago
|
if subheader:
|
||
|
text.bold(subheader)
|
||
|
text.br()
|
||
|
text.br_half()
|
||
|
text.normal(content)
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx,
|
||
3 years ago
|
Confirm(text, confirm=button_confirm, cancel=button_cancel),
|
||
3 years ago
|
br_type,
|
||
3 years ago
|
br_code,
|
||
3 years ago
|
),
|
||
|
exc,
|
||
3 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
async def show_error_and_raise(
|
||
3 years ago
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
content: str,
|
||
|
header: str = "Error",
|
||
3 years ago
|
subheader: str | None = None,
|
||
3 years ago
|
button: str = "Close",
|
||
3 years ago
|
red: bool = False,
|
||
3 years ago
|
exc: ExceptionType = wire.ActionCancelled,
|
||
|
) -> NoReturn:
|
||
|
await _show_modal(
|
||
3 years ago
|
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,
|
||
3 years ago
|
icon_color=ui.RED if red else ui.ORANGE_ICON,
|
||
3 years ago
|
exc=exc,
|
||
3 years ago
|
)
|
||
3 years ago
|
raise exc
|
||
3 years ago
|
|
||
|
|
||
|
def show_warning(
|
||
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
content: str,
|
||
3 years ago
|
header: str = "Warning",
|
||
3 years ago
|
subheader: str | None = None,
|
||
3 years ago
|
button: str = "Try again",
|
||
3 years ago
|
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
||
3 years ago
|
) -> Awaitable[None]:
|
||
3 years ago
|
return _show_modal(
|
||
|
ctx,
|
||
|
br_type=br_type,
|
||
3 years ago
|
br_code=br_code,
|
||
|
header=header,
|
||
3 years ago
|
subheader=subheader,
|
||
|
content=content,
|
||
|
button_confirm=button,
|
||
|
button_cancel=None,
|
||
|
icon=ui.ICON_WRONG,
|
||
|
icon_color=ui.RED,
|
||
|
)
|
||
|
|
||
|
|
||
|
def show_success(
|
||
3 years ago
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
content: str,
|
||
3 years ago
|
subheader: str | None = None,
|
||
3 years ago
|
button: str = "Continue",
|
||
3 years ago
|
) -> Awaitable[None]:
|
||
3 years ago
|
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,
|
||
3 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_output(
|
||
4 years ago
|
ctx: wire.GenericContext,
|
||
|
address: str,
|
||
|
amount: str,
|
||
3 years ago
|
font_amount: int = ui.NORMAL, # TODO cleanup @ redesign
|
||
3 years ago
|
title: str = "Confirm sending",
|
||
|
subtitle: str | None = None, # TODO cleanup @ redesign
|
||
3 years ago
|
color_to: int = ui.FG, # TODO cleanup @ redesign
|
||
3 years ago
|
to_str: str = " to\n", # TODO cleanup @ redesign
|
||
3 years ago
|
to_paginated: bool = False, # TODO cleanup @ redesign
|
||
3 years ago
|
width: int = MONO_ADDR_PER_LINE,
|
||
|
width_paginated: int = MONO_ADDR_PER_LINE - 1,
|
||
3 years ago
|
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
header_lines = to_str.count("\n") + int(subtitle is not None)
|
||
|
if len(address) > (TEXT_MAX_LINES - header_lines) * width:
|
||
|
para = []
|
||
|
if subtitle is not None:
|
||
|
para.append((ui.NORMAL, subtitle))
|
||
|
para.append((font_amount, amount))
|
||
|
if to_paginated:
|
||
|
para.append((ui.NORMAL, "to"))
|
||
3 years ago
|
para.extend((ui.MONO, line) for line in chunks(address, width_paginated))
|
||
|
content: ui.Layout = paginate_paragraphs(para, title, ui.ICON_SEND, ui.GREEN)
|
||
|
else:
|
||
|
text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
3 years ago
|
if subtitle is not None:
|
||
|
text.normal(subtitle, "\n")
|
||
3 years ago
|
text.content = [font_amount, amount, ui.NORMAL, color_to, to_str, ui.FG]
|
||
3 years ago
|
text.mono(*chunks_intersperse(address, width))
|
||
|
content = Confirm(text)
|
||
|
|
||
|
await raise_if_cancelled(interact(ctx, content, "confirm_output", br_code))
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
async def confirm_decred_sstx_submission(
|
||
4 years ago
|
ctx: wire.GenericContext,
|
||
|
address: str,
|
||
|
amount: str,
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
text = Text("Purchase ticket", ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
4 years ago
|
text.normal(amount)
|
||
3 years ago
|
text.normal("\nwith voting rights to\n")
|
||
4 years ago
|
text.mono(*_split_address(address))
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx,
|
||
|
Confirm(text),
|
||
|
"confirm_decred_sstx_submission",
|
||
|
ButtonRequestType.ConfirmOutput,
|
||
|
)
|
||
4 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_blob(
|
||
4 years ago
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
title: str,
|
||
3 years ago
|
data: bytes | str,
|
||
3 years ago
|
description: str | None = None,
|
||
3 years ago
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||
3 years ago
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
"""Confirm data blob.
|
||
|
|
||
|
Applicable for public keys, signatures, hashes. In general, any kind of
|
||
|
data that is not human-readable, and can be wrapped at any character.
|
||
|
|
||
|
For addresses, use `confirm_address`.
|
||
|
|
||
|
Displays in monospace font. Paginates automatically.
|
||
|
If data is provided as bytes or bytearray, it is converted to hex.
|
||
|
"""
|
||
|
if isinstance(data, (bytes, bytearray)):
|
||
|
data_str = hexlify(data).decode()
|
||
|
else:
|
||
|
data_str = data
|
||
|
|
||
|
span = Span()
|
||
|
lines = 0
|
||
|
if description is not None:
|
||
|
span.reset(description, 0, ui.NORMAL)
|
||
|
lines += span.count_lines()
|
||
|
data_lines = (len(data_str) + MONO_HEX_PER_LINE - 1) // MONO_HEX_PER_LINE
|
||
|
lines += data_lines
|
||
|
|
||
|
if lines <= TEXT_MAX_LINES:
|
||
3 years ago
|
text = Text(title, icon, icon_color, new_lines=False)
|
||
|
if description is not None:
|
||
3 years ago
|
text.normal(description)
|
||
3 years ago
|
text.br()
|
||
3 years ago
|
|
||
|
# special case:
|
||
|
if len(data_str) % 16 == 0:
|
||
|
# sanity checks:
|
||
|
# (a) we must not exceed MONO_HEX_PER_LINE
|
||
|
assert MONO_HEX_PER_LINE > 16
|
||
|
# (b) we must not increase number of lines
|
||
|
assert (len(data_str) // 16) <= data_lines
|
||
|
# the above holds true for MONO_HEX_PER_LINE == 18 and TEXT_MAX_LINES == 5
|
||
|
per_line = 16
|
||
|
|
||
3 years ago
|
else:
|
||
3 years ago
|
per_line = MONO_HEX_PER_LINE
|
||
|
text.mono(ui.FG, *chunks_intersperse(data_str, per_line))
|
||
3 years ago
|
content: ui.Layout = Confirm(text)
|
||
3 years ago
|
|
||
3 years ago
|
else:
|
||
3 years ago
|
para = []
|
||
|
if description is not None:
|
||
|
para.append((ui.NORMAL, description))
|
||
3 years ago
|
para.extend((ui.MONO, line) for line in chunks(data_str, MONO_HEX_PER_LINE - 2))
|
||
3 years ago
|
content = paginate_paragraphs(para, title, icon, icon_color)
|
||
3 years ago
|
await raise_if_cancelled(interact(ctx, content, br_type, br_code))
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
def confirm_address(
|
||
|
ctx: wire.GenericContext,
|
||
|
title: str,
|
||
|
address: str,
|
||
|
description: str | None = "Address:",
|
||
|
br_type: str = "confirm_address",
|
||
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||
|
) -> Awaitable[None]:
|
||
|
# TODO clarify API - this should be pretty limited to support mainly confirming
|
||
|
# destinations and similar
|
||
|
return confirm_blob(
|
||
|
ctx,
|
||
|
br_type=br_type,
|
||
|
title=title,
|
||
|
data=address,
|
||
|
description=description,
|
||
|
br_code=br_code,
|
||
|
icon=icon,
|
||
|
icon_color=icon_color,
|
||
|
)
|
||
|
|
||
|
|
||
|
async def confirm_text(
|
||
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
title: str,
|
||
|
data: str,
|
||
|
description: str | None = None,
|
||
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||
|
) -> None:
|
||
|
"""Confirm textual data.
|
||
|
|
||
|
Applicable for human-readable strings, numbers, date/time values etc.
|
||
|
|
||
|
For amounts, use `confirm_amount`.
|
||
|
|
||
|
Displays in bold font. Paginates automatically.
|
||
|
"""
|
||
|
span = Span()
|
||
|
lines = 0
|
||
|
if description is not None:
|
||
|
span.reset(description, 0, ui.NORMAL)
|
||
|
lines += span.count_lines()
|
||
|
span.reset(data, 0, ui.BOLD)
|
||
|
lines += span.count_lines()
|
||
|
|
||
|
if lines <= TEXT_MAX_LINES:
|
||
|
text = Text(title, icon, icon_color, new_lines=False)
|
||
|
if description is not None:
|
||
|
text.normal(description)
|
||
|
text.br()
|
||
|
text.bold(data)
|
||
|
content: ui.Layout = Confirm(text)
|
||
|
|
||
|
else:
|
||
|
para = []
|
||
|
if description is not None:
|
||
|
para.append((ui.NORMAL, description))
|
||
|
para.append((ui.BOLD, data))
|
||
|
content = paginate_paragraphs(para, title, icon, icon_color)
|
||
|
await raise_if_cancelled(interact(ctx, content, br_type, br_code))
|
||
|
|
||
|
|
||
|
def confirm_amount(
|
||
|
ctx: wire.GenericContext,
|
||
|
title: str,
|
||
|
amount: str,
|
||
|
description: str = "Amount:",
|
||
|
br_type: str = "confirm_amount",
|
||
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||
|
) -> Awaitable[None]:
|
||
|
"""Confirm amount."""
|
||
|
# TODO clarify API - this should be pretty limited to support mainly confirming
|
||
|
# destinations and similar
|
||
|
return confirm_text(
|
||
|
ctx,
|
||
|
br_type=br_type,
|
||
|
title=title,
|
||
|
data=amount,
|
||
|
description=description,
|
||
|
br_code=br_code,
|
||
|
icon=icon,
|
||
|
icon_color=icon_color,
|
||
|
)
|
||
|
|
||
|
|
||
3 years ago
|
_SCREEN_FULL_THRESHOLD = const(2)
|
||
|
|
||
|
|
||
3 years ago
|
# TODO keep name and value on the same page if possible
|
||
|
async def confirm_properties(
|
||
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
title: str,
|
||
|
props: Sequence[PropertyType],
|
||
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||
|
hold: bool = False,
|
||
|
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||
|
) -> None:
|
||
3 years ago
|
span = Span()
|
||
3 years ago
|
para = []
|
||
3 years ago
|
used_lines = 0
|
||
|
for key, val in props:
|
||
3 years ago
|
span.reset(key or "", 0, ui.NORMAL, line_width=LINE_WIDTH_PAGINATED)
|
||
3 years ago
|
key_lines = span.count_lines()
|
||
3 years ago
|
|
||
|
if isinstance(val, str):
|
||
3 years ago
|
span.reset(val, 0, ui.BOLD, line_width=LINE_WIDTH_PAGINATED)
|
||
3 years ago
|
val_lines = span.count_lines()
|
||
|
elif isinstance(val, bytes):
|
||
|
val_lines = (len(val) * 2 + MONO_HEX_PER_LINE - 1) // MONO_HEX_PER_LINE
|
||
|
else:
|
||
|
val_lines = 0
|
||
3 years ago
|
|
||
|
remaining_lines = TEXT_MAX_LINES - used_lines
|
||
|
used_lines = (used_lines + key_lines + val_lines) % TEXT_MAX_LINES
|
||
|
|
||
|
if key_lines + val_lines > remaining_lines:
|
||
|
if remaining_lines <= _SCREEN_FULL_THRESHOLD:
|
||
|
# there are only 2 remaining lines, don't try to fit and put everything
|
||
|
# on next page
|
||
|
para.append(PAGEBREAK)
|
||
|
used_lines = (key_lines + val_lines) % TEXT_MAX_LINES
|
||
|
|
||
|
elif val_lines > 0 and key_lines >= remaining_lines:
|
||
|
# more than 2 remaining lines so try to fit something -- but won't fit
|
||
|
# at least one line of value
|
||
|
para.append(PAGEBREAK)
|
||
|
used_lines = (key_lines + val_lines) % TEXT_MAX_LINES
|
||
|
|
||
|
elif key_lines + val_lines <= TEXT_MAX_LINES:
|
||
|
# Whole property won't fit to the page, but it will fit on a page
|
||
|
# by itself
|
||
|
para.append(PAGEBREAK)
|
||
|
used_lines = (key_lines + val_lines) % TEXT_MAX_LINES
|
||
|
|
||
|
# else:
|
||
|
# None of the above. Continue fitting on the same page.
|
||
|
|
||
|
if key:
|
||
|
para.append((ui.NORMAL, key))
|
||
3 years ago
|
if isinstance(val, bytes):
|
||
|
para.extend(
|
||
|
(ui.MONO, line)
|
||
|
for line in chunks(hexlify(val).decode(), MONO_HEX_PER_LINE - 2)
|
||
|
)
|
||
|
elif isinstance(val, str):
|
||
3 years ago
|
para.append((ui.BOLD, val))
|
||
3 years ago
|
content = paginate_paragraphs(
|
||
|
para, title, icon, icon_color, confirm=HoldToConfirm if hold else Confirm
|
||
|
)
|
||
|
await raise_if_cancelled(interact(ctx, content, br_type, br_code))
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_total(
|
||
3 years ago
|
ctx: wire.GenericContext,
|
||
|
total_amount: str,
|
||
|
fee_amount: str,
|
||
|
title: str = "Confirm transaction",
|
||
|
total_label: str = "Total amount:\n",
|
||
|
fee_label: str = "\nincluding fee:\n",
|
||
|
icon_color: int = ui.GREEN,
|
||
|
br_type: str = "confirm_total",
|
||
3 years ago
|
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
text = Text(title, ui.ICON_SEND, icon_color, new_lines=False)
|
||
|
text.normal(total_label)
|
||
4 years ago
|
text.bold(total_amount)
|
||
3 years ago
|
text.normal(fee_label)
|
||
4 years ago
|
text.bold(fee_amount)
|
||
3 years ago
|
await raise_if_cancelled(interact(ctx, HoldToConfirm(text), br_type, br_code))
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
# TODO cleanup @ redesign
|
||
|
async def confirm_total_ethereum(
|
||
|
ctx: wire.GenericContext, total_amount: str, gas_price: str, fee_max: str
|
||
|
) -> None:
|
||
|
text = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
|
text.bold(total_amount)
|
||
|
text.normal(" ", ui.GREY, "Gas price:", ui.FG)
|
||
|
text.bold(gas_price)
|
||
|
text.normal(" ", ui.GREY, "Maximum fee:", ui.FG)
|
||
|
text.bold(fee_max)
|
||
|
await raise_if_cancelled(
|
||
|
interact(ctx, HoldToConfirm(text), "confirm_total", ButtonRequestType.SignTx)
|
||
|
)
|
||
|
|
||
|
|
||
3 years ago
|
# TODO cleanup @ redesign
|
||
|
async def confirm_total_ripple(
|
||
|
ctx: wire.GenericContext,
|
||
|
address: str,
|
||
|
amount: str,
|
||
|
) -> None:
|
||
|
title = "Confirm sending"
|
||
|
text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
|
text.bold("{} XRP\n".format(amount))
|
||
|
text.normal("to\n")
|
||
|
text.mono(*_split_address(address))
|
||
|
|
||
|
await raise_if_cancelled(
|
||
|
interact(ctx, HoldToConfirm(text), "confirm_output", ButtonRequestType.SignTx)
|
||
|
)
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_joint_total(
|
||
4 years ago
|
ctx: wire.GenericContext, spending_amount: str, total_amount: str
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
text = Text("Joint transaction", ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
|
text.normal("You are contributing:\n")
|
||
4 years ago
|
text.bold(spending_amount)
|
||
3 years ago
|
text.normal("\nto the total amount:\n")
|
||
4 years ago
|
text.bold(total_amount)
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx, HoldToConfirm(text), "confirm_joint_total", ButtonRequestType.SignTx
|
||
|
)
|
||
4 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_metadata(
|
||
4 years ago
|
ctx: wire.GenericContext,
|
||
|
br_type: str,
|
||
|
title: str,
|
||
|
content: str,
|
||
3 years ago
|
param: str | None = None,
|
||
3 years ago
|
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
||
3 years ago
|
hide_continue: bool = False,
|
||
|
hold: bool = False,
|
||
3 years ago
|
param_font: int = ui.BOLD,
|
||
3 years ago
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||
3 years ago
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||
3 years ago
|
larger_vspace: bool = False, # TODO cleanup @ redesign
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
text = Text(title, icon, icon_color, new_lines=False)
|
||
3 years ago
|
text.format_parametrized(
|
||
|
content, param if param is not None else "", param_font=param_font
|
||
|
)
|
||
4 years ago
|
|
||
3 years ago
|
if not hide_continue:
|
||
|
text.br()
|
||
3 years ago
|
if larger_vspace:
|
||
|
text.br_half()
|
||
3 years ago
|
text.normal("Continue?")
|
||
|
|
||
|
cls = HoldToConfirm if hold else Confirm
|
||
4 years ago
|
|
||
3 years ago
|
await raise_if_cancelled(interact(ctx, cls(text), br_type, br_code))
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
async def confirm_replacement(
|
||
4 years ago
|
ctx: wire.GenericContext, description: str, txid: str
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
text = Text(description, ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
|
text.normal("Confirm transaction ID:\n")
|
||
3 years ago
|
text.mono(*_truncate_hex(txid, TEXT_MAX_LINES - 1))
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(ctx, Confirm(text), "confirm_replacement", ButtonRequestType.SignTx)
|
||
3 years ago
|
)
|
||
4 years ago
|
|
||
|
|
||
3 years ago
|
async def confirm_modify_output(
|
||
3 years ago
|
ctx: wire.GenericContext,
|
||
|
address: str,
|
||
|
sign: int,
|
||
|
amount_change: str,
|
||
|
amount_new: str,
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
page1 = Text("Modify amount", ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
|
page1.normal("Address:\n")
|
||
3 years ago
|
page1.br_half()
|
||
|
page1.mono(*_split_address(address))
|
||
|
|
||
3 years ago
|
page2 = Text("Modify amount", ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
3 years ago
|
if sign < 0:
|
||
3 years ago
|
page2.normal("Decrease amount by:\n")
|
||
3 years ago
|
else:
|
||
3 years ago
|
page2.normal("Increase amount by:\n")
|
||
3 years ago
|
page2.bold(amount_change)
|
||
|
page2.br_half()
|
||
3 years ago
|
page2.normal("\nNew amount:\n")
|
||
3 years ago
|
page2.bold(amount_new)
|
||
|
|
||
3 years ago
|
await raise_if_cancelled(
|
||
3 years ago
|
interact(
|
||
|
ctx,
|
||
|
Paginated([page1, Confirm(page2)]),
|
||
|
"modify_output",
|
||
|
ButtonRequestType.ConfirmOutput,
|
||
|
)
|
||
3 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
async def confirm_modify_fee(
|
||
4 years ago
|
ctx: wire.GenericContext,
|
||
|
sign: int,
|
||
|
user_fee_change: str,
|
||
|
total_fee_new: str,
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
text = Text("Modify fee", ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||
4 years ago
|
if sign == 0:
|
||
3 years ago
|
text.normal("Your fee did not change.\n")
|
||
4 years ago
|
else:
|
||
|
if sign < 0:
|
||
3 years ago
|
text.normal("Decrease your fee by:\n")
|
||
4 years ago
|
else:
|
||
3 years ago
|
text.normal("Increase your fee by:\n")
|
||
4 years ago
|
text.bold(user_fee_change)
|
||
3 years ago
|
text.br()
|
||
4 years ago
|
text.br_half()
|
||
3 years ago
|
text.normal("Transaction fee:\n")
|
||
4 years ago
|
text.bold(total_fee_new)
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(ctx, HoldToConfirm(text), "modify_fee", ButtonRequestType.SignTx)
|
||
3 years ago
|
)
|
||
3 years ago
|
|
||
|
|
||
3 years ago
|
async def confirm_coinjoin(
|
||
3 years ago
|
ctx: wire.GenericContext, fee_per_anonymity: str | None, total_fee: str
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
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)
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(ctx, HoldToConfirm(text), "coinjoin_final", ButtonRequestType.Other)
|
||
3 years ago
|
)
|
||
|
|
||
|
|
||
3 years ago
|
# TODO cleanup @ redesign
|
||
|
async def confirm_sign_identity(
|
||
3 years ago
|
ctx: wire.GenericContext, proto: str, identity: str, challenge_visual: str | None
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
text = Text("Sign %s" % proto, new_lines=False)
|
||
3 years ago
|
if challenge_visual:
|
||
3 years ago
|
text.normal(challenge_visual)
|
||
|
text.br()
|
||
|
text.mono(*chunks_intersperse(identity, 18))
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(ctx, Confirm(text), "sign_identity", ButtonRequestType.Other)
|
||
3 years ago
|
)
|
||
3 years ago
|
|
||
|
|
||
|
async def confirm_signverify(
|
||
3 years ago
|
ctx: wire.GenericContext, coin: str, message: str, address: str | None = None
|
||
3 years ago
|
) -> None:
|
||
3 years ago
|
if address:
|
||
|
header = "Verify {} message".format(coin)
|
||
|
font = ui.MONO
|
||
|
br_type = "verify_message"
|
||
|
|
||
3 years ago
|
text = Text(header, new_lines=False)
|
||
|
text.bold("Confirm address:\n")
|
||
3 years ago
|
text.mono(*_split_address(address))
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(ctx, Confirm(text), br_type, ButtonRequestType.Other)
|
||
|
)
|
||
3 years ago
|
else:
|
||
|
header = "Sign {} message".format(coin)
|
||
|
font = ui.NORMAL
|
||
|
br_type = "sign_message"
|
||
|
|
||
3 years ago
|
await raise_if_cancelled(
|
||
|
interact(
|
||
3 years ago
|
ctx,
|
||
|
paginate_text(message, header, font=font),
|
||
|
br_type,
|
||
|
ButtonRequestType.Other,
|
||
|
)
|
||
|
)
|
||
3 years ago
|
|
||
|
|
||
|
# TODO cleanup @ redesign
|
||
|
async def confirm_timebounds_stellar(
|
||
|
ctx: wire.GenericContext, start: int, end: int
|
||
|
) -> None:
|
||
|
text = Text("Confirm timebounds", ui.ICON_SEND, ui.GREEN)
|
||
|
text.bold("Valid from (UTC):")
|
||
|
if start:
|
||
|
text.normal(str(start))
|
||
|
else:
|
||
|
text.mono("[no restriction]")
|
||
|
|
||
|
text.bold("Valid to (UTC):")
|
||
|
if end:
|
||
|
text.normal(str(end))
|
||
|
else:
|
||
|
text.mono("[no restriction]")
|
||
|
|
||
|
await raise_if_cancelled(
|
||
|
interact(
|
||
|
ctx, Confirm(text), "confirm_timebounds", ButtonRequestType.ConfirmOutput
|
||
|
)
|
||
|
)
|
||
3 years ago
|
|
||
|
|
||
3 years ago
|
# TODO cleanup @ redesign
|
||
|
async def confirm_transfer_binance(
|
||
|
ctx: wire.GenericContext, inputs_outputs: Sequence[Tuple[str, str, str]]
|
||
|
) -> None:
|
||
|
pages: list[ui.Component] = []
|
||
|
for title, amount, address in inputs_outputs:
|
||
|
coin_page = Text(title, ui.ICON_SEND, icon_color=ui.GREEN, new_lines=False)
|
||
|
coin_page.bold(amount)
|
||
|
coin_page.normal("\nto\n")
|
||
|
coin_page.mono(*_split_address(address))
|
||
|
pages.append(coin_page)
|
||
|
|
||
|
pages[-1] = HoldToConfirm(pages[-1])
|
||
|
|
||
|
await raise_if_cancelled(
|
||
|
interact(
|
||
|
ctx, Paginated(pages), "confirm_transfer", ButtonRequestType.ConfirmOutput
|
||
|
)
|
||
|
)
|
||
3 years ago
|
|
||
|
|
||
|
async def show_popup(
|
||
|
title: str,
|
||
|
description: str,
|
||
|
subtitle: Optional[str] = None,
|
||
|
description_param: str = "",
|
||
|
timeout_ms: int = 3000,
|
||
|
) -> None:
|
||
|
text = Text(title, ui.ICON_WRONG, ui.RED)
|
||
|
if subtitle is not None:
|
||
|
text.bold(subtitle)
|
||
|
text.br_half()
|
||
|
text.format_parametrized(description, description_param)
|
||
|
await Popup(text, timeout_ms)
|
||
|
|
||
|
|
||
|
async def confirm_webauthn(
|
||
|
ctx: Optional[wire.GenericContext],
|
||
|
info: ConfirmInfo,
|
||
|
pageable: Optional[Pageable] = None,
|
||
|
) -> bool:
|
||
|
if pageable is not None:
|
||
|
confirm: ui.Layout = ConfirmPageable(pageable, ConfirmContent(info))
|
||
|
else:
|
||
|
confirm = Confirm(ConfirmContent(info))
|
||
|
|
||
|
if ctx is None:
|
||
|
return is_confirmed(await confirm)
|
||
|
else:
|
||
|
return is_confirmed(
|
||
|
await interact(ctx, confirm, "confirm_webauthn", ButtonRequestType.Other)
|
||
|
)
|
||
|
|
||
|
|
||
|
async def confirm_webauthn_reset() -> bool:
|
||
|
text = Text("FIDO2 Reset", ui.ICON_CONFIG)
|
||
|
text.normal("Do you really want to")
|
||
|
text.bold("erase all credentials?")
|
||
|
return is_confirmed(await Confirm(text))
|
||
3 years ago
|
|
||
|
|
||
|
def draw_simple_text(title: str, description: str = "") -> None:
|
||
|
text = Text(title, ui.ICON_CONFIG, new_lines=False)
|
||
|
text.normal(description)
|
||
|
ui.draw_simple(text)
|
||
|
|
||
|
|
||
|
async def request_passphrase_on_device(ctx: wire.GenericContext, max_len: int) -> str:
|
||
|
await button_request(
|
||
|
ctx, "passphrase_device", code=ButtonRequestType.PassphraseEntry
|
||
|
)
|
||
|
|
||
|
keyboard = passphrase.PassphraseKeyboard("Enter passphrase", max_len)
|
||
|
result = await ctx.wait(keyboard)
|
||
|
if result is passphrase.CANCELLED:
|
||
|
raise wire.ActionCancelled("Passphrase entry cancelled")
|
||
|
|
||
|
assert isinstance(result, str)
|
||
|
return result
|
||
|
|
||
|
|
||
|
async def request_pin_on_device(
|
||
|
ctx: wire.GenericContext,
|
||
|
prompt: str,
|
||
|
attempts_remaining: int,
|
||
|
allow_cancel: bool,
|
||
|
) -> str:
|
||
|
await button_request(ctx, "pin_device", code=ButtonRequestType.PinEntry)
|
||
|
|
||
|
if attempts_remaining is None:
|
||
|
subprompt = None
|
||
|
elif attempts_remaining == 1:
|
||
|
subprompt = "This is your last attempt"
|
||
|
else:
|
||
|
subprompt = "%s attempts remaining" % attempts_remaining
|
||
|
|
||
|
dialog = pin.PinDialog(prompt, subprompt, allow_cancel)
|
||
|
while True:
|
||
|
result = await ctx.wait(dialog)
|
||
|
if result is pin.CANCELLED:
|
||
|
raise wire.PinCancelled
|
||
|
assert isinstance(result, str)
|
||
|
return result
|