Learn how to build a Bitcoin Ordinals and BRC-20 tracker using CoinMarketCap API for BTC price monitoring, BRC-20 token discovery and Bitcoin ecosystem narrative trends.
Bitcoin Ordinals and BRC-20 tokens brought programmability to Bitcoin without changing the base layer.
Ordinals inscribe arbitrary data directly into Bitcoin transaction witness fields, giving each satoshi a unique serial number and the ability to carry NFT-like content. BRC-20 is a token standard built on top of Ordinals: it uses JSON inscriptions to deploy, mint, and transfer fungible tokens on Bitcoin, using Bitcoin's UTXO model as the ledger.
The leading BRC-20 tokens by market cap include ORDI, SATS, and several others. These tokens trade on both CEXs (Binance, OKX) and Bitcoin-native DEXs like UniSat and Magic Eden. They have no smart contract execution environment — settlement is verified by indexers that parse inscription data off-chain.
For developers building trackers, this architecture has a critical implication: there are no EVM contracts, no Solana programs, and no DEX pools indexable by the CMC DEX API. BRC-20 tokens are discovered and tracked through the Core API only, using their CMC IDs assigned when they gain sufficient liquidity for listing.
- CoinMarketCap Core API powers the price, volume, and narrative signal engine
- Bitcoin node, Ordinals indexers, and BRC-20 indexers handle on-chain inscription and transfer state
Why No DEX Endpoints
BRC-20 tokens do not live on EVM, Solana, or BNB Chain. They exist as inscription data on the Bitcoin blockchain. The CMC DEX API indexes EVM, Solana, and BNB Chain AMMs only.
This means:
- /v4/dex/spot-pairs/latest — not applicable for BRC-20
- /v1/dex/token/pools — not applicable for BRC-20
- /v1/k-line/candles — not applicable for BRC-20
All price and market signals come from the Core API. CEX volume on Binance and OKX is the primary liquidity source for BRC-20 tokens.
Architecture Clarification
The CoinMarketCap API acts strictly as an off-chain Signal Layer for BTC price monitoring, BRC-20 token price and momentum tracking, Bitcoin ecosystem narrative trend detection, and macro regime filtering. It is not an Ordinals inscription indexer, BRC-20 transfer monitor, or UTXO state engine.
Real inscription state, BRC-20 transfer validity, mint progress, and UTXO ownership must be validated directly via Bitcoin node RPC or dedicated Ordinals/BRC-20 indexers such as UniSat API or Hiro API.
Project Setup
import os
import time
import datetime
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 BRC-20 assets to track
BRC20_ASSETS = ["ORDI", "SATS", "BTC"]
# Bitcoin/Ordinals ecosystem tags for local filtering
BITCOIN_TAGS = {"bitcoin-ecosystem", "ordinals", "brc-20", "layer-1"}
Step 1: Map BRC-20 Assets to CoinMarketCap IDs
def map_assets(symbols="ORDI,SATS,BTC"):
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"]
BTC has multiple CMC entries. Real Bitcoin is id=1 (rank 1). Filter by rank:
def resolve_btc_id(map_data):
btc_entries = [a for a in map_data if a.get("symbol") == "BTC"]
return min(btc_entries, key=lambda a: a.get("rank") or 9999)["id"] if btc_entries else None
ORDI and SATS may also have multiple entries due to symbol collisions. Filter by slug:
def resolve_by_slug_keyword(map_data, symbol, keyword):
for asset in map_data:
if (
asset.get("symbol") == symbol
and keyword in (asset.get("slug") or "").lower()
):
return asset["id"]
return next((a["id"] for a in map_data if a.get("symbol") == symbol), None)
Key notes:
- BTC is a native L1 coin — platform will be null
- ORDI and SATS are Bitcoin-native — platform will also be null or show Bitcoin as the parent chain
- These tokens have no ERC-20 or SPL contract addresses
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):
# quote is a LIST in v3 — use next() to extract USD entry
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 BRC-20 — no smart contracts
"num_market_pairs": asset.get("num_market_pairs"),
"cmc_rank": asset.get("cmc_rank"),
}
# raw_quotes is a list — build dict keyed by string ID
quotes = {str(a["id"]): parse_quote(a) for a in raw_quotes}
tvl will always be null for BRC-20 tokens. There are no smart contracts. Use volume_24h and num_market_pairs as the primary liquidity signals.
Step 3: Score BRC-20 Tokens
def compute_brc20_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 > 15: score += 30
elif pct_24h > 8: score += 20
elif pct_24h > 3: score += 10
elif pct_24h < -20: score -= 25
if pct_7d > 30: score += 20
elif pct_7d > 15: score += 10
if pct_1h > 3: score += 15
elif pct_1h > 1: score += 8
# Volume — CEX liquidity is the only market for BRC-20
vol = quote.get("volume_24h") or 0
if vol > 50_000_000: score += 25
elif vol > 10_000_000: score += 15
elif vol > 2_000_000: score += 8
# Market cap
mcap = quote.get("market_cap") or 0
if mcap > 500_000_000: score += 15
elif mcap > 100_000_000: score += 8
# CEX depth via num_market_pairs
pairs = quote.get("num_market_pairs") or 0
if pairs > 30: score += 10
elif pairs > 10: score += 5
return score
Step 4: Correlate with BTC Price
BRC-20 tokens are highly correlated with BTC. Track BTC momentum as a leading indicator for BRC-20 sector rotation.
def analyse_btc_correlation(btc_quote, brc20_quotes):
btc_pct24h = btc_quote.get("pct_change_24h") or 0
btc_pct7d = btc_quote.get("pct_change_7d") or 0
correlations = []
for symbol, quote in brc20_quotes.items():
if symbol == "BTC":
continue
token_pct24h = quote.get("pct_change_24h") or 0
token_pct7d = quote.get("pct_change_7d") or 0
# Beta approximation: how much does the token move relative to BTC
beta_24h = (token_pct24h / btc_pct24h) if btc_pct24h != 0 else None
beta_7d = (token_pct7d / btc_pct7d) if btc_pct7d != 0 else None
correlations.append({
"symbol": symbol,
"token_pct24h": token_pct24h,
"btc_pct24h": btc_pct24h,
"beta_24h": beta_24h,
"beta_7d": beta_7d,
"outperforming": token_pct24h > btc_pct24h,
})
return {
"btc_pct24h": btc_pct24h,
"btc_pct7d": btc_pct7d,
"correlations": correlations,
}
Step 5: Discover Bitcoin Ecosystem Narrative Trends
def fetch_bitcoin_listings():
url = f"{CMC_BASE_URL}/v3/cryptocurrency/listings/latest"
params = {
"sort": "volume_24h",
"sort_dir": "desc",
"limit": 200,
"volume_24h_min": 1_000_000,
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
def filter_bitcoin_assets(assets):
# Filter locally — "brc-20" and "ordinals" tags cannot be passed as query params
results = []
for asset in assets:
tags = set(asset.get("tags") or [])
if tags & BITCOIN_TAGS or asset.get("symbol") in BRC20_ASSETS:
results.append(asset)
return results
Step 6: Apply Macro Regime Filters
BRC-20 tokens are extremely high-beta relative to BTC. They perform best during Bitcoin bull runs and altcoin seasons.
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"),
"fear_greed_classification": fg.get("value_classification"),
"altcoin_index": as_idx.get("altcoin_index"),
}
def is_regime_favorable(regime):
fg = regime.get("fear_greed_value") or 0
as_i = regime.get("altcoin_index") or 0
# BRC-20 needs strong greed + altcoin season condition
return fg > 65 and as_i >= 65
Step 7: Track Momentum via Quote Polling
Without DEX candle data, track price acceleration by comparing successive quote polls:
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
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
return {
"price_delta_pct": price_delta_pct,
"vol_delta_pct": vol_delta_pct,
"accelerating": price_delta_pct > 0 and vol_delta_pct > 0,
}
Poll every 60 seconds. Store the previous quote and diff on each cycle. This is the same pattern used for Celestia TIA.
Step 8: End-to-End Flow
def run_brc20_tracker(asset_ids, prev_quotes=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}
btc_id = asset_ids.get("BTC")
btc_q = quotes.get(str(btc_id), {})
# Score BRC-20 tokens
signals = {}
for symbol in ("ORDI", "SATS"):
asset_id = asset_ids.get(symbol)
if not asset_id:
continue
q = quotes.get(str(asset_id), {})
score = compute_brc20_score(q)
if not is_regime_favorable(regime):
score -= 20
momentum = None
if prev_quotes and symbol in prev_quotes:
momentum = compute_momentum_delta(prev_quotes[symbol], q)
signals[symbol] = {
"score": score,
"price": q.get("price"),
"pct_24h": q.get("pct_change_24h"),
"pct_7d": q.get("pct_change_7d"),
"volume_24h": q.get("volume_24h"),
"market_cap": q.get("market_cap"),
"num_pairs": q.get("num_market_pairs"),
"momentum": momentum,
}
btc_correlation = analyse_btc_correlation(
btc_q,
{s: quotes.get(str(id_), {}) for s, id_ in asset_ids.items()}
)
try:
listings = fetch_bitcoin_listings()
bitcoin_assets = filter_bitcoin_assets(listings)
except Exception:
bitcoin_assets = []
bitcoin_trending = [
{
"symbol": a.get("symbol"),
"pct_24h": (a.get("quote") or [{}])[0].get("percent_change_24h"),
}
for a in bitcoin_assets[:10]
]
return {
"signals": signals,
"btc_correlation": btc_correlation,
"bitcoin_trending": bitcoin_trending,
"regime": regime,
"curr_quotes": {s: quotes.get(str(id_), {}) for s, id_ in asset_ids.items()},
}
Pass curr_quotes as prev_quotes on the next cycle.
Rate Limits and Polling
Cache intervals: quotes and listings — 60 seconds. Fear and Greed, Altcoin Season — 15 minutes. There is no DEX polling for BRC-20 tokens. Use exponential backoff for HTTP 429.
Common Mistakes
Using DEX endpoints for BRC-20 tokens
BRC-20 tokens exist as inscription data on Bitcoin. The CMC DEX API only covers EVM, Solana, and BNB Chain. /v4/dex/spot-pairs/latest, /v1/dex/token/pools, and /v1/k-line/candles will not return data for ORDI or SATS.
Not disambiguating BTC by rank
symbol=BTC may return multiple entries. Real Bitcoin is id=1, rank 1. Always filter by rank.
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 BRC-20
There are no smart contracts in the BRC-20 standard. tvl will always be null.
Passing tag="brc-20" as a query parameter
Returns a 400 error. The tag parameter only accepts "all", "defi", or "filesharing". Filter Bitcoin ecosystem assets locally by inspecting the tags array.
Treating CMC as an Ordinals indexer
CMC tracks market prices and CEX volume. It does not track inscription state, BRC-20 transfer validity, or UTXO ownership. For on-chain inscription data, use UniSat API or Hiro API.
Final Thoughts
BRC-20 tokens are the purest expression of Bitcoin-native speculation. No smart contracts. No oracles. No AMMs. Just inscription data, CEX order books, and off-chain indexers.
CoinMarketCap API gives you the market signal layer: price, volume, BTC correlation, and ecosystem narrative trends.
The key separation:
- CoinMarketCap identifies market conditions and BRC-20 sector momentum
- Bitcoin node, UniSat API, and Hiro API validate on-chain inscription and transfer state
Next Steps
- add ORDI and SATS beta tracking relative to BTC on rolling windows
- track BRC-20 sector volume as a Bitcoin narrative activity signal
- integrate UniSat API for inscription mint progress and holder counts
- cross-reference CMC Fear and Greed with Bitcoin dominance for BRC-20 rotation timing
- monitor new BRC-20 CMC listings as an early adoption discovery signal
