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/aligned?series=eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:gettotalpooledether&freq=1h"

The response is a shared time index plus one value column per series. Each value is the last on-chain observation at or before that 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, 5m resolution and coarser

df = client.aligned([
    "eth:aave:v3:0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2/weth:call:getreservedata[currentvariableborrowrate]",
    "eth:lido:v2:0xae7ab96520de3a18e5e111b5eaab095312d7fe84:call:gettotalpooledether",
], freq="1h")

df.tail()

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

Aligned bars are right for analysis; for microstructure work you want the raw rows. With an API key (plans), the same client returns every observation with the exact uint256 value as a string:

Python
raw = Client(api_key="dp_...").series(
    "eth:uniswap:v3:0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640:call:slot0[sqrtpricex96]",
    since="2026-07-01",
)
raw.head()  # block_number -> ts, value_raw, value

value_raw is the untouched on-chain integer (uint256-safe as a string); value is the unit-scaled float. Pagination, 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.