mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-05-08 09:58:46 +00:00
517 lines
16 KiB
Python
517 lines
16 KiB
Python
from typing import TYPE_CHECKING
|
|
|
|
from trezor import TR
|
|
from trezor.crypto import base58
|
|
from trezor.enums import ButtonRequestType
|
|
from trezor.strings import format_amount
|
|
from trezor.ui.layouts import (
|
|
confirm_address,
|
|
confirm_metadata,
|
|
confirm_properties,
|
|
confirm_solana_recipient,
|
|
confirm_solana_tx,
|
|
confirm_value,
|
|
show_danger,
|
|
show_warning,
|
|
)
|
|
|
|
from apps.common.paths import address_n_to_str
|
|
|
|
from .types import AddressType
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Sequence
|
|
|
|
from trezor.messages import SolanaTokenInfo
|
|
|
|
from .definitions import Definitions
|
|
from .transaction import Fee
|
|
from .transaction.instructions import Instruction, SystemProgramTransferInstruction
|
|
from .types import AddressReference
|
|
|
|
|
|
def _format_path(path: list[int]) -> str:
|
|
from micropython import const
|
|
|
|
from apps.common.paths import unharden
|
|
|
|
if len(path) < 4:
|
|
return address_n_to_str(path)
|
|
|
|
ACCOUNT_PATH_INDEX = const(3)
|
|
account_index = path[ACCOUNT_PATH_INDEX]
|
|
return f"Solana #{unharden(account_index) + 1}"
|
|
|
|
|
|
def _get_address_reference_props(
|
|
address: AddressReference, display_name: str
|
|
) -> Sequence[tuple[str, str]]:
|
|
return (
|
|
(TR.solana__is_provided_via_lookup_table_template.format(display_name), ""),
|
|
(f"{TR.solana__lookup_table_address}:", base58.encode(address[0])),
|
|
(f"{TR.solana__account_index}:", f"{address[1]}"),
|
|
)
|
|
|
|
|
|
async def confirm_instruction(
|
|
instruction: Instruction,
|
|
instructions_count: int,
|
|
instruction_index: int,
|
|
signer_path: list[int],
|
|
signer_public_key: bytes,
|
|
definitions: Definitions,
|
|
) -> None:
|
|
instruction_title = (
|
|
f"{instruction_index}/{instructions_count}: {instruction.ui_name}"
|
|
)
|
|
|
|
if instruction.is_deprecated_warning is not None:
|
|
await confirm_metadata(
|
|
"confirm_deprecated_warning",
|
|
instruction_title,
|
|
instruction.is_deprecated_warning,
|
|
br_code=ButtonRequestType.Other,
|
|
)
|
|
|
|
if instruction.multisig_signers:
|
|
await confirm_metadata(
|
|
"confirm_multisig",
|
|
TR.solana__confirm_multisig,
|
|
TR.solana__instruction_is_multisig,
|
|
br_code=ButtonRequestType.Other,
|
|
)
|
|
|
|
for ui_property in instruction.ui_properties:
|
|
if ui_property.parameter is not None:
|
|
property_template = instruction.get_property_template(ui_property.parameter)
|
|
value = instruction.parsed_data[ui_property.parameter]
|
|
|
|
if property_template.optional and value is None:
|
|
continue
|
|
|
|
if ui_property.default_value_to_hide == value:
|
|
continue
|
|
|
|
if (
|
|
property_template.is_pubkey()
|
|
and ui_property.default_value_to_hide == "signer"
|
|
and signer_public_key == value
|
|
):
|
|
continue
|
|
|
|
args = []
|
|
for arg in property_template.args:
|
|
if arg == "#definitions":
|
|
args.append(definitions)
|
|
elif arg in instruction.parsed_data:
|
|
args.append(instruction.parsed_data[arg])
|
|
elif arg in instruction.parsed_accounts:
|
|
args.append(instruction.parsed_accounts[arg][0])
|
|
else:
|
|
raise ValueError # Invalid property template
|
|
|
|
await confirm_properties(
|
|
"confirm_instruction",
|
|
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
|
|
(
|
|
(
|
|
ui_property.display_name,
|
|
property_template.format(value, *args),
|
|
),
|
|
),
|
|
)
|
|
elif ui_property.account is not None:
|
|
# optional account, skip if not present
|
|
if ui_property.account not in instruction.parsed_accounts:
|
|
continue
|
|
|
|
account_value = instruction.parsed_accounts[ui_property.account]
|
|
|
|
if (
|
|
ui_property.default_value_to_hide == "signer"
|
|
and signer_public_key == account_value[0]
|
|
):
|
|
continue
|
|
|
|
account_data: list[tuple[str, str]] = []
|
|
# account included in the transaction directly
|
|
if len(account_value) == 2:
|
|
account_description = f"{base58.encode(account_value[0])}"
|
|
if definitions.has_token(account_value[0]):
|
|
token = definitions.get_token(account_value[0])
|
|
account_description = f"{token.name}\n{account_description}"
|
|
elif account_value[0] == signer_public_key:
|
|
account_description = f"{account_description} ({TR.words__signer})"
|
|
|
|
account_data.append((ui_property.display_name, account_description))
|
|
# lookup table address reference
|
|
elif len(account_value) == 3:
|
|
account_data += _get_address_reference_props(
|
|
account_value, ui_property.display_name
|
|
)
|
|
else:
|
|
raise ValueError # Invalid account value
|
|
|
|
await confirm_properties(
|
|
"confirm_instruction",
|
|
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
|
|
account_data,
|
|
)
|
|
else:
|
|
raise ValueError # Invalid ui property
|
|
|
|
if instruction.multisig_signers:
|
|
signers: list[tuple[str, str]] = []
|
|
for i, multisig_signer in enumerate(instruction.multisig_signers, 1):
|
|
multisig_signer_public_key = multisig_signer[0]
|
|
|
|
path_str = ""
|
|
if multisig_signer_public_key == signer_public_key:
|
|
path_str = f" ({address_n_to_str(signer_path)})"
|
|
|
|
signers.append(
|
|
(
|
|
f"{TR.words__signer} {i}{path_str}:",
|
|
base58.encode(multisig_signer[0]),
|
|
)
|
|
)
|
|
|
|
await confirm_properties(
|
|
"confirm_instruction",
|
|
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
|
|
signers,
|
|
)
|
|
|
|
|
|
def get_address_type(address_type: AddressType) -> str:
|
|
if address_type == AddressType.AddressSig:
|
|
return f"({TR.words__writable}, {TR.words__signer})"
|
|
if address_type == AddressType.AddressSigReadOnly:
|
|
return f"({TR.words__signer})"
|
|
if address_type == AddressType.AddressReadOnly:
|
|
return ""
|
|
if address_type == AddressType.AddressRw:
|
|
return f"({TR.words__writable})"
|
|
raise ValueError # Invalid address type
|
|
|
|
|
|
async def confirm_unsupported_instruction_details(
|
|
instruction: Instruction,
|
|
title: str,
|
|
signer_path: list[int],
|
|
signer_public_key: bytes,
|
|
) -> None:
|
|
from trezor.ui.layouts import confirm_properties, should_show_more
|
|
|
|
should_show_instruction_details = await should_show_more(
|
|
title,
|
|
(
|
|
(
|
|
TR.solana__instruction_accounts_template.format(
|
|
len(instruction.accounts), len(instruction.instruction_data)
|
|
),
|
|
False,
|
|
),
|
|
),
|
|
TR.buttons__show_details,
|
|
confirm=TR.buttons__continue,
|
|
)
|
|
|
|
if should_show_instruction_details:
|
|
await confirm_properties(
|
|
"instruction_data",
|
|
title,
|
|
((f"{TR.solana__instruction_data}:", bytes(instruction.instruction_data)),),
|
|
)
|
|
|
|
accounts = []
|
|
for i, account in enumerate(instruction.accounts, 1):
|
|
if len(account) == 2:
|
|
account_public_key = account[0]
|
|
address_type = get_address_type(account[1])
|
|
|
|
path_str = ""
|
|
if account_public_key == signer_public_key:
|
|
path_str = f" ({address_n_to_str(signer_path)})"
|
|
|
|
accounts.append(
|
|
(
|
|
f"{TR.words__account} {i}{path_str} {address_type}:",
|
|
base58.encode(account_public_key),
|
|
)
|
|
)
|
|
elif len(account) == 3:
|
|
address_type = get_address_type(account[2])
|
|
accounts += _get_address_reference_props(
|
|
account, f"{TR.words__account} {i} {address_type}"
|
|
)
|
|
else:
|
|
raise ValueError # Invalid account value
|
|
|
|
await confirm_properties(
|
|
"accounts",
|
|
title,
|
|
accounts,
|
|
)
|
|
|
|
|
|
async def confirm_unsupported_instruction_confirm(
|
|
instruction: Instruction,
|
|
instructions_count: int,
|
|
instruction_index: int,
|
|
signer_path: list[int],
|
|
signer_public_key: bytes,
|
|
) -> None:
|
|
formatted_instruction_id = (
|
|
instruction.instruction_id if instruction.instruction_id is not None else "N/A"
|
|
)
|
|
title = f"{instruction_index}/{instructions_count}: {instruction.ui_name}: instruction id ({formatted_instruction_id})"
|
|
|
|
return await confirm_unsupported_instruction_details(
|
|
instruction, title, signer_path, signer_public_key
|
|
)
|
|
|
|
|
|
async def confirm_unsupported_program_confirm(
|
|
instruction: Instruction,
|
|
instructions_count: int,
|
|
instruction_index: int,
|
|
signer_path: list[int],
|
|
signer_public_key: bytes,
|
|
) -> None:
|
|
title = f"{instruction_index}/{instructions_count}: {instruction.ui_name}"
|
|
|
|
return await confirm_unsupported_instruction_details(
|
|
instruction, title, signer_path, signer_public_key
|
|
)
|
|
|
|
|
|
async def confirm_system_transfer(
|
|
transfer_instruction: SystemProgramTransferInstruction,
|
|
fee: Fee,
|
|
blockhash: bytes,
|
|
) -> None:
|
|
await confirm_solana_recipient(
|
|
recipient=base58.encode(transfer_instruction.recipient_account[0]),
|
|
title=TR.words__recipient,
|
|
)
|
|
|
|
await confirm_custom_transaction(
|
|
transfer_instruction.lamports, 9, "SOL", fee, blockhash
|
|
)
|
|
|
|
|
|
async def confirm_token_transfer(
|
|
destination_account: bytes,
|
|
token_account: bytes,
|
|
token: SolanaTokenInfo,
|
|
amount: int,
|
|
decimals: int,
|
|
fee: Fee,
|
|
blockhash: bytes,
|
|
) -> None:
|
|
items = []
|
|
if token_account != destination_account:
|
|
items.append(
|
|
(f"{TR.solana__associated_token_account}:", base58.encode(token_account))
|
|
)
|
|
|
|
await confirm_solana_recipient(
|
|
recipient=base58.encode(destination_account),
|
|
title=TR.words__recipient,
|
|
items=items,
|
|
)
|
|
|
|
value = token.name + "\n" + base58.encode(token.mint)
|
|
|
|
await confirm_value(
|
|
title=TR.solana__title_token,
|
|
value=value,
|
|
description="",
|
|
br_name="confirm_token_address",
|
|
br_code=ButtonRequestType.ConfirmOutput,
|
|
verb=TR.buttons__continue,
|
|
)
|
|
|
|
await confirm_custom_transaction(amount, decimals, token.symbol, fee, blockhash)
|
|
|
|
|
|
def _fee_ui_info(
|
|
has_unsupported_instructions: bool, fee: Fee
|
|
) -> tuple[str, str, list[tuple[str, str]]]:
|
|
fee_items: list[tuple[str, str]] = []
|
|
if has_unsupported_instructions:
|
|
fee_title = f"{TR.solana__max_fees_rent}:"
|
|
fee_str = TR.words__unknown
|
|
else:
|
|
fee_str = f"{format_amount(fee.total, 9)} SOL"
|
|
base_fee_str = f"{format_amount(fee.base, 9)} SOL"
|
|
fee_items.append((TR.solana__base_fee, base_fee_str))
|
|
if fee.priority:
|
|
priority_fee_str = f"{format_amount(fee.priority, 9)} SOL"
|
|
fee_items.append((TR.solana__priority_fee, priority_fee_str))
|
|
if fee.rent:
|
|
fee_title = f"{TR.solana__max_fees_rent}:"
|
|
rent_str = f"{format_amount(fee.rent, 9)} SOL"
|
|
fee_items.append((TR.solana__max_rent_fee, rent_str))
|
|
else:
|
|
fee_title = f"{TR.solana__transaction_fee}:"
|
|
return fee_title, fee_str, fee_items
|
|
|
|
|
|
async def confirm_custom_transaction(
|
|
amount: int,
|
|
decimals: int,
|
|
unit: str,
|
|
fee: Fee,
|
|
blockhash: bytes,
|
|
) -> None:
|
|
fee_title, fee_str, fee_items = _fee_ui_info(False, fee)
|
|
fee_items.append((TR.words__blockhash, base58.encode(blockhash)))
|
|
await confirm_solana_tx(
|
|
amount=f"{format_amount(amount, decimals)} {unit}",
|
|
fee=fee_str,
|
|
fee_title=fee_title,
|
|
items=fee_items,
|
|
)
|
|
|
|
|
|
def _fee_details(fee: Fee) -> tuple[tuple[str, str], ...]:
|
|
return (
|
|
(TR.solana__base_fee, f"{format_amount(fee.base, 9)} SOL"),
|
|
(TR.solana__priority_fee, f"{format_amount(fee.priority, 9)} SOL"),
|
|
)
|
|
|
|
|
|
def _fee_summary(fee: Fee) -> tuple[str, str]:
|
|
return (f"{TR.solana__expected_fee}:", f"{format_amount(fee.total, 9)} SOL")
|
|
|
|
|
|
async def confirm_stake_withdrawer(withdrawer_account: bytes) -> None:
|
|
await show_danger(
|
|
title=TR.words__important,
|
|
content=TR.solana__stake_withdrawal_warning,
|
|
verb_cancel=TR.words__cancel_and_exit,
|
|
br_name="confirm_stake_warning",
|
|
)
|
|
await confirm_address(
|
|
title=TR.solana__stake_withdrawal_warning_title,
|
|
address=base58.encode(withdrawer_account),
|
|
br_name="confirm_stake_warning_address",
|
|
)
|
|
|
|
|
|
async def confirm_claim_recipient(recipient_account: bytes) -> None:
|
|
await show_warning(
|
|
content=TR.solana__claim_recipient_warning,
|
|
br_name="confirm_claim_warning",
|
|
)
|
|
await confirm_address(
|
|
title=TR.address_details__title_receive_address,
|
|
address=base58.encode(recipient_account),
|
|
br_name="confirm_claim_warning_address",
|
|
)
|
|
|
|
|
|
async def confirm_stake_transaction(
|
|
fee: Fee,
|
|
signer_path: list[int],
|
|
blockhash: bytes,
|
|
create: Instruction,
|
|
delegate: Instruction,
|
|
) -> None:
|
|
from trezor.ui.layouts import confirm_solana_staking_tx
|
|
|
|
vote_account = base58.encode(delegate.vote_account[0])
|
|
KNOWN_ACCOUNTS = {
|
|
"9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF": "Everstake",
|
|
}
|
|
vote_account_label = KNOWN_ACCOUNTS.get(vote_account)
|
|
if vote_account_label is None:
|
|
description = TR.solana__stake_question
|
|
vote_account_label = vote_account
|
|
else:
|
|
description = TR.solana__stake_on_question.format(vote_account_label)
|
|
vote_account_label = ""
|
|
|
|
await confirm_solana_staking_tx(
|
|
title=TR.solana__stake,
|
|
description=description,
|
|
account=_format_path(signer_path),
|
|
account_path=address_n_to_str(signer_path),
|
|
vote_account=vote_account_label,
|
|
stake_item=(
|
|
TR.solana__stake_account,
|
|
base58.encode(delegate.initialized_stake_account[0]),
|
|
),
|
|
amount_item=(
|
|
f"{TR.words__amount}:",
|
|
f"{format_amount(create.lamports, 9)} SOL",
|
|
),
|
|
fee_item=_fee_summary(fee),
|
|
fee_details=_fee_details(fee),
|
|
blockhash_item=(TR.words__blockhash, base58.encode(blockhash)),
|
|
)
|
|
|
|
|
|
async def confirm_unstake_transaction(
|
|
fee: Fee,
|
|
signer_path: list[int],
|
|
blockhash: bytes,
|
|
) -> None:
|
|
from trezor.ui.layouts import confirm_solana_staking_tx
|
|
|
|
await confirm_solana_staking_tx(
|
|
title=TR.solana__unstake,
|
|
description=TR.solana__unstake_question,
|
|
account=_format_path(signer_path),
|
|
account_path=address_n_to_str(signer_path),
|
|
vote_account="",
|
|
stake_item=None,
|
|
amount_item=_fee_summary(fee),
|
|
fee_item=("", ""),
|
|
fee_details=_fee_details(fee),
|
|
blockhash_item=(TR.words__blockhash, base58.encode(blockhash)),
|
|
)
|
|
|
|
|
|
async def confirm_claim_transaction(
|
|
fee: Fee,
|
|
signer_path: list[int],
|
|
blockhash: bytes,
|
|
total_amount: int,
|
|
) -> None:
|
|
from trezor.ui.layouts import confirm_solana_staking_tx
|
|
|
|
await confirm_solana_staking_tx(
|
|
title=TR.solana__claim,
|
|
description=TR.solana__claim_question,
|
|
account=_format_path(signer_path),
|
|
account_path=address_n_to_str(signer_path),
|
|
vote_account="",
|
|
stake_item=None,
|
|
amount_item=(
|
|
f"{TR.words__amount}:",
|
|
f"{format_amount(total_amount, 9)} SOL",
|
|
),
|
|
fee_item=_fee_summary(fee),
|
|
fee_details=_fee_details(fee),
|
|
blockhash_item=(TR.words__blockhash, base58.encode(blockhash)),
|
|
)
|
|
|
|
|
|
async def confirm_transaction(
|
|
blockhash: bytes,
|
|
fee: Fee,
|
|
has_unsupported_instructions: bool,
|
|
) -> None:
|
|
fee_title, fee_str, fee_items = _fee_ui_info(has_unsupported_instructions, fee)
|
|
fee_items.append((TR.words__blockhash, base58.encode(blockhash)))
|
|
await confirm_solana_tx(
|
|
amount="",
|
|
amount_title="",
|
|
fee=fee_str,
|
|
fee_title=fee_title,
|
|
items=fee_items,
|
|
)
|