feat(ethereum): Add support for vault deposit
What changed, and why it matters
This commit adds a new user-facing feature to Trezor hardware wallets: clear signing support for ERC-4626 'vault deposit' transactions on Ethereum. It introduces code that recognizes a specific smart-contract function call, shows the user a friendly confirmation screen with the vault name and deposit amount, and only proceeds if safety checks pass. There is no indication in the commit that this fixes a security bug; it appears to be a normal feature addition.
No immediate security action required. Treat as a normal feature commit. Because tests are noted as work-in-progress and the feature is debug-only, reviewers should ensure the validation rules (receiver == sender, non-zero amount, zero msg.value) are preserved in final production builds and that the vault allowlist is reviewed before release.
Security signals we found
New transaction parsing path added for ERC-4626 deposit function signature 0x6e553f65
Input validation enforces non-zero asset amount, zero ETH value, and receiver == sender
Feature is debug-gated (`if __debug__:`) and uses a hardcoded test vault
No changelog entry; commit message states tests are work-in-progress
No vendor statement of security relevance, CVE, or researcher attribution in commit
Evidence from the diff
The patch implements an Ethereum clear-signing flow for ERC-4626 deposit(uint256,address) calls. New files yielding.py and yielding_vaults.py parse the call data, validate that msg.value is zero, the asset amount is non-zero, and the receiver equals the sender, then route to a new confirm_ethereum_vault_tx UI screen across four device layouts (bolt, caesar, delizia, eckhart). The flow is gated behind if __debug__: and only recognizes a single test vault on Base chain. Existing staking logic is slightly refactored but not materially changed.
Changed components
core/src/apps/ethereum/sign_tx.pycore/src/apps/ethereum/sign_tx_eip1559.pycore/src/apps/ethereum/yielding.pycore/src/apps/ethereum/yielding_vaults.pycore/src/apps/ethereum/layout.pycore/src/apps/ethereum/sc_constants.pycore/src/trezor/ui/layouts/bolt/__init__.pycore/src/trezor/ui/layouts/caesar/__init__.pycore/src/trezor/ui/layouts/delizia/__init__.pycore/src/trezor/ui/layouts/eckhart/__init__.pyInspect captured patch +490 / −3
diff --git a/core/embed/upymod/qstrdefsport.h b/core/embed/upymod/qstrdefsport.h
index 9b3ca666..978368ea 100644
--- a/core/embed/upymod/qstrdefsport.h
+++ b/core/embed/upymod/qstrdefsport.h
@@ -561,6 +561,8 @@ Q(apps.ethereum.sign_typed_data)
Q(apps.ethereum.staking)
Q(apps.ethereum.tokens)
Q(apps.ethereum.verify_message)
+Q(apps.ethereum.yielding)
+Q(apps.ethereum.yielding_vaults)
Q(apps.monero)
Q(apps.monero.diag)
Q(apps.monero.get_address)
@@ -825,6 +827,8 @@ Q(verify_message)
Q(webauthn)
Q(writers)
Q(xmr)
+Q(yielding)
+Q(yielding_vaults)
Q(zcash)
Q(zcash_v4)
#endif
diff --git a/core/src/apps/ethereum/layout.py b/core/src/apps/ethereum/layout.py
index ad001ef9..b95cc21f 100644
--- a/core/src/apps/ethereum/layout.py
+++ b/core/src/apps/ethereum/layout.py
@@ -5,6 +5,7 @@ from trezor.enums import ButtonRequestType
from trezor.ui.layouts import (
confirm_blob,
confirm_ethereum_staking_tx,
+ confirm_ethereum_vault_tx,
confirm_text,
should_show_more,
)
@@ -227,13 +228,44 @@ async def require_confirm_stake(
account,
account_path,
maximum_fee,
- addr_str, # address
+ addr_str,
TR.ethereum__staking_stake_address, # address_title
fee_info_items, # info_items
chunkify=chunkify,
)
+async def require_confirm_deposit(
+ value: int,
+ address_n: list[int],
+ maximum_fee: str,
+ fee_info_items: Iterable[StrPropertyType],
+ network: EthereumNetworkInfo,
+ vault_addr: AnyBytes,
+) -> None:
+
+ from .yielding_vaults import lookup_vault
+
+ vault_name, token = lookup_vault(vault_addr, network)
+
+ total_amount = format_ethereum_amount(value, token, network)
+ account, account_path = get_account_and_path(address_n)
+
+ await confirm_ethereum_vault_tx(
+ title=TR.words__deposit,
+ intro_question=TR.ethereum__vault_deposit_intro,
+ verb=TR.ethereum__deposit_to,
+ vault_str=vault_name,
+ total_amount=total_amount,
+ account=account,
+ account_path=account_path,
+ maximum_fee=maximum_fee,
+ info_items=fee_info_items,
+ chain=network.name,
+ br_name="ethereum/vault/deposit",
+ )
+
+
async def require_confirm_unstake(
addr_bytes: bytes,
value: int,
diff --git a/core/src/apps/ethereum/sc_constants.py b/core/src/apps/ethereum/sc_constants.py
index bb8c3c6b..5ccb1a56 100644
--- a/core/src/apps/ethereum/sc_constants.py
+++ b/core/src/apps/ethereum/sc_constants.py
@@ -12,3 +12,7 @@ KNOWN_ADDRESSES = {
# https://etherscan.io/address/0xe592427a0aece92de3edee1f18e0157c05861564
unhexlify("e592427a0aece92de3edee1f18e0157c05861564"): "Uniswap V3 Router",
}
+if __debug__:
+ from .yielding_vaults import KNOWN_VAULT
+
+ KNOWN_ADDRESSES[KNOWN_VAULT[0]] = KNOWN_VAULT[2]
diff --git a/core/src/apps/ethereum/sign_tx.py b/core/src/apps/ethereum/sign_tx.py
index 39811a78..36e538ec 100644
--- a/core/src/apps/ethereum/sign_tx.py
+++ b/core/src/apps/ethereum/sign_tx.py
@@ -87,6 +87,7 @@ async def sign_tx(
# have the user confirm signing
await paths.validate_path(keychain, msg.address_n)
+ sender_bytes = keychain.derive(msg.address_n).ethereum_pubkeyhash()
gas_price = int.from_bytes(msg.gas_price, "big")
gas_limit = int.from_bytes(msg.gas_limit, "big")
maximum_fee = format_ethereum_amount(gas_price * gas_limit, None, network)
@@ -128,6 +129,7 @@ async def sign_tx(
maximum_fee,
fee_items,
payment_req_verifier,
+ sender_bytes,
)
# `confirm_data_chunk` and `confirm_summary` can be `None`
@@ -211,13 +213,14 @@ async def confirm_tx_data(
maximum_fee: str,
fee_items: Iterable[StrPropertyType],
payment_request_verifier: PaymentRequestVerifier | None,
+ sender_bytes: AnyBytes,
) -> tuple[ConfirmDataFn | None, Coroutine[Any, Any, None] | None]:
"""Returns data chunk callback and transaction summary layout to be awaited.
[None, None] implies clear signing attempted and succeeded."""
from trezor.ui.layouts import confirm_value
- from . import clear_signing, staking
+ from . import clear_signing, staking, yielding
from .helpers import format_ethereum_amount
from .layout import require_confirm_payment_request, require_confirm_tx
@@ -233,6 +236,15 @@ async def confirm_tx_data(
raise DataError("Payment Requests don't support staking")
return staking_approver
+ if __debug__:
+ yielding_approver = yielding.get_approver(
+ msg, network, address_bytes, maximum_fee, fee_items, sender_bytes
+ )
+ if yielding_approver is not None:
+ if payment_request_verifier is not None:
+ raise DataError("Payment Requests don't support yielding")
+ return yielding_approver
+
if tx_type == _EIP_7702_TX_TYPE:
# we have already made sure that the address is a known address
# as part of the initial validation
diff --git a/core/src/apps/ethereum/sign_tx_eip1559.py b/core/src/apps/ethereum/sign_tx_eip1559.py
index 4769c427..82054ca4 100644
--- a/core/src/apps/ethereum/sign_tx_eip1559.py
+++ b/core/src/apps/ethereum/sign_tx_eip1559.py
@@ -63,6 +63,7 @@ async def sign_tx_eip1559(
# have a user confirm signing
await paths.validate_path(keychain, msg.address_n)
+ sender_bytes = keychain.derive(msg.address_n).ethereum_pubkeyhash()
address_bytes = bytes_from_address(msg.to)
max_gas_fee = int.from_bytes(msg.max_gas_fee, "big")
@@ -113,6 +114,7 @@ async def sign_tx_eip1559(
maximum_fee,
fee_items,
payment_req_verifier,
+ sender_bytes,
)
if confirm_data_chunk is not None:
diff --git a/core/src/apps/ethereum/staking.py b/core/src/apps/ethereum/staking.py
index e614993f..3970f896 100644
--- a/core/src/apps/ethereum/staking.py
+++ b/core/src/apps/ethereum/staking.py
@@ -48,7 +48,7 @@ def get_approver(
# local_cache_attribute
data_length = msg.data_length
- if msg.data_length > len(msg.data_initial_chunk):
+ if data_length > len(msg.data_initial_chunk):
return None
data_reader = BufferReader(msg.data_initial_chunk)
diff --git a/core/src/apps/ethereum/yielding.py b/core/src/apps/ethereum/yielding.py
new file mode 100644
index 00000000..72c01ace
--- /dev/null
+++ b/core/src/apps/ethereum/yielding.py
@@ -0,0 +1,97 @@
+from typing import TYPE_CHECKING
+
+from trezor.utils import BufferReader
+from trezor.wire import DataError
+
+if TYPE_CHECKING:
+ from buffer_types import AnyBytes
+ from typing import Any, Coroutine, Iterable
+
+ from trezor.messages import EthereumNetworkInfo
+ from trezor.ui.layouts import StrPropertyType
+
+ from .helpers import ConfirmDataFn
+ from .keychain import MsgInSignTx
+
+
+def get_approver(
+ msg: MsgInSignTx,
+ network: EthereumNetworkInfo,
+ address_bytes: AnyBytes,
+ maximum_fee: str,
+ fee_items: Iterable[StrPropertyType],
+ sender_bytes: AnyBytes,
+) -> tuple[ConfirmDataFn, Coroutine[Any, Any, None]] | None:
+
+ from .clear_signing import SC_FUNC_SIG_BYTES
+ from .helpers import get_progress_indicator
+
+ # https://ethereum.org/developers/docs/standards/tokens/erc-4626/#deposit
+ # keccak256("deposit(uint256,address)")[:4]
+ FUNC_SIG_DEPOSIT = b"\x6e\x55\x3f\x65"
+
+ if msg.data_length > len(msg.data_initial_chunk):
+ return None
+
+ data_reader = BufferReader(msg.data_initial_chunk)
+ if data_reader.remaining_count() < SC_FUNC_SIG_BYTES:
+ return None
+
+ func_sig = data_reader.read_memoryview(SC_FUNC_SIG_BYTES)
+ if func_sig == FUNC_SIG_DEPOSIT:
+ return get_progress_indicator(msg.data_length), _handle_deposit(
+ data_reader,
+ msg,
+ network,
+ maximum_fee,
+ fee_items,
+ address_bytes,
+ sender_bytes,
+ )
+
+ return None
+
+
+async def _handle_deposit(
+ data_reader: BufferReader,
+ msg: MsgInSignTx,
+ network: EthereumNetworkInfo,
+ maximum_fee: str,
+ fee_items: Iterable[StrPropertyType],
+ vault_addr: AnyBytes,
+ sender_bytes: AnyBytes,
+) -> None:
+
+ from .clear_signing import InvalidFunctionCall, parse_address, parse_uint256
+ from .layout import require_confirm_deposit
+
+ # deposit(uint256 assets, address receiver)
+ # - arg0: asset(USDC) quantity
+ # - arg1: user address
+ try:
+ asset_amount = parse_uint256(data_reader.read_memoryview(32))
+ receiver_bytes = parse_address(data_reader.read_memoryview(32))
+ if (
+ data_reader.remaining_count() != 0
+ or not isinstance(asset_amount, int)
+ or not isinstance(receiver_bytes, bytes)
+ or int.from_bytes(msg.value, "big") != 0
+ ):
+ raise ValueError
+ except (ValueError, EOFError, InvalidFunctionCall):
+ raise DataError("Invalid data for vault deposit")
+
+ if asset_amount == 0:
+ raise DataError("Invalid asset amount for vault deposit")
+
+ if receiver_bytes != sender_bytes:
+ raise DataError("Receiver must equal sender for vault deposit")
+
+ await require_confirm_deposit(
+ asset_amount,
+ msg.address_n,
+ maximum_fee,
+ fee_items,
+ network,
+ vault_addr,
+ )
diff --git a/core/src/apps/ethereum/yielding_vaults.py b/core/src/apps/ethereum/yielding_vaults.py
new file mode 100644
index 00000000..abcf78a3
--- /dev/null
+++ b/core/src/apps/ethereum/yielding_vaults.py
@@ -0,0 +1,34 @@
+if __debug__:
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from buffer_types import AnyBytes
+
+ from trezor.messages import EthereumNetworkInfo, EthereumTokenInfo
+
+ # Stablecoin Yielding Vaults
+ # Each entry: (vault_address, owner_name, asset_decimals, asset_identifier, chain_id)
+ # Will be a list of tuples for each chain/network.
+ KNOWN_VAULT = (
+ b"\xac\x8c\x6e\x87\x79\xdd\xdc\x60\xf5\xce\xf7\x70\x1d\xce\x70\xec\xba\x5e\xf5\x18", # vault contract address
+ 8453, # chain id (Base)
+ "Trezor Test Vault (Base)", # owner/protocol name
+ EthereumTokenInfo(
+ symbol="USDC",
+ decimals=6,
+ address=b"\xa0\xb8\x69\x91\xc6\x21\x8b\x36\xc1\xd1\x9d\x4a\x2e\x9e\xb0\xce\x36\x06\xeb\x48",
+ chain_id=8453,
+ name="USD Coin",
+ ),
+ )
+
+ def lookup_vault(
+ vault_addr: AnyBytes, network: EthereumNetworkInfo
+ ) -> tuple[str, EthereumTokenInfo]:
+ from .helpers import address_from_bytes
+ from .tokens import UNKNOWN_TOKEN
+
+ if vault_addr == KNOWN_VAULT[0] and network.chain_id == KNOWN_VAULT[1]:
+ return KNOWN_VAULT[2], KNOWN_VAULT[3]
+ else:
+ return address_from_bytes(vault_addr, network), UNKNOWN_TOKEN
diff --git a/core/src/trezor/ui/layouts/bolt/__init__.py b/core/src/trezor/ui/layouts/bolt/__init__.py
index 18a2f0bc..fe53f00d 100644
--- a/core/src/trezor/ui/layouts/bolt/__init__.py
+++ b/core/src/trezor/ui/layouts/bolt/__init__.py
@@ -1322,6 +1322,72 @@ if not utils.BITCOIN_ONLY:
br_code=br_code,
)
+ async def confirm_ethereum_vault_tx(
+ title: str,
+ intro_question: str,
+ verb: str,
+ vault_str: str,
+ total_amount: str,
+ account: str | None,
+ account_path: str | None,
+ maximum_fee: str,
+ info_items: Iterable[StrPropertyType],
+ chain: str,
+ br_name: str = "ethereum/vault",
+ br_code: ButtonRequestType = ButtonRequestType.SignTx,
+ ) -> None:
+
+ account_properties: list[StrPropertyType] = []
+ if account:
+ account_properties.append((TR.words__account, account, None))
+ if account_path:
+ account_properties.append(
+ (TR.address_details__derivation_path, account_path, None)
+ )
+
+ await confirm_value(
+ title=title,
+ value=intro_question,
+ description="",
+ br_name=br_name + "/intro",
+ br_code=br_code,
+ verb=TR.buttons__continue,
+ is_data=False,
+ info_items=account_properties if account_properties else None,
+ info_title=TR.address_details__account_info,
+ )
+
+ await confirm_value(
+ title=verb,
+ value=vault_str,
+ description="",
+ br_name=br_name + "/vault",
+ br_code=br_code,
+ verb=TR.buttons__continue,
+ )
+
+ await confirm_properties(
+ br_name=br_name + "/amount",
+ title=title,
+ props=[
+ (TR.ethereum__deposit_amount, total_amount, False),
+ (TR.words__chain, chain, False),
+ ],
+ br_code=br_code,
+ )
+
+ await _confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=TR.send__maximum_fee,
+ title=title,
+ extra_items=info_items,
+ extra_title=TR.confirm_total__title_fee,
+ br_name=br_name + "/summary",
+ br_code=br_code,
+ )
+
def confirm_solana_unknown_token_warning() -> Awaitable[None]:
return show_danger(
"unknown_token_warning", content=TR.solana__unknown_token_address
diff --git a/core/src/trezor/ui/layouts/caesar/__init__.py b/core/src/trezor/ui/layouts/caesar/__init__.py
index 65a090d6..160a37ff 100644
--- a/core/src/trezor/ui/layouts/caesar/__init__.py
+++ b/core/src/trezor/ui/layouts/caesar/__init__.py
@@ -1296,6 +1296,76 @@ if not utils.BITCOIN_ONLY:
br_code=br_code,
)
+ async def confirm_ethereum_vault_tx(
+ title: str,
+ intro_question: str,
+ verb: str,
+ vault_str: str,
+ total_amount: str,
+ account: str | None,
+ account_path: str | None,
+ maximum_fee: str,
+ info_items: Iterable[StrPropertyType],
+ chain: str,
+ br_name: str = "ethereum/vault",
+ br_code: ButtonRequestType = ButtonRequestType.SignTx,
+ ) -> None:
+ from ..properties import with_colon
+
+ account_properties: list[StrPropertyType] = []
+ if account:
+ account_properties.append((TR.words__account, account, None))
+ if account_path:
+ account_properties.append(
+ (TR.address_details__derivation_path, account_path, None)
+ )
+
+ await confirm_value(
+ title=title,
+ value=intro_question,
+ is_data=False,
+ description=None,
+ verb=TR.buttons__continue,
+ chunkify=False,
+ info_items=account_properties if account_properties else None,
+ cancel=True,
+ br_name=br_name + "/intro",
+ br_code=br_code,
+ )
+
+ await confirm_value(
+ title=title,
+ value=vault_str,
+ description=verb,
+ verb=TR.buttons__continue,
+ cancel=True,
+ br_name=br_name + "/vault",
+ br_code=br_code,
+ )
+
+ await confirm_properties(
+ br_name + "/amount",
+ title,
+ [
+ (TR.ethereum__deposit_amount, total_amount, False),
+ (TR.words__chain, chain, False),
+ ],
+ )
+
+ await raise_if_not_confirmed(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=with_colon(TR.send__maximum_fee),
+ extra_title=TR.confirm_total__title_fee,
+ extra_items=with_colon(info_items),
+ title=title,
+ ),
+ br_name=br_name + "/summary",
+ br_code=br_code,
+ )
+
def confirm_solana_unknown_token_warning() -> Awaitable[None]:
return show_danger(
"unknown_token_warning",
diff --git a/core/src/trezor/ui/layouts/delizia/__init__.py b/core/src/trezor/ui/layouts/delizia/__init__.py
index f318e9ca..6f1bb8b7 100644
--- a/core/src/trezor/ui/layouts/delizia/__init__.py
+++ b/core/src/trezor/ui/layouts/delizia/__init__.py
@@ -1289,6 +1289,89 @@ if not utils.BITCOIN_ONLY:
),
)
+ async def confirm_ethereum_vault_tx(
+ title: str,
+ intro_question: str,
+ verb: str,
+ vault_str: str,
+ total_amount: str,
+ account: str | None,
+ account_path: str | None,
+ maximum_fee: str,
+ info_items: Iterable[StrPropertyType],
+ chain: str,
+ br_name: str = "ethereum/vault",
+ br_code: ButtonRequestType = ButtonRequestType.SignTx,
+ ) -> None:
+ from trezor.ui.layouts.menu import Menu, interact_with_menu
+
+ menu_items = []
+ account_info_items = _get_account_info_items(account, account_path)
+ if account_info_items:
+ menu_items.append(
+ create_details(
+ TR.address_details__account_info,
+ account_info_items[0][1],
+ title=TR.address_details__account_info,
+ )
+ )
+
+ await confirm_linear_flow(
+ lambda: interact_with_menu(
+ trezorui_api.confirm_value(
+ title=title,
+ value=intro_question,
+ is_data=False,
+ description=None,
+ chunkify=False,
+ external_menu=True,
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/intro",
+ ButtonRequestType.SignTx,
+ ),
+ lambda: interact_with_menu(
+ trezorui_api.confirm_value(
+ title=title,
+ value=vault_str,
+ is_data=False,
+ description=verb,
+ verb="",
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/vault_name",
+ ),
+ lambda: interact_with_menu(
+ trezorui_api.confirm_properties(
+ title=title,
+ items=[
+ (TR.ethereum__deposit_amount, total_amount, False),
+ (TR.words__chain, chain, False),
+ ],
+ hold=False,
+ verb=TR.buttons__continue,
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/amount",
+ br_code,
+ ),
+ lambda: interact_with_menu(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=TR.send__maximum_fee,
+ extra_title=TR.confirm_total__title_fee,
+ extra_items=list(info_items),
+ title=title,
+ back_button=False,
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/summary",
+ br_code,
+ ),
+ )
+
def confirm_solana_unknown_token_warning() -> Awaitable[None]:
return show_danger(
"unknown_token_warning",
diff --git a/core/src/trezor/ui/layouts/eckhart/__init__.py b/core/src/trezor/ui/layouts/eckhart/__init__.py
index eb4aa57b..ca8f98e5 100644
--- a/core/src/trezor/ui/layouts/eckhart/__init__.py
+++ b/core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -1285,6 +1285,89 @@ if not utils.BITCOIN_ONLY:
br_name="confirm_ethereum_tx",
)
+ async def confirm_ethereum_vault_tx(
+ title: str,
+ intro_question: str,
+ verb: str,
+ vault_str: str,
+ total_amount: str,
+ account: str | None,
+ account_path: str | None,
+ maximum_fee: str,
+ info_items: Iterable[StrPropertyType],
+ chain: str,
+ br_name: str = "ethereum/vault",
+ br_code: ButtonRequestType = ButtonRequestType.SignTx,
+ ) -> None:
+ from trezor.ui.layouts.menu import Menu, interact_with_menu
+
+ menu_items = []
+ account_properties = _get_account_info_items(account, account_path)
+ if account_properties:
+ menu_items.append(
+ create_details(
+ TR.address_details__account_info,
+ account_properties,
+ title=TR.address_details__account_info,
+ subtitle=TR.send__send_from,
+ )
+ )
+
+ await confirm_linear_flow(
+ lambda: interact_with_menu(
+ trezorui_api.confirm_action(
+ title=title,
+ action=intro_question,
+ description=None,
+ external_menu=True,
+ cancel=False,
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/intro",
+ br_code,
+ ),
+ lambda: interact_with_menu(
+ trezorui_api.confirm_with_info(
+ title=title,
+ subtitle=verb,
+ items=[(vault_str, True)],
+ verb=TR.buttons__continue,
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/vault_name",
+ br_code,
+ ),
+ lambda: interact_with_menu(
+ trezorui_api.confirm_properties(
+ title=title,
+ items=[
+ (TR.ethereum__deposit_amount, total_amount, False),
+ (TR.words__chain, chain, False),
+ ],
+ hold=False,
+ verb=TR.buttons__continue,
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/amount",
+ br_code,
+ ),
+ lambda: interact_with_menu(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=TR.send__maximum_fee,
+ extra_title=TR.confirm_total__title_fee,
+ extra_items=list(info_items),
+ title=title,
+ back_button=False,
+ ),
+ Menu.root(menu_items, TR.send__cancel_sign),
+ br_name + "/summary",
+ br_code,
+ ),
+ )
+
async def confirm_ethereum_staking_tx(
title: str,
intro_question: str,
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.