Why Bots Need Risk Screening
Speed + Safety
Sub-second screening that doesn’t slow down execution
Honeypot Detection
Catch traps before capital gets locked
Sniper Detection
Know when you’re competing against coordinated actors
- Trading Lite API — Sub-200ms token screening optimized for bots
- Honeypot detection — Don’t buy what you can’t sell
- Hidden tax detection — Expose the real cost before buying
- Holder analysis — Detect sniper clusters and bundler activity
- Liquidity analysis — Verify there’s enough depth to exit
- Multi-chain support — Solana, Base, ETH, and more
Prerequisites
Before implementing risk screening, ensure you have:- A Webacy API key (sign up here)
- Basic familiarity with REST APIs or the Webacy SDK
- Your bot’s trade execution flow identified for integration points
Fast Token Screening
Speed is critical for trading bots. The Trading Lite API is optimized for sub-second decisions.Trading Lite API (Solana)
Get essential risk data in under 200ms.Trading Lite is currently available for Solana only. For EVM chains, use the standard contract analysis endpoints.
curl -X GET "https://api.webacy.com/trading-lite/TokenMintAddress..." \
-H "x-api-key: YOUR_API_KEY"
import { TradingClient, Chain } from '@webacy-xyz/sdk';
const client = new TradingClient({ apiKey: process.env.WEBACY_API_KEY });
const analysis = await client.tradingLite.analyze('PumpTokenMint...');
// Quick decision
const shouldBuy = analysis.riskScore < 40 &&
!analysis.isHoneypot &&
analysis.liquidity > 10000;
if (shouldBuy) {
console.log('✓ Token passed screening');
} else {
console.log('✗ Token failed screening');
console.log(` Risk: ${analysis.riskScore}, Honeypot: ${analysis.isHoneypot}`);
}
import requests
response = requests.get(
"https://api.webacy.com/trading-lite/TokenMintAddress...",
headers={"x-api-key": "YOUR_API_KEY"}
)
analysis = response.json()
# Quick decision
should_buy = (
analysis.get("riskScore", 100) < 40 and
not analysis.get("isHoneypot", True) and
analysis.get("liquidity", 0) > 10000
)
| Field | Type | Description |
|---|---|---|
riskScore | number | 0-100 overall risk score |
isHoneypot | boolean | Can you sell this token? |
liquidity | number | Total liquidity in USD |
holderCount | number | Number of token holders |
Honeypot Detection
The most critical check—don’t buy what you can’t sell.interface TradeDecision {
action: 'buy' | 'skip';
reason: string;
riskScore: number;
}
async function evaluateToken(mint: string): Promise<TradeDecision> {
const analysis = await client.tradingLite.analyze(mint);
// Immediate rejection: honeypot
if (analysis.isHoneypot) {
return {
action: 'skip',
reason: 'Honeypot detected - cannot sell',
riskScore: 100,
};
}
// Risk score threshold
if (analysis.riskScore > 60) {
return {
action: 'skip',
reason: `High risk score: ${analysis.riskScore}`,
riskScore: analysis.riskScore,
};
}
// Liquidity threshold
if (analysis.liquidity < 5000) {
return {
action: 'skip',
reason: `Insufficient liquidity: $${analysis.liquidity}`,
riskScore: analysis.riskScore,
};
}
return {
action: 'buy',
reason: 'Passed all checks',
riskScore: analysis.riskScore,
};
}
Tax Detection
Hidden taxes can destroy profitability. A 10x gain means nothing with a 90% sell tax.curl -X GET "https://api.webacy.com/contracts/0xTokenAddress.../tax?chain=eth" \
-H "x-api-key: YOUR_API_KEY"
import { ThreatClient, Chain } from '@webacy-xyz/sdk';
const threatClient = new ThreatClient({ apiKey: process.env.WEBACY_API_KEY });
const tax = await threatClient.contracts.getTax(
'0xTokenAddress...',
{ chain: Chain.ETH }
);
// Calculate effective profit threshold
const buyTax = tax.buyTax / 100;
const sellTax = tax.sellTax / 100;
const taxDrag = 1 - ((1 - buyTax) * (1 - sellTax));
console.log(`Total tax drag: ${(taxDrag * 100).toFixed(2)}%`);
// Example: If buy tax is 5% and sell tax is 10%
// You lose 5% on buy, then 10% of remaining on sell
// Total drag: 1 - (0.95 * 0.90) = 14.5%
if (taxDrag > 0.20) {
console.log('⚠️ High tax drag - need significant price movement to profit');
}
response = requests.get(
"https://api.webacy.com/contracts/0xTokenAddress.../tax",
params={"chain": "eth"},
headers={"x-api-key": "YOUR_API_KEY"}
)
tax = response.json()
buy_tax = tax.get("buyTax", 0) / 100
sell_tax = tax.get("sellTax", 0) / 100
tax_drag = 1 - ((1 - buy_tax) * (1 - sell_tax))
print(f"Total tax drag: {tax_drag * 100:.2f}%")
| Scenario | Max Tax | Reasoning |
|---|---|---|
| Scalping | 3% total | Need tight spreads |
| Swing trading | 10% total | Can absorb some tax |
| Long-term | 15% total | Price appreciation covers tax |
| Never trade | > 25% | Likely scam |
Sniper & Bundler Detection
Know what you’re up against. If coordinated actors are already in, you’re probably late.Holder Analysis
curl -X GET "https://api.webacy.com/holder-analysis/TokenMint...?chain=sol" \
-H "x-api-key: YOUR_API_KEY"
const holders = await client.holderAnalysis.get(
'TokenMint...',
{ chain: Chain.SOL }
);
console.log(`Snipers: ${holders.sniperCount}`);
console.log(`Bundlers: ${holders.bundlerCount}`);
console.log(`Top 10 %: ${holders.top10Percentage}%`);
// Sniper analysis
if (holders.sniperCount > 5) {
console.warn('⚠️ Heavy sniper activity - likely coordinated');
}
// Bundler analysis (multiple wallets acting as one)
if (holders.bundlerCount > 2) {
console.warn('⚠️ Bundler activity detected - insider accumulation');
}
// Concentration analysis
if (holders.top10Percentage > 70) {
console.warn('⚠️ High concentration - dump risk');
}
response = requests.get(
"https://api.webacy.com/holder-analysis/TokenMint...",
params={"chain": "sol"},
headers={"x-api-key": "YOUR_API_KEY"}
)
holders = response.json()
# Check for coordinated activity
is_sniped = holders.get("sniperCount", 0) > 5
has_bundlers = holders.get("bundlerCount", 0) > 2
high_concentration = holders.get("top10Percentage", 0) > 70
Launch Screening
For new token launches, check the first buyers.interface LaunchAnalysis {
safeToEnter: boolean;
sniperRisk: 'low' | 'medium' | 'high';
bundlerRisk: 'low' | 'medium' | 'high';
concentrationRisk: 'low' | 'medium' | 'high';
reasons: string[];
}
async function analyzeLaunch(mint: string): Promise<LaunchAnalysis> {
const holders = await client.holderAnalysis.get(mint, { chain: Chain.SOL });
const reasons: string[] = [];
// Evaluate sniper risk
let sniperRisk: 'low' | 'medium' | 'high' = 'low';
if (holders.sniperCount > 10) {
sniperRisk = 'high';
reasons.push(`${holders.sniperCount} snipers detected`);
} else if (holders.sniperCount > 5) {
sniperRisk = 'medium';
}
// Evaluate bundler risk
let bundlerRisk: 'low' | 'medium' | 'high' = 'low';
if (holders.bundlerCount > 3) {
bundlerRisk = 'high';
reasons.push(`${holders.bundlerCount} bundler clusters`);
} else if (holders.bundlerCount > 1) {
bundlerRisk = 'medium';
}
// Evaluate concentration risk
let concentrationRisk: 'low' | 'medium' | 'high' = 'low';
if (holders.top10Percentage > 80) {
concentrationRisk = 'high';
reasons.push(`Top 10 hold ${holders.top10Percentage}%`);
} else if (holders.top10Percentage > 60) {
concentrationRisk = 'medium';
}
// Overall decision
const highRisks = [sniperRisk, bundlerRisk, concentrationRisk]
.filter(r => r === 'high').length;
const safeToEnter = highRisks === 0;
return {
safeToEnter,
sniperRisk,
bundlerRisk,
concentrationRisk,
reasons,
};
}
Liquidity Checks
Ensure you can exit your position.Pool Analysis
curl -X GET "https://api.webacy.com/tokens/TokenAddress.../pools?chain=sol" \
-H "x-api-key: YOUR_API_KEY"
const pools = await client.tokens.getPools(
'TokenAddress...',
{ chain: Chain.SOL }
);
// Calculate total liquidity
const totalLiquidity = pools.pools.reduce(
(sum, pool) => sum + pool.liquidity,
0
);
// Calculate safe position size (max 2% of liquidity)
const maxPositionSize = totalLiquidity * 0.02;
console.log(`Total liquidity: $${totalLiquidity.toLocaleString()}`);
console.log(`Max safe position: $${maxPositionSize.toLocaleString()}`);
// Check if position is viable
const intendedPosition = 1000; // $1000
if (intendedPosition > maxPositionSize) {
console.warn(`⚠️ Position too large for liquidity`);
console.warn(` Intended: $${intendedPosition}, Max: $${maxPositionSize}`);
}
response = requests.get(
"https://api.webacy.com/tokens/TokenAddress.../pools",
params={"chain": "sol"},
headers={"x-api-key": "YOUR_API_KEY"}
)
pools = response.json()
total_liquidity = sum(p.get("liquidity", 0) for p in pools.get("pools", []))
max_position = total_liquidity * 0.02 # 2% of liquidity
print(f"Total liquidity: ${total_liquidity:,.2f}")
print(f"Max safe position: ${max_position:,.2f}")
Slippage Estimation
function estimateSlippage(positionSize: number, liquidity: number): number {
// Simple constant product formula approximation
// Real slippage depends on pool type and depth distribution
const impactRatio = positionSize / liquidity;
const slippage = impactRatio * 100 * 2; // 2x for round trip
return Math.min(slippage, 100); // Cap at 100%
}
// Usage
const liquidity = 50000; // $50k in pool
const position = 5000; // $5k trade
const expectedSlippage = estimateSlippage(position, liquidity);
console.log(`Expected slippage: ~${expectedSlippage.toFixed(2)}%`);
// Decision threshold
if (expectedSlippage > 5) {
console.warn('⚠️ High slippage expected - reduce position size');
}
Complete Integration Workflow
Bot Decision Flow
Full TypeScript Implementation
Complete Trading Bot Risk Engine
Complete Trading Bot Risk Engine
import { TradingClient, ThreatClient, Chain } from '@webacy-xyz/sdk';
const tradingClient = new TradingClient({
apiKey: process.env.WEBACY_API_KEY,
defaultChain: Chain.SOL,
});
const threatClient = new ThreatClient({
apiKey: process.env.WEBACY_API_KEY,
});
interface RiskEngineResult {
shouldTrade: boolean;
maxPositionUsd: number;
riskScore: number;
flags: string[];
metrics: {
liquidity: number;
buyTax: number;
sellTax: number;
sniperCount: number;
bundlerCount: number;
top10Concentration: number;
};
}
// Main risk engine function
async function evaluateToken(
tokenAddress: string,
chain: Chain,
intendedPositionUsd: number
): Promise<RiskEngineResult> {
const flags: string[] = [];
// Step 1: Quick check (Trading Lite for Solana, contract analysis for EVM)
let quickCheck: { riskScore: number; isHoneypot: boolean; liquidity: number };
if (chain === Chain.SOL) {
quickCheck = await tradingClient.tradingLite.analyze(tokenAddress);
} else {
// EVM chains - use contract analysis
const contract = await threatClient.contracts.analyze(tokenAddress, { chain });
const pools = await tradingClient.tokens.getPools(tokenAddress, { chain });
const totalLiquidity = pools.pools.reduce((sum, p) => sum + p.liquidity, 0);
quickCheck = {
riskScore: contract.overallRisk,
isHoneypot: contract.isHoneypot ?? false,
liquidity: totalLiquidity,
};
}
// Immediate rejection: honeypot
if (quickCheck.isHoneypot) {
return {
shouldTrade: false,
maxPositionUsd: 0,
riskScore: 100,
flags: ['HONEYPOT DETECTED'],
metrics: {
liquidity: quickCheck.liquidity,
buyTax: 0,
sellTax: 100,
sniperCount: 0,
bundlerCount: 0,
top10Concentration: 0,
},
};
}
// Step 2: Tax analysis (EVM only)
let buyTax = 0;
let sellTax = 0;
if (chain !== Chain.SOL) {
try {
const tax = await threatClient.contracts.getTax(tokenAddress, { chain });
buyTax = tax.buyTax;
sellTax = tax.sellTax;
if (sellTax > 25) {
flags.push(`EXTREME SELL TAX: ${sellTax}%`);
} else if (sellTax > 10) {
flags.push(`High sell tax: ${sellTax}%`);
}
if (buyTax > 10) {
flags.push(`High buy tax: ${buyTax}%`);
}
} catch {
// Tax check failed - proceed with caution
flags.push('Tax check unavailable');
}
}
// Step 3: Holder analysis
let sniperCount = 0;
let bundlerCount = 0;
let top10Concentration = 0;
try {
const holders = await tradingClient.holderAnalysis.get(tokenAddress, { chain });
sniperCount = holders.sniperCount;
bundlerCount = holders.bundlerCount;
top10Concentration = holders.top10Percentage;
if (sniperCount > 10) {
flags.push(`Heavy sniping: ${sniperCount} snipers`);
}
if (bundlerCount > 3) {
flags.push(`Bundler activity: ${bundlerCount} clusters`);
}
if (top10Concentration > 80) {
flags.push(`High concentration: Top 10 hold ${top10Concentration}%`);
}
} catch {
flags.push('Holder analysis unavailable');
}
// Step 4: Calculate max position based on liquidity
const liquidity = quickCheck.liquidity;
const maxPositionUsd = liquidity * 0.02; // 2% of liquidity
if (intendedPositionUsd > maxPositionUsd) {
flags.push(`Position too large for liquidity`);
}
if (liquidity < 5000) {
flags.push(`Low liquidity: $${liquidity.toLocaleString()}`);
}
// Final decision
const criticalFlags = flags.filter(f =>
f.includes('EXTREME') ||
f.includes('HONEYPOT') ||
f.includes('Position too large')
);
const shouldTrade =
!quickCheck.isHoneypot &&
quickCheck.riskScore < 60 &&
sellTax < 25 &&
liquidity >= intendedPositionUsd * 50 &&
criticalFlags.length === 0;
return {
shouldTrade,
maxPositionUsd,
riskScore: quickCheck.riskScore,
flags,
metrics: {
liquidity,
buyTax,
sellTax,
sniperCount,
bundlerCount,
top10Concentration,
},
};
}
// Batch evaluation for multiple tokens
async function evaluateTokenBatch(
tokens: Array<{ address: string; chain: Chain }>,
positionSizeUsd: number
): Promise<Map<string, RiskEngineResult>> {
const results = new Map<string, RiskEngineResult>();
// Process in parallel (up to 10 at a time)
const batchSize = 10;
for (let i = 0; i < tokens.length; i += batchSize) {
const batch = tokens.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(t => evaluateToken(t.address, t.chain, positionSizeUsd))
);
batch.forEach((token, index) => {
results.set(token.address, batchResults[index]);
});
}
return results;
}
// Pre-trade checklist
async function preTradeCheck(
tokenAddress: string,
chain: Chain,
positionUsd: number
): Promise<{
approved: boolean;
checklist: Array<{ check: string; passed: boolean; details: string }>;
}> {
const evaluation = await evaluateToken(tokenAddress, chain, positionUsd);
const checklist = [
{
check: 'Not a honeypot',
passed: evaluation.metrics.sellTax < 100,
details: evaluation.metrics.sellTax >= 100 ? 'Cannot sell' : 'Sellable',
},
{
check: 'Acceptable tax',
passed: evaluation.metrics.sellTax < 15,
details: `Buy: ${evaluation.metrics.buyTax}%, Sell: ${evaluation.metrics.sellTax}%`,
},
{
check: 'Sufficient liquidity',
passed: evaluation.metrics.liquidity >= positionUsd * 50,
details: `$${evaluation.metrics.liquidity.toLocaleString()} available`,
},
{
check: 'Not heavily sniped',
passed: evaluation.metrics.sniperCount < 10,
details: `${evaluation.metrics.sniperCount} snipers detected`,
},
{
check: 'Not concentrated',
passed: evaluation.metrics.top10Concentration < 80,
details: `Top 10 hold ${evaluation.metrics.top10Concentration}%`,
},
{
check: 'Risk score acceptable',
passed: evaluation.riskScore < 60,
details: `Score: ${evaluation.riskScore}/100`,
},
];
return {
approved: checklist.every(c => c.passed),
checklist,
};
}
// Usage example
async function main() {
const token = 'PumpTokenMint...';
const positionSize = 500; // $500
const check = await preTradeCheck(token, Chain.SOL, positionSize);
console.log('\nPre-Trade Checklist:');
console.log('==================');
for (const item of check.checklist) {
const status = item.passed ? '✓' : '✗';
console.log(`${status} ${item.check}: ${item.details}`);
}
console.log('==================');
console.log(`Decision: ${check.approved ? 'APPROVED' : 'REJECTED'}`);
}
Example Tokens for Testing
Solana Tokens
Test against tokens from Pump.fun, Raydium, and Jupiter trending lists. Scam tokens are deployed constantly, so live data is the best test data.EVM Test Tokens
| Address | Chain | Description |
|---|---|---|
0xdAC17F958D2ee523a2206206994597C13D831ec7 | ETH | USDT - safe, no tax |
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | ETH | USDC - safe baseline |
Testing tip: For realistic testing, grab tokens from live trending feeds and run them through your risk engine. This gives you real data on current scam patterns.
Performance Optimization
Caching Strategy
// Cache results to reduce API calls
const cache = new Map<string, { result: RiskEngineResult; timestamp: number }>();
const CACHE_TTL = 60 * 1000; // 1 minute
async function evaluateWithCache(
tokenAddress: string,
chain: Chain,
positionUsd: number
): Promise<RiskEngineResult> {
const cacheKey = `${tokenAddress}-${chain}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.result;
}
const result = await evaluateToken(tokenAddress, chain, positionUsd);
cache.set(cacheKey, { result, timestamp: Date.now() });
return result;
}
Parallel Processing
// Evaluate multiple tokens in parallel
async function batchEvaluate(
tokens: string[],
chain: Chain,
positionUsd: number
): Promise<Map<string, RiskEngineResult>> {
const results = await Promise.all(
tokens.map(t => evaluateToken(t, chain, positionUsd))
);
return new Map(tokens.map((t, i) => [t, results[i]]));
}
API Quick Reference
| Endpoint | Use Case | Response Time |
|---|---|---|
GET /trading-lite/{address} | Quick analysis (SOL) | ~200ms |
GET /contracts/{address} | Contract analysis | ~500ms |
GET /contracts/{address}/tax | Tax detection | ~300ms |
GET /holder-analysis/{address} | Holder distribution | ~400ms |
GET /tokens/{address}/pools | Liquidity pools | ~300ms |
Next Steps
Get Your API Key
Start screening tokens today
Trading Client SDK
SDK for holder analysis
Honeypots Explained
Deep dive on honeypot mechanics
Snipers & Bundlers
Understand coordinated trading
