Build a wash trading detector on Solana with Birdeye Data: read volume against wallets, trace churning wallets, and flag fake volume by behavior tag.
July 28, 2026

A wash trading detector scores how much of a token’s reported volume is real, so you stop trusting a number that a handful of wallets can inflate on purpose. A token can show 10 million dollars of daily volume and still be hollow, because the same small cluster of wallets keeps passing supply back and forth to make the token look busy and climb a trending list. This guide builds a detector that reads that pattern from five Birdeye Data signals: volume against wallet count, trade level wallet concentration, trade cadence, top trader churn, and behavior tag overlap. Every call runs over REST, because the detector scores a token on demand rather than streaming a feed.
No single signal proves wash trading on its own. Real tokens sometimes trade in tight clusters, and manipulated tokens sometimes look clean on one axis. The detector works by stacking five independent signals into one suspicion score, so a token that trips several of them at once is the one worth flagging.

You can build a working wash trading detector with five Birdeye Data calls, each one an independent signal. Read these five steps as a self contained recipe.
GET /defi/token_overview and compare volume against liquidity, trade count, and unique wallet count. Volume that dwarfs liquidity is the first flag.GET /defi/v3/token/txs-by-volume to pull individual trades with the wallet behind each, then count how few wallets drive the volume.GET /defi/v3/token/trade-data/single with fine frames to catch the even, mechanical rhythm of a bot.GET /defi/v2/tokens/top_traders and flag wallets whose buy volume almost exactly matches their sell volume.GET /token/v1/holder-profile to see whether bundler and sniper wallets dominate the float.Each step below expands one signal with the exact parameters, a curl example, and the mistake that skews your result.
Start with the cheapest signal, because it filters out the obvious cases before you spend credits on trade level analysis. Genuine volume spreads across many wallets and stays in some proportion to liquidity. Fake volume does not.
Endpoint: GET /defi/token_overview
Chains: all
Plan availability: Standard and above
Docs: token overview
One call returns every number this signal needs: liquidity, v24hUSD for 24 hour volume, trade24h for trade count, uniqueWallet24h for the wallet count, and buy24h and sell24h for the split.
| Parameter | Where | Notes |
|---|---|---|
address | query | The token address. This path uses address. |
x-chain | header | Defaults to solana. |
This signal is a set of ratios, not a single field. Three patterns raise a flag: volume that dwarfs liquidity, a high trade count against a low unique wallet count, and buy volume that sits almost exactly on top of sell volume. Any one of them alone is weak, so read all three together. As a starting point, volume more than roughly 50 times liquidity, or a few hundred trades that map to only a few dozen wallets, is worth escalating to the trade level checks below.
curl --request GET \
--url '<https://public-api.birdeye.so/defi/token_overview?address=YOUR_TOKEN>' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-chain: solana'
Watch the casing, because it is mixed. Most fields are camelCase, such as v24hUSD and uniqueWallet24h, but holder and global_fees_paid are snake_case. If you map the response assuming camelCase throughout, those two read as undefined. Copy each field name exactly.
The baseline tells you the token looks suspicious in aggregate. It cannot tell you which wallets are behind the volume. That is the next signal, and it is where the detector gets its teeth.
Ratios raise suspicion, but they cannot name a single wallet. To prove concentration you have to go down to the trades themselves and count how few wallets produce the volume.
Endpoint: GET /defi/v3/token/txs-by-volume
Chains: Solana
Plan availability: Starter and above
Docs: trades by volume
Each item in the response carries the owner wallet behind the trade, a side of buy or sell, the from and to legs with amounts, and a source such as raydium. Group the trades by owner, count the unique owners, and compare that against total volume. When a large share of volume traces back to a handful of wallets, the volume is manufactured.
| Parameter | Where | Notes |
|---|---|---|
token_address | query | This path uses token_address, not address. |
volume_type | query | usd or amount. Required. |
sort_type | query | desc or asc. Required. |
limit | query | Max 500. |
The parameter to watch is token_address, because this path breaks the usual /defi/ convention and uses token_address. Send address and the call fails.
curl --request GET \
--url '<https://public-api.birdeye.so/defi/v3/token/txs-by-volume?token_address=YOUR_TOKEN&volume_type=usd&sort_type=desc&limit=100>' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-chain: solana'
There is one counting trap that quietly ruins this signal. A single swap can appear across more than one instruction, so if you count raw response rows you overcount trades and inflate the very volume you are trying to audit. The fix is to deduplicate on the composite key tx_hash plus ins_index plus inner_ins_index, which is unique per real trade. Page through with has_next until you have the window you need.
Wallet concentration shows you that few wallets move the token. It does not show you the rhythm of how they move it. A human cluster and a bot cluster can look similar on concentration alone, so the next signal reads timing.
Wash bots trade on a timer. That regularity is invisible at hourly resolution and obvious at the resolution of seconds. This signal reads the token’s trade rhythm at a fine grain that the overview call cannot reach.
Endpoint: GET /defi/v3/token/trade-data/single
Chains: all chains, fine frames on Solana, Base, BSC, and Ethereum
Plan availability: Starter and above
Docs: trade data single
The response returns per frame counts for trade, buy, sell, volume, and unique_wallet. Request fine frames, and an even spread of trades and volume across every frame, with a flat unique wallet count, is the mechanical signature of a bot.
| Parameter | Where | Notes |
|---|---|---|
address | query | This path uses address, not token_address. |
frames | query | Up to 8 frames, down to 5 second resolution. |
x-chain | header | Defaults to solana. |
The parameter here is address, which trips up anyone who just called txs-by-volume. Both live under /defi/v3/token/, yet txs-by-volume wants token_address and this endpoint wants address. Send the wrong one and the call fails. The response is also snake_case, such as unique_wallet, unlike the camelCase overview fields.
curl --request GET \
--url '<https://public-api.birdeye.so/defi/v3/token/trade-data/single?address=YOUR_TOKEN&frames=1m,5m>' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-chain: solana'
One caveat belongs in your code comment: the endpoint itself runs on every chain, but the custom fine frames down to 5 seconds only work on Solana, Base, BSC, and Ethereum. On other chains you get the 8 default frames and lose the resolution this signal depends on.
Cadence catches the machines. The next signal catches a subtler pattern: wallets that trade a lot but never actually take a position, which is the fingerprint of churn.
A wallet that buys and sells nearly equal volume is not investing. It is churning, cycling the same capital to print volume. This signal ranks the busiest wallets and reads their buy against their sell.
Endpoint: GET /defi/v2/tokens/top_traders
Chains: all
Plan availability: Standard and above
Docs: top traders
The response ranks wallets and splits each one’s volume into buy and sell. When volumeBuyUSD sits almost exactly on volumeSellUSD for a top wallet, that wallet is churning. Each item also carries a tags array, so a top wallet tagged bundler or sniper doubles as a signal for the last check.
| Parameter | Where | Notes |
|---|---|---|
address | query | This /defi/v2/ path uses address. |
time_frame | query | 30m through 90d, default 24h. |
sort_by | query | volume, trade, total_pnl, unrealized_pnl, realized_pnl, or volume_usd. |
limit | query | Max 10. |
Sort by volume to surface the wallets doing the most trading, then read the buy and sell split on each. A genuine investor shows a lopsided split, because they entered or exited a position. A wallet within a few percent of even is the one to flag. Watch the casing, because the total is volumeUsd while the splits are volumeBuyUSD and volumeSellUSD, and the suffix changes between sibling fields.
curl --request GET \
--url '<https://public-api.birdeye.so/defi/v2/tokens/top_traders?address=YOUR_TOKEN&time_frame=24h&sort_by=volume&sort_type=desc&limit=10>' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-chain: solana'
Churn tells you which wallets fake activity. The last signal tells you what kind of wallets they are, by pulling the behavior tags for the whole float.
Whoever runs a wash operation usually holds the token too, and those wallets tend to already carry a behavior tag from how they acquired it. Reading that overlap turns four trading signals into a picture of who is behind them.
Endpoint: GET /token/v1/holder-profile
Chains: Solana
Plan availability: all plans
Docs: holder profile
The response breaks holders into tags, bundler, sniper, insider, dev, and smart trader, each with holder_count, percent_of_supply, and buy and sell volume. When bundler or sniper wallets hold a large share of supply on a token that already tripped the earlier signals, the case for wash trading gets much stronger.
| Parameter | Where | Notes |
|---|---|---|
token_address | query | This path uses token_address. |
include_zero_balance | query | Defaults to true, which counts wallets that traded but no longer hold. |
x-chain | header | Solana only for this endpoint. |
The parameter is token_address again, matching txs-by-volume and not trade-data/single. Keep the two straight per call. Leave include_zero_balance at its default for a wash check, since wallets that churned volume and exited are exactly the ones you want counted.
curl --request GET \
--url '<https://public-api.birdeye.so/token/v1/holder-profile?token_address=YOUR_TOKEN>' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'x-chain: solana'
One timing note: the bundler tag is accurate for tokens created from 1 March 2026 onward, because older tokens are still backfilling. On a legacy token, weight this signal a little lighter and lean on the trade level checks instead.
With all five signals in hand, you have everything the detector needs to score a token.
Scoring one token spends five calls, and screening a long watchlist adds up fast. Call GET /utils/v1/credits to read your remaining allowance, and render it while you develop, so a runaway screen shows up as a falling number rather than a surprise suspension.
curl --request GET \
--url '<https://public-api.birdeye.so/utils/v1/credits>' \
--header 'x-api-key: YOUR_API_KEY'
Before you act on a high score, rule out the legitimate patterns that trip the same signals. Skipping this step is how a detector earns a reputation for crying wolf.
Market makers produce exactly the profile the churn signal looks for. A market maker quotes both sides continuously, so its buy volume tracks its sell volume closely and its wallet shows constant activity without accumulating a position. The tell is consistency across time rather than a burst: a market maker runs the same way all day, while a wash operation usually clusters around a listing, a marketing push, or a chart it wants to look busy.
The cadence signal has a similar blind spot around arbitrage bots. They fire on a schedule because they poll for price gaps on a schedule, and the regular timing that follows has nothing to do with faking volume. Look at the source field: arbitrage spans two pools by definition, while wash trading usually cycles inside one.
Then there are tokens minted an hour ago, which set off almost every signal at once. Few holders, thin liquidity, a high trade to wallet ratio, and a float dominated by whoever bought first: none of that is evidence of manipulation, it is simply what a new token looks like. Weight the signals lighter inside the first day of trading, or pull fresh tokens out of the score entirely and screen them under a separate rule.
Low float distorts the concentration check in the same direction. When only a small share of supply actually circulates, a handful of wallets will naturally account for most of the trading, so measure concentration against circulating supply rather than total supply wherever the data allows.
All four cases share a shape: read one signal alone and an innocent explanation fits it comfortably. Stacking the signals is what removes that ambiguity, and it is the reason the combined score carries more weight than any single flag.
No signal is a verdict, so the detector’s real job is to combine them. Assign each signal a threshold and a point, then sum the points into a wash trading score you can rank a whole watchlist by.
A practical starting scheme gives one point per flag: 24 hour volume running more than roughly 50 times liquidity, a trade count that maps to only a small fraction as many unique wallets, buy and sell volume sitting within a few percent of each other, a single owner driving more than a fifth of the traced volume after deduplication, and bundler or sniper wallets holding a large share of the float. A token at four or five points earns a hard look. A token at one point is usually noise.
The thresholds are the part you tune. Calibrate them against two reference sets: tokens you already trust, and tokens you know were manipulated. Slide each cutoff until the clean set scores low and the manipulated set scores high, because the right numbers depend on the market and the chain you screen. Weight the signals if you want, giving the trade level concentration check more pull than the aggregate baseline, since concentration is harder to fake than a single volume number.
Log the per signal breakdown next to the total, not just the score. When you later act on a flag, the breakdown tells you which pattern tripped it, and that record is what lets you sharpen the thresholds over time.
The five signals share one input, the token address, and run independently, so you can fire them in parallel and combine the results into a single suspicion score. No signal is a verdict on its own. The score rises with each flag a token trips.

