DOCS/EXTENDED DATA

Chain intelligence

Two tables link on-chain activity to exchange order flow. chain_entities groups addresses into anonymous entities; chain_flows records transfers, attributes them to venues and links them to trades. Together with book_l4 they make up L4+ data.

Chain intelligence is included in Enterprise and is an add-on for Desk; keys need the chain scope (see Authentication). The method is explained on Chain intelligence. This page is the column reference for schema 0.9 (draft).

Concepts

  • Address: one address on one chain. The same string on two chains is two addresses.
  • Entity: addresses that on-chain evidence shows share an owner. Entity ids such as E-4417 are random and encode nothing about their addresses. Only institutional wallets (exchanges, protocols, bridges) carry a name, in label.
  • Flow: one transfer between two addresses, attributed to a venue when either side is a known venue wallet.
  • Link: a match between a flow and a trade on a venue, with the method in link_type and a calibrated confidence.

Chains

ChainHistory fromFlows published after
bitcoinJanuary 20193 blocks
ethereumJanuary 201912 blocks
arbitrumMainnet launch, August 2021batch posted to Ethereum
solanaMainnet beta, March 2020finalized commitment
hyperliquidMainnet launch, 20231 block

These five chains make up Phase 1. tron is planned for Phase 2. History goes back to January 2019, or to the chain's launch if later. Clustering and flows are computed over the full history shown. Deposits and withdrawals are attributed for all twelve venues on our plan from Phase 1, including venues whose market data is planned for later phases.

chain_entities

One row per address per version. Clusters are versioned hourly: when new evidence merges two entities or moves an address, the old row is closed (valid_to set) and a new one opened. Querying with a point-in-time filter returns the clustering exactly as it stood then.

ColumnTypeDescription
entity_idstringAnonymous id, for example E-4417. Stable across addresses and venues.
chainenumbitcoin, ethereum, arbitrum, solana or hyperliquid.
addressstringOne address per row, in the chain's canonical form. EVM and Hyperliquid addresses are lowercase hex with 0x.
entity_typeenumexchange, protocol, bridge or unlabeled.
labelstringInstitutional label such as binance:hot. Always null for unlabeled.
cluster_methodenumEvidence that put the address in the entity. See Cluster methods.
confidencefloat64Calibrated probability that the address belongs to the entity, 0.50 to 1.
first_seen, last_seenint64First and last on-chain activity of the address, nanoseconds.
valid_from, valid_toint64Version window, nanoseconds. valid_to is null for the current version.
merged_intostringThe surviving entity_id when this entity was merged into another.
model_versionstringClustering and calibration version, for example 2026.09.

For a backtest at time T, always filter valid_from <= T AND (valid_to IS NULL OR valid_to > T). The current version includes evidence that did not exist at T.

Cluster methods

cluster_methodChainsEvidence
common_inputbitcoinInputs spent together in one transaction share an owner.
deposit_reuseallAddresses that send to the same exchange deposit address belong to that deposit's owner.
gas_fundingethereum, arbitrumA fresh address whose first gas comes from another address, followed by matching activity.
venue_nativehyperliquidThe trading account is the on-chain address, so bridge deposits and trades link directly.

chain_flows

One row per transfer. Market-data tables use venue as the partition key; chain tables use chain, and carry the block time in ts_block next to our ts_capture.

ColumnTypeDescription
flow_idstringStable id for the flow.
entity_idstringEntity the flow belongs to: the non-venue side of a deposit or withdrawal, or the sender of a transfer.
chain, tx_hashenum, stringChain and transaction.
ts_blockint64Block timestamp, nanoseconds.
ts_captureint64When our node received the block, on the same clock as market data.
directionenumdeposit, withdrawal, transfer or bridge.
from_address, to_addressstringSender and receiver.
from_entity, to_entitystringEntity ids of both sides at the time of the flow, where resolved.
assetstringAsset moved, for example USDC.
amountdecimal128(38,18)Amount in asset units.
amount_usddecimal128(38,18)USD value at ts_block.
venueenumAttributed exchange, or null when neither side is a known venue wallet.
link_venue, link_symbol, link_trade_idstringThe trade this flow is linked to, where one was matched. link_trade_id joins to trades.trade_id.
link_typeenumaddress_native or timing_size. See below.
confidencefloat64Calibrated probability that the venue attribution and, where present, the trade link are correct.
model_versionstringAttribution and calibration version.
  • address_native: the venue publishes participant ids and the participant is the on-chain address (Hyperliquid, dYdX). The link is exact; confidence reflects only the clustering behind the entity.
  • timing_size: a flow is matched to trades by timing and size. Each candidate is scored against every competing explanation on the public tape in the same window.
VenuePhaseTrade links
Hyperliquid1address_native
Binance, Bybit, OKX2timing_size
Coinbase, Deribit, Kraken3L4 ids on the venue, timing_size to chain
Bitget3timing_size
KuCoin4timing_size
Bitfinex, Bitstamp4L4 ids on the venue, timing_size to chain
dYdX4address_native

Confidence and calibration

Every link and every address assignment carries a confidence between 0 and 1, and the scores are calibrated, so they can be read as probabilities: among links scored near 0.90, about 90% are correct. Links below 0.50 are not published.

