How to Build a Kamino Finance Yield Vault Tracker with CoinMarketCap API
CoinMarketCap API DIY

How to Build a Kamino Finance Yield Vault Tracker with CoinMarketCap API

Learn how to build a Kamino Finance yield vault tracker using CoinMarketCap API on Solana.

How to Build a Kamino Finance Yield Vault Tracker with CoinMarketCap API

İçindekiler

Introduction

Kamino Finance dominates Solana lending.

With over $3.5 billion in TVL and a modular two-layer architecture — a permissionless Market Layer and a curator-managed Vault Layer — Kamino has become the foundational yield infrastructure on Solana. Expert curators like Gauntlet, Steakhouse Financial, and Allez Labs manage vaults across risk tiers: Conservative, Balanced, and Aggressive. Users deposit once and let the protocol rebalance automatically.

The challenge for developers is not execution. It is knowing which assets and markets are worth tracking before allocating capital.

CoinMarketCap API solves that. It gives you a structured data layer to discover vault-relevant assets, monitor TVL and price momentum, validate DEX pool liquidity, and apply macro regime filters — all before you interact with any Kamino vault on-chain.

In this guide, you will build a Kamino Finance Yield Vault Tracker with CoinMarketCap API, where:
  • CoinMarketCap API powers the signal engine
  • Kamino SDK and Solana RPC handle on-chain vault state validation

Why Use CoinMarketCap API for a Kamino Yield Vault Tracker?

Kamino gives you yield automation. CoinMarketCap gives you market context.

Instead of blindly depositing into vaults, you can use CoinMarketCap to:

  • discover KMNO and vault-relevant assets with their Solana contract addresses
  • monitor TVL and price momentum across Kamino ecosystem tokens
  • validate DEX pool liquidity for assets in target vaults
  • detect whale transaction flow on Raydium and Orca pools
  • apply macro regime filters before deploying capital
  • identify trending DeFi assets generating yield rotation on Solana

This turns your tracker from a deposit dashboard into a decision-making system.

System Architecture

CoinMarketCap API (Signal Layer)

├─ Asset Discovery (KMNO, vault assets, SPL addresses)

├─ TVL + Price Context (quotes, momentum, tvl_ratio)

├─ DEX Pool Validation (liquidity, LP burn/lock)

├─ Transaction Flow (whale detection)

├─ Sub-minute Candles (momentum)

└─ Macro Regime (fear/greed, altcoin season)



Yield Signal Engine



Kamino SDK / Solana RPC (Validation Layer)



Vault APY + On-chain State Verification

Architecture Clarification

The CoinMarketCap API acts strictly as an off-chain Signal Layer for Kamino vault asset discovery, yield context monitoring, and lending market regime detection. It is not an on-chain yield oracle or vault state engine.

Real vault APYs, utilization rates, borrow/supply rates, liquidation thresholds, and kToken NAV must be validated directly on-chain via Kamino's SDK or Solana RPC. CMC data reflects market conditions with cache delays and does not track individual vault allocations, curator rebalancing events, or Kamino's internal risk parameters.

Project Setup

Python Dependencies

import os

import time

import requests

import pandas as pd

import numpy as np

Environment Variables

CMC_API_KEY  = os.getenv("CMC_API_KEY")

CMC_BASE_URL = "https://pro-api.coinmarketcap.com"

Headers

HEADERS = {

"Accept":            "application/json",

"Content-Type":      "application/json",

"X-CMC_PRO_API_KEY": CMC_API_KEY,

}

Target Assets

# Core assets to track across Kamino vaults

KAMINO_ASSETS = ["KMNO", "USDC", "SOL", "USDT", "JitoSOL", "mSOL"]

# DEX slugs for Kamino-relevant pools on Solana

# Note: "meteora" returns 400 — the correct slug is "meteora-dlmm"

SOLANA_DEX_SLUGS = ["raydium", "orca", "meteora-dlmm"]

Step 1: Map Assets to CoinMarketCap IDs

Resolve all tracked assets to their CoinMarketCap IDs. Use IDs — not symbols — for all subsequent Core API calls. Symbols are ambiguous.

Endpoint

GET /v1/cryptocurrency/map

def map_assets(symbols="KMNO,USDC,SOL,USDT"):

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"]

