DeFiLlama TVL
Total value locked on each blockchain, from DeFiLlama.
defillama_tvl — the dataset name to pass to the Obscura API.
What one row means
One blockchain's total value locked (TVL) in USD as reported by DeFiLlama's live `/v2/chains` snapshot endpoint at the moment of a single scrape, for one calendar day (one row per chain per day, upserted repeatedly through that day as the scrape re-runs).
One row per One row per (chain, captured_date) — composite unique key enforced by the upsert's OnConflict on (chain, captured_date); a chain's row for a given day is overwritten each time the task reruns that day..
Point-in-time availability
Every Obscura dataset carries available_date: the calendar day the publisher made the row
available, day-of, with no session rounding. It is the one column a backtest filters on, and it means the
same thing on every dataset in the catalog.
For defillama_tvl: snapshot: available_date = captured_date, a STORED GENERATED column. WHY THAT IS THE DAY IT BECAME AVAILABLE: /v2/chains is a live keyless current-state endpoint with no historical or period parameter, so a TVL reading for a past day cannot be fetched at all — the record becomes obtainable at the instant of the HTTP GET and on no earlier day. EVIDENCE captured_date is the capture day: Utc::now().date_naive() on a live run, BronzeObject::captured_day() on a bronze replay. The column was called `observed_on` until migration 000205 renamed it: that name described the SUBJECT ('the day the value was observed') where the fact it holds is about US, and an availability audit duly misclassified it as a source column.
Refresh cadence
Obscura refreshes defillama_tvl daily — the most frequent scheduled job that re-collects or re-exports it. This is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when a row became public is recorded per row in available_date.
Schema — 8 columns
The full public column list for defillama_tvl, with the meaning of every field. The same
schema is served unauthenticated at https://api.obscura.trade/v1/catalog/defillama_tvl.
| Column | Type | Description |
|---|---|---|
| chain | text · not null | Blockchain/chain display name as returned by DeFiLlama's `name` field (e.g. 'Ethereum', 'Mantle', 'Plasma', 'Robinhood Chain'); half of the composite unique key with captured_date. Free-text from the vendor, not a normalized ticker/slug. |
| captured_date | date · not null | The UTC calendar day the collector CONTACTED DeFiLlama — Utc::now().date_naive() on a live run, the bronze object's capture day on a replay — and the natural per-day snapshot key alongside chain. Part of the primary key, and the column available_date is generated from: /v2/chains serves only the current moment, so the day we asked is the day this reading was obtainable. Named `observed_on` before migration 000205, which described the subject rather than the act. |
| tvl_usd | double precision | Chain's total value locked in USD at scrape time, from the API's `tvl` field. Rows below args.min_tvl_usd (default 0) are filtered out before upsert. |
| token_symbol | text | Native gas/governance token ticker for the chain, from the API's `tokenSymbol` field (e.g. 'ETH', 'MNT', 'XPL'). Nullable — chains without a distinct native token store NULL. |
| chain_id | bigint | EVM chain ID (e.g. 1 for Ethereum, 5000 for Mantle) from the API's `chainId` field. Nullable — non-EVM chains or unassigned IDs come through as NULL. |
| gecko_id | text | CoinGecko slug for the chain's native token, from the API's `gecko_id` field (e.g. 'mantle', 'plasma', 'corn-3'). Stable cross-dataset external identifier that joins to CoinGecko price/market data far more reliably than the collision-prone token ticker. Nullable — chains without a mapped CoinGecko asset come through as NULL. |
| cmc_id | text | CoinMarketCap numeric id for the chain's native token, from the API's `cmcId` field (a numeric string, e.g. '27075'). Stable cross-dataset external identifier keying into CMC data, not collision-prone like the ticker. Nullable — chains without a mapped CMC asset come through as NULL. |
| available_date | date | PUBLIC-availability date = captured_date, STORED generated directly from it. Since the source is a live current-state endpoint with no historical parameter, the day we polled is the day this reading was obtainable — the point-in-time column to filter and join on. |
Access defillama_tvl
Two delivery paths, one identifier. Both require an Obscura account and an active subscription; the catalog entry and the schema above are public.
import obscura
client = obscura.Client("obs_live_…")
df = client.query(
dataset="defillama_tvl",
start="2024-01-01",
)
Create a free account Browse all 95 datasets
Frequently asked questions
What is in the defillama_tvl dataset?
Total value locked on each blockchain, from DeFiLlama. One blockchain's total value locked (TVL) in USD as reported by DeFiLlama's live `/v2/chains` snapshot endpoint at the moment of a single scrape, for one calendar day (one row per chain per day, upserted repeatedly through that day as the scrape re-runs).
How do I avoid look-ahead bias with defillama_tvl?
Filter on defillama_tvl.available_date, the day the publisher made the row public. For this dataset that date is derived as follows — snapshot: available_date = captured_date, a STORED GENERATED column. WHY THAT IS THE DAY IT BECAME AVAILABLE: /v2/chains is a live keyless current-state endpoint with no historical or period parameter, so a TVL reading for a past day cannot be fetched at all — the record becomes obtainable at the instant of the HTTP GET and on no earlier day. EVIDENCE captured_date is the capture day: Utc::now().date_naive() on a live run, BronzeObject::captured_day() on a bronze replay. The column was called `observed_on` until migration 000205 renamed it: that name described the SUBJECT ('the day the value was observed') where the fact it holds is about US, and an availability audit duly misclassified it as a source column. A query of the form WHERE available_date <= '<as-of date>' never sees a row before it existed.
In what formats can I get defillama_tvl?
As a Parquet bulk export (POST https://api.obscura.trade/v1/download) or as JSON from the typed query API (POST https://api.obscura.trade/v1/query), both with dataset="defillama_tvl". The column schema is public at https://api.obscura.trade/v1/catalog/defillama_tvl.
How often is defillama_tvl updated?
Obscura refreshes defillama_tvl on a daily schedule — that is the most frequent scheduled job that re-collects or re-exports the table. It is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when the publisher makes a row available is described by the availability rule above, and is recorded per row in available_date.