token-sniffer-lite
Version:
Advanced configurable token sniffer for EVM chains with safety scoring
98 lines (84 loc) • 2.9 kB
JavaScript
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;
}