How to Build a Bitcoin Ordinals/BRC-20 Tracker with CoinMarketCap API
CoinMarketCap API DIY

How to Build a Bitcoin Ordinals/BRC-20 Tracker with CoinMarketCap API

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.

How to Build a Bitcoin Ordinals/BRC-20 Tracker with CoinMarketCap API

Tabla de contenidos

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.

In this guide, you will build a Bitcoin Ordinals/BRC-20 Tracker with CoinMarketCap API, where:
  • 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,

}
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
0 people liked this article