DexraMany open-source sniper and copy bots filter the same way: fetch the new launch, run a few checks,...
Many open-source sniper and copy bots filter the same way: fetch the new launch, run a few checks, and skip it if something looks bad. Mint authority still active, skip. Creator rugged before, skip. Otherwise, buy.
That is a blacklist. It feels safe because every rule you add removes a known bad case. The problem is the base rate. When most launches fail, removing the obviously bad ones barely moves the odds of the ones you keep.
The alternative is a whitelist: buy only when every check positively passes, and treat anything else (a warning, a missing answer, a timeout) as "no".
This post shows the difference on real numbers, then gives you a gate you can drop into a bot and a script to measure your own filter.
I build Dexra, a risk API for Solana launches, and it publishes an accuracy report at /score_performance. It scores past launches whose liquidity peaked at 5 SOL or $500 or more (so a collapse is measurable) and that are at least 24 hours old. The outcome it measures:
Collapse: liquidity fell at least 80% from its peak within 24 hours of launch.
Survived: liquidity was still there 24 hours after launch. That says nothing about price. A token can survive and still go to zero for whoever bought it.
The report is public, no key needed:
curl -s https://api.dexra.dev/score_performance | jq '.result | {analyzed_launches, survival_base_rate, filters}'
Over the last 30 days, 5,818 launches had their first buyers analyzed. 5,380 of them collapsed (92.5%). Only 438 kept their liquidity for a day.
Each launch also has a verdict rebuilt from what was known at launch time: ok, caution or avoid. The report then asks what two filters would have done on those same 5,818 launches (trimmed response):
"filters": [
{ "skips": ["avoid"],
"taken": 3479, "collapses_avoided_share": 0.422,
"survivors_skipped_share": 0.151, "collapse_rate_taken": 0.893 },
{ "skips": ["avoid", "caution"],
"taken": 664, "collapses_avoided_share": 0.922,
"survivors_skipped_share": 0.438, "collapse_rate_taken": 0.630 }
]
The same thing in a table:
| Policy | Launches bought | Collapsed within 24h | Kept liquidity 24h | Collapses avoided | Survivors missed |
|---|---|---|---|---|---|
| Buy everything | 5,818 | 92.5% | 7.5% | ā | ā |
Blacklist: skip avoid
|
3,479 | 89.3% | 10.7% | 42.2% | 15.1% |
Whitelist: buy only ok
|
664 | 63.0% | 37.0% | 92.2% | 43.8% |
Read the blacklist row again. It removed 42% of the collapses, which sounds good in a changelog. But of what it still buys, 89 in 100 lose most of their liquidity within a day, against 92.5 in 100 with no filter at all. The filter is working; it just cannot beat the base rate.
The whitelist buys far fewer launches, 664 out of 5,818 (11.4%). Of those, 37.0% kept their liquidity for 24 hours (95% confidence interval 33.5% to 40.8%), 4.9 times the 7.5% base. By verdict, launches marked caution survived at 4.5% and avoid at 2.8%, both below the base.
A blacklist removes launches that match a known bad pattern. On a market where nearly everything fails, most failures do not match any single pattern: a fresh creator wallet, first buyers funded a few hops away, a dev who sells at the first pump. So most of them pass through.
A whitelist inverts the burden of proof. A launch has to show that it is clean on every axis you can measure: authorities renounced, no creator history of collapses, first buyers not funded from one wallet, no bot cluster. Anything you could not read yet counts against it.
The cost is real and you should plan for it: in this data the whitelist skipped 192 of the 438 survivors, 43.8%. You will miss launches that would have been fine. A bot that buys 664 times instead of 3,479 also needs position sizing and exits that make sense on fewer trades.
And 37% is not a win rate. Even among the launches the whitelist kept, 63% still collapsed within a day. The check changes the odds you start from; it does not replace stop losses, sizing or exits.
The most common bug in a pre-buy check is not a wrong rule, it is a missing answer treated as a pass. The RPC times out, the holder query errors, the analysis is not done yet, and the code falls through to buy().
Here is a gate that makes "no answer" mean "no". It does not depend on any service; plug in your own checks.
type Outcome = 'pass' | 'fail' | 'unknown'
type Check = { name: string; run: (mint: string) => Promise<Outcome> }
// A whitelist gate: buy only when every check positively passes.
// A timeout, an error or "unknown" counts as "no", never as "probably fine".
export async function allowBuy(mint: string, checks: Check[], timeoutMs = 1500) {
const timeout = new Promise<Outcome>((resolve) => setTimeout(() => resolve('unknown'), timeoutMs))
const results = await Promise.all(
checks.map(async (check) => {
const outcome = await Promise.race([check.run(mint).catch(() => 'unknown' as const), timeout])
return { name: check.name, outcome }
}),
)
const blocking = results.filter((r) => r.outcome !== 'pass')
return { buy: blocking.length === 0, blocking }
}
With one slow check, the gate refuses and tells you why:
{"buy":false,"blocking":[{"name":"first_buyers","outcome":"unknown"}]}
Log blocking on every refusal. After a day you will know which check is slow, which one fires on everything, and which one never fires.
You do not need anyone's report to know whether your filter works. Log every launch your bot sees (not only the ones it bought) with the verdict it had at decision time, then mark which ones lost 80% of their liquidity within 24 hours. Then compute the same four numbers:
from collections import Counter
def filter_report(rows, skip):
"""rows: (verdict, collapsed) pairs; skip: verdicts the filter refuses to buy."""
collapses = sum(1 for _, c in rows if c)
survivors = len(rows) - collapses
taken = [(v, c) for v, c in rows if v not in skip]
skipped = [(v, c) for v, c in rows if v in skip]
return {
"skips": sorted(skip),
"taken": len(taken),
"collapse_rate_taken": round(sum(c for _, c in taken) / len(taken), 3) if taken else None,
"collapses_avoided": round(sum(c for _, c in skipped) / collapses, 3) if collapses else None,
"survivors_skipped": round(sum(not c for _, c in skipped) / survivors, 3) if survivors else None,
}
# Replace with your own log: one row per launch your bot saw, with the verdict it had at
# decision time and whether liquidity fell 80% from its peak within 24 hours.
rows = [("ok", False), ("ok", True), ("caution", True), ("caution", False),
("avoid", True), ("avoid", True), ("caution", True), ("ok", False)]
print(Counter(v for v, _ in rows))
for skip in ({"avoid"}, {"avoid", "caution"}):
print(filter_report(rows, skip))
The number to watch is collapse_rate_taken: what happens to the launches you actually buy. "Collapses avoided" is the number that looks good in a README; collapse_rate_taken is the one that shows up in your PnL.
Two traps when you build this log:
If you would rather not build the checks yourself: Dexra's new_launches returns fresh launches with a verdict and the reasons behind it, so the whitelist becomes one filter. Disclosure again: I build it.
type Launch = { mint: string; verdict?: string; reasons?: string[] }
async function okLaunches(key: string): Promise<Launch[]> {
const res = await fetch('https://api.dexra.dev/new_launches?minutes=10&limit=20', {
headers: { Authorization: `Bearer ${key}` },
signal: AbortSignal.timeout(5000),
})
if (!res.ok) return [] // fail closed: no answer, no buy
const body = await res.json()
const launches: Launch[] = body.result?.launches ?? []
return launches.filter((l) => l.verdict === 'ok')
}
What a typical answer looks like (one call today, 20 pump.fun launches from the last 30 minutes, trimmed):
{ "mint": "Eb9Q2Jp1gt5CQLUEyzyR9ro7faFNZH3yoz3FmA29pump", "verdict": "avoid",
"reasons": ["the creator's earlier tokens collapsed", "liquidity is low"] }
{ "mint": "vmxT7NqUzyMnrNiBSn95KDWVDraUzhe6Z1yfmbXpump", "verdict": "caution",
"reasons": ["liquidity is low", "first buyers are not analyzed yet; ask again shortly"] }
{ "mint": "F9QQge7i9xuygy96eN2uvCQvJ1qCZQsu6aH6h9rZpump", "verdict": "unknown",
"reasons": ["first buyers are not analyzed yet; ask again shortly"] }
None of the 20 was ok, which is the point: on most calls the right number of buys is zero. unknown means the first buyers or the mint authorities were not read yet. A whitelist treats it as "not yet" and asks again, rather than buying on a partial answer.
Limits worth knowing before you rely on it:
coverage.untracked_platforms.collapse_rate_taken on everything your bot saw, with the verdict it had at the time.If you want to try the whitelist without writing the checks, there are 100 free credits a day with an account, or pay per call in USDC over x402 with no account: dexra.dev/docs. The live numbers above are at dexra.dev/accuracy and change daily.