Learn how to build a Centrifuge RWA portfolio monitor using CoinMarketCap API for CFG token monitoring and real-world asset narrative discovery.
Centrifuge is the infrastructure layer for real-world assets in DeFi.
Where Ondo Finance tokenises T-bills and Maple Finance originates institutional loans, Centrifuge provides the underlying protocol: a set of smart contracts and a financing primitive called Centrifuge Pools that lets any asset originator — invoice financiers, real estate lenders, trade finance providers — turn their off-chain cash flows into on-chain tranched debt positions.
Centrifuge pools are structured as senior and junior tranches. Senior tranche holders receive lower yield with principal protection. Junior tranche holders absorb first losses in exchange for higher returns. This is standard structured finance applied to DeFi.
By early 2026, Centrifuge has tokenised over $500M in real-world assets across dozens of pools including trade finance, real estate, and consumer credit. The protocol runs as a Polkadot parachain (Centrifuge Chain) but its pools are accessible via Ethereum smart contracts through the Centrifuge Connectors bridge.
CFG is the native token of Centrifuge Chain, used for governance, transaction fees, and staking.
For developers building RWA monitors, the key signals are: CFG price and momentum, RWA narrative trends across the broader sector, comparison against competing RWA tokens, and macro regime conditions that favour institutional on-chain credit.
- CoinMarketCap Core API powers the market signal engine
- Centrifuge's official API and on-chain contracts handle real pool state, NAV, and tranche yields
Why No DEX Endpoints
CFG is the native token of Centrifuge Chain, a Polkadot parachain. It is not an ERC-20 or SPL token. The CMC DEX API indexes EVM, Solana, and BNB Chain AMMs only.
CFG trades primarily on centralised exchanges. Like TIA (Celestia) and STX (Stacks), all price and market signals come from the Core API. CEX volume is the primary liquidity proxy.
Architecture Clarification
The CoinMarketCap API acts strictly as an off-chain Signal Layer for CFG token price monitoring, RWA narrative trend detection, and market regime filtering. It is not a pool NAV oracle, tranche yield feed, or asset originator monitor.
Real pool NAV, tranche yields, asset originator credit risk, and pool utilisation must be validated directly via Centrifuge's official API or Centrifuge Chain RPC.
Project Setup
import os
import time
import requests
CMC_API_KEY = os.getenv("CMC_API_KEY")
CMC_BASE_URL = "https://pro-api.coinmarketcap.com"
HEADERS = {
"Accept": "application/json",
"X-CMC_PRO_API_KEY": CMC_API_KEY,
}
# Core assets — MPL is deprecated (is_active=0), use SYRUP instead
RWA_ASSETS = ["CFG", "ONDO", "SYRUP", "POLYX"]
# RWA tags for local filtering — exact CMC tag strings
RWA_TAGS = {"real-world-assets-protocols", "lending-borowing", "polkadot-ecosystem"}
Step 1: Map Assets
def map_assets(symbols="CFG,ONDO,SYRUP,POLYX"):
url = f"{CMC_BASE_URL}/v1/cryptocurrency/map"
params = {"symbol": symbols}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
def resolve_cfg_id(map_data):
for asset in map_data:
if (
asset.get("symbol") == "CFG"
and "centrifuge" in (asset.get("slug") or "").lower()
):
return asset["id"]
return next((a["id"] for a in map_data if a.get("symbol") == "CFG"), None)
CFG is a Polkadot parachain native token. platform will be null — it is not an ERC-20.
Step 2: Fetch Quotes
def fetch_quotes(ids):
url = f"{CMC_BASE_URL}/v3/cryptocurrency/quotes/latest"
params = {"id": ",".join(str(i) for i in ids)}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
def parse_quote(asset):
usd = next(
(q for q in asset.get("quote", []) if q.get("symbol") == "USD"),
{}
)
return {
"id": asset.get("id"),
"symbol": asset.get("symbol"),
"price": usd.get("price"),
"volume_24h": usd.get("volume_24h"),
"market_cap": usd.get("market_cap"),
"fdv": usd.get("fully_diluted_market_cap"),
"pct_change_1h": usd.get("percent_change_1h"),
"pct_change_24h": usd.get("percent_change_24h"),
"pct_change_7d": usd.get("percent_change_7d"),
"tvl": usd.get("tvl"), # null for CFG — parachain token, not DeFi protocol
"tvl_ratio": asset.get("tvl_ratio"),
"num_market_pairs": asset.get("num_market_pairs"),
}
quotes = {str(a["id"]): parse_quote(a) for a in raw_quotes}
Step 3: Score CFG
def compute_cfg_score(quote):
score = 0
pct_1h = quote.get("pct_change_1h") or 0
pct_24h = quote.get("pct_change_24h") or 0
pct_7d = quote.get("pct_change_7d") or 0
if pct_24h > 10: score += 30
elif pct_24h > 5: score += 20
elif pct_24h > 2: score += 10
elif pct_24h < -15: score -= 25
if pct_7d > 20: score += 20
elif pct_7d > 10: score += 10
if pct_1h > 2: score += 15
elif pct_1h > 0.5: score += 8
vol = quote.get("volume_24h") or 0
if vol > 10_000_000: score += 20
elif vol > 2_000_000: score += 10
mcap = quote.get("market_cap") or 0
if mcap > 200_000_000: score += 15
elif mcap > 50_000_000: score += 8
pairs = quote.get("num_market_pairs") or 0
if pairs > 20: score += 8
elif pairs > 5: score += 4
return score
Step 4: Compare RWA Sector
def compare_rwa_sector(quotes, asset_ids):
comparison = []
for symbol in RWA_ASSETS:
asset_id = asset_ids.get(symbol)
if not asset_id:
continue
q = quotes.get(str(asset_id), {})
comparison.append({
"symbol": symbol,
"market_cap": q.get("market_cap") or 0,
"volume_24h": q.get("volume_24h") or 0,
"pct_change_24h": q.get("pct_change_24h") or 0,
"pct_change_7d": q.get("pct_change_7d") or 0,
})
return sorted(comparison, key=lambda x: -x["pct_change_24h"])
Step 5: Track Momentum via Quote Polling
Without DEX candle data, compare successive polls to detect price acceleration:
def compute_momentum_delta(prev_quote, curr_quote):
prev_price = prev_quote.get("price") or 0
curr_price = curr_quote.get("price") or 0
prev_vol = prev_quote.get("volume_24h") or 0
curr_vol = curr_quote.get("volume_24h") or 0
if prev_price == 0:
return None
return {
"price_delta_pct": ((curr_price - prev_price) / prev_price) * 100,
"vol_delta_pct": ((curr_vol - prev_vol) / prev_vol) * 100 if prev_vol else 0,
"accelerating": curr_price > prev_price and curr_vol > prev_vol,
}
Step 6: RWA Narrative and Macro
def fetch_rwa_listings():
url = f"{CMC_BASE_URL}/v3/cryptocurrency/listings/latest"
params = {"sort": "volume_24h", "sort_dir": "desc", "limit": 200, "volume_24h_min": 500_000}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
def filter_rwa_assets(assets):
results = []
for asset in assets:
tags = set(asset.get("tags") or [])
if tags & RWA_TAGS or asset.get("symbol") in RWA_ASSETS:
results.append(asset)
return results
def fetch_macro_regime():
fg_url = f"{CMC_BASE_URL}/v3/fear-and-greed/latest"
as_url = f"{CMC_BASE_URL}/v1/altcoin-season-index/latest"
fg = requests.get(fg_url, headers=HEADERS).json()["data"]
as_idx = requests.get(as_url, headers=HEADERS).json()["data"]
return {
"fear_greed_value": fg.get("value"),
"altcoin_index": as_idx.get("altcoin_index"),
}
def is_regime_favorable(regime):
return (regime.get("fear_greed_value") or 0) > 50 and (regime.get("altcoin_index") or 0) >= 45
Step 7: End-to-End Flow
def run_centrifuge_monitor(asset_ids, prev_cfg_quote=None):
regime = fetch_macro_regime()
raw_quotes = fetch_quotes(list(asset_ids.values()))
quotes = {str(a["id"]): parse_quote(a) for a in raw_quotes}
cfg_id = asset_ids.get("CFG")
cfg_quote = quotes.get(str(cfg_id), {})
cfg_score = compute_cfg_score(cfg_quote)
if not is_regime_favorable(regime):
cfg_score -= 15
momentum = None
if prev_cfg_quote:
momentum = compute_momentum_delta(prev_cfg_quote, cfg_quote)
rwa_comparison = compare_rwa_sector(quotes, asset_ids)
try:
listings = fetch_rwa_listings()
rwa_assets = filter_rwa_assets(listings)
except Exception:
rwa_assets = []
rwa_trending = [
{
"symbol": a.get("symbol"),
"pct_24h": (a.get("quote") or [{}])[0].get("percent_change_24h"),
}
for a in rwa_assets[:10]
]
return {
"cfg_signal": {
"score": cfg_score,
"price": cfg_quote.get("price"),
"pct_24h": cfg_quote.get("pct_change_24h"),
"volume_24h": cfg_quote.get("volume_24h"),
"market_cap": cfg_quote.get("market_cap"),
"momentum": momentum,
"regime_favorable": is_regime_favorable(regime),
},
"rwa_comparison": rwa_comparison,
"rwa_trending": rwa_trending,
"regime": regime,
"curr_quote": cfg_quote,
}
Pass curr_quote as prev_cfg_quote on the next poll cycle.
Common Mistakes
Using DEX endpoints for CFG
CFG is a Polkadot parachain native token. The CMC DEX API covers EVM, Solana, and BNB Chain only. /v4/dex/spot-pairs/latest, /v1/dex/token/pools, and /v1/k-line/candles will not return data for CFG.
Not filtering CFG by slug
symbol=CFG may return multiple entries. Filter by slug containing "centrifuge".
Parsing quote as a dict in v3
quote is a list. Use next((q for q in asset.get("quote", []) if q.get("symbol") == "USD"), {}).
Expecting tvl to be populated for CFG
CFG is a parachain governance token, not a DeFi protocol with on-chain TVL tracked by CMC. Parse defensively.
Treating CMC as a pool NAV oracle
CMC tracks market prices, not Centrifuge pool NAV or tranche yields. Use Centrifuge's official API for real pool data.
Final Thoughts
The key separation:
- CoinMarketCap identifies market conditions and RWA narrative momentum
- Centrifuge's official API validates real pool NAV, tranche yields, and asset originator state
Next Steps
- compare CFG against ONDO, SYRUP, POLYX for RWA sector rotation signals
- integrate Centrifuge API for live pool NAV and tranche utilisation data
- track new pool launches as a protocol growth signal
