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'}`);
}