mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-22 12:32:02 +00:00
feat(core): add Solana staking layouts
This commit is contained in:
parent
afb8963a40
commit
6d205a416f
1
core/.changelog.d/4560.added
Normal file
1
core/.changelog.d/4560.added
Normal file
@ -0,0 +1 @@
|
||||
Add Solana staking confirmation dialogs.
|
@ -5,10 +5,13 @@ 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_tx,
|
||||
confirm_value,
|
||||
show_danger,
|
||||
show_warning,
|
||||
)
|
||||
|
||||
from apps.common.paths import address_n_to_str
|
||||
@ -46,6 +49,10 @@ def _get_address_reference_props(
|
||||
)
|
||||
|
||||
|
||||
def _blockhash_item(blockhash: bytes) -> tuple[str, str]:
|
||||
return (f"{TR.words__blockhash}:", base58.encode(blockhash))
|
||||
|
||||
|
||||
async def confirm_instruction(
|
||||
instruction: Instruction,
|
||||
instructions_count: int,
|
||||
@ -345,21 +352,151 @@ async def confirm_custom_transaction(
|
||||
fee_title=f"{TR.solana__expected_fee}:",
|
||||
items=(
|
||||
(f"{TR.words__account}:", _format_path(signer_path)),
|
||||
(f"{TR.words__blockhash}:", base58.encode(blockhash)),
|
||||
_blockhash_item(blockhash),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
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="Withdraw authority address",
|
||||
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
|
||||
|
||||
KNOWN_ACCOUNTS = {
|
||||
"9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF": "Everstake",
|
||||
}
|
||||
|
||||
vote_account = base58.encode(delegate.vote_account[0])
|
||||
vote_account = KNOWN_ACCOUNTS.get(vote_account, vote_account)
|
||||
|
||||
await confirm_solana_staking_tx(
|
||||
title=TR.solana__stake,
|
||||
message=TR.solana__stake_question,
|
||||
account=_format_path(signer_path),
|
||||
account_path=address_n_to_str(signer_path),
|
||||
vote_account=vote_account,
|
||||
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=_blockhash_item(blockhash),
|
||||
br_name="confirm_stake_transaction",
|
||||
)
|
||||
|
||||
|
||||
async def confirm_unstake_transaction(
|
||||
fee: Fee,
|
||||
signer_path: list[int],
|
||||
blockhash: bytes,
|
||||
deactivate: Instruction,
|
||||
) -> None:
|
||||
from trezor.ui.layouts import confirm_solana_staking_tx
|
||||
|
||||
await confirm_solana_staking_tx(
|
||||
title=TR.solana__unstake,
|
||||
message=TR.solana__unstake_question,
|
||||
account=_format_path(signer_path),
|
||||
account_path=address_n_to_str(signer_path),
|
||||
vote_account="",
|
||||
stake_item=(
|
||||
TR.solana__stake_account,
|
||||
base58.encode(deactivate.delegated_stake_account[0]),
|
||||
),
|
||||
amount_item=("", ""),
|
||||
fee_item=_fee_summary(fee),
|
||||
fee_details=_fee_details(fee),
|
||||
blockhash_item=_blockhash_item(blockhash),
|
||||
br_name="confirm_unstake_transaction",
|
||||
)
|
||||
|
||||
|
||||
async def confirm_claim_transaction(
|
||||
fee: Fee,
|
||||
signer_path: list[int],
|
||||
blockhash: bytes,
|
||||
withdraw: Instruction,
|
||||
) -> None:
|
||||
from trezor.ui.layouts import confirm_solana_staking_tx
|
||||
|
||||
await confirm_solana_staking_tx(
|
||||
title=TR.solana__claim,
|
||||
message=TR.solana__claim_question,
|
||||
account=_format_path(signer_path),
|
||||
account_path=address_n_to_str(signer_path),
|
||||
vote_account="",
|
||||
stake_item=(
|
||||
TR.solana__stake_account,
|
||||
base58.encode(withdraw.stake_account[0]),
|
||||
),
|
||||
amount_item=(
|
||||
f"{TR.words__amount}:",
|
||||
f"{format_amount(withdraw.lamports, 9)} SOL",
|
||||
),
|
||||
fee_item=_fee_summary(fee),
|
||||
fee_details=_fee_details(fee),
|
||||
blockhash_item=_blockhash_item(blockhash),
|
||||
br_name="confirm_claim_transaction",
|
||||
)
|
||||
|
||||
|
||||
async def confirm_transaction(
|
||||
signer_path: list[int], blockhash: bytes, fee: Fee
|
||||
) -> None:
|
||||
(fee_title, fee_value) = _fee_summary(fee)
|
||||
await confirm_solana_tx(
|
||||
amount="",
|
||||
amount_title="",
|
||||
fee=f"{format_amount(fee.total, 9)} SOL",
|
||||
fee_title=f"{TR.solana__expected_fee}:",
|
||||
fee=fee_value,
|
||||
fee_title=fee_title,
|
||||
items=(
|
||||
(f"{TR.words__account}:", _format_path(signer_path)),
|
||||
(f"{TR.words__blockhash}:", base58.encode(blockhash)),
|
||||
_blockhash_item(blockhash),
|
||||
),
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ from trezor.crypto import base58
|
||||
|
||||
from .transaction import Transaction
|
||||
from .transaction.instructions import (
|
||||
_SYSTEM_PROGRAM_ID,
|
||||
AssociatedTokenAccountProgramCreateInstruction,
|
||||
Instruction,
|
||||
Token2022ProgramTransferCheckedInstruction,
|
||||
@ -11,6 +12,8 @@ from .transaction.instructions import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
||||
from trezor.messages import SolanaTxAdditionalInfo
|
||||
|
||||
from .transaction import Fee
|
||||
@ -173,6 +176,7 @@ async def try_confirm_predefined_transaction(
|
||||
transaction: Transaction,
|
||||
fee: Fee,
|
||||
signer_path: list[int],
|
||||
signer_public_key: bytes,
|
||||
blockhash: bytes,
|
||||
additional_info: SolanaTxAdditionalInfo | None = None,
|
||||
) -> bool:
|
||||
@ -191,6 +195,117 @@ async def try_confirm_predefined_transaction(
|
||||
await confirm_system_transfer(instructions[0], fee, signer_path, blockhash)
|
||||
return True
|
||||
|
||||
if await try_confirm_staking_transaction(
|
||||
transaction,
|
||||
fee,
|
||||
signer_path,
|
||||
signer_public_key,
|
||||
blockhash,
|
||||
# TODO: do we need to confirm `additional_info`?
|
||||
):
|
||||
return True
|
||||
|
||||
return await try_confirm_token_transfer_transaction(
|
||||
transaction, fee, signer_path, blockhash, additional_info
|
||||
)
|
||||
|
||||
|
||||
async def try_confirm_staking_transaction(
|
||||
transaction: Transaction,
|
||||
fee: Fee,
|
||||
signer_path: list[int],
|
||||
signer_public_key: bytes,
|
||||
blockhash: bytes,
|
||||
) -> bool:
|
||||
from .transaction.instructions import (
|
||||
ComputeBudgetProgramSetComputeUnitPriceInstruction,
|
||||
StakeProgramDeactivateInstruction,
|
||||
StakeProgramDelegateStakeInstruction,
|
||||
StakeProgramInitializeInstruction,
|
||||
StakeProgramWithdrawInstruction,
|
||||
SystemProgramCreateAccountWithSeedInstruction,
|
||||
)
|
||||
|
||||
instructions = transaction.instructions
|
||||
|
||||
def _match_instructions(*expected_types: Type[Instruction]) -> bool:
|
||||
if len(instructions) != len(expected_types):
|
||||
return False
|
||||
return all(
|
||||
expected_type.is_type_of(instruction)
|
||||
for instruction, expected_type in zip(instructions, expected_types)
|
||||
)
|
||||
|
||||
if _match_instructions(
|
||||
ComputeBudgetProgramSetComputeUnitPriceInstruction,
|
||||
SystemProgramCreateAccountWithSeedInstruction,
|
||||
StakeProgramInitializeInstruction,
|
||||
StakeProgramDelegateStakeInstruction,
|
||||
):
|
||||
from .layout import confirm_stake_transaction, confirm_stake_withdrawer
|
||||
|
||||
_budget, create, init, delegate = instructions
|
||||
if signer_public_key != create.funding_account[0]:
|
||||
return False
|
||||
if signer_public_key != create.base:
|
||||
return False
|
||||
if signer_public_key != init.withdrawer:
|
||||
await confirm_stake_withdrawer(init.withdrawer)
|
||||
if signer_public_key != init.staker:
|
||||
return False
|
||||
if signer_public_key != delegate.stake_authority[0]:
|
||||
return False
|
||||
|
||||
if base58.encode(init.custodian) != _SYSTEM_PROGRAM_ID:
|
||||
return False
|
||||
|
||||
stake_account = create.created_account[0]
|
||||
if stake_account != init.uninitialized_stake_account[0]:
|
||||
return False
|
||||
if stake_account != delegate.initialized_stake_account[0]:
|
||||
return False
|
||||
|
||||
await confirm_stake_transaction(
|
||||
fee=fee,
|
||||
signer_path=signer_path,
|
||||
blockhash=blockhash,
|
||||
create=create,
|
||||
delegate=delegate,
|
||||
)
|
||||
return True
|
||||
|
||||
if _match_instructions(
|
||||
ComputeBudgetProgramSetComputeUnitPriceInstruction,
|
||||
StakeProgramDeactivateInstruction,
|
||||
):
|
||||
from .layout import confirm_unstake_transaction
|
||||
|
||||
_budget, deactivate = instructions
|
||||
if signer_public_key != deactivate.stake_authority[0]:
|
||||
return False
|
||||
|
||||
await confirm_unstake_transaction(
|
||||
fee=fee, signer_path=signer_path, blockhash=blockhash, deactivate=deactivate
|
||||
)
|
||||
return True
|
||||
|
||||
if _match_instructions(
|
||||
ComputeBudgetProgramSetComputeUnitPriceInstruction,
|
||||
StakeProgramWithdrawInstruction,
|
||||
):
|
||||
from .layout import confirm_claim_recipient, confirm_claim_transaction
|
||||
|
||||
_budget, withdraw = instructions
|
||||
if signer_public_key != withdraw.withdrawal_authority[0]:
|
||||
return False
|
||||
if signer_public_key != withdraw.recipient_account[0]:
|
||||
await confirm_claim_recipient(withdraw.recipient_account[0])
|
||||
|
||||
await confirm_claim_transaction(
|
||||
fee=fee, signer_path=signer_path, blockhash=blockhash, withdraw=withdraw
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
# not a staking transaction
|
||||
return False
|
||||
|
@ -59,7 +59,12 @@ async def sign_tx(
|
||||
fee = transaction.calculate_fee()
|
||||
|
||||
if not await try_confirm_predefined_transaction(
|
||||
transaction, fee, address_n, transaction.blockhash, msg.additional_info
|
||||
transaction,
|
||||
fee,
|
||||
address_n,
|
||||
signer_public_key,
|
||||
transaction.blockhash,
|
||||
msg.additional_info,
|
||||
):
|
||||
await confirm_instructions(address_n, signer_public_key, transaction)
|
||||
await confirm_transaction(
|
||||
|
@ -950,6 +950,57 @@ if not utils.BITCOIN_ONLY:
|
||||
br_code=br_code,
|
||||
)
|
||||
|
||||
async def confirm_solana_staking_tx(
|
||||
title: str,
|
||||
message: str,
|
||||
account: str,
|
||||
account_path: str,
|
||||
vote_account: str,
|
||||
stake_item: tuple[str, str],
|
||||
amount_item: tuple[str, str],
|
||||
fee_item: tuple[str, str],
|
||||
fee_details: Iterable[tuple[str, str]],
|
||||
blockhash_item: tuple[str, str],
|
||||
br_name: str,
|
||||
) -> None:
|
||||
(amount_label, amount) = amount_item
|
||||
(fee_label, fee) = fee_item
|
||||
|
||||
confirm_layout = trezorui_api.confirm_value(
|
||||
title=title,
|
||||
description=message,
|
||||
extra=f"{TR.solana__stake_provider}:" if vote_account else None,
|
||||
value=vote_account,
|
||||
verb=TR.buttons__continue,
|
||||
info=True,
|
||||
)
|
||||
|
||||
info_layout = trezorui_api.show_info_with_cancel(
|
||||
title=title,
|
||||
items=(
|
||||
(f"{TR.words__account}:", account),
|
||||
(f"{TR.address_details__derivation_path}:", account_path),
|
||||
stake_item,
|
||||
blockhash_item,
|
||||
),
|
||||
horizontal=True,
|
||||
)
|
||||
|
||||
await with_info(confirm_layout, info_layout, br_name, ButtonRequestType.SignTx)
|
||||
|
||||
await _confirm_summary(
|
||||
amount=amount,
|
||||
amount_label=amount_label,
|
||||
fee=fee,
|
||||
fee_label=fee_label,
|
||||
account_items=None,
|
||||
title=title,
|
||||
extra_title=TR.confirm_total__title_fee,
|
||||
extra_items=fee_details,
|
||||
br_name=br_name,
|
||||
br_code=ButtonRequestType.SignTx,
|
||||
)
|
||||
|
||||
def confirm_cardano_tx(
|
||||
amount: str,
|
||||
fee: str,
|
||||
|
@ -926,6 +926,56 @@ if not utils.BITCOIN_ONLY:
|
||||
br_code=br_code,
|
||||
)
|
||||
|
||||
async def confirm_solana_staking_tx(
|
||||
title: str,
|
||||
message: str,
|
||||
account: str,
|
||||
account_path: str,
|
||||
vote_account: str,
|
||||
stake_item: tuple[str, str],
|
||||
amount_item: tuple[str, str],
|
||||
fee_item: tuple[str, str],
|
||||
fee_details: Iterable[tuple[str, str]],
|
||||
blockhash_item: tuple[str, str],
|
||||
br_name: str,
|
||||
) -> None:
|
||||
(amount_label, amount) = amount_item
|
||||
(fee_label, fee) = fee_item
|
||||
|
||||
items = (
|
||||
(f"{TR.words__account}:", account),
|
||||
(f"{TR.address_details__derivation_path}:", account_path),
|
||||
stake_item,
|
||||
blockhash_item,
|
||||
)
|
||||
await raise_if_not_confirmed(
|
||||
trezorui_api.confirm_summary(
|
||||
amount="",
|
||||
amount_label=message,
|
||||
fee=vote_account,
|
||||
fee_label=f"{TR.solana__stake_provider}:" if vote_account else "",
|
||||
extra_title=title,
|
||||
extra_items=items,
|
||||
),
|
||||
br_name,
|
||||
ButtonRequestType.SignTx,
|
||||
)
|
||||
|
||||
await raise_if_not_confirmed(
|
||||
trezorui_api.confirm_summary(
|
||||
amount=amount,
|
||||
amount_label=amount_label,
|
||||
fee=fee,
|
||||
fee_label=fee_label,
|
||||
account_items=None,
|
||||
title=title,
|
||||
extra_title=TR.confirm_total__title_fee,
|
||||
extra_items=fee_details,
|
||||
),
|
||||
br_name,
|
||||
ButtonRequestType.SignTx,
|
||||
)
|
||||
|
||||
def confirm_cardano_tx(
|
||||
amount: str,
|
||||
fee: str,
|
||||
|
@ -862,6 +862,46 @@ if not utils.BITCOIN_ONLY:
|
||||
br_code=br_code,
|
||||
)
|
||||
|
||||
async def confirm_solana_staking_tx(
|
||||
title: str,
|
||||
message: str,
|
||||
account: str,
|
||||
account_path: str,
|
||||
vote_account: str,
|
||||
stake_item: tuple[str, str],
|
||||
amount_item: tuple[str, str] | None,
|
||||
fee_item: tuple[str, str],
|
||||
fee_details: Iterable[tuple[str, str]],
|
||||
blockhash_item: tuple[str, str],
|
||||
br_name: str,
|
||||
) -> None:
|
||||
(address_title, address) = stake_item
|
||||
summary_items = (amount_item,) if amount_item else ()
|
||||
summary_items += (fee_item, blockhash_item)
|
||||
await raise_if_not_confirmed(
|
||||
trezorui_api.flow_confirm_output(
|
||||
title=title,
|
||||
subtitle=None,
|
||||
message=message,
|
||||
amount=None,
|
||||
chunkify=False,
|
||||
text_mono=False,
|
||||
account=account,
|
||||
account_path=account_path,
|
||||
br_code=ButtonRequestType.SignTx,
|
||||
br_name=br_name,
|
||||
address=address,
|
||||
address_title=address_title,
|
||||
fee_items=fee_details,
|
||||
summary_title=title,
|
||||
summary_items=summary_items,
|
||||
summary_br_name="confirm_solana_staking_tx_total",
|
||||
summary_br_code=ButtonRequestType.SignTx,
|
||||
cancel_text=TR.buttons__cancel,
|
||||
),
|
||||
br_name=None,
|
||||
)
|
||||
|
||||
def confirm_cardano_tx(
|
||||
amount: str,
|
||||
fee: str,
|
||||
|
Loading…
Reference in New Issue
Block a user