Skip to main content

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.


← Decision log index