UNPKG

token-sniffer-lite

Version:

Advanced configurable token sniffer for EVM chains with safety scoring

98 lines (84 loc) 2.9 kB
import { ethers } from "ethers"; import fetch from "node-fetch"; export async function sniffToken(address, config) { const { rpcUrl, explorerApiKey, explorerApiBase, chainName = "evm" } = config; const result = { isVerified: false, hasBlacklist: false, canMint: false, hasHiddenFees: false, honeypotRisk: false, ownerControlsAll: false, isRenounced: false, liquidityLocked: false, score: 100 }; try { // Fetch contract verification + ABI const explorerUrl = `${explorerApiBase}?module=contract&action=getsourcecode&address=${address}&apikey=${explorerApiKey}`; const explorerRes = await fetch(explorerUrl); const explorerData = await explorerRes.json(); const contractInfo = explorerData.result?.[0]; if (contractInfo?.ABI && contractInfo.ABI !== "Contract source code not verified") { result.isVerified = true; } else { result.score -= 20; } if (result.isVerified) { const abi = JSON.parse(contractInfo.ABI); const functions = abi.filter(f => f.type === "function").map(f => f.name); if (functions.includes("mint") || functions.includes("mintTo")) { result.canMint = true; result.score -= 15; } if (functions.includes("blacklist") || functions.includes("addToBlacklist")) { result.hasBlacklist = true; result.score -= 15; } if (functions.includes("setFees") || functions.includes("setTax")) { result.hasHiddenFees = true; result.score -= 15; } if (functions.includes("owner") || functions.includes("getOwner")) { const provider = new ethers.JsonRpcProvider(rpcUrl); const contract = new ethers.Contract(address, abi, provider); try { const owner = await contract.owner?.() || await contract.getOwner?.(); if (owner === ethers.ZeroAddress) { result.isRenounced = true; } else { result.ownerControlsAll = true; result.score -= 15; } } catch (e) { // owner call failed } } } // Honeypot check try { const provider = new ethers.JsonRpcProvider(rpcUrl); const contract = new ethers.Contract( address, ["function transfer(address to, uint256 amount) public returns (bool)"], provider ); await contract.callStatic.transfer("0x000000000000000000000000000000000000dead", 1); } catch (err) { result.honeypotRisk = true; result.score -= 20; } // Placeholder: liquidity lock (future improvement) result.liquidityLocked = false; result.score -= 10; } catch (err) { console.error("Error during sniffing:", err); result.score = 0; } return result; }