Sources
Nine venues feed the price pipeline. The blended price is cNGN-token-only — just three venues (Quidax, uni-base, uni-bsc) contribute to fair-value. The other six are display-only, including the two fiat NGN references (Bybit, Paycrest), which are shown separately as an NGN reference rather than mixed into the cNGN blend.
Bybit P2P — REST + fraud filtering
Bybit's P2P market is the primary NGN/USD reference rate. Raw listings contain manipulated and retail-noise prices, so the engine filters before using any data:
- Ads are filtered by merchant quality: minimum completed order count, completion rate ≥ 90%, and maximum release time
- Prices more than 2% from the median of the remaining ads are removed
- The modal price (most frequently occurring integer NGN rate) of the survivors is used as the side price
The result is the rate the largest cohort of reputable mid-market merchants agree on. This is a fiat NGN rate (naira per USD), not the cNGN token price, so it is shown as an NGN reference and excluded from the cNGN blend.
Quidax order book — REST polling
The Quidax CEX API is polled on a short interval (price_update_interval, default 10s) for the full order book depth on the cNGN/USDT pair. The raw bids and asks are stored in memory and passed directly into the signal layer. Market data hits the public depth endpoint; the configured Quidax API key is used only for authenticated trading and subaccount balance/order calls.
Uniswap V4 pools (Base + BSC) — WebSocket
Pool state is maintained via Alchemy WebSocket subscriptions. The listener subscribes to the PoolManager contract on each chain, filtered by the V4 Swap topic and pool ID:
V4_SWAP_TOPIC = 0x40e9cecb9f5f1f1c5b9c97dec2917b7ee92e57ba5563708daca94dd84ad7112f
Each swap event carries the new sqrtPriceX96 and liquidity values inline, so the pool state cache is updated with zero RPC calls. This is what makes CEX-DEX detection latency sub-second. If the cache is cold at startup, seed_pool_states() is called as a background task to perform the initial state fetch.
The listener also subscribes to ERC-20 Transfer logs for the tracked uni-base / uni-bsc trade wallets on their stablecoin and cNGN token contracts. Wallet-affecting swaps and manual transfers both surface as token transfers, so this lets the engine refresh executable inventory when wallet state changes instead of waiting for the next periodic balance sweep.
AssetChain — WebSocket (display only)
AssetChain hosts a small cNGN/USDT pool. Its price is included in the dashboard price display but excluded from fair-value calculations — volume is negligible and including it would dilute the VWAP without improving accuracy.
Blockradar — REST (display only)
Blockradar provides a quote API for its fixed-rate swap system. Its price is included in the dashboard display but excluded from VWAP and TWAP fair-value calculations — it is a rate-setter, not a price-taker, so it reflects where Blockradar wants to trade, not where the market is.
Paycrest — REST (display only)
Paycrest aggregates fiat on/off-ramp liquidity providers. Its public /v2/markets endpoint (no auth) returns a book of provider offers plus network aggregates. The engine queries the NGN/USDT corridor and derives a bid (best sell/offramp rate) and ask (best buy/onramp rate), restricted to providers above a minimum balance so a dust offer cannot set the top of book, cached ~60s. Like Bybit it is a fiat NGN reference (NGN per USDT), but it is excluded from the fair-value blend, so adding it leaves the blended price unchanged. Its tile also surfaces Paycrest's 24h settled volume and live liquidity, which no other venue exposes.
Textile — REST (display only)
Textile Credit runs an RFQ order book on BSC (Base is currently empty). Its authenticated REST API (/v1/order-book, quotes:read bearer key in TEXTTILE_API_KEY) returns, per direction, the top-of-book bestRateRay and the aggregate depth (availableSellAmount / availableBuyAmount). The engine reads both directions once per cycle — sell cNGN→USDT for the bid, sell USDT→cNGN for the ask — to derive a cNGN/USDT mid and the total two-sided liquidity in USD, cached ~60s. It is a real cNGN token price (~1,398) but excluded from the fair-value blend — the book is thin and concentrated (a handful of makers, most depth in one wallet), so including it would add noise, not accuracy. The tile surfaces its live BSC liquidity; Textile exposes no 24h volume.
Numo — REST (display only)
Numo runs an onchain USDC/cNGN spot order book on Base. Its public /v1/book?symbol=USDCcNGN-SPOT response exposes both reciprocal engine prices and user-facing prices. The engine deliberately reads bids[0] and asks[0] from the nested spot_contract.ui_intent.price, takes their midpoint in cNGN per USDC, then inverts it once to the pipeline's canonical USDC-per-cNGN basis. The visible remaining USDC depth is summed across both returned sides using each order's documented USDC notional and unfilled fraction. Numo is display-only and does not affect fair value, arbitrage, or confidence.
Price Normalisation
All venues quote in different units. The normaliser converts everything to a common basis: USD per 1 cNGN.
| Venue | Raw pair | Conversion |
|---|---|---|
| Bybit P2P | USDT/NGN (e.g. 1600) | 1 / rate → ~0.000625 |
| Quidax | cNGN/USDT | Direct (USDT ≈ USD) |
| Uniswap Base | sqrtPriceX96, cNGN/USDC pool | Unpack Q96 fixed-point |
| Uniswap BSC | sqrtPriceX96, cNGN/USDT pool, inverted | Unpack Q96 fixed-point, invert |
| AssetChain | sqrtPriceX96, cNGN/USDT pool | Unpack Q96 fixed-point, invert |
| Blockradar | cNGN/USDC | Direct |
| Numo | UI intent cNGN per USDC | Midpoint, then invert to USDC per cNGN |
Adding a new pair from any venue only requires adding its string to CNGN_USD_PAIRS or INVERTED_PAIRS in price_aggregation.py.
Blended Price
The blended price combines a VWAP (current snapshot) with two TWAP windows (5-minute and 1-hour). It is published on the prices WebSocket channel and used for arbitrage and portfolio delta management. LP range-setting is intentionally separate and uses venue-local pool history only.
VWAP
The VWAP is computed across the three cNGN-token fair-value venues (Quidax, uni-base, uni-bsc) with each venue weighted by its effective market depth or volume. The weights are not equal — they reflect how much liquidity each venue actually represents. Bybit is a fiat NGN reference and is no longer part of the blend, so its depth proxy no longer feeds the VWAP weighting.
Quidax — The /markets/tickers response includes a vol field (24h traded volume in USDT). This is used directly as the VWAP weight.
Uniswap Base — 24h volume is tracked on-chain. Every V4 Swap event carries the stable-side token delta inline; the engine extracts the USDC amount from each event and accumulates it in a rolling 24h window. First-ever startup seeds the window from swap logs; later restarts and WebSocket reconnects resume from each pool's persisted block cursor instead of rescanning the full 24h. Live events keep the window and cursor current with zero additional RPC calls.
Uniswap BSC — Same on-chain rolling window as uni-base, using the USDT delta from each swap event. Both pools now have independent real volume figures; the previous 0.33× ratio derived from uni-base is removed.
If a venue has no volume data and no derivable proxy, it is excluded from that VWAP cycle with a warning log. There is no silent fallback to equal weighting.
TWAP
TWAP is computed from stored price snapshots in the database. The 5-minute window smooths out short-term noise; the 1-hour window provides a slower-moving anchor. Blockradar and AssetChain snapshots are excluded from TWAP for the same reasons they are excluded from VWAP. If either TWAP window has insufficient history (e.g. at startup), it falls back to the current VWAP.
Confidence Score
The blended price carries a confidence score between 0 and 0.9 (never 1.0 — full confidence is never appropriate for NGN price data). It starts at 0.9 when all blend venues report successfully, and drops by 0.2 for each blend venue that fails to return a valid price. Only the three cNGN fair-value venues count — display-only tiles (Bybit, Paycrest, etc.) do not affect the score. The count is derived from the live fetch each cycle, not hardcoded.
| Blend venues reporting | Confidence |
|---|---|
| All 3 | 0.90 |
| 2 | 0.70 |
| 1 | 0.50 |
| 0 | 0.30 (blend has no price; card is hidden) |
The Numeraire
What unit do we measure profit in?
Consider a DEX↔DEX arbitrage:
- Base leg: spend USDC, receive cNGN
- BSC leg: spend cNGN, receive USDT
Net cNGN delta = zero. We've converted USDC on Base into USDT on BSC at a favourable rate. The profit is naturally USD-denominated: a stablecoin surplus.
So USD is the numeraire because it matches what we actually earn.
There is a deeper layer: as the cNGN issuer, cNGN we buy back is a liability we've extinguished at a discount. Each completed arb round-trip slightly reduces net cNGN supply. This is worth tracking for long-term reporting even if it doesn't affect short-term P&L. Since global cNGN delta is zero per trade, the real risk is not directional cNGN exposure — it is per-chain stablecoin exhaustion and the cross-chain rebalancing cost when BSC is full of USDT but Base is short of USDC.