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:
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
pip install defipipe
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:
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:
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.