An orderbook exchange for stablecoin FX.
Renders the cNGN-USDC spot market through an orderbook UI, with off/on ramping via Busha and Coinbase APIs for instant USD/USDC and NGN/cNGN conversions. Integrated with markets-service for live books and trades.
The app renders cNGN-USDC spot at /trade/cngn-usdc and the front-month cNGN future
(USDC-cNGN-<DDMONYY>) at its lowercased symbol, e.g. /trade/usdc-cngn-30nov26, switched from the
market selector in the header. cNGN-PERP is delisted (exchange #251): out of the selector, and
/trade/cngn-perp is a 404 unless /v1/markets lists it again; /perp no longer redirects. One route, one shell: the /trade layout holds the header, the
wallet, the trading account and the selector, and a switch replaces only the market's panels under
it (keyed by the slug, so a ticket or a book tab never carries over). The slugs live in
lib/market-routes.ts. / opens the front-month future, chosen per request from /v1/markets, or
spot when none is listed (app/route.ts, 307). A slug in another casing is redirected to lowercase
by proxy.ts with its query string, and an unknown slug, or a future series the venue does not
list, is a 404 from the segment's layout. Spot is shown as USDC-cNGN (since 2026-10-14), quoted
the way the future is: prices in cNGN per USDC (about 1,362, two decimals), sizes in USDC (the book
and the ticket can switch to cNGN), and a buy is a buy of USDC, paid in cNGN. The venue and the
engine still trade it as cNGN-USDC; lib/spot-orientation.ts turns the book, tape, candles, stats,
open orders and history over for display, and the ticket works in the engine's terms underneath
(see "Order Contract"). The perp is shown exactly as the engine trades it: USDC per cNGN, sizes in
cNGN, a long is long cNGN. Since 2026-10-08 the venue identifies the markets as cNGN-USDC and cNGN-PERP; the pre-rename
USDCcNGN-SPOT / USDCcNGN-PERP are deprecated aliases until 2027-01-06 and this app no longer
sends them. The display names live in lib/market-labels.ts and nowhere else. The perp is a USDC-settled perpetual on its own stack (numofx/exchange
deploy-cngn-perp-stack.s.sol): its own CashAsset, SRM and TradeModule. The app reads everything
about it from markets-service, with no env of its own:
/v1/marketslistscNGN-PERP(contract_type: perpetual) with aperpobject: mark, index, funding, margin rates, max leverage, and the module, cash and SRM a trader signs and deposits for. Without that listing,/trade/cngn-perpis a 404 (the segment's layout asks the venue, and renders the not-live state only when the venue cannot be read).- Orders are signed exactly as entered (USDC per cNGN, cNGN contracts, a long is the on-chain long)
for the perp's module and asset. A trader's perp margin is their account under the perp SRM,
opened by the first deposit. From 2026-10-04 to the MarginEngine cutover it was also the spot
account; since then spot trades from the wallet's MarginEngine account (see "The future"
below). The perp is winding down: the venue takes every order as reduce-only
(
close_only), the ticket locks to Reduce Only and opens no new perp accounts, and what is left is withdrawn. It is out of the market selector, reached at/perp. "Withdraw" on the Balances tab signs a WithdrawalModule action for the perp's CashAsset, like a spot withdrawal, and the venue pays USDC to the wallet; cash backing an open position is refused by the venue's simulation. "Close" on a position row sends a market order on the opposite side sized in the engine's own cNGN contracts (engineAmountWhole), so the account lands on exactly zero; the venue has no reduce-only flag, the exact size is what keeps a close from becoming a flip. /v1/positions(proxied at/api/positions) serves positions and margin, polled every 15s.- cNGN as margin. When the perp block lists
collateral_assets(the perp stack's own cNGN escrow, whitelisted on the perp SRM as a base asset), "Deposit margin" offers cNGN beside USDC, depositing into that escrow through the same deposit machine; the Balances tab shows one row per asset (cash, then each collateral asset at its index value and the share the SRM credits — 50% for cNGN), and each row's Withdraw signs a WithdrawalModule action for that row's escrow. Withoutcollateral_assetsthe terminal is cash-only, exactly as before. The haircut is what liquidates a leveraged short when the naira strengthens, so the dialog tells a cNGN depositor to post about as much cNGN as they short. cNGN and USDC both count as margin under the SRM's own check (cNGN at its factor), in either direction and at the normal maximum leverage: the venue no longer limits a cNGN-holding account to long USD or to the cNGN posted. A short (long naira) on such an account carries a warning that it doubles the naira exposure. Margin is shown by source: the ticket's "Available to trade" lists the cash, each collateral asset with its value and margin credit, and what positions already use (tooltip: cross-margin, P&L in USDC); the Balances tab has a row per asset with balance, value and margin credit, listing an accepted asset at zero with a Deposit action until it is held; and the Size field takes cNGN as well as USDC, converted at the ticket's own price.
The earlier dated-futures terminal was removed; the perp is a new market, not a restoration of it.
USDC-cNGN-30NOV26 is at /trade/usdc-cngn-30nov26, under the selector's Futures tab: a
cash-settled cNGN future on the venue's MarginEngine (0xA19E…0758), quoted the way it trades,
cNGN per USDC, one contract 1,000 USDC. A long gains as cNGN weakens. Everything about it is read
from its /v1/markets entry (contract_type: future, the future block, fill_band_low /
fill_band_high):
- One account for spot and the future. Only accounts under the MarginEngine can hold the
future, and an account's manager is fixed when it is opened, so the wallet's trading account
is resolved and opened under the engine (
NEXT_PUBLIC_USDCCNGN_MANAGER_ADDRESS, default the engine). Spot settles into the same cash and cNGN escrow, so spot holdings and futures positions margin one balance. The perp is winding down on its SRM and does not join; its terminal resolves its own account there (NEXT_PUBLIC_PERP_MANAGER_ADDRESS), and balances left in that account, perp margin and spot fills from 2026-10-04 on, withdraw from the perp's Balances tab. - Routes follow the venue. A series lives at its symbol lowercased
(
/trade/usdc-cngn-30nov26); a slug of that shape is a 404 unless/v1/marketslists it./opens the front-month future (the nearest unexpired series,app/route.ts), else spot, and the selector's Futures tab lists the same series, so a roll needs no deploy. - Orders are signed for the future's asset and series (
sub_id, the expiry) on the perp's TradeModule, sized in USDC of notional: the venue's unit is 1 USDC (0.001 contract, signed as itsamount_scale, 1e15), so the smallest order is 1 USDC, as on the old SRM's perp. A venue listing noamount_scaleis read as whole contracts. The fee ceiling is per contract (3 USDC, against the venue's 2.5 USDC). Book, trades and history show sizes in USDC. The ticket refuses a limit outside the fill band, and a market order is held inside it. There is no reduce-only: the venue refuses it on the future. - Position and margin come from chain, every 15s: the ledger,
getMarginat initial and maintenance,getPendingVMand the engine's futures rates./v1/positionscovers the perp only. There is no entry price, uPnL or liquidation price; the engine keeps none. - The series closes for new orders at its last trade time (13:00 UTC on 30 Nov) and settles at 14:00; the header counts down to the first.
A second spot market, at /trade/eurc-usdc under the selector's Spot tab: EURC against USDC on
the same MarginEngine account, TradeModule and USDC cash as cNGN-USDC (numofx/exchange
deploy-eurc-spot.s.sol). It is shown as it trades, USDC per EURC to five decimals, sized in whole
EURC, a buy a buy of EURC; there is no inversion and no order_entry_spec, so its book, orders and
fills are read from the engine fields. Fees come from /v1/markets (5 bps taker, 0 maker).
It is dormant until the venue lists it: like a future series, /trade/eurc-usdc is a 404 and
the selector omits it until /v1/markets serves EURC-USDC (markets-service
EURC_SPOT_ASSET_ADDRESS). Orders sign for the asset_address the venue serves, never a default;
with none served the ticket refuses to sign. The escrow has no code default:
NEXT_PUBLIC_EURC_ASSET_ADDRESS— the EURC-USDC escrow (itsasset_address). Set, it adds EURC to Deposit and Withdraw and the account's EURC leg. The build check fails once the venue lists EURC-USDC unless this matches it.NEXT_PUBLIC_EURC_TOKEN_ADDRESS— optional; defaults to Circle EURC0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42on Base mainnet (none on Sepolia).
The app reads the live spot market from markets-service and renders it through the orderbook UI.
There is no mock, preview or sample data anywhere in the render path. Every price, level, trade,
candle and balance on screen is the venue's own or the connected wallet's. When markets-service is
unreachable or serves no spot market, the panels render empty states ("No resting bids", "No chart
data", a — price) rather than invented depth — a trader cannot tell fabricated depth from real
depth, and the prices would be ones nothing can fill at. lib/spot-market.test.mjs pins that
contract.
Set:
MARKETS_SERVICE_URL
Local development against the live venue (what .env.local ships with):
MARKETS_SERVICE_URL=https://api.numofx.comAgainst a local markets-service:
MARKETS_SERVICE_URL=http://127.0.0.1:8080Production must override that local default:
MARKETS_SERVICE_URL=https://api.numofx.comapi.numofx.com is the stable public hostname for markets-service; it is a CNAME onto the Railway
deployment, which also still answers on markets-service-production.up.railway.app. Prefer the
api.numofx.com name everywhere — the Railway hostname is a fallback and should not be handed to
external consumers.
Do not deploy the frontend with MARKETS_SERVICE_URL=http://127.0.0.1:8080.
In production, MARKETS_SERVICE_URL must point at the live markets-service deployment. The frontend throws at request time if NODE_ENV=production and the URL is missing or points at localhost.
The frontend is deployed on Vercel; MARKETS_SERVICE_URL is encoded in that project's production
environment and should be treated as required production configuration rather than tribal knowledge.
The spot order-book panel streams live depth and trades from markets-service over its WebSocket API (GET /v1/ws). The browser connects directly to the socket (no Next.js proxy), so the URL must be client-reachable:
NEXT_PUBLIC_MARKETS_WS_URL— e.g.wss://api.numofx.com/v1/wsin production,ws://127.0.0.1:8080/v1/wslocally.
The client subscribes to the public book and trades channels for the cNGN-USDC symbol, seeds from the snapshot frame, and applies update deltas. Both channels are unauthenticated; the only server-side gate is WS_ALLOWED_ORIGINS on the markets-service deployment, which must include the frontend origin or the browser handshake is rejected. When the socket is unreachable, still connecting, empty or crossed, the panel falls back to the server-rendered REST snapshot — also real venue data, just fetched at page render. A genuinely one-sided live book is shown as it rests rather than being replaced by the older snapshot. When neither source has depth, the ladder says so. That fallback is silent — there is no on-screen indicator of which source is rendering, so a stream that never goes live looks identical to a healthy one. (A "Live liquidity" badge used to signal this and was removed in bf5688e; its absence is what let the spot stream sit permanently in fallback, fixed in c7c2f2e.)
GET /api/strails/egress remains as an ops diagnostic that reports the deployment's current egress IP (used when registering an IP allowlist upstream).
markets-service has no seeding script, admin endpoint, or on-chain auto-discovery. Its market
list is a static registry in Go — services/markets/internal/instruments/registry.go in the
numofx/exchange monorepo — defining spot plus three USDC/cNGN deliverable futures. Each market is
served from GET /v1/markets only when its env var(s) are set on the markets-service deployment:
| Market | Expiry (UTC) | Env vars on markets-service | Live today |
|---|---|---|---|
cNGN-USDC |
— | CNGN_SPOT_ASSET_ADDRESS |
yes |
USDCcNGN-SEP16-2026 |
2026-09-16 14:00 | CNGN_SEP16_2026_FUTURE_ASSET_ADDRESS + CNGN_SEP16_2026_FUTURE_SUB_ID |
yes |
USDCcNGN-NOV30-2026 |
2026-11-30 00:00 | CNGN_NOV30_2026_FUTURE_ASSET_ADDRESS + CNGN_NOV30_2026_FUTURE_SUB_ID |
no |
USDCcNGN-MAY31-2027 |
2027-05-31 00:00 | CNGN_MAY31_2027_FUTURE_ASSET_ADDRESS + CNGN_MAY31_2027_FUTURE_SUB_ID |
no |
The address/sub-id pairs identify the instrument in the on-chain Matching contract. A market whose
pair is unset is simply absent from /v1/markets — that is the only reason a registry entry does
not appear, so an empty [] means none of the pairs are configured on the backend deployment.
Verify the "live today" column against
GET https://api.numofx.com/v1/marketsbefore relying on it. Until 2026-08-08 this table described aCNGN_JUN30_2026_*pair that no longer exists in the registry — it dated from the standalonemarkets-servicerepo and was never updated when the service moved intonumofx/exchange, so it sent readers looking for env vars matching nothing.
The frontend picks its markets out of that list with findSpotMarket, findPerpMarket and
findFutureMarket (lib/market-pickers.ts, used by the terminals and the selector's overview
route alike): spot and the perp are the first entry whose contract_type is spot or perpetual
with base_asset_symbol cNGN and quote_asset_symbol USDC; the future is the first future,
which the venue names USDC over cNGN.
Spot is live again. markets-service serves cNGN-USDC (contract_type=spot,
order_entry_spec=cngn_usdc_spot_v1) from GET /v1/markets, gated on CNGN_SPOT_ASSET_ADDRESS
being set on that deployment. Depth, trades and candles are real, and the spot order translation
contract below is what the engine actually expects.
An earlier revision of this section said spot had been removed in
e75d513(May 2026). That was true at the time and is no longer — verify againstGET /v1/marketsbefore trusting it again.
Legacy override envs: NEXT_PUBLIC_USDCCNGN_APR_FUTURE_ASSET_ADDRESS / NEXT_PUBLIC_USDCCNGN_APR_FUTURE_SUB_ID are no longer read at all — the code that applied them went with the futures UI. Leave them unset.
The app runs on Base mainnet (8453) — the chain the live venue settles on. getAppChain
returns mainnet unless NEXT_PUBLIC_MATCHING_CHAIN_ID=84532 asks for Sepolia by name; an unset or
malformed value lands on mainnet rather than quietly on a testnet whose contracts do not exist here.
NEXT_PUBLIC_BASE_RPC_URL must be set to the venue's keyed Alchemy endpoint on every deployment.
The code falls back to the public https://mainnet.base.org only so a missing env does not hard-fail
locally — that endpoint is shared and aggressively throttled, and a rate limit there is what made
wallet balance reads silently come back empty. Because the var is NEXT_PUBLIC_, the key ships in
the browser bundle: keep the Alchemy key domain-restricted, and rotate it there rather than in code.
Since the unified-account cutover on 2026-10-04, every market trades on one stack: the
perp's TradeModule, quoted in the perp's CashAsset, with the perp's cNGN escrow as the spot asset.
Since the MarginEngine cutover, a wallet's trading account for spot and the future lives under
the MarginEngine; the perp winds down under its StandardManager (SRM). The build check fails until /v1/markets reports the engine as spot's
margin_manager_address (markets-service SPOT_MARGIN_MANAGER_ADDRESS), so the venue moves
first; markets-service's DEPOSIT_MANAGER_ADDRESS should move with it.
GET /v1/markets is the source of truth for the stack: the ticket signs spot orders for the
asset_address and trade_module_address the venue reports, and the perp reads its whole stack
from the perp block. Deposits, withdrawals, the legacy rows and account resolution use the
NEXT_PUBLIC_* envs below, which fall back to the code defaults in
lib/matching-stack-defaults.json; both name the live stack. scripts/check-market-stack.mjs
runs before every build and compares the resolved stack (env if set, else default) against the
venue, failing any build that disagrees, since a half-flipped deployment would deposit into one
escrow and trade another. A production build also fails when the venue cannot be reached.
| Env | Mainnet address (live, and the code default) | What it is |
|---|---|---|
NEXT_PUBLIC_MATCHING_ADDRESS |
0x9E90A9cD13d859Bd6a08168082FB1F6F7405F191 |
Matching — EIP-712 domain, DepositedSubAccount source |
NEXT_PUBLIC_TRADE_MODULE_ADDRESS |
0xDea968188598BA0E3F58A56C0fdfF338C74F699f |
the perp TradeModule — the one module both markets' trades go through; it trades any base asset the order names |
NEXT_PUBLIC_WITHDRAWAL_MODULE_ADDRESS |
0x0a10AE2f5D2482cE1e43bC309D430B8861C2b5aB |
WithdrawalModule — signed withdrawals from accounts Matching holds, submitted by the venue's executor |
NEXT_PUBLIC_SUBACCOUNT_CREATOR_ADDRESS |
0x568890A8D63Ba8a03b6eCbEedA1bD9f6ea014D5D |
periphery for createAndDepositSubAccount |
NEXT_PUBLIC_USDCCNGN_MANAGER_ADDRESS |
0xA19E296021c9DF25b90D96C2A0793CC3D0800758 |
the MarginEngine — the manager the trading account spot and the future share is resolved and created under |
NEXT_PUBLIC_PERP_MANAGER_ADDRESS |
0xDE0423D0a1E15536265C9513d2e0c10DAb5835D4 |
the perp SRM — where perp accounts close out and withdraw during the wind-down |
NEXT_PUBLIC_SUBACCOUNTS_ADDRESS |
0x7019244E25FA416e6Ca2ed2F3cA25277aef72843 |
SubAccounts ERC-721 ledger |
NEXT_PUBLIC_WRAPPED_USDC_ASSET_ADDRESS |
0xA74E49b4Ed7cb176bc02ef4D8a1A3240C9aD4272 |
the perp CashAsset — the USDC deposit escrow and the module's quoteAsset(), holding real USDC |
NEXT_PUBLIC_SPOT_ASSET_ADDRESS / NEXT_PUBLIC_CNGN_ASSET_ADDRESS |
0x37c976bb5d4887a714ef19AF6B83e34fe2f37c98 |
the perp cNGN escrow — the spot asset, the cNGN deposit escrow, and the collateral the SRM credits at its factor |
NEXT_PUBLIC_USDC_TOKEN_ADDRESS |
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
canonical Base USDC, 6 decimals |
The perp's own asset (0xC74EfC8B4808803dBCF439E76Fde076d56625b8E) is read from the perp
block only; no env names it.
Legacy balances. Accounts under the two retired stacks can withdraw but never trade again: Matching has disallowed their modules, and SubAccounts cannot change an account's manager. With
NEXT_PUBLIC_LEGACY_SPOT_MANAGER_ADDRESS,NEXT_PUBLIC_LEGACY_SPOT_USDC_ESCROW_ADDRESSandNEXT_PUBLIC_LEGACY_SPOT_CNGN_ESCROW_ADDRESSset (production: the 2026-09-10 stack above), the spot Assets tab lists a wallet's account on that stack withdraw-only. The original CashAsset0x6B232A21…6fc6and DeliverableFXManager0xcE01f3D7…4d49from before 2026-09-10 are offered nowhere, withdrawals included: that ledger's claims exceed the USDC it holds, so it cannot pay out.
The stack moves as one. None of the Sepolia addresses have code on mainnet, so a half-flipped config is not a degraded app — it builds transactions against contracts that do not exist.
lib/subaccount-deposit-config.test.mjspins both stacks and the mainnet default.
Reachable only with NEXT_PUBLIC_MATCHING_CHAIN_ID=84532:
NEXT_PUBLIC_MATCHING_ADDRESS=0x1599636347FD5bA1fBE21D58AfE0b8B9cbe283FFNEXT_PUBLIC_TRADE_MODULE_ADDRESS=0x0AAE65AaA66Fe7f54486cDbD007956d3De611990NEXT_PUBLIC_WITHDRAWAL_MODULE_ADDRESS=0xfdDb0D00Df6d1569E46e72D35e7B6CEE4Bb7F9FBNEXT_PUBLIC_USDCCNGN_MANAGER_ADDRESS=0x1917960763BF3a0DfA10a05f0a112E828C1A934fNEXT_PUBLIC_WRAPPED_USDC_ASSET_ADDRESS=0xdC3f31B61a2128B3D1ECB8b6f6d0DE82eBd6c7AeNEXT_PUBLIC_USDC_TOKEN_ADDRESS=0x8b3C43D2b2555ca3fc4Fa1BC34544133B8576110
Deposit flow address semantics (naming follows the risk-core deployment artifacts and is easy to invert):
-
NEXT_PUBLIC_WRAPPED_USDC_ASSET_ADDRESSis theWLWrappedERC20Assetcontract (baseinrisk-core/deployments/*/WRAPPED_USDC_DELIVERABLE.json). It receives deposits and is the ERC-20 spender for deposits to an existing subaccount. -
NEXT_PUBLIC_USDC_TOKEN_ADDRESSis the underlying USDC ERC-20 pulled from the wallet (wrappedAssetin the same artifact). The legacyNEXT_PUBLIC_USDC_DELIVERABLE_BASE_ASSET_ADDRESSenv is honored as a fallback alias for the token address. -
The cNGN pair (
NEXT_PUBLIC_CNGN_ASSET_ADDRESS+NEXT_PUBLIC_CNGN_TOKEN_ADDRESS) mirrorsrisk-core/deployments/<chainId>/WRAPPED_CNGN.json, whosebaseis the escrow andwrappedAssetthe token. The asset is the cNGN counterpart toNEXT_PUBLIC_WRAPPED_USDC_ASSET_ADDRESS— what a cNGN deposit approves and pays into, and the id labeling the cNGN leg of a subaccount balance. The token is the ERC-20 pulled from the wallet, which the spot terminal's Assets tab reads. Defaults per chain:Chain Asset (escrow) Token (ERC-20) Token decimals Base mainnet (8453) 0x37c976bb5d4887a714ef19AF6B83e34fe2f37c98(the perp cNGN escrow)0x46C85152bFe9f96829aA94755D9f915F9B10EF5F6 Base Sepolia (84532) 0x1c08f30c204EE18EbBDc161c0f0864AFb826934b0x6B232A2155Bd0C9bf741dB4cf8E7e8A0176A6fc618 Each escrow is verified on-chain:
wrappedAsset()returns the paired token,deposit(uint256,uint256)is present, and none has awlEnabled()gate. The live mainnet escrow is also the spot market'sasset_addressfromGET /v1/marketsand the perp's collateral escrow, so cNGN deposits, cNGN orders and cNGN margin all settle against one contract; the build check enforces that the resolved address matches it. The escrow of the 2026-09-10 stack (0x9d806fd040a719d27a8e5e77dc5ae0ed1e089493) is reachable withdraw-only through the legacy envs.Override the two together or not at all. An escrow only accepts the exact ERC-20 it wraps. Base Sepolia also hosts
0xe2387F04d3858e7Cb64Ef5Ed6617f9B2fcEEAfa2— likewise namedcNGN, but 6 decimals and not wrapped by this venue's escrow. The app pointed at it until 2026-08-11; approving it produces a deposit that cannot settle. Note the decimals differ by chain, so never hardcode 6: the deposit flow reads them from the token contract. -
Deposits may be whitelist-gated on-chain (
WLWrappedERC20Asset.wlEnabled). The app probes for the whitelist at preflight: plainWrappedERC20Assetdeployments (including the current Base Sepolia one) have no gate and deposits are open; on WL deployments only operator-whitelisted subaccounts can deposit, and the create-and-deposit path cannot activate.
Spot, shown USDC-cNGN. The ticket takes a buy or sell of USDC, a limit in cNGN per USDC and a size
in USDC (or cNGN), and signs the engine order under the identity spec cngn_usdc_spot_v1 below:
engine_side = ui_side == BUY ? SELL : BUY (a buy of USDC is a sell of cNGN)
engine_price = 1 / ui_limit, to 18 decimals (rounded up for a buy of USDC, down for a sell,
so the order never trades past the limit typed)
engine_amount = floor(ui_size_usdc * ui_limit) cNGN (a market order converts at its expected fill)
The rounding lives in engineLimitPrice (lib/spot-orientation.ts, tested against exact
rationals). The venue, the matcher and the market maker are unchanged: they see a cNGN-USDC order.
The engine's own contract, which the perp ticket shows as is and spot signs underneath, under the
identity order-entry specs cngn_usdc_spot_v1 and cngn_usdc_perp_v1 (lib/order-entry-spec.ts):
- UI price:
USDC per cNGN(shown to seven decimals; the engine's tick is 1e-18) - UI size:
cNGN(the engine rests whole cNGN, so the signer floors the size) - UI
BUY/ perpLong: acquire cNGN, pay USDC - UI
SELL/ perpShort: deliver cNGN, receive USDC
engine_side = ui_side
engine_price = ui_price
engine_amount = floor(ui_size)
Fill deltas reconcile as:
BUY -> d cNGN = +size, dUSDC = -(size * price)
SELL -> d cNGN = -size, dUSDC = +(size * price)
Until 2026-10-07 the app showed both markets the other way up under usdc_cngn_*_v1, a translation
markets-service did; that is gone from the venue. Since 2026-10-14 spot is shown that way up again,
translated in the app alone.