|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
from trezor import io, log, loop, ui, wire, workflow
|
|
|
|
from trezor.enums import ButtonRequestType
|
|
|
|
|
|
|
|
import trezorui2
|
|
|
|
|
|
|
|
from ...constants.tt import MONO_ADDR_PER_LINE
|
|
|
|
from ..common import button_request, interact
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from typing import Any, Awaitable, Iterable, NoReturn, Sequence
|
|
|
|
|
|
|
|
from ..common import PropertyType, ExceptionType
|
|
|
|
|
|
|
|
|
|
|
|
class _RustLayout(ui.Layout):
|
|
|
|
# pylint: disable=super-init-not-called
|
|
|
|
def __init__(self, layout: Any):
|
|
|
|
self.layout = layout
|
|
|
|
self.timer = loop.Timer()
|
|
|
|
|
|
|
|
def set_timer(self, token: int, deadline: int) -> None:
|
|
|
|
self.timer.schedule(deadline, token)
|
|
|
|
|
|
|
|
if __debug__:
|
|
|
|
|
|
|
|
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
|
|
|
from apps.debug import confirm_signal, input_signal
|
|
|
|
|
|
|
|
return (
|
|
|
|
self.handle_timers(),
|
|
|
|
self.handle_input_and_rendering(),
|
|
|
|
confirm_signal(),
|
|
|
|
input_signal(),
|
|
|
|
)
|
|
|
|
|
|
|
|
def read_content(self) -> list[str]:
|
|
|
|
result = []
|
|
|
|
|
|
|
|
def callback(*args):
|
|
|
|
for arg in args:
|
|
|
|
result.append(str(arg))
|
|
|
|
|
|
|
|
self.layout.trace(callback)
|
|
|
|
result = " ".join(result).split("\n")
|
|
|
|
return result
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
|
|
|
return self.handle_timers(), self.handle_input_and_rendering()
|
|
|
|
|
|
|
|
def _before_render(self) -> None:
|
|
|
|
# Clear the screen of any leftovers.
|
|
|
|
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
|
|
|
|
ui.display.clear()
|
|
|
|
|
|
|
|
if __debug__ and self.should_notify_layout_change:
|
|
|
|
from apps.debug import notify_layout_change
|
|
|
|
|
|
|
|
# notify about change and do not notify again until next await.
|
|
|
|
# (handle_rendering might be called multiple times in a single await,
|
|
|
|
# because of the endless loop in __iter__)
|
|
|
|
self.should_notify_layout_change = False
|
|
|
|
notify_layout_change(self)
|
|
|
|
|
|
|
|
# Turn the brightness on again.
|
|
|
|
ui.backlight_fade(self.BACKLIGHT_LEVEL)
|
|
|
|
|
|
|
|
def handle_input_and_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator]
|
|
|
|
touch = loop.wait(io.TOUCH)
|
|
|
|
self._before_render()
|
|
|
|
self.layout.attach_timer_fn(self.set_timer)
|
|
|
|
self.layout.paint()
|
|
|
|
# self.layout.bounds()
|
|
|
|
while True:
|
|
|
|
# Using `yield` instead of `await` to avoid allocations.
|
|
|
|
event, x, y = yield touch
|
|
|
|
workflow.idle_timer.touch()
|
|
|
|
msg = None
|
|
|
|
if event in (io.TOUCH_START, io.TOUCH_MOVE, io.TOUCH_END):
|
|
|
|
msg = self.layout.touch_event(event, x, y)
|
|
|
|
self.layout.paint()
|
|
|
|
# self.layout.bounds()
|
|
|
|
if msg is not None:
|
|
|
|
raise ui.Result(msg)
|
|
|
|
|
|
|
|
def handle_timers(self) -> loop.Task: # type: ignore [awaitable-is-generator]
|
|
|
|
while True:
|
|
|
|
# Using `yield` instead of `await` to avoid allocations.
|
|
|
|
token = yield self.timer
|
|
|
|
msg = self.layout.timer(token)
|
|
|
|
self.layout.paint()
|
|
|
|
if msg is not None:
|
|
|
|
raise ui.Result(msg)
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_action(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
br_type: str,
|
|
|
|
title: str,
|
|
|
|
action: str | None = None,
|
|
|
|
description: str | None = None,
|
|
|
|
description_param: str | None = None,
|
|
|
|
description_param_font: int = ui.BOLD,
|
|
|
|
verb: str | bytes | None = "CONFIRM",
|
|
|
|
verb_cancel: str | bytes | None = None,
|
|
|
|
hold: bool = False,
|
|
|
|
hold_danger: bool = False,
|
|
|
|
icon: str | None = None,
|
|
|
|
icon_color: int | None = None,
|
|
|
|
reverse: bool = False,
|
|
|
|
larger_vspace: bool = False,
|
|
|
|
exc: ExceptionType = wire.ActionCancelled,
|
|
|
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
|
|
|
) -> None:
|
|
|
|
if isinstance(verb, bytes) or isinstance(verb_cancel, bytes):
|
|
|
|
raise NotImplementedError
|
|
|
|
elif isinstance(verb, str):
|
|
|
|
verb = verb.upper()
|
|
|
|
|
|
|
|
if description is not None and description_param is not None:
|
|
|
|
if description_param_font != ui.BOLD:
|
|
|
|
log.error(__name__, "confirm_action description_param_font not implemented")
|
|
|
|
description = description.format(description_param)
|
|
|
|
|
|
|
|
if hold:
|
|
|
|
log.error(__name__, "confirm_action hold not implemented")
|
|
|
|
|
|
|
|
if verb_cancel:
|
|
|
|
log.error(__name__, "confirm_action verb_cancel not implemented")
|
|
|
|
|
|
|
|
result = await interact(
|
|
|
|
ctx,
|
|
|
|
_RustLayout(
|
|
|
|
trezorui2.confirm_action(
|
|
|
|
title=title.upper(),
|
|
|
|
action=action,
|
|
|
|
description=description,
|
|
|
|
verb=verb,
|
|
|
|
hold=hold,
|
|
|
|
reverse=reverse,
|
|
|
|
)
|
|
|
|
),
|
|
|
|
br_type,
|
|
|
|
br_code,
|
|
|
|
)
|
|
|
|
if result is not trezorui2.CONFIRMED:
|
|
|
|
raise exc
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_reset_device(
|
|
|
|
ctx: wire.GenericContext, prompt: str, recovery: bool = False
|
|
|
|
) -> None:
|
|
|
|
return await confirm_action(
|
|
|
|
ctx,
|
|
|
|
"recover_device" if recovery else "setup_device",
|
|
|
|
"not implemented",
|
|
|
|
action="not implemented",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# TODO cleanup @ redesign
|
|
|
|
async def confirm_backup(ctx: wire.GenericContext) -> bool:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_path_warning(
|
|
|
|
ctx: wire.GenericContext, path: str, path_type: str = "Path"
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def show_xpub(
|
|
|
|
ctx: wire.GenericContext, xpub: str, title: str, cancel: str
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def show_address(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
address: str,
|
|
|
|
*,
|
|
|
|
case_sensitive: bool = True,
|
|
|
|
address_qr: str | None = None,
|
|
|
|
title: str = "Confirm address",
|
|
|
|
network: str | None = None,
|
|
|
|
multisig_index: int | None = None,
|
|
|
|
xpubs: Sequence[str] = (),
|
|
|
|
address_extra: str | None = None,
|
|
|
|
title_qr: str | None = None,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
def show_pubkey(
|
|
|
|
ctx: wire.Context, pubkey: str, title: str = "Confirm public key"
|
|
|
|
) -> Awaitable[None]:
|
|
|
|
return confirm_blob(
|
|
|
|
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: ButtonRequestType,
|
|
|
|
header: str,
|
|
|
|
subheader: str | None,
|
|
|
|
content: str,
|
|
|
|
button_confirm: str | None,
|
|
|
|
button_cancel: str | None,
|
|
|
|
icon: str,
|
|
|
|
icon_color: int,
|
|
|
|
exc: ExceptionType = wire.ActionCancelled,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def show_error_and_raise(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
br_type: str,
|
|
|
|
content: str,
|
|
|
|
header: str = "Error",
|
|
|
|
subheader: str | None = 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: str | None = None,
|
|
|
|
button: str = "Try again",
|
|
|
|
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
|
|
|
icon: str = ui.ICON_WRONG,
|
|
|
|
icon_color: int = ui.RED,
|
|
|
|
) -> 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=icon,
|
|
|
|
icon_color=icon_color,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def show_success(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
br_type: str,
|
|
|
|
content: str,
|
|
|
|
subheader: str | None = 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,
|
|
|
|
font_amount: int = ui.NORMAL, # TODO cleanup @ redesign
|
|
|
|
title: str = "Confirm sending",
|
|
|
|
subtitle: str | None = None, # TODO cleanup @ redesign
|
|
|
|
color_to: int = ui.FG, # TODO cleanup @ redesign
|
|
|
|
to_str: str = " to\n", # TODO cleanup @ redesign
|
|
|
|
to_paginated: bool = False, # TODO cleanup @ redesign
|
|
|
|
width: int = MONO_ADDR_PER_LINE,
|
|
|
|
width_paginated: int = MONO_ADDR_PER_LINE - 1,
|
|
|
|
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
|
|
|
icon: str = ui.ICON_SEND,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_payment_request(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
recipient_name: str,
|
|
|
|
amount: str,
|
|
|
|
memos: list[str],
|
|
|
|
) -> Any:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def should_show_more(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
title: str,
|
|
|
|
para: Iterable[tuple[int, str]],
|
|
|
|
button_text: str = "Show all",
|
|
|
|
br_type: str = "should_show_more",
|
|
|
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
|
|
|
icon: str = ui.ICON_DEFAULT,
|
|
|
|
icon_color: int = ui.ORANGE_ICON,
|
|
|
|
) -> bool:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_blob(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
br_type: str,
|
|
|
|
title: str,
|
|
|
|
data: bytes | str,
|
|
|
|
description: str | None = None,
|
|
|
|
hold: bool = False,
|
|
|
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
|
|
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
|
|
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
|
|
|
ask_pagination: bool = False,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
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]:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
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]:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_properties(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
br_type: str,
|
|
|
|
title: str,
|
|
|
|
props: Iterable[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:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_total(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
total_amount: str,
|
|
|
|
fee_amount: str,
|
|
|
|
fee_rate_amount: str | None = None,
|
|
|
|
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",
|
|
|
|
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_joint_total(
|
|
|
|
ctx: wire.GenericContext, spending_amount: str, total_amount: str
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_metadata(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
br_type: str,
|
|
|
|
title: str,
|
|
|
|
content: str,
|
|
|
|
param: str | None = None,
|
|
|
|
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
|
|
|
hide_continue: bool = False,
|
|
|
|
hold: bool = False,
|
|
|
|
param_font: int = ui.BOLD,
|
|
|
|
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
|
|
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
|
|
|
larger_vspace: bool = False, # TODO cleanup @ redesign
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_replacement(
|
|
|
|
ctx: wire.GenericContext, description: str, txid: str
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_modify_output(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
address: str,
|
|
|
|
sign: int,
|
|
|
|
amount_change: str,
|
|
|
|
amount_new: str,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_modify_fee(
|
|
|
|
ctx: wire.GenericContext,
|
|
|
|
sign: int,
|
|
|
|
user_fee_change: str,
|
|
|
|
total_fee_new: str,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_coinjoin(
|
|
|
|
ctx: wire.GenericContext, coin_name: str, max_rounds: int, max_fee_per_vbyte: str
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
# TODO cleanup @ redesign
|
|
|
|
async def confirm_sign_identity(
|
|
|
|
ctx: wire.GenericContext, proto: str, identity: str, challenge_visual: str | None
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_signverify(
|
|
|
|
ctx: wire.GenericContext, coin: str, message: str, address: str, verify: bool
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
async def show_popup(
|
|
|
|
title: str,
|
|
|
|
description: str,
|
|
|
|
subtitle: str | None = None,
|
|
|
|
description_param: str = "",
|
|
|
|
timeout_ms: int = 3000,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
def draw_simple_text(title: str, description: str = "") -> None:
|
|
|
|
log.error(__name__, "draw_simple_text not implemented")
|
|
|
|
|
|
|
|
|
|
|
|
async def request_passphrase_on_device(ctx: wire.GenericContext, max_len: int) -> str:
|
|
|
|
await button_request(
|
|
|
|
ctx, "passphrase_device", code=ButtonRequestType.PassphraseEntry
|
|
|
|
)
|
|
|
|
|
|
|
|
keyboard = _RustLayout(
|
|
|
|
trezorui2.request_passphrase(prompt="Enter passphrase", max_len=max_len)
|
|
|
|
)
|
|
|
|
result = await ctx.wait(keyboard)
|
|
|
|
if result is trezorui2.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 | None,
|
|
|
|
allow_cancel: bool,
|
|
|
|
) -> str:
|
|
|
|
await button_request(ctx, "pin_device", code=ButtonRequestType.PinEntry)
|
|
|
|
|
|
|
|
warning = "Wrong PIN" if "Wrong" in prompt else None
|
|
|
|
|
|
|
|
if attempts_remaining is None:
|
|
|
|
subprompt = ""
|
|
|
|
elif attempts_remaining == 1:
|
|
|
|
prompt = "Enter PIN"
|
|
|
|
subprompt = "Last attempt"
|
|
|
|
else:
|
|
|
|
prompt = "Enter PIN"
|
|
|
|
subprompt = f"{attempts_remaining} tries left"
|
|
|
|
|
|
|
|
dialog = _RustLayout(
|
|
|
|
trezorui2.request_pin(
|
|
|
|
prompt=prompt,
|
|
|
|
subprompt=subprompt,
|
|
|
|
allow_cancel=allow_cancel,
|
|
|
|
warning=warning,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
while True:
|
|
|
|
result = await ctx.wait(dialog)
|
|
|
|
if result is trezorui2.CANCELLED:
|
|
|
|
raise wire.PinCancelled
|
|
|
|
assert isinstance(result, str)
|
|
|
|
return result
|