D90 — Typed supported-asset vocabulary: the Foundation SupportedAsset enum, with symfony/intl backing the ISO-4217 fiat data (RESOLVED, extends D61/D84)
Architecture decision record, relocated verbatim from the retired single-file
docs/tenancy/decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.
The supported asset/currency set lived as loose strings scattered across treasury (the AssetResolver
hand map, the warm_assets/stablecoin_pegs/rates config keys, SupportedBaseCurrencies's
hardcoded list). It is now a single typed vocabulary: a string-backed
Modules\Foundation\ValueObjects\SupportedAsset enum (the D84 FinanceProvider precedent — a
Foundation enum multiple modules consume without a boundary inversion) carrying each asset's OFFICIAL
notation: type() (the AssetType class), decimals() (ISO-4217 minor units for fiat; canonical
crypto precision — satoshi 1e-8, wei 1e-18, lamport 1e-9; token precision for stablecoins),
symbol()/displayName(), pegAsset() (a stablecoin's fiat peg), chains() (the issuance networks,
in the Chain vocabulary — the chain-awareness the rails data model carries at the wallet level), an
asset() bridge to the Asset VO, and values() for DB CHECK constraints. The V1 set: 9 fiat
(USD/EUR/GBP/CHF/JPY/AUD/CAD/SGD/HKD), 3 crypto (BTC/ETH/SOL), 5 stablecoins
(USDC/USDT/DAI/PYUSD/EURC).
The library decision (dependency adopted): symfony/intl. ISO-4217/CLDR data should not be
hand-transcribed wholesale. Candidates: moneyphp/money and brick/money carry ISO codes + minor
units but no symbols, and each ships a competing Money arithmetic type — hazardous next to our
bespoke brick/math-backed Money VO (D27/D61); alcohol/iso4217 is data-only but symbol-less with
light maintenance; punic is a heavy general-purpose i18n suite. symfony/intl is the only
candidate carrying all three needed facets (code existence, minor units, official symbol + name via
CLDR), is data-only for our use (no parallel Money type), and is the same actively-maintained vendor
family already in the tree (symfony/* 8.1). Crypto + stablecoins are NOT in ISO-4217, so their data
is hand-rolled on the enum regardless — the library backs the FIAT facts.
Pinned-not-proxied decimals (deliberate). Fiat decimals() are PINNED on the enum and VALIDATED
against the library by a test (SupportedAssetTest), not read live: minor units feed money rounding
scale, so an upstream CLDR dataset change must fail a test, never silently shift money math.
Display-only metadata (symbol()/displayName()) IS read live (drift there is harmless). Treasury
now drives its known set (AssetResolver), warm list, base allow-list, and config coherence off the
enum (TreasuryConfigCoherenceTest proves every case is priceable into the default base). Other
modules' loose asset strings (e.g. the account_balances.asset column's CHECK) are per-module
follow-ups, per the D84 each-module-owns-its-schema scoping — tracked in
docs/tracking/conduit-treasury-deferrals.md.