Tutorial

How to get per-block DeFi data in Python

Most DeFi data is built from events: swaps, deposits, liquidations. But the numbers quants actually backtest against, like borrow rates, pool prices, and staked supply, are state: the answer a contract's read function gives at a specific block. This tutorial fetches that state as clean per-block time series in Python. It takes about five minutes and the first half needs no account or API key.

1. One request, no key

Every series has a canonical ID of the form {chain}:{protocol}:{version}:{instance}[/{scope}]:{kind}:{metric}. Here is Lido's total staked ETH at hourly resolution, straight from the terminal:

Terminal
curl "https://api.defipipe.io/v1/data?series=eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:totalsupply&freq=1h&from=2026-07-09&to=2026-07-12"

The response is a shared time index plus, per series, three parallel arrays: the unit-scaled floats, the exact on-chain integers behind them (uint256-safe strings), and the source block of every point. Each value is the last on-chain observation at or before its boundary, resolved per block. No interpolation, and never a value from later than the boundary, which means no lookahead bias when you backtest on it.

2. The same thing as a DataFrame

Terminal
pip install defipipe
Python
from defipipe import Client

client = Client()  # no key: free tier, 15m resolution and coarser

df = client.data([
    "eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2/weth:call:getreservedata[currentvariableborrowrate]",
    "eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:totalsupply",
], freq="1h", since="2026-07-09", until="2026-07-12")

df.tail()
df.attrs["units"]  # the unit/decimals recipe behind every float

Two series from two unrelated protocols, one clean time axis. The column names are the canonical series IDs; find more in the catalog, where every dataset page has copy buttons for its IDs and a live preview chart.

3. A real analysis in ten lines

Is the Aave WETH borrow rate unusually high right now relative to its own recent history? Z-score it:

Python
rate = df["eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2/weth:call:getreservedata[currentvariableborrowrate]"]

window = 24 * 7  # one week of hourly bars
z = (rate - rate.rolling(window).mean()) / rate.rolling(window).std()

print(f"current borrow rate z-score: {z.iloc[-1]:+.2f}")
print(f"correlation with staked ETH: {df.corr().iloc[0, 1]:+.3f}")

This is the pattern behind most rate and carry signals: per-block state, aligned without lookahead, then ordinary pandas. Nothing here is pre-aggregated by us; you are working from what the contracts actually returned.

4. When you need every block

Bars are right for analysis; for microstructure work you want every block. With an API key (plans), the same call takes freq="block", and raw=True returns the untouched on-chain integers as exact Python ints (arbitrary precision, no float loss):

Python
raw = Client(api_key="dp_...").data(
    ["eth:uniswap:v3:0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640:call:slot0[sqrtpricex96]"],
    freq="block", since="2026-07-11", until="2026-07-12", raw=True,
)
raw.head()  # one row per block; exact on-chain integers

Windows of any size work: the client pages through the API cursor transparently. Rate limits and tier windows are covered in the API reference.

Where to go next

Browse the dataset catalog for every covered protocol, read the API reference for the full grammar and endpoints, or check your live entitlements at GET https://api.defipipe.io/v1/limits. Missing a pool or market? Coverage is registry-driven and ships fast: request it.