SPOOR indexes every transaction on Robinhood Chain, resolves the entity behind each address, scores its behaviour against fourteen measurable signals, and streams the result to a terminal, a webhook or your own database — in the same block it happened.
Live stream · rhc-mainnetrpc.mainnet.chain.robinhood.com
BLOCK HEIGHT
…
GAS · AVG
…
ETH
…
● ROBINHOOD CHAIN NATIVE● 1.9s INDEX LAG● 480M LABELLED EDGES● NON-CUSTODIAL● WEBSOCKET + REST + SQL● OPEN METHODOLOGY● 14 BEHAVIOURAL SIGNALS● NO WALLET CONNECT REQUIRED● ROBINHOOD CHAIN NATIVE● 1.9s INDEX LAG● 480M LABELLED EDGES● NON-CUSTODIAL● WEBSOCKET + REST + SQL● OPEN METHODOLOGY● 14 BEHAVIOURAL SIGNALS● NO WALLET CONNECT REQUIRED
01 Activity has shape
Wallets don't move at random.
Every address on Robinhood Chain sits somewhere in a behavioural space. SPOOR places it there. Distance from the core is behavioural distance from ordinary retail flow; size is the balance under control; colour is the cohort the clustering engine assigned it to. Nothing is hand-curated — the map is recomputed every 4 hours from the last 90 days of edges.
Behavioural cluster map90d window · recomputed 04:00 UTC
A balance snapshot tells you what a wallet holds. It doesn't tell you where the money came from or who it moves with. SPOOR stores the transfer graph itself — 480M labelled edges — so co-movement, funding ancestry and timing correlation are queryable in one hop.
Recomputed, not curated
Cohorts come out of a density clustering pass over 14 normalised features. When a wallet's behaviour changes, its cohort changes with it — usually within one recompute cycle. Manual labels exist, but they are marked as such and never overwrite the model.
Flow volume · trailing 24hUTC
00:0008:0016:00NOW
02 Know who you're watching
An address is not an identity. A pattern is.
The terminal classifies an address from what the chain shows about it directly — contract or EOA, verification status, holdings breadth, counterparty spread, transaction history. Every label carries the evidence that produced it, and an address whose shape is ambiguous is left unlabelled rather than guessed at.
smart money
Consistently early
Enters positions before a 3× volume expansion more often than chance across ≥ 20 independent events. Not "profitable" — early, which is measurable without knowing exit prices.
Population
6.9K
market maker
Two-sided, always on
Quotes both directions in the same pool within the same block band, inventory mean-reverting, gas priced tightly. Flow that looks like conviction but is inventory management.
Population
1.2K
sybil ring
Many hands, one head
Shared funding ancestor, near-identical gas settings, action timing correlated above 0.86. Counted once, not two hundred times, when a "holder count" is reported.
Population
41.8K
Entity resolution · liveconfidence ≥ 0.60 shown
Address
Label
Cohort
Evidence
30d flow
Conf.
Trail score
0x7a41…c0d2
smart money
SM-04
early-entry 31/44 · funding: CEX withdraw
0.94
91
0x1b9e…8f37
market maker
MM-01
two-sided 98.2% · inventory σ 0.04
0.97
78
0xd304…21ab
bridge relay
BR-02
canonical bridge egress · 12.4K hops
0.99
74
0x55c1…9e40
fresh wallet
FW-07
age 3d · single funder · 2 counterparties
0.71
38
0xbe72…4a19
sybil ring
SY-11
219 siblings · timing ρ 0.91 · same gas cap
0.88
17
0x0fa8…77b5
dormant whale
DW-03
no outflow 214d · balance top 0.1%
0.83
55
Resolution runs continuously against the index. Rows below 0.60 confidence are withheld rather than shown as a guess — method in Docs → Entity resolution.
03 One number, fully shown
The Trail Score.
A single 0–100 read on how much attention an address deserves, built from five published components. The weights are in the docs. The inputs are queryable. When a component can't be computed from available data, the score is withheld and the address shows INSUFFICIENT DATA instead of a confident-looking number.
0x7a41…c0d2 · component breakdownSM-04
Timing · w 0.28
88
Conviction · w 0.24
76
Breadth · w 0.16
64
Persistence · w 0.20
93
Cleanliness · w 0.12
81
WEIGHTED TRAIL SCORE91
≥85
sharp
Early, repeatedly, with size that survives the exit. Worth a notification.
≥70
steady
Consistent, sizeable, unremarkable timing. The backbone of real volume.
≥50
noisy
Active but incoherent. High turnover, low persistence, mixed cohorts.
<50
thin / synthetic
Wash-shaped, sybil-shaped, or too young to say anything. Flagged, never silently averaged in.
Read this before you act on a score
A Trail Score describes observed behaviour in a historical window. It is not a prediction, not a recommendation, and not a claim about anyone's identity. Thresholds are published so you can disagree with them — see Docs → Scoring.
04 Alerts on the trail
Describe it once. Hear about it forever.
Alert rules are plain expressions over the same fields the API exposes. Compile once, evaluate against every block, deliver in under two seconds to a webhook, a socket, Telegram, Discord or a Postgres table.
rule · smart-money-accumulationarmed
# TQL — Trail Query LanguageWHEN transfer
WHERE to.cohort IN ('SM-*')
AND to.trail_score >= 85AND token.liquidity_usd > 250_000AND value_usd > 25_000AND to.first_seen_token IS TRUEWITHIN3 blocks
GROUP BY token
HAVINGcount(distinct to) >= 3EMITwebhook('https://…/hooks/spoor')
MATCHES · 7D 14MEDIAN LATENCY 1.4sFALSE-POSITIVE GUARD ON
Webhook
Signed JSON, HMAC-SHA256, at-least-once with idempotency keys.
WebSocket
Subscribe to rules or raw channels. Backfill on reconnect.
Telegram / Discord
Rendered cards with the evidence trail attached, not just a ping.
Warehouse sink
Postgres, Snowflake, BigQuery, or plain Parquet on S3.
fired · last 4rule matches
05 Connections
Plugs into what you already run.
SPOOR is a data layer, not a walled garden. Reads come out over REST, WebSocket and a read-only SQL endpoint. Sinks push into the tools your team already opens.
CHAIN
Robinhood Chain
Native. Full archive from genesis, trace-level.
primary
CHAIN
Arbitrum · Base
Cross-chain funding ancestry for bridge attribution.
indexed
MARKET DATA
GeckoTerminal
Pool metadata, price reference, liquidity depth.
read
MARKET DATA
DexScreener
Secondary pricing cross-check and pair discovery.
read
TRANSPORT
REST + WSS
Cursor-paginated reads, streaming subscriptions.
live
QUERY
SQL endpoint
Read-only Postgres wire protocol over the curated views.
beta
SINK
Kafka · Redpanda
Ordered per-partition event log for your own consumers.
$SPOOR meters access to the index. Holding a threshold balance unlocks terminal tiers and higher API rate limits; the same data is available to anyone paying in stablecoins. The token buys throughput, not information asymmetry — every published methodology stays public.
Supply
1.00B
fixed · no mint fn
Chain
Robinhood
RHC mainnet
Contract pending publication
OFFICIAL CONTRACT ADDRESS
PUBLISHES ON LAUNCH
Verify before you trade
The official address appears in this panel and on the project's verified account, and nowhere else. This panel reads the contract on-chain and renders it only once it matches. Treat every other address claiming to be $SPOOR as unofficial.
Name
Spoor
Symbol
SPOOR
Decimals
18
Total supply
1,000,000,000
Owner
renounced at launch
Tax
0 / 0
07 Watch the flow
Stop reading balances. Start reading behaviour.
Terminal, API and alert engine on one index. Estimates are labelled as estimates. Missing data is shown as missing.
SPOOR is an on-chain activity layer for Robinhood Chain. It ingests every block, decodes every transfer and call, resolves the entity behind each address, scores that entity's behaviour, and exposes the result through four interfaces: a terminal, a REST API, a WebSocket stream and a read-only SQL endpoint.
The product is built around one claim: a balance tells you what someone holds; the transfer graph tells you what they are doing. SPOOR stores the graph.
What it does
Indexes — full archive from genesis, trace-level, reorg-safe to 12 blocks.
Resolves — every active address is mapped to one of nine entity classes with a confidence value and a viewable evidence trail.
Scores — the Trail Score condenses five published components into a 0–100 read on how much attention an address deserves.
Delivers — REST, WSS, webhooks, SQL, or a batch sink into your warehouse.
What it explicitly does not do
It does not identify people. Labels describe behavioural classes, never real-world identities.
It does not predict price, and no endpoint returns a forecast.
It does not custody funds, request signatures, or require a wallet connection to read data.
It does not fill gaps with plausible numbers. Missing inputs produce null and an insufficient_data reason code.
Design rule
Every derived number in this API carries the window it was computed over and the inputs it was computed from. If you cannot reproduce a number from the documented inputs, that is a bug — report it.
Quickstart
Three calls to go from nothing to a live stream of labelled activity.
1 · Get a key
# keys are issued from the terminal → Settings → APIexport SPOOR_KEY="spr_live_7f3c…"
Median time from block inclusion to a delivered stream frame is 1.9s; p99 is 3.4s. Webhook delivery adds a median 240ms on top. Numbers are measured at the edge and published on the status page.
Authentication
All requests carry a bearer key. Keys are scoped, revocable, and never appear in query strings — a key in a URL is treated as compromised and auto-revoked within one minute.
Prefix
Kind
Scopes
Default rate
spr_live_
Server key
read, stream, alerts, sinks
600 rpm
spr_pub_
Browser key
read (origin-locked)
60 rpm
spr_test_
Sandbox
read against replayed blocks
120 rpm
Token-gated tiers
Holding a threshold $SPOOR balance in a wallet linked to the key raises limits. Linking proves control by signing a nonce; the signature is verified and discarded, and no approval or transfer is ever requested.
POST /v1/keys/link
{ "address": "0x…", "nonce": "spoor:link:8f21…", "signature": "0x…" }
Never
SPOOR will never ask for a seed phrase, a private key, or a token approval. Any site or message doing so while using this name is fraudulent.
Architecture
Four stages, each independently replayable. Any stage can be rebuilt from the stage before it without touching the chain again.
Stage graphreplayable end to end
Reorg handling
Events are written optimistically at head and marked finality: "pending" until 12 blocks have passed. A reorg emits a revert frame carrying the exact event IDs to drop — consumers are expected to handle it, and the SDKs do it for you. Historical reads never return pending rows unless ?include_pending=true is set.
Delivery guarantees
Interface
Guarantee
Ordering
Dedup key
WebSocket
at-least-once
per-channel
event_id
Webhook
at-least-once, 5 retries
none
Idempotency-Key
Kafka sink
at-least-once
per-partition
event_id
SQL / warehouse
exactly-once per batch
batch order
(event_id, rev)
Data model
Five core objects. Everything the API returns is one of these, or a projection of one.
Nine classes. An address gets at most one class, plus a confidence. Below 0.60 confidence the class is suppressed and the address returns label: null rather than a guess.
Class
Defining behaviour
Primary features
Median conf.
smart_money
Enters before volume expansion, repeatedly
early-entry rate, hold persistence
0.91
market_maker
Two-sided quoting, mean-reverting inventory
side balance, inventory σ, gas tightness
0.96
bridge_relay
Canonical bridge ingress/egress
contract ancestry, hop count
0.99
cex_hot
High-fan-out custodial sweep pattern
fan-out, batch cadence, nonce density
0.95
lp_provider
Position lifecycle in AMM pools
mint/burn ratio, range churn
0.88
fresh_wallet
Age < 14d, single funder, thin graph
age, funder count, counterparties
0.74
dormant_whale
Top-decile balance, no outflow > 180d
balance rank, idle days
0.85
sybil_ring
Shared ancestry + correlated timing
funding ancestor, timing ρ, gas fingerprint
0.87
wash_pattern
Round-trip flow with no net exposure
cycle detection, net/gross ratio
0.82
Feature set
Fourteen normalised features feed both resolution and clustering. All are computed from public chain data alone.
01
Funding ancestry depth
02
Counterparty entropy
03
Inter-action time distribution
04
Gas price fingerprint
05
Nonce cadence regularity
06
Contract-call shape vector
07
Net / gross flow ratio
08
Token breadth & concentration
09
Early-entry rate vs. baseline
10
Hold persistence half-life
11
Cycle / round-trip density
12
Bridge exposure share
13
Timing correlation with siblings
14
Approval hygiene
Evidence
Every label carries the rows that produced it. Fetch them with ?expand=evidence:
A label is a statement about a behavioural pattern in a window, not an assertion about a person or organisation. Confidence below 1.0 means exactly what it says. Do not present a SPOOR label to end users as a fact about identity.
The Trail Score
A weighted sum of five components, each normalised to 0–100 against the population of active addresses in the same 90-day window.
// published, fixed for the life of a major version
trail_score = round(
0.28 * timing
+ 0.24 * conviction
+ 0.16 * breadth
+ 0.20 * persistence
+ 0.12 * cleanliness
)
Component
Weight
Measures
Withheld when
timing
0.28
Entry position relative to a 3× volume expansion, vs. chance
< 8 qualifying events
conviction
0.24
Position size relative to the address's own balance history
no priced token held
breadth
0.16
Distinct tokens and venues touched, entropy-adjusted
never
persistence
0.20
Half-life of holdings after entry
age < 14d
cleanliness
0.12
Absence of wash-shape, sybil-shape and approval risk
never
Bands
Band
Range
Reading
sharp
85 – 100
Early repeatedly, with size, and it sticks
steady
70 – 84
Consistent and sizeable; unremarkable timing
noisy
50 – 69
Active but incoherent; high turnover, low persistence
thin
25 – 49
Sparse or synthetic-looking flow
flagged
0 – 24
Wash- or sybil-shaped; excluded from aggregates by default
withheld
null
A required component could not be computed
Withholding
If timing, conviction or persistence cannot be computed, the score is null and withheld_reason is set. SPOOR never substitutes a population mean for a missing component — a confident-looking 62 built from two real components and three imputed ones is worse than no number at all.
Reproducibility
Add ?explain=true to any score read to get the raw feature values, the population baselines and the arithmetic. Anything you cannot re-derive from that payload is a bug.
Cohorts & clustering
Cohorts are density clusters over the 14-feature space, recomputed every 4 hours over a 90-day rolling window. They are named <PREFIX>-<NN>, where the prefix is the dominant entity class in the cluster.
Prefix
Dominant class
Typical size
Stability (7d)
SM-*
smart_money
120 – 900
0.88
MM-*
market_maker
20 – 180
0.96
BR-*
bridge_relay
5 – 40
0.99
FW-*
fresh_wallet
2K – 40K
0.31
SY-*
sybil_ring
50 – 4K
0.72
DW-*
dormant_whale
40 – 300
0.94
Stability is the Jaccard overlap of a cohort's membership between consecutive recomputes. Low stability is expected for FW-* — fresh wallets stop being fresh — and is a red flag anywhere else.
REST reference
Base URL https://api.spoorlabs.xyz/v1. JSON in, JSON out. Cursor pagination on every list endpoint. All timestamps are RFC 3339 UTC.
Resume from the last seq you processed. The server replays up to 15 minutes of buffered frames; beyond that it responds op:"gap" with a REST cursor to backfill from.
The server pings every 20s. A client that misses two pings is disconnected. The official SDKs handle ping, resume and revert; hand-rolled clients must too.
TQL — Trail Query Language
A small declarative language for expressing "tell me when this happens". Rules compile to a streaming plan and evaluate against every block.
Grammar
WHEN <event-kind>
[WHERE <predicate> (AND|OR <predicate>)*]
[WITHIN <n> (blocks|seconds|minutes)]
[GROUP BY <field> (, <field>)*]
[HAVING <aggregate-predicate>]
EMIT <sink>(<args>)
Three or more sharp wallets buy the same new token within three blocks
WHEN transfer
WHERE to.trail_score >= 85AND to.first_seen_token IS TRUEAND token.liquidity_usd > 250_000WITHIN3 blocks
GROUP BY token
HAVINGcount(distinct to) >= 3EMITwebhook('https://hooks.example.com/spoor')
A dormant whale wakes up
WHEN transfer
WHERE from.label = 'dormant_whale'AND from.idle_days > 180AND value_usd > 100_000EMITtelegram('@my_channel')
A sybil ring starts distributing
WHEN transfer
WHERE from.cohort LIKE'SY-%'AND to.label = 'cex_hot'WITHIN10 minutes
GROUP BY from.cohort, token
HAVINGsum(value_usd) > 50_000EMITstream('alert.match')
False-positive guard
Rules with guard: true suppress a match if the same (rule, group) fired within the previous cooldown window — default 15 minutes. Without it, a single busy block can produce dozens of identical alerts.
Webhooks
Delivered as POST with a JSON body, an Idempotency-Key and an HMAC-SHA256 signature over the raw body. Retries: 5 attempts with exponential backoff over ~30 minutes, then the endpoint is marked degraded.
Alerts fire at head, so a payload can carry finality: "pending". If your handler moves money or posts publicly, either wait for the matching finality: "final" delivery or subscribe to chain.revert and undo.
SQL endpoint beta
A read-only Postgres wire-protocol endpoint over curated views. Connect with any Postgres client. Statement timeout 30s; result cap 1M rows.
SELECT c.cohort,
sum(f.net_usd) AS net_usd
FROM v_token_flow_hourly f
JOIN v_cohort_members c USING (address)
WHERE f.token = '0x…'AND f.hour >= now() - interval'24 hours'GROUP BY1ORDER BY net_usd DESC;
SDKs
TypeScript
npm i @spoor/sdk
import { Spoor } from"@spoor/sdk";
const spoor = newSpoor({ key: process.env.SPOOR_KEY });
const a = await spoor.address.get("0x7a41…", { explain: true });
console.log(a.trailScore.value, a.label.class);
// streaming — handles ping, resume and reverts for youforawait (const ev of spoor.stream.cohortActivity({ cohorts: ["SM-*"], minUsd: 25_000 })) {
if (ev.reverted) continue;
console.log(ev.kind, ev.valueUsd, ev.to.cohort);
}
Python
pip install spoor-sdk
from spoor import Spoor
f = Spoor(key=os.environ["SPOOR_KEY"])
df = f.token.holders("0x…", collapse=True, band="sharp").to_pandas()
for ev in f.stream.alert_matches(rule_ids=["rul_4a2"]):
print(ev.rule.name, ev.evidence)
Also available
Go (github.com/spoorlabs/go-spoor), Rust (spoor on crates.io), and an OpenAPI 3.1 spec at /v1/openapi.json for generating anything else.
Rate limits & tiers
Tier
Requirement
REST
Streams
Alert rules
History
Free
—
60 rpm
1
3
30d
Pro
$49/mo or 250K $SPOOR
600 rpm
5
50
400d
Desk
$399/mo or 2.5M $SPOOR
6,000 rpm
25
500
full
Sink
contact
custom
custom
unlimited
full + backfill
Limits are per key, enforced with a token bucket at the edge. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset. A 429 includes Retry-After in seconds — respect it; repeated ignores trigger a longer cool-down.
Errors
{ "error": {
"code": "insufficient_data",
"message": "persistence requires age >= 14d; address age is 3d",
"field": "trail_score.components.persistence",
"request_id": "req_7c19…" } }
HTTP
Code
Meaning
400
invalid_request
Malformed params; field names the offender
401
unauthenticated
Missing, malformed or revoked key
403
scope_denied
Key lacks the scope for this route
404
not_indexed
Address exists on chain but has no activity yet
409
tql_parse_error
Rule failed to compile; includes line and column
422
insufficient_data
Computable in principle, not from available inputs
429
rate_limited
See Retry-After
503
index_degraded
Lag beyond SLO; /v1/status has detail
Always log request_id. It is the only thing that lets support trace a specific call.
Reliability
SLO
Target
Measured at
REST availability
99.9%
edge, 30d rolling
Stream availability
99.5%
edge, 30d rolling
Index lag p50
≤ 2.0s
block ts → queryable
Index lag p99
≤ 4.0s
block ts → queryable
Cohort recompute
every 4h ± 10m
scheduler
When lag exceeds the p99 target, /v1/status reports degraded and every response carries X-Spoor-Degraded: true. Scores computed during a degraded window are re-emitted once the index catches up.
What SPOOR can't see
Read this section before you build anything that acts on SPOOR output automatically.
Off-chain intent. An on-chain transfer is the shadow of a decision, not the decision. Custody moves, internal rebalances and OTC settlement all look like conviction and aren't.
Custodial commingling. Behind an exchange hot wallet are thousands of users. SPOOR labels the wallet, not the people behind it, and treats everything downstream of it as ancestry-broken.
Privacy tooling. Mixers, private relays and intent-based fills sever funding ancestry by design. Affected addresses get ancestry: "broken" and a lower resolution confidence.
New tokens. Anything under 14 days old has no persistence signal, so no full score. This is the single most common cause of insufficient_data.
Adversarial adaptation. Every published heuristic is a target. Sybil detection in particular degrades as rings adapt; measured precision is republished monthly, and drops are announced.
Price truth. USD values are derived from pool prices at block time. Thin pools produce nonsense USD figures — SPOOR marks them value_usd: null rather than printing a number it doesn't believe.
Not advice
Nothing SPOOR produces is investment advice, a recommendation, or a claim about any person's identity or intent. Labels and scores are probabilistic descriptions of public on-chain behaviour in a stated window, and they can be wrong.
Glossary
Term
Definition
Ancestry
The chain of funding transfers leading back from an address to its first external source.
Band
The named bucket a Trail Score falls into: sharp, steady, noisy, thin, flagged.
Cohort
A density cluster of addresses with similar behaviour, recomputed every 4 hours.
Early entry
A buy that precedes a 3× volume expansion in the same token within 72 hours.
Edge
An aggregated relationship between two addresses over a window.
Evidence
The feature values and baselines that produced a label or score.
Finality
Whether an event has survived the 12-block reorg window.
TQL
Trail Query Language — the declarative rule syntax for alerts.
Sybil collapsing
Counting a detected ring of addresses as a single entity in aggregates.
Timing ρ
Pearson correlation of action timestamps between two addresses.
Withheld
A score deliberately not produced because a required component was uncomputable.
Changelog
v0.9.4 — indexer
Sybil detection now uses gas fingerprint + timing ρ jointly; measured precision 0.87 (was 0.79).
value_usd returns null for pools below $25K TVL instead of a derived figure.
Stream resume buffer extended from 5 to 15 minutes.
v0.9.3
Added chain.revert channel and finality on every event.
GET /v1/token/{token}/holders collapses sybil rings by default. Breaking for consumers that relied on raw counts — pass collapse=false.
SQL endpoint entered public beta.
v0.9.2
Trail Score weights frozen for the v1 major line.
?explain=true added to all score reads.
Cohort stability published per cohort.
v0.9.0
First public index of Robinhood Chain from genesis.
Nine entity classes, five score components, REST + WSS.
›
Reads live from rpc.mainnet.chain.robinhood.com and the public Blockscout index, in your browser. Nothing is stored.
Robinhood Chainmainnet · 4663
Blocks
…
Transactions
…
Addresses
…
Txns today
…
Block time
…
Gas (avg)
…
ETH price
…
Live at the headclick any address
Addresslive read
Counterparty graphgreen = net inbound · red = net outbound
Recent transactionsnewest first
Trail Scoreheuristic
Computed in your browser from the events listed on this page — not from a historical index. Weights are shown next to each component; method.