From each asset object, extract:

  • id — use for all Core API calls
  • platform.token_address — SPL mint address on Solana
  • symbol

Note: /v1/cryptocurrency/map does not support filtering by chain natively. Filter locally by platform.name == "Solana" after fetching results.

kTokens note: Kamino kTokens (vault shares like kSOL, kUSDC) are protocol-internal SPL tokens. They are not indexed in the CMC DEX API unless they have active secondary pools on Raydium or Orca. Track the underlying assets (SOL, USDC) for market signals rather than kTokens directly.

Step 2: Fetch Quotes and TVL Context

Pull market data and TVL for all tracked assets. This is the base layer for yield rotation signals.

Endpoint

GET /v3/cryptocurrency/quotes/latest

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"]
In the V3 API, quote is a list, not a dict. Use next() to extract the USD entry:
def parse_quote(asset):

# quote is a LIST in v3 — use next() to find USD entry by symbol

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"),       # lives inside quote -> USD
"tvl_ratio":      asset.get("tvl_ratio"),  # lives at asset root

}

tvl lives inside quote -> USD. tvl_ratio lives at the asset root. Both may return null — always parse defensively. For KMNO and major Kamino vault assets, TVL is generally populated given Kamino's scale, but treat nulls as expected behavior under network load.

The data field is a list. Build the lookup dict by iterating:

# raw_quotes is a list — build dict keyed by string ID

quotes = {str(a["id"]): parse_quote(a) for a in raw_quotes}

Step 3: Score Assets for Yield Rotation

Rank assets by yield rotation signals before checking vault availability.

def compute_yield_score(quote):

score = 0

# TVL signal — high TVL indicates protocol confidence

tvl = quote.get("tvl") or 0

if tvl > 500_000_000:   score += 30

elif tvl > 50_000_000:  score += 15

# Momentum — rising price suggests active capital rotation

pct_24h = quote.get("pct_change_24h") or 0

if pct_24h > 5:    score += 25

elif pct_24h > 2:  score += 15

elif pct_24h < -10: score -= 20

# Volume — high volume signals active market

vol = quote.get("volume_24h") or 0

if vol > 50_000_000:   score += 25

elif vol > 10_000_000: score += 10

# TVL ratio — price relative to TVL

tvl_ratio = quote.get("tvl_ratio") or 0

if 0 < tvl_ratio < 1:  score += 20  # undervalued relative to TVL

return score

Step 4: Validate DEX Pool Liquidity

Confirm that sufficient on-chain liquidity exists before treating a signal as actionable.

Endpoint

GET /v1/dex/token/pools

def fetch_token_pools(token_address):

url = f"{CMC_BASE_URL}/v1/dex/token/pools"

params = {

"address":  token_address,
"platform": "solana"
}

r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()

return r.json()["data"]

Key fields per pool:

  • exn — DEX name (Raydium, Orca, Meteora)
  • liqUsd - liquidity in USD
  • v24 - 24h volume
  • addr - pool address
  • pubAt - pool creation timestamp
  • lr - locked rate, percentage of LP locked
  • br - burned rate, percentage of LP burned

Note: lr and br are documented as available for Solana pools but are frequently absent in live data. Do not rely on them as a signal. Use liqUsd and v24 as the primary pool quality indicators. For LP lock validation, query the pool contract directly on-chain.

def get_best_pool(pools, min_liquidity=100_000):

valid = [
p for p in pools

if (p.get("liqUsd") or 0) >= min_liquidity

]

return max(valid, key=lambda p: p.get("liqUsd", 0)) if valid else None

Step 5: Get Pool-Level Price and Reserves

Fetch precise pool-level pricing for execution context.

Endpoint

GET /v4/dex/pairs/quotes/latest

def fetch_pool_quote(pool_address, network_slug="solana"):

url = f"{CMC_BASE_URL}/v4/dex/pairs/quotes/latest"

params = {

"network_slug":     network_slug,   # required — disambiguates contracts across chains
"contract_address": pool_address,
"aux": "pool_base_asset,pool_quote_asset,buy_tax,sell_tax"

}

r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()

return r.json()["data"]

network_slug is required alongside contract_address. Omitting it returns a 400 error.