Before you ship, walk this checklist:
v24hUSD and uniqueWallet24h as camelCase and compares volume against liquidity.txs-by-volume with token_address and deduplicates on tx_hash plus ins_index plus inner_ins_index.trade-data/single with address, not token_address, and only trusts 5 second frames on Solana, Base, BSC, and Ethereum.volumeBuyUSD against volumeSellUSD and maps them apart from volumeUsd.holder-profile with token_address and weights the bundler tag lighter on tokens created before 1 March 2026.For background on how automated trading concentrates activity into a few accounts on Solana, the Solana documentation on programs and accounts explains the account model these wallets operate through.
It is a tool that scores how much of a token’s reported volume is likely fake, by stacking several independent signals rather than trusting the headline volume number. This guide builds one on Solana with five Birdeye Data endpoints.
No. Every signal here has innocent explanations on its own. The wash trading detector works by combining volume ratios, wallet concentration, cadence, churn, and tag overlap, so a token that trips several at once is the one to flag.
Two of the endpoints, trades by volume and holder profile, return data for Solana only, and the fine cadence frames are limited to four chains. The volume baseline and top trader signals work across chains, so you can extend part of the detector, but the core trade level checks stay Solana bound.
A single swap can appear across more than one instruction row. Deduplicate on the composite key tx_hash plus ins_index plus inner_ins_index, which is unique per real trade, before you count wallets or volume.
All five endpoints are available across the Birdeye Data packages, so the constraint is volume rather than access. Scoring one token costs five calls, so pick a package based on how many tokens you plan to screen rather than which signals you want.
Wash patterns shift, so a token that scores clean today can be manipulated next week. For a live watchlist, rescore on the cadence your credit budget allows, and always rescore right before you act on a token’s volume rather than trusting a stale result.
Ready to build your own wash trading detector? Grab an API key and compare plans at Birdeye Data, and browse the full endpoint reference at docs.birdeye.so/reference.
Birdeye provides expansive data covering tokens, wallets, trades, and protocols across 300+ exchanges on 10 chains.
Whether you’re a solo tinkerer or a large team looking to scale, Birdeye offers plans that caters for your data needs and budget.
Dive into our docs and start querying data on 60+ APIs and 8 WebSocket types today!
Insights is a feature that allows users to analyze market trends in various aspects and dive deep into many industry sectors.
Find Gems is a feature that helps user identify potential Tokens at the current time.
Launch Explorer is a feature that enables users to access real-time data of tokens on popular launchpads like pump.fun, letsbonk.fun,...
New insight article by Birdeye reveals USDC's breakout growth in recent years
Data by Birdeye shows total trading volume of xStocks, PreStocks, and Ondo Global Markets on Solana peaked in March 2026
After the Drift Protocol's hack, Solana Foundation initiated programs such as STRIDE and SIRIN to tighten security for ecosystem teams

July 28, 2026

July 28, 2026
July 24, 2026