check_token
Verify token safety before trading by detecting honeypot mechanics, concentrated holdings, fake ownership renouncement, and other scam indicators.
Instructions
Check if a token is safe to trade. Detects honeypot mechanics (can't sell), concentrated holdings, fake ownership renouncement, and other scam indicators. Use this before swapping into any unfamiliar token.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| tokenAddress | Yes | The token contract address to check | |
| chainId | No | Chain ID (1=Ethereum, 8453=Base) | |
| holderAddress | No | Optional: address to check balance for |
Implementation Reference
- src/mcp-server/server.ts:115-157 (handler)The MCP tool registration and handler implementation for 'check_token'.
// --- Tool: check_token --- server.tool( "check_token", "Check if a token is safe to trade. Detects honeypot mechanics (can't sell), concentrated holdings, fake ownership renouncement, and other scam indicators. Use this before swapping into any unfamiliar token.", { tokenAddress: z.string().describe("The token contract address to check"), chainId: z.number().default(1).describe("Chain ID (1=Ethereum, 8453=Base)"), holderAddress: z.string().optional().describe("Optional: address to check balance for"), }, async ({ tokenAddress, chainId, holderAddress }) => { const holder = (holderAddress || "0x0000000000000000000000000000000000000001") as Address; const sellCheck = await checkTokenSellability( chainId, tokenAddress as Address, holder, ); // Also scan the contract if we can fetch source const fetched = await fetchContractSource(tokenAddress, chainId); let scanResult = null; if (fetched.source) { scanResult = scanContractSource(fetched.source); } else if (fetched.bytecode) { scanResult = scanBytecode(fetched.bytecode); } return { content: [{ type: "text" as const, text: JSON.stringify({ tokenAddress, chainId, sellability: sellCheck, contractScan: scanResult, overallAssessment: sellCheck.canSell && (!scanResult || scanResult.riskScore < 70) ? "LIKELY_SAFE" : "POTENTIALLY_DANGEROUS", }, null, 2), }], }; }, ); - src/risk-engine/simulator.ts:134-215 (handler)The core logic for checking token sellability ('checkTokenSellability'), which is invoked by the 'check_token' MCP tool.
/** * Check if a token contract allows selling (anti-honeypot check). * Simulates: approve(router, amount) + router.swapExactTokensForETH(...) */ export async function checkTokenSellability( chainId: number, tokenAddress: Address, holderAddress: Address, ): Promise<{ canSell: boolean; indicators: string[] }> { const chainConfig = CHAIN_MAP[chainId]; if (!chainConfig) { return { canSell: false, indicators: ["unsupported_chain"] }; } const client = createPublicClient({ chain: chainConfig.chain, transport: http(chainConfig.rpcUrl), }); const indicators: string[] = []; try { // Check basic ERC20 functions exist const erc20Abi = parseAbi([ "function balanceOf(address) view returns (uint256)", "function totalSupply() view returns (uint256)", "function allowance(address,address) view returns (uint256)", "function decimals() view returns (uint8)", "function owner() view returns (address)", ]); // Check if owner() returns address(0) - potential fake renounce try { const owner = await client.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "owner", }); if (owner === "0x0000000000000000000000000000000000000000") { indicators.push("ownership_renounced_or_faked"); } } catch { // No owner function - could be fine } // Check total supply and holder balance try { const [totalSupply, balance] = await Promise.all([ client.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "totalSupply", }), client.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [holderAddress], }), ]); if (balance === 0n) { indicators.push("zero_balance"); } // Check if single address holds >90% of supply if (totalSupply > 0n && (balance * 100n) / totalSupply > 90n) { indicators.push("concentrated_holdings"); } } catch { indicators.push("failed_to_read_balances"); } return { canSell: !indicators.includes("zero_balance"), indicators, }; } catch (error: any) { indicators.push("contract_interaction_failed"); return { canSell: false, indicators }; } }