Cache Warning: This endpoint has a ~60s cache. For deposit decisions, validate vault state directly on-chain via Kamino SDK or Solana RPC immediately before submitting any transaction.

Step 6: Discover Active Pairs on Solana DEXs

Find active trading pairs for vault-relevant assets across Raydium, Orca, and Meteora.

Endpoint

GET /v4/dex/spot-pairs/latest

dex_slug is required alongside network_slug. Passing only network_slug returns a 400 error. Make one request per DEX:

def fetch_pairs_for_dex(dex_slug, network_slug="solana"):

url = f"{CMC_BASE_URL}/v4/dex/spot-pairs/latest"

params = {

"network_slug": network_slug,
"dex_slug":     dex_slug,
"sort":         "liquidity",
"sort_dir":     "desc"
}

r = requests.get(url, headers=HEADERS, params=params)

r.raise_for_status()

return r.json()["data"]

def fetch_all_solana_pairs(asset_symbol):
results = {}

for dex in SOLANA_DEX_SLUGS:

try:

pairs = fetch_pairs_for_dex(dex)

matched = [

p for p in pairs

if asset_symbol.upper() in (

p.get("base_asset_symbol",  "").upper(),

p.get("quote_asset_symbol", "").upper()

)

]

if matched:

results[dex] = matched[0]

except Exception:

continue

return results

Price, liquidity, and volume live inside the quote array. Filter by convert_id == "2781" for USD values:

def parse_pair_quote(pair):

quotes = pair.get("quote", [])

usd = next(

(q for q in quotes if str(q.get("convert_id")) == "2781"), {}

)

return {

"dex":        pair.get("dex_slug"),
"price":      usd.get("price"),
"liquidity":  usd.get("liquidity"),
"volume_24h": usd.get("volume_24h"),
}

Step 7: Monitor On-Chain Transaction Flow

Track whale-level activity to detect institutional rotation into or out of vault assets.

Endpoint

GET /v1/dex/tokens/transactions
def fetch_transactions(token_address, min_volume=50_000):

url = f"{CMC_BASE_URL}/v1/dex/tokens/transactions"

params = {

"address":   token_address,
"platform":  "solana",    # "solana" only — "sol" returns 400
"minVolume": min_volume
}

r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()

return r.json()["data"]

Returns raw swap records. Key fields per record: tx (hash), v (volume USD), t0a (base asset), t1a (quote asset). Large inflows into USDC or SOL pools often precede vault deposit surges on Kamino.

Step 8: Sub-Minute Momentum Detection

Track fast price movements on vault assets to detect entry timing signals.

Endpoint

GET /v1/k-line/candles
def fetch_candles(token_address, interval="1min"):

url = f"{CMC_BASE_URL}/v1/k-line/candles"

params = {

"platform": "solana",
"address":  token_address,
"interval": interval    # 1s, 5s, 30s, 1min, 3min

}

r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()

return r.json()["data"]

Each candle is a positional array of 7 elements — not a dict:

Index

Field

[0]

open

[1]

high

[2]

low

[3]

close

[4]

volume

[5]

timestamp, UNIX seconds

[6]

traders, unique trader count

Do not call .get() on a candle. Use parse_candle() to convert:

def parse_candle(c):

return {

"open":      c[0],

"high":      c[1],

"low":       c[2],

"close":     c[3],

"volume":    c[4],

"timestamp": c[5],

"traders":   c[6],

}

Note: /v4/dex/pairs/ohlcv/historical returns 500 in production. Use /v1/k-line/candles for all DEX candle and historical data on Solana.

Step 9: Apply Macro Regime Filters

Yield rotation is more reliable in risk-on macro conditions. Filter out adverse regimes before flagging vault opportunities.

Endpoints

GET /v3/fear-and-greed/latest
GET /v1/altcoin-season-index/latest
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

# Avoid extreme fear; prefer Solana DeFi conditions

return fg > 35 and as_i >= 40

Fear & Greed updates every 15 minutes. Altcoin Season Index: ≥75 signals Altcoin Season, <25 signals Bitcoin Season. Both endpoints are available on the Basic plan.

Identify which Solana DeFi and lending assets are attracting the most volume.

GET /v1/dex/tokens/trending/list   (POST)

Paid Endpoint Warning

Error Code: 1006 [API_KEY_PLAN_NOT_AUTHORIZED]

