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.
362 lines
11 KiB
362 lines
11 KiB
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_metadata,
|
|
confirm_properties,
|
|
confirm_solana_tx,
|
|
confirm_value,
|
|
)
|
|
|
|
from apps.common.paths import address_n_to_str
|
|
|
|
from .types import AddressType
|
|
|
|
if TYPE_CHECKING:
|
|
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):
|
|
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,
|
|
) -> 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.is_authority and signer_public_key == value:
|
|
continue
|
|
|
|
if property_template.is_optional and value is None:
|
|
continue
|
|
|
|
if ui_property.default_value_to_hide == value:
|
|
continue
|
|
|
|
await confirm_properties(
|
|
"confirm_instruction",
|
|
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
|
|
(
|
|
(
|
|
ui_property.display_name,
|
|
property_template.format(instruction, value),
|
|
),
|
|
),
|
|
)
|
|
elif ui_property.account is not None:
|
|
account_template = instruction.get_account_template(ui_property.account)
|
|
|
|
# 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 account_template.is_authority:
|
|
if signer_public_key == account_value[0]:
|
|
continue
|
|
|
|
account_data: list[tuple[str, str]] = []
|
|
if len(account_value) == 2:
|
|
signer_suffix = ""
|
|
if account_value[0] == signer_public_key:
|
|
signer_suffix = f" ({TR.words__signer})"
|
|
|
|
account_data.append(
|
|
(
|
|
ui_property.display_name,
|
|
f"{base58.encode(account_value[0])}{signer_suffix}",
|
|
)
|
|
)
|
|
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 import NORMAL
|
|
from trezor.ui.layouts import confirm_properties, should_show_more
|
|
|
|
should_show_instruction_details = await should_show_more(
|
|
title,
|
|
(
|
|
(
|
|
NORMAL,
|
|
TR.solana__instruction_accounts_template.format(
|
|
len(instruction.accounts), len(instruction.instruction_data)
|
|
),
|
|
),
|
|
),
|
|
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: int,
|
|
signer_path: list[int],
|
|
blockhash: bytes,
|
|
) -> None:
|
|
await confirm_value(
|
|
title=TR.words__recipient,
|
|
value=base58.encode(transfer_instruction.recipient_account[0]),
|
|
description="",
|
|
br_type="confirm_recipient",
|
|
br_code=ButtonRequestType.ConfirmOutput,
|
|
verb=TR.buttons__continue,
|
|
)
|
|
|
|
await confirm_custom_transaction(
|
|
transfer_instruction.lamports,
|
|
9,
|
|
"SOL",
|
|
fee,
|
|
signer_path,
|
|
blockhash,
|
|
)
|
|
|
|
|
|
async def confirm_token_transfer(
|
|
destination_account: bytes,
|
|
token_account: bytes,
|
|
token_mint: bytes,
|
|
amount: int,
|
|
decimals: int,
|
|
fee: int,
|
|
signer_path: list[int],
|
|
blockhash: bytes,
|
|
):
|
|
await confirm_value(
|
|
title=TR.words__recipient,
|
|
value=base58.encode(destination_account),
|
|
description="",
|
|
br_type="confirm_recipient",
|
|
br_code=ButtonRequestType.ConfirmOutput,
|
|
verb=TR.buttons__continue,
|
|
info_items=(
|
|
((f"{TR.solana__associated_token_account}:", base58.encode(token_account)),)
|
|
if token_account != destination_account
|
|
else None
|
|
),
|
|
)
|
|
|
|
await confirm_value(
|
|
title=TR.solana__token_address,
|
|
value=base58.encode(token_mint),
|
|
description="",
|
|
br_type="confirm_token_address",
|
|
br_code=ButtonRequestType.ConfirmOutput,
|
|
verb=TR.buttons__continue,
|
|
)
|
|
|
|
await confirm_custom_transaction(
|
|
amount,
|
|
decimals,
|
|
"[TOKEN]",
|
|
fee,
|
|
signer_path,
|
|
blockhash,
|
|
)
|
|
|
|
|
|
async def confirm_custom_transaction(
|
|
amount: int,
|
|
decimals: int,
|
|
unit: str,
|
|
fee: int,
|
|
signer_path: list[int],
|
|
blockhash: bytes,
|
|
) -> None:
|
|
await confirm_solana_tx(
|
|
amount=f"{format_amount(amount, decimals)} {unit}",
|
|
fee=f"{format_amount(fee, 9)} SOL",
|
|
fee_title=f"{TR.solana__expected_fee}:",
|
|
items=(
|
|
(f"{TR.words__account}:", _format_path(signer_path)),
|
|
(f"{TR.words__blockhash}:", base58.encode(blockhash)),
|
|
),
|
|
)
|
|
|
|
|
|
async def confirm_transaction(
|
|
signer_path: list[int], blockhash: bytes, fee: int
|
|
) -> None:
|
|
await confirm_solana_tx(
|
|
amount="",
|
|
amount_title="",
|
|
fee=f"{format_amount(fee, 9)} SOL",
|
|
fee_title=f"{TR.solana__expected_fee}:",
|
|
items=(
|
|
(f"{TR.words__account}:", _format_path(signer_path)),
|
|
(f"{TR.words__blockhash}:", base58.encode(blockhash)),
|
|
),
|
|
)
|