refactor(core/stellar): describe amounts with StellarToken instead of StellarAsset.
What changed, and why it matters
This is a code cleanup (refactor) in the Stellar cryptocurrency support. It replaces an older way of describing tokens on screen with a new internal data structure, but explicitly keeps the same user-visible behavior: every token is still shown with seven decimal places. The change also adds a test confirming that for the native Stellar asset (XLM), any code or issuer supplied by a host computer is ignored, preventing a host from mislabeling XLM as another asset. There is no indication this fixes an active security bug; it is preparation for future SEP-41 token support.
No immediate action required. Treat as routine maintenance/refactor. Monitor follow-up commits that may actually enable non-7-decimal SEP-41 tokens, as that is where display-precision bugs could affect user confirmations.
Security signals we found
Refactor only; vendor explicitly claims no behavior change
New defensive test ignores code/issuer for native asset to prevent host mislabeling
Removes hard-coded 7-decimal assumption in formatting helper, though current behavior remains 7 decimals
No changelog entry
No CVE, advisory, or security disclosure referenced
Evidence from the diff
The commit refactors Stellar amount formatting. It introduces StellarToken (symbol, decimals, optional issuer) and replaces format_asset/format_amount helpers. All call sites now use StellarToken.from_asset(asset) or NATIVE_TOKEN. The UI layout functions accept StellarToken instead of StellarAsset. The commit message states ‘No behavior change: every token still resolves to seven decimals.’ A new test verifies that a NATIVE asset hint with forged code/issuer fields is normalized to XLM, which is a defensive hardening check but is presented as part of the refactor rather than a disclosed vulnerability fix.
Changed components
core/src/apps/stellar/layout.pycore/src/apps/stellar/operations/layout.pycore/src/apps/stellar/tokens.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__.pycore/tests/test_apps.stellar.tokens.pyInspect captured patch +190 / −126
### core/src/apps/stellar/layout.py
@@ -7,8 +7,7 @@
from apps.common.paths import address_n_to_str
-from . import consts
-from .tokens import resolve_sep41_token
+from .tokens import NATIVE_TOKEN, StellarToken, resolve_sep41_token
if TYPE_CHECKING:
from buffer_types import AnyBytes
@@ -87,7 +86,9 @@ async def require_confirm_payment_request(
from apps.common.payment_request import parse_amount
- total_amount = format_amount(parse_amount(verified_payment_request), asset)
+ total_amount = StellarToken.from_asset(asset).format(
+ parse_amount(verified_payment_request)
+ )
texts: list[tuple[str | None, str]] = []
refunds = []
@@ -174,7 +175,7 @@ async def require_confirm_final(
raise DataError("Stellar: Invalid account name")
await layouts.confirm_stellar_tx(
- format_amount(fee),
+ NATIVE_TOKEN.format(fee), # the fee is always in XLM
account_name,
account_path,
is_sending_from_trezor_account,
@@ -370,13 +371,13 @@ async def confirm_invoke_contract(
"""
br_name_prefix = "op_invoke" if authorization_title is None else "op_auth"
- asset = resolve_sep41_token(args, network_id)
- if asset is not None:
+ token = resolve_sep41_token(args, network_id)
+ if token is not None:
transfer = _parse_sep41_transfer(args)
if transfer is not None:
await _confirm_sep41_transfer(
transfer,
- asset,
+ token,
args.contract_address,
authorizing_address,
br_name_prefix,
@@ -388,7 +389,7 @@ async def confirm_invoke_contract(
if approve is not None:
await _confirm_sep41_approve(
approve,
- asset,
+ token,
args.contract_address,
authorizing_address,
br_name_prefix,
@@ -405,7 +406,7 @@ async def confirm_invoke_contract(
async def _confirm_sep41_transfer(
transfer: tuple[str, str, int],
- asset: StellarAsset,
+ token: StellarToken,
token_contract: str,
authorizing_address: str,
br_name_prefix: str,
@@ -446,24 +447,24 @@ async def _confirm_sep41_transfer(
await layouts.confirm_stellar_output_amount(
screen_title,
subtitle,
- format_amount(amount, asset),
- asset,
+ token.format(amount),
+ token,
TR.words__amount,
token_contract=token_contract,
)
else:
await layouts.confirm_stellar_output(
to_address,
- format_amount(amount, asset),
+ token.format(amount),
output_index=0, # a Soroban operation is always the only one
- asset=asset,
+ token=token,
token_contract=token_contract,
)
async def _confirm_sep41_approve(
approve: tuple[str, str, int, int],
- asset: StellarAsset,
+ token: StellarToken,
token_contract: str,
authorizing_address: str,
br_name_prefix: str,
@@ -492,16 +493,16 @@ async def _confirm_sep41_approve(
if amount == 0:
# "Revoke approval" already communicates the zero allowance, so
# identify the token instead of displaying the omitted amount.
- display_value = format_asset(asset)
+ display_value = token.symbol
value_label = TR.words__token
else:
- display_value = format_amount(amount, asset)
+ display_value = token.format(amount)
value_label = TR.words__amount
await layouts.confirm_stellar_output_amount(
screen_title,
subtitle,
display_value,
- asset,
+ token,
value_label,
token_contract=token_contract,
)
@@ -735,23 +736,3 @@ def _format_i256(parts: StellarInt256Parts) -> str:
if parts.hi_hi < 0:
value -= 1 << 256
return str(value)
-
-
-def format_asset(asset: StellarAsset | None) -> str:
- from trezor.enums import StellarAssetType
- from trezor.wire import DataError
-
- if asset is None or asset.type == StellarAssetType.NATIVE:
- return "XLM"
- else:
- if asset.code is None:
- raise DataError("Stellar asset code is missing")
- return asset.code
-
-
-def format_amount(amount: int, asset: StellarAsset | None = None) -> str:
- return (
- strings.format_amount(amount, consts.AMOUNT_DECIMALS)
- + " "
- + format_asset(asset)
- )
### core/src/apps/stellar/operations/layout.py
@@ -10,7 +10,8 @@
)
from trezor.wire import DataError, ProcessError
-from ..layout import confirm_invocation, confirm_invoke_contract, format_amount
+from ..layout import confirm_invocation, confirm_invoke_contract
+from ..tokens import NATIVE_TOKEN, StellarToken
if TYPE_CHECKING:
from buffer_types import AnyBytes, StrOrBytes
@@ -87,7 +88,7 @@ async def confirm_bump_sequence_op(op: StellarBumpSequenceOp) -> None:
async def confirm_change_trust_op(op: StellarChangeTrustOp) -> None:
await confirm_value(
TR.stellar__delete_trust if op.limit == 0 else TR.stellar__add_trust,
- format_amount(op.limit, op.asset),
+ StellarToken.from_asset(op.asset).format(op.limit),
description=TR.stellar__limit,
br_name="op_change_trust",
is_data=False,
@@ -100,14 +101,12 @@ async def confirm_change_trust_op(op: StellarChangeTrustOp) -> None:
async def confirm_create_account_op(
op: StellarCreateAccountOp, output_index: int
) -> None:
- from trezor.enums import StellarAssetType
- from trezor.messages import StellarAsset
-
+ token = NATIVE_TOKEN
await confirm_stellar_output(
op.new_account,
- format_amount(op.starting_balance),
+ token.format(op.starting_balance),
output_index=output_index,
- asset=StellarAsset(type=StellarAssetType.NATIVE),
+ token=token,
)
@@ -150,10 +149,10 @@ async def _confirm_offer(
) -> None:
from trezor.messages import StellarManageBuyOfferOp
- from ..layout import format_asset
-
buying_asset = op.buying_asset # local_cache_attribute
selling_asset = op.selling_asset # local_cache_attribute
+ buying_token = StellarToken.from_asset(buying_asset)
+ selling_token = StellarToken.from_asset(selling_asset)
buying: PropertyType
selling: PropertyType
@@ -162,16 +161,16 @@ async def _confirm_offer(
if StellarManageBuyOfferOp.is_type_of(op):
buying = (
TR.stellar__buying,
- format_amount(op.amount, buying_asset),
+ buying_token.format(op.amount),
False,
)
selling = (
TR.stellar__selling,
- format_asset(selling_asset),
+ selling_token.symbol,
False,
)
price = (
- TR.stellar__price_per_template.format(format_asset(selling_asset)),
+ TR.stellar__price_per_template.format(selling_token.symbol),
str(op.price_n / op.price_d),
False,
)
@@ -184,12 +183,12 @@ async def _confirm_offer(
else:
selling = (
TR.stellar__selling,
- format_amount(op.amount, selling_asset),
+ selling_token.format(op.amount),
False,
)
- buying = (TR.stellar__buying, format_asset(buying_asset), False)
+ buying = (TR.stellar__buying, buying_token.symbol, False)
price = (
- TR.stellar__price_per_template.format(format_asset(buying_asset)),
+ TR.stellar__price_per_template.format(buying_token.symbol),
str(op.price_n / op.price_d),
False,
)
@@ -230,20 +229,22 @@ async def confirm_path_payment_strict_receive_op(
op: StellarPathPaymentStrictReceiveOp,
output_index: int,
) -> None:
+ destination_token = StellarToken.from_asset(op.destination_asset)
+ send_token = StellarToken.from_asset(op.send_asset)
await confirm_stellar_output(
op.destination_account,
- format_amount(op.destination_amount, op.destination_asset),
+ destination_token.format(op.destination_amount),
output_index,
- op.destination_asset,
+ destination_token,
address_description=TR.stellar__path_pay,
amount_description=TR.stellar__path_pay,
)
await confirm_stellar_output_amount(
TR.stellar__debited_amount,
f"{TR.words__recipient} #{output_index + 1}",
- format_amount(op.send_max, op.send_asset),
- op.send_asset,
+ send_token.format(op.send_max),
+ send_token,
TR.stellar__pay_at_most,
)
@@ -252,30 +253,33 @@ async def confirm_path_payment_strict_send_op(
op: StellarPathPaymentStrictSendOp,
output_index: int,
) -> None:
+ destination_token = StellarToken.from_asset(op.destination_asset)
+ send_token = StellarToken.from_asset(op.send_asset)
await confirm_stellar_output(
op.destination_account,
- format_amount(op.destination_min, op.destination_asset),
+ destination_token.format(op.destination_min),
output_index,
- op.destination_asset,
+ destination_token,
address_description=TR.stellar__path_pay_at_least,
amount_description=TR.stellar__path_pay_at_least,
)
await confirm_stellar_output_amount(
TR.stellar__debited_amount,
f"{TR.words__recipient} #{output_index + 1}",
- format_amount(op.send_amount, op.send_asset),
- op.send_asset,
+ send_token.format(op.send_amount),
+ send_token,
TR.stellar__pay,
)
async def confirm_payment_op(op: StellarPaymentOp, output_index: int) -> None:
+ token = StellarToken.from_asset(op.asset)
await confirm_stellar_output(
op.destination_account,
- format_amount(op.amount, op.asset),
+ token.format(op.amount),
output_index,
- op.asset,
+ token,
)
### core/src/apps/stellar/tokens.py
@@ -1,8 +1,10 @@
from typing import TYPE_CHECKING
+from trezor import strings
from trezor.crypto.hashlib import sha256
from trezor.wire import DataError
+from .consts import AMOUNT_DECIMALS
from .helpers import STRKEY_CONTRACT, encode_strkey
from .writers import write_asset, write_bytes_fixed, write_uint32
@@ -12,6 +14,43 @@
from trezor.messages import StellarAsset, StellarInvokeContractArgs
+class StellarToken:
+ """Identity of the token an amount is denominated in.
+
+ Only a token backed by a classic asset has an issuer; one that exists purely
+ as a SEP-41 contract does not. The contract being invoked is not part of the
+ identity -- it is a property of the invocation and is passed alongside.
+ """
+
+ def __init__(self, symbol: str, decimals: int, issuer: str | None) -> None:
+ self.symbol = symbol
+ self.decimals = decimals
+ self.issuer = issuer
+
+ @classmethod
+ def from_asset(cls, asset: StellarAsset) -> "StellarToken":
+ """Describe a classic asset."""
+ from trezor.enums import StellarAssetType
+ from trezor.wire import DataError
+
+ if asset.type == StellarAssetType.NATIVE:
+ # A native asset has neither a code nor an issuer, and `write_asset`
+ # leaves both out of the SAC address preimage. They must be ignored
+ # here as well, or a host could relabel XLM as an asset of its choice.
+ return NATIVE_TOKEN
+ if asset.code is None or asset.issuer is None:
+ raise DataError("Stellar: invalid asset definition")
+ return cls(asset.code, AMOUNT_DECIMALS, asset.issuer)
+
+ def format(self, amount: int) -> str:
+ """Format an amount with this token's precision and symbol."""
+ return strings.format_amount(amount, self.decimals) + " " + self.symbol
+
+
+# XLM, the native asset.
+NATIVE_TOKEN = StellarToken("XLM", AMOUNT_DECIMALS, None)
+
+
def sac_address_from_asset(network_id: AnyBytes, asset: StellarAsset) -> str:
"""Derive the address of the Stellar Asset Contract (SAC) of an asset (C...).
@@ -27,7 +66,7 @@ def sac_address_from_asset(network_id: AnyBytes, asset: StellarAsset) -> str:
def resolve_sep41_token(
args: StellarInvokeContractArgs, network_id: AnyBytes
-) -> StellarAsset | None:
+) -> StellarToken | None:
"""Resolve token metadata for the dedicated SEP-41 UI.
Currently, the host may identify a Stellar Asset Contract by supplying its
@@ -44,4 +83,4 @@ def resolve_sep41_token(
return None
if sac_address != args.contract_address:
return None
- return asset
+ return StellarToken.from_asset(asset)
### core/src/trezor/ui/layouts/bolt/__init__.py
@@ -13,7 +13,7 @@
from buffer_types import AnyBytes, StrOrBytes
from typing import Awaitable, Iterable, NoReturn, Sequence
- from trezor.messages import StellarAsset
+ from apps.stellar.tokens import StellarToken
from ..common import ExceptionType, PropertyType, StrPropertyType
from ..properties import AboveThreshold
@@ -1790,21 +1790,15 @@ async def confirm_stellar_output_amount(
title: str,
subtitle: str,
amount: str,
- asset: StellarAsset,
+ token: StellarToken,
description: str | None = None,
token_contract: str | None = None,
) -> None:
- from trezor.enums import StellarAssetType
-
info_items = []
- if asset.type != StellarAssetType.NATIVE:
- info_items = [
- (
- TR.stellar__issuer_template.format(asset.code),
- asset.issuer or "",
- None,
- )
- ]
+ if token.issuer is not None:
+ info_items.append(
+ (TR.stellar__issuer_template.format(token.symbol), token.issuer, None)
+ )
if token_contract:
info_items.append((TR.stellar__token_contract, token_contract, None))
@@ -1826,7 +1820,7 @@ async def confirm_stellar_output(
address: str,
amount: str,
output_index: int,
- asset: StellarAsset,
+ token: StellarToken,
address_description: str | None = None,
amount_description: str | None = None,
token_contract: str | None = None,
@@ -1844,7 +1838,7 @@ async def confirm_stellar_output(
title=TR.words__send,
subtitle=f"{TR.words__recipient} #{output_index + 1}",
amount=amount,
- asset=asset,
+ token=token,
description=amount_description or TR.words__amount,
token_contract=token_contract,
)
### core/src/trezor/ui/layouts/caesar/__init__.py
@@ -12,7 +12,7 @@
from buffer_types import AnyBytes, StrOrBytes
from typing import Awaitable, Callable, Iterable, NoReturn, Sequence
- from trezor.messages import StellarAsset
+ from apps.stellar.tokens import StellarToken
from ..common import ExceptionType, PropertyType, StrPropertyType
from ..menu import Details
@@ -1879,21 +1879,15 @@ async def confirm_stellar_output_amount(
title: str,
subtitle: str,
amount: str,
- asset: StellarAsset,
+ token: StellarToken,
description: str | None = None,
token_contract: str | None = None,
) -> None:
- from trezor.enums import StellarAssetType
-
info_items = []
- if asset.type != StellarAssetType.NATIVE:
- info_items = [
- (
- TR.stellar__issuer_template.format(asset.code),
- asset.issuer or "",
- None,
- )
- ]
+ if token.issuer is not None:
+ info_items.append(
+ (TR.stellar__issuer_template.format(token.symbol), token.issuer, None)
+ )
if token_contract:
info_items.append((TR.stellar__token_contract, token_contract, None))
@@ -1913,7 +1907,7 @@ async def confirm_stellar_output(
address: str,
amount: str,
output_index: int,
- asset: StellarAsset,
+ token: StellarToken,
address_description: str | None = None,
amount_description: str | None = None,
token_contract: str | None = None,
@@ -1931,7 +1925,7 @@ async def confirm_stellar_output(
title=f"{TR.words__send} #{output_index + 1}",
subtitle="",
amount=amount,
- asset=asset,
+ token=token,
description=amount_description or TR.words__amount,
token_contract=token_contract,
)
### core/src/trezor/ui/layouts/delizia/__init__.py
@@ -17,7 +17,7 @@
from buffer_types import AnyBytes, StrOrBytes
from typing import Awaitable, Iterable, NoReturn, Sequence, TypeVar
- from trezor.messages import StellarAsset
+ from apps.stellar.tokens import StellarToken
from ..common import ExceptionType, PropertyType, StrPropertyType
from ..menu import Details
@@ -1811,21 +1811,15 @@ async def confirm_stellar_output_amount(
title: str,
subtitle: str,
amount: str,
- asset: StellarAsset,
+ token: StellarToken,
description: str | None = None,
token_contract: str | None = None,
) -> None:
- from trezor.enums import StellarAssetType
-
info_items = []
- if asset.type != StellarAssetType.NATIVE:
- info_items = [
- (
- TR.stellar__issuer_template.format(asset.code),
- asset.issuer or "",
- None,
- )
- ]
+ if token.issuer is not None:
+ info_items.append(
+ (TR.stellar__issuer_template.format(token.symbol), token.issuer, None)
+ )
if token_contract:
info_items.append((TR.stellar__token_contract, token_contract, None))
@@ -1845,7 +1839,7 @@ async def confirm_stellar_output(
address: str,
amount: str,
output_index: int,
- asset: StellarAsset,
+ token: StellarToken,
address_description: str | None = None,
amount_description: str | None = None,
token_contract: str | None = None,
@@ -1865,7 +1859,7 @@ async def confirm_stellar_output(
title=TR.words__send,
subtitle=subtitle,
amount=amount,
- asset=asset,
+ token=token,
description=amount_description or TR.words__amount,
token_contract=token_contract,
)
### core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -17,9 +17,10 @@
from buffer_types import AnyBytes, StrOrBytes
from typing import Awaitable, Iterable, NoReturn, Sequence, TypeVar
- from trezor.messages import StellarAsset
from trezor.ui.layouts.menu import Details
+ from apps.stellar.tokens import StellarToken
+
from ..common import ExceptionType, PropertyType, StrPropertyType
from ..properties import AboveThreshold
from ..slip24 import Refund, Trade
@@ -1914,21 +1915,15 @@ async def confirm_stellar_output_amount(
title: str,
subtitle: str,
amount: str,
- asset: StellarAsset,
+ token: StellarToken,
description: str | None = None,
token_contract: str | None = None,
) -> None:
- from trezor.enums import StellarAssetType
-
info_items = []
- if asset.type != StellarAssetType.NATIVE:
- info_items = [
- (
- TR.stellar__issuer_template.format(asset.code),
- asset.issuer or "",
- None,
- )
- ]
+ if token.issuer is not None:
+ info_items.append(
+ (TR.stellar__issuer_template.format(token.symbol), token.issuer, None)
+ )
if token_contract:
info_items.append((TR.stellar__token_contract, token_contract, None))
@@ -1950,7 +1945,7 @@ async def confirm_stellar_output(
address: str,
amount: str,
output_index: int,
- asset: StellarAsset,
+ token: StellarToken,
address_description: str | None = None,
amount_description: str | None = None,
token_contract: str | None = None,
@@ -1971,7 +1966,7 @@ async def confirm_stellar_output(
title=TR.words__send,
subtitle=subtitle,
amount=amount,
- asset=asset,
+ token=token,
description=amount_description or TR.words__amount,
token_contract=token_contract,
)
### core/tests/test_apps.stellar.tokens.py
@@ -4,13 +4,18 @@
if not utils.BITCOIN_ONLY:
from trezor.crypto.hashlib import sha256
from trezor.enums import StellarAssetType
- from trezor.messages import StellarAsset
+ from trezor.messages import StellarAsset, StellarInvokeContractArgs
from apps.stellar.consts import (
NETWORK_PASSPHRASE_PUBLIC,
NETWORK_PASSPHRASE_TESTNET,
)
- from apps.stellar.tokens import sac_address_from_asset
+ from apps.stellar.tokens import (
+ NATIVE_TOKEN,
+ StellarToken,
+ resolve_sep41_token,
+ sac_address_from_asset,
+ )
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
@@ -67,5 +72,67 @@ def test_sac_address_from_asset(self):
self.assertEqual(sac_address_from_asset(network_id, asset), expected)
+def _transfer(contract, asset_hint=None):
+ return StellarInvokeContractArgs(
+ contract_address=contract,
+ function_name="transfer",
+ args=[],
+ asset_hint=asset_hint,
+ )
+
+
+@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
+class TestStellarResolveSep41Token(unittest.TestCase):
+ def test_sac_token(self):
+ public_id = sha256(NETWORK_PASSPHRASE_PUBLIC.encode()).digest()
+ issuer = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
+ usdc = StellarAsset(type=StellarAssetType.ALPHANUM4, code="USDC", issuer=issuer)
+ usdc_sac = "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75"
+
+ token = resolve_sep41_token(_transfer(usdc_sac, usdc), public_id)
+ self.assertEqual(token.symbol, "USDC")
+ self.assertEqual(token.decimals, 7)
+ self.assertEqual(token.issuer, issuer)
+
+ # a hint that derives to some other contract is discarded
+ eurc = StellarAsset(type=StellarAssetType.ALPHANUM4, code="EURC", issuer=issuer)
+ self.assertEqual(
+ resolve_sep41_token(_transfer(usdc_sac, eurc), public_id), None
+ )
+
+ def test_native_sac_ignores_code_and_issuer(self):
+ # code and issuer are not part of a native asset and do not enter the
+ # SAC address preimage, so a host must not be able to smuggle them in
+ public_id = sha256(NETWORK_PASSPHRASE_PUBLIC.encode()).digest()
+ native_sac = "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA"
+ forged = StellarAsset(
+ type=StellarAssetType.NATIVE,
+ code="USDC",
+ issuer="GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
+ )
+ token = resolve_sep41_token(_transfer(native_sac, forged), public_id)
+ self.assertEqual(token.symbol, "XLM")
+ self.assertEqual(token.decimals, 7)
+ self.assertEqual(token.issuer, None)
+
+
+@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
+class TestStellarTokenFormat(unittest.TestCase):
+ # Classic assets are always 7-decimal, but a SEP-41 token contract sets its
+ # own precision and must not be rendered with the classic scale.
+ def test_format(self):
+ TESTS = [
+ # the same amount, scaled by the token's own precision
+ (200000000, 7, "XLM", "20 XLM"),
+ (200000000, 8, "SolvBTC", "2 SolvBTC"),
+ ]
+ for amount, decimals, symbol, expected in TESTS:
+ token = StellarToken(symbol, decimals, None)
+ self.assertEqual(token.format(amount), expected)
+
+ def test_format_native(self):
+ self.assertEqual(NATIVE_TOKEN.format(200000000), "20 XLM")
+
+
if __name__ == "__main__":
unittest.main()
### core/tools/upysize_ignore.json
@@ -110,10 +110,6 @@
"src/apps/nem/writers.py": [
"write_bytes_with_len"
],
- "src/apps/stellar/layout.py": [
- "format_asset",
- "format_amount"
- ],
"src/apps/common/address_type.py": [
"tobytes",
"check"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.