Message: "Your API Key subscription plan doesn't support this endpoint."

Returns HTTP 403 on the Basic plan.

Basic Plan Fallback

def fetch_trending_defi():

url = f"{CMC_BASE_URL}/v3/cryptocurrency/listings/latest"

params = {

"sort":                   "volume_24h",
"sort_dir":               "desc",
"limit":                  200,
"tag":                    "defi",       # accepted value on Basic
"percent_change_24h_min": 2,
"volume_24h_min":         5_000_000,

}

r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()

return r.json()["data"]

# Filter locally for Solana / Kamino ecosystem assets

KAMINO_TAGS = {"solana-ecosystem", "lending", "defi", "yield-farming"}
def filter_kamino_relevant(assets):

results = []

for asset in assets:

tags = set(asset.get("tags") or [])

if tags & KAMINO_TAGS or asset.get("symbol") in KAMINO_ASSETS:

results.append(asset)

return results

The tag query parameter accepts "defi" on the Basic plan. Filter for Solana-ecosystem and lending tags locally — do not pass "solana-ecosystem" or "lending" directly as query params.

Step 11: Liquidity Quality — CEX Volume Proxy

Paid Endpoint Warning

/v2/cryptocurrency/market-pairs/latest returns HTTP 403 on the Basic plan.

Error Code: 1006 [API_KEY_PLAN_NOT_AUTHORIZED]

Despite documentation listing Basic as supported, live testing confirms 403.

Basic Plan Fallback

def estimate_liquidity(quote):
volume  = quote.get("volume_24h") or 0

mkt_cap = quote.get("market_cap") or 0

return {

"volume_24h":       volume,
"market_cap":       mkt_cap,
"liquidity_signal": "high"   if volume > 20_000_000

else "medium" if volume > 5_000_000

else "low",

}

If you have a paid plan, use /v2/cryptocurrency/market-pairs/latest with aux="effective_liquidity,market_score,market_reputation". Depth fields at quote -> USD -> depth_negative_two are frequently null for DeFi tokens.

Step 12: Historical Backtesting

Paid Endpoint Warning

/v3/cryptocurrency/quotes/historical returns HTTP 403 on the Basic plan.

Despite documentation listing Basic as supported, any time-series request returns 403.

Backtesting requires a paid CMC plan.

Cache: 5 minutes. Cost: 1 credit per 100 datapoints.

def fetch_historical_quotes(asset_id, time_start, time_end, interval="1h"):

url = f"{CMC_BASE_URL}/v3/cryptocurrency/quotes/historical"

params = {

"id":         asset_id,
"time_start": time_start,
"time_end":   time_end,
"interval":   interval,   # "1h", "4h", "daily"

}

r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()

return r.json()["data"]

Use /v1/k-line/candles as the free alternative for DEX-level candle history on Solana.

Step 13: Minimal End-to-End Flow

def run_kamino_vault_tracker(asset_ids, asset_map):

# 1. Macro regime — poll every 15 min
regime = fetch_macro_regime()

# 2. Quotes — raw_quotes is a list, build dict by id
raw_quotes = fetch_quotes(list(asset_ids.values()))
quotes = {str(a["id"]): parse_quote(a) for a in raw_quotes}

results = []

for symbol, asset_id in asset_ids.items():
quote = quotes.get(str(asset_id), {})

# 3. Score the asset
yield_score = compute_yield_score(quote)

if yield_score < 30:

continue

# 4. Pool validation
token_addr = asset_map.get(symbol, {}).get("token_address")

if not token_addr:

continue

try:

pools = fetch_token_pools(token_addr)
best_pool = get_best_pool(pools)

except Exception:

continue

if not best_pool:

continue

# 5. Momentum check

try:

candles = fetch_candles(token_addr, interval="5min")
latest  = candles[-1] if candles else None
traders = latest[6] if latest and len(latest) > 6 else 0

except Exception:

traders = 0

# 6. Regime filter

if not is_regime_favorable(regime):

yield_score -= 20
results.append({

"symbol":       symbol,
"yield_score":  yield_score,
"price":        quote.get("price"),
"tvl":          quote.get("tvl"),
"pct_24h":      quote.get("pct_change_24h"),
"volume_24h":   quote.get("volume_24h"),
"pool_liq":     best_pool.get("liqUsd"),
"pool_dex":     best_pool.get("exn"),
"traders_5min": traders,
})

