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.
293 lines
7.5 KiB
293 lines
7.5 KiB
from typing import TYPE_CHECKING
|
|
|
|
from trezor import ui
|
|
from trezor.enums import ButtonRequestType
|
|
from trezor.strings import format_plural
|
|
from trezor.ui.layouts import (
|
|
confirm_blob,
|
|
confirm_ethereum_tx,
|
|
confirm_text,
|
|
should_show_more,
|
|
)
|
|
|
|
from .helpers import address_from_bytes, decode_typed_data
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Awaitable, Iterable
|
|
|
|
from trezor.messages import (
|
|
EthereumFieldType,
|
|
EthereumNetworkInfo,
|
|
EthereumStructMember,
|
|
EthereumTokenInfo,
|
|
)
|
|
|
|
|
|
async def require_confirm_smart_contract(
|
|
func_name: str | bytes, func_args: list[tuple[str, str | bytes]]
|
|
):
|
|
from trezor.ui.layouts import confirm_properties
|
|
|
|
await confirm_properties(
|
|
"confirm_smart_contract", "Smart contract", [("Function:", func_name)]
|
|
)
|
|
await confirm_properties("confirm_smart_contract", "Smart contract", func_args)
|
|
|
|
|
|
async def require_confirm_tx(
|
|
to_bytes: bytes,
|
|
value: int,
|
|
gas_price: int,
|
|
gas_limit: int,
|
|
network: EthereumNetworkInfo,
|
|
token: EthereumTokenInfo | None,
|
|
chunkify: bool,
|
|
) -> None:
|
|
if to_bytes:
|
|
to_str = address_from_bytes(to_bytes, network)
|
|
else:
|
|
to_str = "new contract?"
|
|
chunkify = False
|
|
|
|
total_amount = format_ethereum_amount(value, token, network)
|
|
maximum_fee = format_ethereum_amount(gas_price * gas_limit, None, network)
|
|
gas_limit_str = f"{gas_limit} units"
|
|
gas_price_str = format_ethereum_amount(
|
|
gas_price, None, network, force_unit_gwei=True
|
|
)
|
|
|
|
items = (
|
|
("Gas limit:", gas_limit_str),
|
|
("Gas price:", gas_price_str),
|
|
)
|
|
|
|
await confirm_ethereum_tx(
|
|
to_str, total_amount, maximum_fee, items, chunkify=chunkify
|
|
)
|
|
|
|
|
|
async def require_confirm_tx_eip1559(
|
|
to_bytes: bytes,
|
|
value: int,
|
|
max_gas_fee: int,
|
|
max_priority_fee: int,
|
|
gas_limit: int,
|
|
network: EthereumNetworkInfo,
|
|
token: EthereumTokenInfo | None,
|
|
chunkify: bool,
|
|
) -> None:
|
|
if to_bytes:
|
|
to_str = address_from_bytes(to_bytes, network)
|
|
else:
|
|
to_str = "new contract?"
|
|
chunkify = False
|
|
|
|
total_amount = format_ethereum_amount(value, token, network)
|
|
maximum_fee = format_ethereum_amount(max_gas_fee * gas_limit, None, network)
|
|
gas_limit_str = f"{gas_limit} units"
|
|
max_gas_fee_str = format_ethereum_amount(
|
|
max_gas_fee, None, network, force_unit_gwei=True
|
|
)
|
|
max_priority_fee_str = format_ethereum_amount(
|
|
max_priority_fee, None, network, force_unit_gwei=True
|
|
)
|
|
|
|
items = (
|
|
("Gas limit:", gas_limit_str),
|
|
("Max gas price:", max_gas_fee_str),
|
|
("Priority fee:", max_priority_fee_str),
|
|
)
|
|
|
|
await confirm_ethereum_tx(
|
|
to_str, total_amount, maximum_fee, items, chunkify=chunkify
|
|
)
|
|
|
|
|
|
def require_confirm_unknown_token(address_bytes: bytes) -> Awaitable[None]:
|
|
from ubinascii import hexlify
|
|
|
|
from trezor.ui.layouts import confirm_address
|
|
|
|
contract_address_hex = "0x" + hexlify(address_bytes).decode()
|
|
return confirm_address(
|
|
"Unknown token",
|
|
contract_address_hex,
|
|
"Contract:",
|
|
"unknown_token",
|
|
br_code=ButtonRequestType.SignTx,
|
|
)
|
|
|
|
|
|
def require_confirm_address(address_bytes: bytes) -> Awaitable[None]:
|
|
from ubinascii import hexlify
|
|
|
|
from trezor.ui.layouts import confirm_address
|
|
|
|
address_hex = "0x" + hexlify(address_bytes).decode()
|
|
return confirm_address(
|
|
"Signing address",
|
|
address_hex,
|
|
br_code=ButtonRequestType.SignTx,
|
|
)
|
|
|
|
|
|
def require_confirm_data(data: bytes, data_total: int) -> Awaitable[None]:
|
|
return confirm_blob(
|
|
"confirm_data",
|
|
"Confirm data",
|
|
data,
|
|
f"Size: {data_total} bytes",
|
|
br_code=ButtonRequestType.SignTx,
|
|
ask_pagination=True,
|
|
)
|
|
|
|
|
|
async def confirm_typed_data_final() -> None:
|
|
from trezor.ui.layouts import confirm_action
|
|
|
|
await confirm_action(
|
|
"confirm_typed_data_final",
|
|
"Confirm typed data",
|
|
"Really sign EIP-712 typed data?",
|
|
verb="Hold to confirm",
|
|
hold=True,
|
|
)
|
|
|
|
|
|
def confirm_empty_typed_message() -> Awaitable[None]:
|
|
return confirm_text(
|
|
"confirm_empty_typed_message",
|
|
"Confirm message",
|
|
"",
|
|
"No message field",
|
|
)
|
|
|
|
|
|
async def should_show_domain(name: bytes, version: bytes) -> bool:
|
|
domain_name = decode_typed_data(name, "string")
|
|
domain_version = decode_typed_data(version, "string")
|
|
|
|
para = (
|
|
(ui.NORMAL, "Name and version"),
|
|
(ui.DEMIBOLD, domain_name),
|
|
(ui.DEMIBOLD, domain_version),
|
|
)
|
|
return await should_show_more(
|
|
"Confirm domain",
|
|
para,
|
|
"Show full domain",
|
|
"should_show_domain",
|
|
)
|
|
|
|
|
|
async def should_show_struct(
|
|
description: str,
|
|
data_members: list[EthereumStructMember],
|
|
title: str = "Confirm struct",
|
|
button_text: str = "Show full struct",
|
|
) -> bool:
|
|
para = (
|
|
(ui.DEMIBOLD, description),
|
|
(
|
|
ui.NORMAL,
|
|
format_plural("Contains {count} {plural}", len(data_members), "key"),
|
|
),
|
|
(ui.NORMAL, ", ".join(field.name for field in data_members)),
|
|
)
|
|
return await should_show_more(
|
|
title,
|
|
para,
|
|
button_text,
|
|
"should_show_struct",
|
|
)
|
|
|
|
|
|
async def should_show_array(
|
|
parent_objects: Iterable[str],
|
|
data_type: str,
|
|
size: int,
|
|
) -> bool:
|
|
para = ((ui.NORMAL, format_plural("Array of {count} {plural}", size, data_type)),)
|
|
return await should_show_more(
|
|
limit_str(".".join(parent_objects)),
|
|
para,
|
|
"Show full array",
|
|
"should_show_array",
|
|
)
|
|
|
|
|
|
async def confirm_typed_value(
|
|
name: str,
|
|
value: bytes,
|
|
parent_objects: list[str],
|
|
field: EthereumFieldType,
|
|
array_index: int | None = None,
|
|
) -> None:
|
|
from trezor.enums import EthereumDataType
|
|
|
|
from .helpers import get_type_name
|
|
|
|
type_name = get_type_name(field)
|
|
|
|
if array_index is not None:
|
|
title = limit_str(".".join(parent_objects + [name]))
|
|
description = f"[{array_index}] ({type_name})"
|
|
else:
|
|
title = limit_str(".".join(parent_objects))
|
|
description = f"{name} ({type_name})"
|
|
|
|
data = decode_typed_data(value, type_name)
|
|
|
|
if field.data_type in (EthereumDataType.ADDRESS, EthereumDataType.BYTES):
|
|
await confirm_blob(
|
|
"confirm_typed_value",
|
|
title,
|
|
data,
|
|
description,
|
|
ask_pagination=True,
|
|
)
|
|
else:
|
|
await confirm_text(
|
|
"confirm_typed_value",
|
|
title,
|
|
data,
|
|
description,
|
|
)
|
|
|
|
|
|
def format_ethereum_amount(
|
|
value: int,
|
|
token: EthereumTokenInfo | None,
|
|
network: EthereumNetworkInfo,
|
|
force_unit_gwei: bool = False,
|
|
) -> str:
|
|
from trezor.strings import format_amount
|
|
|
|
if token:
|
|
suffix = token.symbol
|
|
decimals = token.decimals
|
|
else:
|
|
suffix = network.symbol
|
|
decimals = 18
|
|
|
|
if force_unit_gwei:
|
|
assert token is None
|
|
assert decimals >= 9
|
|
decimals = decimals - 9
|
|
suffix = "Gwei"
|
|
elif decimals > 9 and value < 10 ** (decimals - 9):
|
|
# Don't want to display wei values for tokens with small decimal numbers
|
|
suffix = "Wei " + suffix
|
|
decimals = 0
|
|
|
|
amount = format_amount(value, decimals)
|
|
return f"{amount} {suffix}"
|
|
|
|
|
|
def limit_str(s: str, limit: int = 16) -> str:
|
|
"""Shortens string to show the last <limit> characters."""
|
|
if len(s) <= limit + 2:
|
|
return s
|
|
|
|
return ".." + s[-limit:]
|