A live screener for onchain pools. It traces every swap to the wallet that made it, judges each wallet from its onchain record, and scores whether credible wallets are buying or selling.
A block explorer names the signer of each swap. On Robinhood Chain about half of all swaps are signed by a relayer, so that address is often someone other than the trader. Another tenth use the pool as one hop of a longer route: that volume moves the price and says nothing about demand for the token.
JevScreener finds the trader behind each swap, sets pass-through volume aside, labels the wallet, and weighs its flow by how credible that wallet is. A sell from a wallet with years of deliberate trades counts for much more than a buy from a bot that entered in the first second.
The score says who is buying or selling right now and how credible they are. You can check it trade by trade on the window it covers. JevScreener makes no price prediction.
The screener has three kinds of page: the live home, one page per pair, and one page per wallet. Every column header, tag and label shows its definition on hover.
On the left, What just happened: a live feed with one observation per line, showing its age, the pair, the actor, what it did and the amount. Click a line to open the evidence underneath, with links to each wallet and transaction.
On the right, the pairs table. One pair per row:
| Column | What it shows |
|---|---|
| Price, 5m, 1h, 24h | USD price and its change over each window. |
| Volume, liquidity | 24-hour traded volume and the USD value held in the pool. |
| Flow bar | Credible selling grows left from the centre, credible buying grows right, each segment coloured by cohort. |
| Verdict | Two words on who drives the pool. Hover for the full sentence. An amber dot marks a caveat. |
| Score | 0 to 100. Green from 65, red at 35 and below. Faded when less than half the volume is judged. |
| Risk | Dump risk, 0 to 100. |
Above the table, filter by market cap and volume ranges. Ranges combine, and each shows how many pairs it would leave. Clear all resets the ranges, the highlight, the search and the feed filter in one click.
The top of the page states the reading in one sentence. While only a handful of wallets are judged, or less than half of the volume, the sentence starts with Early reading.
Open any wallet to see its cohort, the group it belongs to if any, and its trades on every followed pair where it appears, with PnL inside the observed window and its current share of supply.
Type a token name or paste an address. Pairs already followed come first. Any other pool on the chain can be picked from the results, and the engine starts following it. The pair appears as soon as its recent history is read and its first wallets are judged.
Top right: the chain, the number of pairs, and how fresh the data is.
| Status | Meaning |
|---|---|
| Live · 3s | Seconds since the last update from the engine. |
| Live · 31s | Amber after 25 seconds without an update. The page keeps showing the last reading. |
| Indexing 4/8 | The engine is starting. Each pair appears when its window is read and its first wallets are judged. |
| Engine unreachable | The page shows the last reading it received and retries. |
JevScreener reads Uniswap v3 and v4 pools. Robinhood Chain is live on the public screener.
| Chain | Chain ID | Status | Pools |
|---|---|---|---|
| Robinhood Chain | 4663 | Live | Uniswap v3, Uniswap v4 |
| Base | 8453 | Engine validated | Uniswap v3, Uniswap v4 |
| Ethereum | 1 | Planned | Uniswap v3, Uniswap v4 |
On Base the full pipeline has run end to end and attribution has been verified. Base is not on the public screener yet. On Ethereum the data the engine depends on has been checked, and the pipeline has not run there yet.
A Uniswap v3 pool is its own contract, identified by a 20-byte address. A Uniswap v4 pool lives inside the chain's singleton PoolManager and is identified by a 32-byte PoolId. The screener, its search box and the upcoming API accept both forms.
On Robinhood Chain about half of all swaps are signed by a relayer, and around one in ten uses a pool as a hop of a longer route. On Base neither happens in our samples: the signer is the trader every time. The attribution step handles both without configuration.
One engine follows a fleet of pools and turns raw swap logs into the score on your screen.
sqrtPriceX96, direction from the token amounts.The AI model answers one kind of question: what is this wallet. Everything after that is deterministic and replayable. When the score turns red, the answer to “why” is a list of wallets and trades you can open.
A wallet that has not been judged yet is shown as unrated and left out of the score. If the judge is slow or unavailable, coverage drops and the screen says so.
The score always ships with its coverage and its concentration, on the screen and in the API.
Swaps, the covered block range and judged wallets are kept in a local store. After a restart the engine reads only the blocks it missed, and wallets already judged are not sent to the model again.
The stream carries a version number, and each page fetches the part it shows. Pushing the full dataset on every swap would cost each open tab about a megabyte every two seconds.
The fields that seem to name the trader of a swap often point somewhere else.
| Field | What it is |
|---|---|
sender of the Swap event | The router contract that called the pool. Never the trader. |
from of the transaction | The signer. On Robinhood Chain about half of all swaps are signed by a relayer on behalf of someone else. In a spot check of six random swaps, the signer was the trader once. |
| Largest balance change | On a multi-hop route, often an intermediate pool. When a route burns tokens, the burn address. |
A deterministic rule reads where the tokens moved inside the transaction and rules out every address that behaves like infrastructure: pools, routers, hooks, and sentinel addresses such as 0x…dead. It runs on the receipt the engine fetched for the swap, so it costs no extra network call.
The same rule works unchanged on chains where the signer is always the trader, like Base.
Around 10% of swaps on Robinhood Chain use a pool as one hop of a longer route. The trader was exchanging something else and never held the token. That volume moves the price, so it stays on the chart. It is left out of the score, the flow bar and every wallet's history, because it says nothing about appetite for the token.
Attribution is checked against an independent index of token transfers.
| Chain | Swaps checked | Correct |
|---|---|---|
| Robinhood Chain | 25 | 25 |
| Base | 12 | 12 |
More checks on prices and trade direction are listed in Accuracy.
Every wallet that trades a followed pool is judged by Jev, an AI model that reads the wallet's public onchain record and returns a cohort with a confidence.
| Cohort | Typical profile |
|---|---|
| Smart money | Old wallet with a long, broad history across many tokens. Entered deliberately rather than in the first second, and still holds a meaningful share. |
| Retail | An ordinary participant: modest size, unremarkable gas bidding, some history and nothing that stands out. |
| Fresh wallet | Newly created or newly funded, with almost no history behind it. |
| Exit liquidity | On its way out: it has sold most of what it held, or its sells outnumber its buys. |
| Bot | Automated. Entered within seconds, bid gas near the top of the block, young wallet with a high transfer count, touches many tokens briefly. |
| Unrated | Not judged yet. Left out of the score entirely, and never counted as neutral. |
Only public onchain facts about the wallet: when it was created and who funded it, how long and broad its trading history is, how it entered this pool, and how much of its position it still holds.
Judging runs continuously, about 90 wallets a minute across the fleet. Wallets with the most recent volume go first, because the score reads only the last minutes of trading. On a busy pool, coverage went from 97% to 100% in 45 seconds while the engine worked through a queue of 2,000 wallets.
A wallet that just made its first trade appears as unrated for the time it waits in the queue, then takes its label on the next screen update.
The model version is pinned. Thresholds downstream are calibrated against that exact version, so a silent model update cannot shift readings. Every judgement is journaled, including failures, which lets a session be replayed to check whether the labels held up.
The questions the judge answers, its rubric, and the exact form of its inputs. Publishing them would make the labels easy to game.
The score is a number from 0 to 100 that says whether credible wallets are net buyers or net sellers right now. The verdict says the same thing in words.
| Score | Reading |
|---|---|
| 65 to 100 | Credible wallets are net buyers. |
| 36 to 64 | Mixed. 50 is perfectly balanced. |
| 0 to 35 | Credible wallets are net sellers. |
Pass-through volume and unrated wallets are left out.
The share of the window's tradable volume that comes from wallets already judged. Below half, the score is shown faded.
The share of the weighted flow that comes from a single wallet. A high concentration means one participant is driving the number. On one pool, thirty wallets were selling for about eight cents each while one wallet bought 0.05 WETH: by volume the buyer outweighed everyone, and the score read +100% buying.
A short tag in the table, a full sentence on hover and at the top of the pair page. The sentence takes into account what each cohort is doing right now. Wallets labelled exit liquidity that are currently buying do not produce “holders are heading for the exit”. An amber dot marks a caveat, such as low coverage or high concentration.
Credible selling grows left from the centre and credible buying grows right, each side segmented by cohort. Green is smart money, red is exit liquidity, greys are retail, fresh wallets and bots.
A second number from 0 to 100, built from a weighted grid of facts: how much of the supply one group holds and how far it sits in profit, who drives the volume, how thin liquidity is against market cap, how young the pair is, and whether credible flow is turning.
The weights behind the score and dump risk, and the rules that combine them. Publishing them would let a trader shape their flow to move the score.
The score describes who is trading now and how credible they are. It makes no price forecast. Whether it leads price has not been measured yet: see Accuracy.
Wallets first funded from the same address are likely one operator. JevScreener calls such a set a group, or a hand.
JevScreener reads the first funder of every judged wallet, groups the wallets that share one, and treats the group as a single participant when it measures concentration and dump risk.
On one Robinhood Chain pair, eighteen wallets first funded from 0xf70da978… were selling at the same time. Each looked like an ordinary seller in the trade list. JevScreener grouped them as one holder that sold 7.75 ETH.
The link is the first funding transfer. Wallets funded through one or more intermediate addresses appear independent.
Observations are the patterns found in a pair's trades, one line each, in the live feed and on the pair page.
They come from deterministic rules over the labelled trades. The model supplies only the wallet labels. Each observation is keyed by its subject: when a group gains a member, its existing line updates.
| On screen | What happened |
|---|---|
| large trade | A single swap several times the size of the window's reference trade. |
| accumulating | A wallet buying repeatedly without selling. |
| unloading | A wallet selling more than it buys, with the share of its position it still holds. |
| as one group | Several wallets from the same funder trading in the same direction. |
| fresh whale | A wallet only hours old trading far above the reference size. |
| bot wave | Several distinct bots active on the pair within a couple of minutes. |
| dust | A swarm of tiny trades that inflate the transaction count without moving price or score. |
| divergence | Smart money's net flow running against a clear price move. |
| flip | The score crossing 50 by a wide margin since the previous reading. |
“Large” and “tiny” are measured against the reference trade: the size under which half of the window's volume sits. Dust carries almost no volume, so a swarm of it leaves the reference trade where it is. An earlier version used the median trade: on one pool, thirty micro-sells of eight cents dragged it so low that an ordinary buy showed up as 114,108 times the typical trade.
How old each number on the screen is. Every interval on this page comes from the running configuration or from a measurement.
The trade appears at once with its wallet. A wallet seen for the first time shows as unrated until its turn in the judgement queue comes. The judgement itself takes 0.66 s at the median.
| Data | Refresh | Source |
|---|---|---|
| New swaps, all pools | every 5 s | Chain logs |
| Screen update signal | 1.5 s after a change, heartbeat every 10 s | Engine, over SSE |
| Wallet judgement | continuous, about 90 wallets a minute | Jev |
| Score, verdict, observations | with every swap and every judgement | Engine |
| USD price, market cap, liquidity, 24 h volume | every 30 s | Dexscreener |
| Share of supply per wallet | every 5 min | Onchain balances |
| Followed pools | every 15 min | Chain scan |
| Data | Kept |
|---|---|
| Price chart | 24 hours |
| Trades per pool | last 6 hours, up to 20,000 swaps |
| Wallet profiles | 12 hours, then rebuilt |
The chart covers 24 hours. The score, the flow bar and the observations cover the sliding window of the last minutes. Both update live.
Measured on the production engine with 22 followed pools: CPU under 1%, about 430 MB of memory, the home feed served in under 20 ms. A cold start of a 51-pool fleet takes about four minutes. A restart reads only the blocks it missed.
The status line counts the seconds since the last update and turns amber after 25. If the engine becomes unreachable, the page keeps the last reading on screen, labels it, and retries.
Almost everything on the screen is read from the chain by our own engine. Market figures in USD come from Dexscreener.
| Data | Source |
|---|---|
| Swaps, prices in the quote token, trade direction | Swap events of Uniswap v3 pools and the v4 PoolManager, decoded by the engine |
| Trader of each swap | Token movements in the transaction receipt |
| Wallet history and funding | Indexed transfer history from our RPC provider |
| Share of supply | balanceOf onchain, batched through Multicall3 |
| 24-hour price history | Public RPC |
| Cohort and confidence | Jev |
| Score, verdict, dump risk, groups, observations | Engine |
| USD price, market cap, FDV, liquidity, 24 h volume and changes, pair age | Dexscreener public API |
The engine reads public data only: the chain and the Dexscreener API. The screener has no accounts and sets no tracking cookie.
Profit and loss is computed inside the observed window, in the quote token. Tokens a wallet bought before the window have no known cost and are left out of its PnL.
Attribution, price decoding and trade direction have been checked against independent sources.
| What | Checked against | Result |
|---|---|---|
| Trader attribution, Robinhood Chain | Independent transfer index | 25 of 25 |
| Trader attribution, Base | Independent transfer index | 12 of 12 |
| Price decoding | Dexscreener on PONS/WETH | 0.000258829 vs 0.0002588 |
| Trade direction | ERC-20 transfers of each transaction | 12 of 12 |
| Buy/sell mix over one hour | Dexscreener on the same hour | 53/95 vs 46/83, same ratio |
When a route burns tokens at the end, 0x…dead holds the largest net balance change of the transaction. An early version named it as the trader, and it showed up as smart money with 28 buys and no sells. Sentinel addresses are now ruled out.
Five minutes on a pool doing 250 swaps in four hours held three trades. The window now stretches until it contains enough swaps.
See Concentration: the screen now shows how much of the reading one wallet carries.
Whether the score leads price. Every judgement is journaled with its inputs so sessions can be replayed and that question answered with data.
The JevScreener API will serve the same readings as the screener, for trading bots, dashboards and research.
Everything under this section is a preview of the planned design. Paths, fields and limits may change before launch.
The screener already runs on these data shapes. A score you read in the API is the score on the pair page at the same moment.
score always comes with coverage and concentration. Filter on them before acting on a number.
An unjudged wallet has "cohort": null. Missing market data is null, never 0.
The host will be announced at launch. Paths are versioned under /v1. Within a version, changes are additive only: new fields and new endpoints, no renames or removals. Examples on these pages use $JEV_API for the base URL.
Planned: an API key sent in the Authorization header.
curl "$JEV_API/v1/pairs?limit=5" \ -H "Authorization: Bearer $JEV_API_KEY"
Each plan sets its own limits: see Pricing. For live data, prefer the stream to polling: it tells you when something changed.
Formats shared by every endpoint. Preview, subject to change.
| Type | Format | Example |
|---|---|---|
| Wallet or token address | Lowercase hex, 20 bytes | 0x10cc6bd3…26ba |
| Pool id | 20-byte address for Uniswap v3, 32-byte PoolId for v4 | 0x9c1f…e4a0 |
| Time | Unix seconds, UTC | 1790409600 |
| Amounts | Decimal numbers in the token's units, plus a USD value | 0.42, 1318.5 |
| Ratios | 0 to 1 | 0.93 |
| Score, dump risk | Integers, 0 to 100 | 71 |
| Chain | Slug | robinhood, base |
| Field | Values |
|---|---|
cohort | smart_money, retail, fresh_money, exit_liquidity, sniper_bot, or null when unrated |
side | buy, sell |
tone | good, bad, warn, neutral |
observation.kind | big_trade, accumulation, distribution, one_hand, fresh_whale, bot_wave, dust, divergence, flip |
Errors return a JSON body with a stable code and a readable message.
{
"error": {
"code": "pool_not_followed",
"message": "This pool is not followed yet. POST /v1/follow to start following it."
}
}
| Status | Code | When |
|---|---|---|
| 400 | invalid_id | The pool id or address is not 20 or 32 bytes of hex. |
| 401 | unauthorized | Missing or invalid API key. |
| 404 | pool_not_followed | The engine does not follow this pool. Follow it first. |
| 404 | wallet_not_seen | The wallet has not traded any followed pool in the retained window. |
| 429 | rate_limited | Too many requests. Retry after the delay in the Retry-After header. |
| 503 | warming_up | The pool is still being indexed. phase in the body says how far it is. |
The planned surface of /v1. Example values are illustrative.
Preview. None of these endpoints is live yet.
Every followed pair with its market data and current reading. Query: chain, sort (score, volume, change_1h), mcap_min, mcap_max, volume_min, limit.
One pair: market, reading, cohort breakdown and the 24-hour candles.
curl "$JEV_API/v1/pairs?sort=score&limit=20" \ -H "Authorization: Bearer $JEV_API_KEY"
const res = await fetch(`${process.env.JEV_API}/v1/pairs?sort=score&limit=20`, {
headers: { Authorization: `Bearer ${process.env.JEV_API_KEY}` },
});
const { pairs } = await res.json();
// Keep only readings that rest on most of the volume
const solid = pairs.filter((p) => p.reading.coverage >= 0.8);
import os, httpx
r = httpx.get(
f"{os.environ['JEV_API']}/v1/pairs",
params={"sort": "score", "limit": 20},
headers={"Authorization": f"Bearer {os.environ['JEV_API_KEY']}"},
)
# Keep only readings that rest on most of the volume
solid = [p for p in r.json()["pairs"] if p["reading"]["coverage"] >= 0.8]
{
"poolId": "0x10cc6bd38112cac182db90b6a71d8bb5939526ba",
"chain": "robinhood",
"dex": "uniswap",
"version": "v3",
"base": { "symbol": "PONS", "address": "0x5b1e…9c02" },
"quote": { "symbol": "WETH", "address": "0x7f3a…11d4" },
"priceUsd": 0.8123,
"market": {
"mcapUsd": 812300,
"liquidityUsd": 96400,
"volume24hUsd": 318900,
"change": { "m5": 1.2, "h1": -3.4, "h6": 5.1, "h24": 12.8 },
"ageDays": 9.4
},
"reading": {
"score": 71,
"coverage": 0.93,
"concentration": 0.18,
"verdict": {
"tag": "smart buying",
"tone": "good",
"text": "Smart money is buying while retail sells into it.",
"caveat": null
},
"dumpRisk": { "value": 34, "level": "low" },
"window": { "from": 1790409300, "to": 1790409600, "swaps": 84 }
},
"updatedAt": 1790409602
}
Attributed swaps, newest first. Query: limit, before (unix seconds), cohort, include_passthrough (default false).
{
"tx": "0x4be1…a7c3",
"block": 18402211,
"time": 1790409581,
"side": "buy",
"amountBase": 5210.4,
"amountQuote": 0.42,
"valueUsd": 1318.5,
"price": 0.0000806,
"trader": "0x2c9d…8e17",
"cohort": "smart_money",
"confidence": 0.86,
"passthrough": false
}
Every participant in the retained window, with cohort, confidence, volume, PnL and share of supply.
Wallets grouped by common first funder, with the group's combined share of supply and net flow.
One wallet across every followed pair where it traded.
{
"address": "0x2c9d…8e17",
"cohort": "smart_money",
"confidence": 0.86,
"firstFundedBy": "0xa04e…33b9",
"group": { "id": "0xa04e…33b9", "size": 4 },
"pairs": [
{
"poolId": "0x10cc6bd38112cac182db90b6a71d8bb5939526ba",
"buys": 3,
"sells": 0,
"volumeUsd": 3920.1,
"pnl": { "quote": 0.071, "roi": 0.17 },
"supplyShare": 0.0042
}
],
"judgedAt": 1790405912
}
The live feed across all pairs, newest first. Query: since, kind, pool, limit. The id is stable per subject: a group that gains a member keeps its id and bumps updatedAt.
{
"id": "one_hand:0x10cc…26ba:0xf70d…a978",
"kind": "one_hand",
"poolId": "0x10cc6bd38112cac182db90b6a71d8bb5939526ba",
"tone": "bad",
"actor": "0xf70d…a978",
"verb": "sold as one group",
"wallets": 18,
"valueQuote": 7.75,
"time": 1790409120,
"updatedAt": 1790409588
}
Followed pairs matching a token name or address first, then any other pool on the chain.
Start following a pool. Body: { "poolId": "0x…" }. Returns its phase immediately; the pair becomes readable once indexed.
Engine state: phase, number of pairs, time of the last ingested swap.
Live updates over Server-Sent Events: one long HTTP response, one JSON event per change. Preview, subject to change.
Omit pools to receive every followed pair.
| Event | Sent when | Payload |
|---|---|---|
pair | The reading or market data of a pair changed. At most one per pair every 1.5 s. | Pair object |
trade | A swap was ingested and attributed. | Trade object |
judged | A wallet received its cohort. | Address, cohort, confidence |
observation | An observation appeared or updated. | Observation object |
heartbeat | Every 10 s, so you can tell a quiet market from a dead connection. | Engine status |
curl -N "$JEV_API/v1/stream?pools=0x10cc6bd38112cac182db90b6a71d8bb5939526ba" \ -H "Authorization: Bearer $JEV_API_KEY"
// Node 18+: fetch streams the body
const res = await fetch(`${process.env.JEV_API}/v1/stream`, {
headers: { Authorization: `Bearer ${process.env.JEV_API_KEY}` },
});
const decoder = new TextDecoder();
let buf = "";
for await (const chunk of res.body) {
buf += decoder.decode(chunk, { stream: true });
let i;
while ((i = buf.indexOf("\n\n")) >= 0) {
const block = buf.slice(0, i); buf = buf.slice(i + 2);
const event = block.match(/^event: (.*)$/m)?.[1];
const data = block.match(/^data: (.*)$/m)?.[1];
if (event === "pair") console.log(JSON.parse(data).reading.score);
}
}
import os, json, httpx
headers = {"Authorization": f"Bearer {os.environ['JEV_API_KEY']}"}
with httpx.stream("GET", f"{os.environ['JEV_API']}/v1/stream", headers=headers, timeout=None) as r:
event = None
for line in r.iter_lines():
if line.startswith("event: "):
event = line[7:]
elif line.startswith("data: ") and event == "pair":
print(json.loads(line[6:])["reading"]["score"])
/v1/pairs once, then apply events on top.EventSource cannot send an Authorization header, so open the stream from your backend and relay it.API access will be sold by plan. Prices and limits will be published when the API opens.
| Limit | Applies to |
|---|---|
| Request rate | Every REST endpoint, counted per API key |
| Stream connections | Connections open to /v1/stream at the same time |
| Followed pools | Pools you add with POST /v1/follow |
Every response will report where your key stands.
| Header | Value |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | Unix time when the window resets |
Past the limit, the API answers 429 rate_limited with a Retry-After header. See Errors.
Polling spends requests. The stream pushes each change as it happens over a single connection.
It describes who is buying and selling right now and how credible they are. It makes no claim about the next price move.
It traded recently and is still in the judgement queue. Wallets with the most recent volume go first, so unrated wallets are usually small or brand new. Unrated wallets are left out of the score.
Less than half of the window's volume comes from judged wallets. The number is correct for what has been judged, but it rests on a small sample.
Labels are judged from public onchain behaviour at the time of the judgement, and each comes with a confidence. Profiles are rebuilt after 12 hours, so a wallet whose behaviour changes gets a fresh judgement.
Yes. Search for the token or paste the pool id, then pick it from the results. The engine starts following it and never drops a pool opened this way.
USD price, market cap, FDV, liquidity, 24-hour volume and change, and pair age come from Dexscreener. Everything else is read from the chain by our engine. See Data sources.
The inputs and the meaning of every number are documented here. The weights, the judgement rubric and the rules that combine them are proprietary.
No date yet. The API section shows the planned design and will switch to live documentation at launch.
Prices will be published when the API opens. Pricing lists the limits a plan will set.