return sorted(results, key=lambda x: -x["yield_score"])

Pass high-scoring assets to your Kamino SDK layer for on-chain vault APY validation and deposit execution.

Rate Limits and Polling

CoinMarketCap API is REST-only. There is no WebSocket streaming.

Cache Intervals

Endpoint Group

Cache Interval

Quotes, /v3/cryptocurrency/quotes/latest

60 seconds

DEX pairs, /v4/dex/spot-pairs/latest

60 seconds

Pool data, /v1/dex/token/pools

60 seconds

Candles, /v1/k-line/candles

60 seconds

Fear & Greed, Altcoin Season

15 minutes

Historical quotes

5 minutes, paid plan only

Best Practices

  • poll every 60 seconds for all price and pool endpoints
  • poll macro endpoints every 15 minutes
  • cache responses locally between polls
  • use exponential backoff for HTTP 429 errors, rate reset at 60 seconds
def request_with_backoff(fn, retries=3, base_delay=2):

for attempt in range(retries):

try:

return fn()

except requests.exceptions.HTTPError as e:

if e.response.status_code == 429:

time.sleep(base_delay ** attempt)

else:

raise
raise Exception("Max retries exceeded")

Common Mistakes

Parsing quote as a dict in v3

In /v3/cryptocurrency/quotes/latest, quote is a list. Using asset["quote"]["USD"] raises an AttributeError. The correct pattern is:

next((q for q in asset.get("quote", []) if q.get("symbol") == "USD"), {})

Iterating raw_quotes as a dict

The data field from /v3/cryptocurrency/quotes/latest is a list. Build the lookup dict by iterating:

{str(a["id"]): parse_quote(a) for a in raw_quotes}

Passing only network_slug to /v4/dex/spot-pairs/latest

dex_slug is required. Omitting it returns a 400 error. Make one request per DEX slug.

Omitting network_slug from pool quotes

/v4/dex/pairs/quotes/latest requires network_slug alongside contract_address. Omitting it returns a 400 error.

Using "sol" as the platform value

/v1/dex/tokens/transactions only accepts "solana". The abbreviated "sol" returns 400.

Tracking kTokens via the DEX API

Kamino kTokens are protocol-internal vault shares. They are not indexed in the CMC DEX API unless they have active secondary pools. Track the underlying assets (SOL, USDC, etc.) for market signals.

Assuming /v2/cryptocurrency/market-pairs/latest works on Basic

Returns 403 in practice. Use volume_24h from quotes as a liquidity proxy.

Assuming /v3/cryptocurrency/quotes/historical works on Basic

Returns 403 for any time-series request. Backtesting requires a paid plan. Use /v1/k-line/candles for DEX-level candle history.

Using /v4/dex/pairs/ohlcv/historical

Returns 500 in production. Use /v1/k-line/candles for all DEX candle data.

Calling .get() on candle arrays

Candles from /v1/k-line/candles are positional arrays. Use candle[6] for traders — not candle.get("traders").

Treating CMC as a vault state engine

CMC does not track vault APYs, utilization rates, curator rebalancing, or kToken NAV. Always validate vault state on-chain via Kamino SDK or Solana RPC before any deposit.

Final Thoughts

Kamino has made yield on Solana institutional-grade. Curated vaults, risk-tiered strategies, and automated rebalancing remove the manual overhead of managing DeFi positions.

CoinMarketCap API gives you the structured signal layer to decide which assets and market conditions are worth acting on  before you interact with any vault on-chain.

The key separation:

  • CoinMarketCap identifies which assets and regimes are worth tracking
  • Kamino SDK and Solana RPC validate vault state and execute deposits
Better signals lead to better vault decisions.

Next Steps
  • add TVL growth rate tracking to detect vaults gaining inflows
  • set alerts for yield_score threshold breaches per asset
  • integrate Kamino SDK to pull live vault APYs and utilization rates
  • cross-reference CMC momentum signals with on-chain borrow rate changes
  • track curator-managed vault rebalancing events via Solana RPC
  • store snapshots locally to build rolling yield signal history over time
0 people liked this article