fix(tron): Transfer labels instead of Approve
What changed, and why it matters
This commit fixes a user-interface labeling bug in Trezor's Tron (TRC-20) token support. Previously, when a user was about to send a normal token transfer, the device screen incorrectly showed 'Approve' wording and labels, which could mislead the user into thinking they were approving a spender rather than sending funds. The fix adds a new 'transfer' confirmation screen and uses it for token transfers, while keeping the old 'approve' screen for future use. The actual transaction logic and signatures were not changed—only what the user sees on screen.
Treat as a low-to-moderate security hardening/UI-safety fix. Verify that the new confirm_tron_transfer screen accurately labels recipient, amount, chain, and maximum fee for all TRC-20 transfer paths, and that no other token operation still incorrectly uses the approve screen. No urgent firmware rollout is required solely for this change, but it should be included in the next release.
Security signals we found
UI wording mismatch between 'Approve' and 'Transfer' for TRC-20 token sends
New confirm_tron_transfer screen added to four device UI backends
Old confirm_tron_approve screen retained but no longer used for transfers
chunkify default changed from False to True for Tron approve screen in caesar/eckhart
UI test fixture hashes updated for USDT transfer scenarios only
Evidence from the diff
The patch replaces confirm_tron_approve with a new confirm_tron_transfer in core/src/apps/tron/layout.py for known TRC-20 smart-contract interactions. It implements confirm_tron_transfer in four UI layout backends (bolt, caesar, delizia, eckhart), using ‘Send’ titling, recipient/amount/chain/fee summary screens, and chunkify=True for address display. The old confirm_tron_approve remains (with chunkify now True in caesar/eckhart) for future approval flows. UI test fixture hashes for TriggerSmartContract_USDT_transfer cases are updated across T2T1/T3B1/T3T1/T3W1, reflecting changed screen content. No transaction parsing, signing, or validation code is modified.
Changed components
core/src/apps/tron/layout.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__.pytests/ui_tests/fixtures.jsonInspect captured patch +212 / −13
diff --git a/core/src/apps/tron/layout.py b/core/src/apps/tron/layout.py
index a88cfd34..80e35f35 100644
--- a/core/src/apps/tron/layout.py
+++ b/core/src/apps/tron/layout.py
@@ -78,9 +78,9 @@ async def confirm_unknown_smart_contract(
async def confirm_known_trc20_smart_contract(
recipient_addr: bytes, amount: int, fee_limit: int, token: EthereumTokenInfo
) -> None:
- from trezor.ui.layouts import confirm_tron_approve
+ from trezor.ui.layouts import confirm_tron_transfer
- await confirm_tron_approve(
+ await confirm_tron_transfer(
recipient_addr=get_encoded_address(recipient_addr),
total_amount=format_token_amount(amount, token),
maximum_fee=format_energy_amount(fee_limit),
diff --git a/core/src/trezor/ui/layouts/bolt/__init__.py b/core/src/trezor/ui/layouts/bolt/__init__.py
index 5efe36af..d6aa9f51 100644
--- a/core/src/trezor/ui/layouts/bolt/__init__.py
+++ b/core/src/trezor/ui/layouts/bolt/__init__.py
@@ -1516,6 +1516,55 @@ if not utils.BITCOIN_ONLY:
None,
)
+ # TODO: #6364 Consider simplifying with confirm_tron_send like ETH flows.
+ async def confirm_tron_transfer(
+ recipient_addr: str,
+ total_amount: str,
+ maximum_fee: str,
+ chunkify: bool = True,
+ ) -> None:
+
+ br_name = "confirm_tron_transfer"
+ title = TR.words__send
+
+ await confirm_value(
+ title,
+ recipient_addr,
+ "",
+ subtitle=TR.words__recipient,
+ chunkify=chunkify,
+ br_name=br_name,
+ verb=TR.buttons__continue,
+ cancel=True,
+ )
+
+ properties: list[PropertyType] = [
+ (
+ f"{TR.words__amount}:",
+ total_amount,
+ False,
+ ),
+ (f"{TR.words__chain}:", "Tron", True),
+ ]
+
+ await confirm_properties(
+ br_name,
+ title,
+ properties,
+ None,
+ False,
+ verb=TR.buttons__continue,
+ )
+
+ await _confirm_summary(
+ None,
+ None,
+ maximum_fee,
+ f"{TR.send__maximum_fee}:",
+ title,
+ None,
+ )
+
def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]:
return raise_if_not_confirmed(
diff --git a/core/src/trezor/ui/layouts/caesar/__init__.py b/core/src/trezor/ui/layouts/caesar/__init__.py
index 708473dc..b40c47f1 100644
--- a/core/src/trezor/ui/layouts/caesar/__init__.py
+++ b/core/src/trezor/ui/layouts/caesar/__init__.py
@@ -1523,7 +1523,7 @@ if not utils.BITCOIN_ONLY:
recipient_addr: str,
total_amount: str,
maximum_fee: str,
- chunkify: bool = False,
+ chunkify: bool = True,
) -> None:
br_name = "confirm_tron_approve"
@@ -1575,6 +1575,58 @@ if not utils.BITCOIN_ONLY:
br_name=br_name,
)
+ # TODO: #6364 Consider simplifying with confirm_tron_send like ETH flows.
+ async def confirm_tron_transfer(
+ recipient_addr: str,
+ total_amount: str,
+ maximum_fee: str,
+ chunkify: bool = True,
+ ) -> None:
+
+ br_name = "confirm_tron_transfer"
+ title = TR.words__send
+
+ await confirm_value(
+ title,
+ recipient_addr,
+ "",
+ chunkify=chunkify,
+ br_name=br_name,
+ verb=TR.buttons__continue,
+ cancel=True,
+ )
+
+ properties: list[PropertyType] = [
+ (
+ f"{TR.words__amount}:",
+ total_amount,
+ False,
+ ),
+ (f"{TR.words__chain}:", "Tron", True),
+ ]
+
+ await confirm_properties(
+ br_name,
+ title,
+ properties,
+ None,
+ False,
+ verb=TR.buttons__continue,
+ )
+
+ await raise_if_not_confirmed(
+ trezorui_api.confirm_summary(
+ amount=None,
+ amount_label=None,
+ fee=maximum_fee,
+ fee_label=f"{TR.send__maximum_fee}:",
+ title=title,
+ account_title=TR.address_details__account_info,
+ extra_title=TR.confirm_total__title_fee,
+ ),
+ br_name=br_name,
+ )
+
def confirm_joint_total(spending_amount: str, total_amount: str) -> Awaitable[None]:
return confirm_properties(
diff --git a/core/src/trezor/ui/layouts/delizia/__init__.py b/core/src/trezor/ui/layouts/delizia/__init__.py
index 47ddb0d3..2c4ed13a 100644
--- a/core/src/trezor/ui/layouts/delizia/__init__.py
+++ b/core/src/trezor/ui/layouts/delizia/__init__.py
@@ -1444,6 +1444,55 @@ if not utils.BITCOIN_ONLY:
br_code=ButtonRequestType.SignTx,
)
+ # TODO: #6364 Consider simplifying with confirm_tron_send like ETH flows.
+ async def confirm_tron_transfer(
+ recipient_addr: str,
+ total_amount: str,
+ maximum_fee: str,
+ chunkify: bool = True,
+ ) -> None:
+
+ br_name = "confirm_tron_transfer"
+ title = TR.words__send
+
+ await confirm_value(
+ title,
+ recipient_addr,
+ "",
+ subtitle=TR.words__recipient,
+ chunkify=chunkify,
+ br_name=br_name,
+ verb=TR.buttons__continue,
+ cancel=True,
+ )
+
+ properties: list[PropertyType] = [
+ (
+ f"{TR.words__amount}:",
+ total_amount,
+ False,
+ ),
+ (f"{TR.words__chain}:", "Tron", True),
+ ]
+
+ await confirm_properties(
+ br_name,
+ title,
+ properties,
+ None,
+ False,
+ verb=TR.buttons__continue,
+ )
+
+ await _confirm_summary(
+ None,
+ None,
+ maximum_fee,
+ f"{TR.send__maximum_fee}:",
+ title,
+ None,
+ )
+
# TODO: #6359 Reword the TR strings to be ETH agnostic.
async def confirm_tron_approve(
recipient_addr: str,
diff --git a/core/src/trezor/ui/layouts/eckhart/__init__.py b/core/src/trezor/ui/layouts/eckhart/__init__.py
index 72b90b24..40aa43c1 100644
--- a/core/src/trezor/ui/layouts/eckhart/__init__.py
+++ b/core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -1473,12 +1473,61 @@ if not utils.BITCOIN_ONLY:
br_code=ButtonRequestType.SignTx,
)
- # TODO: #6359 Reword the TR strings to be ETH agnostic.
+ # TODO: #6364 Consider simplifying with confirm_tron_send like ETH flows.
+ async def confirm_tron_transfer(
+ recipient_addr: str,
+ total_amount: str,
+ maximum_fee: str,
+ chunkify: bool = True,
+ ) -> None:
+
+ br_name = "confirm_tron_transfer"
+ title = TR.words__send
+
+ await confirm_value(
+ title,
+ recipient_addr,
+ "",
+ subtitle=TR.words__recipient,
+ chunkify=chunkify,
+ br_name=br_name,
+ verb=TR.buttons__continue,
+ cancel=True,
+ )
+
+ properties: list[PropertyType] = [
+ (
+ f"{TR.words__amount}:",
+ total_amount,
+ False,
+ ),
+ (f"{TR.words__chain}:", "Tron", True),
+ ]
+
+ await confirm_properties(
+ br_name,
+ title,
+ properties,
+ None,
+ False,
+ verb=TR.buttons__continue,
+ )
+
+ await _confirm_summary(
+ None,
+ None,
+ maximum_fee,
+ f"{TR.send__maximum_fee}:",
+ title,
+ None,
+ )
+
+ # TODO: #6359 Redo as ETH confirm_tx
async def confirm_tron_approve(
recipient_addr: str,
total_amount: str,
maximum_fee: str,
- chunkify: bool = False,
+ chunkify: bool = True,
) -> None:
br_name = "confirm_tron_approve"
diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json
index 63ac2bbf..616e322f 100644
--- a/tests/ui_tests/fixtures.json
+++ b/tests/ui_tests/fixtures.json
@@ -5897,7 +5897,7 @@
"T2T1_en_tron-test_get_address.py::test_invalid_path": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75",
"T2T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[Note_hello_world]": "4a1ee9e18ac119ad6327008f96964676daafe7db3b42713915d877661e60730f",
"T2T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TransferContract]": "253f793c76f1d12726600114e598741cc91529df36b5820b5d7fd6d80ff53736",
-"T2T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "301ac7877fc96d14c62fac7de286191072ba39caf26676b5fd4042ae63f33596",
+"T2T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "e9bba05188d12d600d9baf877751165e1c31f779ca1a6587b17549ac9499857f",
"T2T1_en_tron-test_sign_tx.py::test_sign_tx[Note_bad_utf8_bytes]": "dfa388e99e97dbca5309a0ee7603e3e3fc6cce4fc129d062a1bac384eecc14c4",
"T2T1_en_tron-test_sign_tx.py::test_sign_tx[Note_hello_world]": "2ad2d76dd6c5830f3ae206390d14acdf5268e2f59e85f97ee8af8735475b906e",
"T2T1_en_tron-test_sign_tx.py::test_sign_tx[Note_too_long_string]": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75",
@@ -5905,7 +5905,7 @@
"T2T1_en_tron-test_sign_tx.py::test_sign_tx[TransferContract_amount_int64_max]": "30a10f207067a9b36a9b9dff2f2b1c2a6e8aedfb09422b0e2bfc966e475a7b28",
"T2T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_fee_too_high]": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75",
"T2T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_transfer]": "2d793998203f8fca2ffbf741a4ffd6ea68e9ae8d78267c90608b40d8ff1859dc",
-"T2T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "dbaa4db3d9a653f2a5fc021c70abaa10097492c61da5c9e3e7c5ba4178d4b325",
+"T2T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "d9666a3d0e544ac2ffdb08a755c49de5204d8ae92ae48c2f208c8f8d0bb70c53",
"T2T1_en_webauthn-test_msg_webauthn.py::test_add_remove": "3e750190e9608a1ea76f00e5ec220576cc7842567c1f01c61a1d5b11ed3ed7c2",
"T2T1_en_webauthn-test_u2f_counter.py::test_u2f_counter": "f740248e1b4e4289052807569a0a1defdd88d8fc0532a47850da0ea26277276d",
"T2T1_en_zcash-test_sign_tx.py::test_external_presigned": "2492bd16ea82738cec9778d3afed746d21da8181d7e2782b701fa240dffbb42a",
@@ -15554,7 +15554,7 @@
"T3B1_en_tron-test_get_address.py::test_invalid_path": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3",
"T3B1_en_tron-test_sign_tx.py::test_cancel_sign_tx[Note_hello_world]": "432a88cc5b21d2bc896729e6974185c4762951338dcd628d01a14d475366d804",
"T3B1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TransferContract]": "46a0135d2a61e245fa02a8cb4a0194f537f07ae105c4f279b5eb4ce962bab9b1",
-"T3B1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "786c572dc3bebd40cea01f402e83b6867501366d82a7ef96acd17d85cdc92935",
+"T3B1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "2a5b45c36d73923e8c22169aaea9cc0b5dd1611a7dbe8afe81bd5470ca5a432a",
"T3B1_en_tron-test_sign_tx.py::test_sign_tx[Note_bad_utf8_bytes]": "f7b9a208dd7b4ec02b5be200597e683bdc0b992b39efe4729a5bc028dff9aabe",
"T3B1_en_tron-test_sign_tx.py::test_sign_tx[Note_hello_world]": "a83c6825cb9bffa876b6f5e79e4825f3492581475f55bbaa9e16ca275a0a71ac",
"T3B1_en_tron-test_sign_tx.py::test_sign_tx[Note_too_long_string]": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3",
@@ -15562,7 +15562,7 @@
"T3B1_en_tron-test_sign_tx.py::test_sign_tx[TransferContract_amount_int64_max]": "c093db704e3358ad217917b4a44866fdb9e2dab7f5389eef4f42f322022aa712",
"T3B1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_fee_too_high]": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3",
"T3B1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_transfer]": "11bcba03619742fe47ec7d5b3b8011862272aa16450b6fe9853db29c1a48676c",
-"T3B1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "deffe231c37a558706d5f98cdb44c0a199afdaeb69db7a3177b242df16964ef3",
+"T3B1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "db39569e56e0a174da7510aa27eefc39a365979eec3df7f87d5b934544ec18da",
"T3B1_en_webauthn-test_msg_webauthn.py::test_add_remove": "540214b367b638082b9beb67e492b59b5ac523b5dedb972ace9b4caca974ef6e",
"T3B1_en_webauthn-test_u2f_counter.py::test_u2f_counter": "49f01fce798fe9d5a22cea12d7f276fb91cd77c04087b1dd2c3e37a6106cc350",
"T3B1_en_zcash-test_sign_tx.py::test_external_presigned": "6bc1f0b265163f105b4b084e518a23e6a248849f3141d82dbe25538a43d69883",
@@ -25131,7 +25131,7 @@
"T3T1_en_tron-test_get_address.py::test_invalid_path": "cb8641952bec9e793e7d19f281a85a0ca1be2c3397ca5c0cf4ee7ad905429984",
"T3T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[Note_hello_world]": "c7a95104ba935de3a1d1595b729dd7096ebbd2c225b0f9b66c1ce38c4aba5091",
"T3T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TransferContract]": "32761e9ceca97387c01081967e9e8af3c433c4e939c5306ed24f99e61df25430",
-"T3T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "4c161d8f9c89d1d164fc9628f6a153bfa92a930f82dbf007321d7debb0c0508e",
+"T3T1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "77b0a165c7110abbbf7ab53c8374f786da91aabd4fface8b03b62a8a579c4e87",
"T3T1_en_tron-test_sign_tx.py::test_sign_tx[Note_bad_utf8_bytes]": "2399eed308d872b49af399144f519530c8aa87d14b6715b6afb4176512e49431",
"T3T1_en_tron-test_sign_tx.py::test_sign_tx[Note_hello_world]": "113ce04f603d0f774e91dc736720c65a198883d1ef43502d8ec4ef2d1c371864",
"T3T1_en_tron-test_sign_tx.py::test_sign_tx[Note_too_long_string]": "cb8641952bec9e793e7d19f281a85a0ca1be2c3397ca5c0cf4ee7ad905429984",
@@ -25139,7 +25139,7 @@
"T3T1_en_tron-test_sign_tx.py::test_sign_tx[TransferContract_amount_int64_max]": "9b8a7c48467afe408a355141a23a07dad5076806eb0f3f41ac271db50ad4ec05",
"T3T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_fee_too_high]": "cb8641952bec9e793e7d19f281a85a0ca1be2c3397ca5c0cf4ee7ad905429984",
"T3T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_transfer]": "787ed4a1e887ca1dc545c661ea4424d13961d486077aa6c4b8b6242a721e8822",
-"T3T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "9d5420d3bf6271c6620ea869d2a65647360bee67dd457224609e10b804537f60",
+"T3T1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "7743807c314cf963a668590f4d495cbcac761a3c00a27afff0726a1ca7badcc9",
"T3T1_en_webauthn-test_msg_webauthn.py::test_add_remove": "95351793ff4ed12276b36d9223bff9456adce515a4ec26e72c966b351c32e179",
"T3T1_en_webauthn-test_u2f_counter.py::test_u2f_counter": "b81ea8c63aa65f04c3f42b6da5b8c4fda384013d468ca7b24a446863984cc788",
"T3T1_en_zcash-test_sign_tx.py::test_external_presigned": "bd9b80d39f8d7500873d4d385171e1b6ae588b8f792b0639ebcaa99295f90727",
@@ -35104,7 +35104,7 @@
"T3W1_en_tron-test_get_address.py::test_invalid_path": "931d9afceb0ba1e4faae891775819277242d889644d5c0c5863fc8c9fcf859b1",
"T3W1_en_tron-test_sign_tx.py::test_cancel_sign_tx[Note_hello_world]": "156f74d5e2bf5fea58d5a5d90738402d9f0981b7ee3cb4d49f5fdde0f0e806ed",
"T3W1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TransferContract]": "9b7678841ee27d5fa3b8fd40164134afecf543201cc0546ea52de795f9fb743a",
-"T3W1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "daf9e39fe62f053e79516e6585ddbc45c9fc91c2b50c51943aa72ef445cc2306",
+"T3W1_en_tron-test_sign_tx.py::test_cancel_sign_tx[TriggerSmartContract_USDT_transfer]": "8a4c46c542f9dbda9290d6e946f80c6578170e0abdc4a92274b0864d94f3594b",
"T3W1_en_tron-test_sign_tx.py::test_sign_tx[Note_bad_utf8_bytes]": "f95e01701175d6d3b7a3791ecf825df4690cab778c0b42601834e2cff13003c8",
"T3W1_en_tron-test_sign_tx.py::test_sign_tx[Note_hello_world]": "1529a066d1588926d6af34dea77dc3b6956089b6e9a8778dbb0efa4ceb0f1eb8",
"T3W1_en_tron-test_sign_tx.py::test_sign_tx[Note_too_long_string]": "931d9afceb0ba1e4faae891775819277242d889644d5c0c5863fc8c9fcf859b1",
@@ -35112,7 +35112,7 @@
"T3W1_en_tron-test_sign_tx.py::test_sign_tx[TransferContract_amount_int64_max]": "c31377205b23c08c0b7312cf16725e54d35eb7be757748fcf54da60163eb30e7",
"T3W1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_fee_too_high]": "931d9afceb0ba1e4faae891775819277242d889644d5c0c5863fc8c9fcf859b1",
"T3W1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDD_transfer]": "e862d155559a0e7f3e13d8348f81574630c6c098d38960c0378eb7505422d6c9",
-"T3W1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "882db00d3e0f0c180705137c56a1701638f1180557701c51575683c9683baf64",
+"T3W1_en_tron-test_sign_tx.py::test_sign_tx[TriggerSmartContract_USDT_transfer]": "bf28fe17266d77e82daa3a70cd32d987e6b9fe58291d33b2a618ad2310be7d18",
"T3W1_en_webauthn-test_msg_webauthn.py::test_add_remove": "222e1dc521b9a3ccfb91530fd9246664a62a102f1595b78b7207ec9348036017",
"T3W1_en_webauthn-test_u2f_counter.py::test_u2f_counter": "109039621b36fd1ebe09cd76f7e898259aafa80401a8bd91799e448a6e134f79",
"T3W1_en_zcash-test_sign_tx.py::test_external_presigned": "6e96cdb9348ef84aa20f54bbba894fa4e51f6194ee74cbc257d15859311ecb45",
Why this scored 59/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.