The raw model output is mapped to a probability with isotonic regression, fitted on a labeled set of links whose truth is known and checked on a held-out part of that set. The labeled set has three sources: Hyperliquid links where the deposit, the account and the trades are all public; transfers and trades we make ourselves between our own accounts on covered venues; and exchange addresses that venues publish themselves, such as proof-of-reserves lists. Calibration is refit monthly. Each row records model_version, so a result can be reproduced after a refit.

RELIABILITY · MODEL 2026.09 · HELD-OUT LINKSSYNTHETIC PREVIEW
Predicted confidenceLinksObserved share correct
0.50 to 0.601,8420.56
0.60 to 0.702,3170.64
0.70 to 0.802,9050.76
0.80 to 0.903,6880.85
0.90 to 0.952,4100.93
0.95 to 1.006,9720.98

As a working rule: use links at 0.95 and above for direct joins, 0.80 and above for aggregates over many flows with a sensitivity check, and lower scores only weighted by their confidence.

Update schedule

Deliverychain_flowschain_entities
WebSocket chain_flows channelEach flow once its block reaches the depth in the chains tableNot streamed
RESTQueryable within 60 s of publicationNew version every hour
S3Daily file per chain, before 02:00 UTC the next dayDaily file per chain with the day's hourly versions

A trade link can be added after a flow is first published, once the matching trade is seen. Links added later appear as a new row version with a later ts_capture, so as-of queries never see a link before it existed.

Joining to order flow

With the add-on, book_l4 rows also carry entity_id on venues with L4 (see Order book depth). On Hyperliquid the participant id is the on-chain address, so the join is exact.

From an order to an entity's flows

import polars as pl
from fdc import Client

fdc = Client(api_key="fdc_live_...")
window = dict(start="2027-02-03T13:00:00Z", end="2027-02-03T14:00:00Z")

# largest attributed participant on Hyperliquid BTC-PERP fills in the hour
l4 = fdc.book(venue="hyperliquid", symbol="BTC-PERP", depth="L4", **window).to_polars()
top = (l4.filter(pl.col("event") == "fill")
         .group_by("entity_id").agg(pl.col("fill_size").sum())
         .sort("fill_size", descending=True)[0, "entity_id"])

# every flow of that entity, high-confidence links only
flows = fdc.chain_flows(entity_id=top, min_confidence=0.8, **window).to_polars()

Point-in-time join in SQL

The same join from S3 files, with the clustering as it stood at each fill. participant_id is the maker of a fill and counterparty_id the taker.

DUCKDB SQL
WITH fills AS (
  SELECT ts_capture, counterparty_id AS taker, side, price, fill_size
  FROM read_parquet('s3://fdc-archive/book_l4/venue=hyperliquid/date=2026-09-15/BTC-PERP.parquet')
  WHERE event = 'fill'
),
ent AS (
  SELECT address, entity_id, confidence, valid_from, valid_to
  FROM read_parquet('s3://fdc-archive/chain_entities/chain=hyperliquid/date=*/entities.parquet')
)
SELECT e.entity_id,
       count(*)                    AS fills,
       sum(f.price * f.fill_size)  AS taker_notional
FROM fills f
JOIN ent e
  ON e.address = f.taker
 AND e.valid_from <= f.ts_capture
 AND (e.valid_to IS NULL OR e.valid_to > f.ts_capture)
WHERE e.confidence >= 0.95
GROUP BY e.entity_id
ORDER BY taker_notional DESC
LIMIT 20;

Flows to trades on another venue

Where a flow has a matched trade, link_venue, link_symbol and link_trade_id join it to trades. Use ts_capture on both sides; it is the same clock for chain and market data, so the join reflects what was knowable at the time.

PYTHON
linked = flows.filter(pl.col("link_trade_id").is_not_null())

trades = fdc.trades(venue="binance", symbol="BTC-PERP", **window).to_polars()

matched = linked.join(trades, left_on="link_trade_id", right_on="trade_id", how="inner",
                      suffix="_trade")

Binance market data is planned for Phase 2, subject to a data agreement with the exchange; until then the same code returns flows attributed to Binance with no trade links.

REST and bulk access

GET/v1/chain_flows

Parameters: entity_id, chain, venue, direction, asset, link_type, min_confidence (default 0.5), min_amount_usd, start, end. Time filters apply to ts_capture.

GET/v1/chain_entities

Parameters: address, entity_id, chain, entity_type, as_of (default now), history=true for every version, min_confidence.

Both follow the pagination, format and error conventions of the REST API. In S3, files sit under chain_flows/chain={chain}/date={YYYY-MM-DD}/flows.parquet and chain_entities/chain={chain}/date={YYYY-MM-DD}/entities.parquet; see Bulk S3.

Limitations

  • Movements inside a centralized venue are invisible. A deposit shows that funds arrived; which account received them stays unknown.
  • timing_size links are statistical. In busy markets several trades can fit a deposit; the confidence score reflects that competition, and many deposits get no link at all.
  • Custodial and omnibus wallets hold funds for many owners. They are labeled where known and their users are not clustered.
  • Mixers and privacy pools end the trail at the pool contract.
  • Bridges other than the Hyperliquid bridge are linked only where the bridge emits matching events on both chains.
  • Entities, never identities. We use public blockchain and market data only, collect no names or contact details, and do not attempt to link an entity to a person. The data license prohibits using chain intelligence to identify or target individuals. Questions go to chris@financedatacloud.com.