goplus-mcp
Version:
Model Context Protocol (MCP) server for GoPlus Security API integration, enabling LLM clients to access blockchain security analysis
392 lines (391 loc) • 17.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerTokenSecurityTool = registerTokenSecurityTool;
const zod_1 = require("zod");
const axios_1 = __importDefault(require("axios"));
const tokenManager_js_1 = require("../utils/tokenManager.js");
const index_js_1 = require("../config/index.js");
const api_js_1 = require("../utils/api.js");
/**
* Analyze token security data and return structured results
* @param contractData Raw contract data from GoPlus API
* @param contractAddress Contract address
* @returns Structured analysis results
*/
function analyzeTokenSecurityData(contractData, contractAddress) {
try {
// Basic information extraction
const basicInfo = {
tokenName: contractData.token_name || 'Unknown',
tokenSymbol: contractData.token_symbol || 'Unknown',
totalSupply: contractData.total_supply || 'Unknown',
holderCount: contractData.holder_count || 'Unknown',
creatorAddress: contractData.creator_address || 'Unknown',
ownerAddress: contractData.owner_address || 'Unknown'
};
// Risk count and classification
let riskCount = 0;
let highRisks = [];
let mediumRisks = [];
let lowRisks = [];
// === Contract security risk check ===
// Open source status
if (contractData.is_open_source === "0") {
highRisks.push("🔴 Contract code is not open source");
riskCount++;
}
else if (contractData.is_open_source === "1") {
lowRisks.push("🟢 Contract code is open source");
}
// Proxy contract
if (contractData.is_proxy === "1") {
mediumRisks.push("🟡 Using proxy contract, implementation may have security risks");
riskCount++;
}
// Minting function
if (contractData.is_mintable === "1") {
mediumRisks.push("🟡 Token can be minted, may lead to price drop");
riskCount++;
}
// Contract owner
if (contractData.owner_address) {
if (contractData.owner_address === "0x0000000000000000000000000000000000000000") {
lowRisks.push("🟢 Contract ownership has been destroyed (black hole address)");
}
else {
mediumRisks.push("🟡 Contract has an owner address, may have administrative privileges");
riskCount++;
}
}
// Reclaim ownership
if (contractData.can_take_back_ownership === "1") {
highRisks.push("🔴 Contract can reclaim ownership, high risk");
riskCount++;
}
// Hidden owner
if (contractData.hidden_owner === "1") {
highRisks.push("🔴 Contract has a hidden owner, extremely high risk");
riskCount++;
}
// Self-destruct function
if (contractData.selfdestruct === "1") {
highRisks.push("🔴 Contract contains self-destruct function, may lead to asset loss");
riskCount++;
}
// External call
if (contractData.external_call === "1") {
mediumRisks.push("🟡 Contract contains external calls, may introduce third-party risks");
riskCount++;
}
// Modify balance permission
if (contractData.owner_change_balance === "1") {
highRisks.push("🔴 Owner can modify any address balance, extremely high risk");
riskCount++;
}
// Anti-whale mechanism
if (contractData.is_anti_whale === "1") {
if (contractData.anti_whale_modifiable === "1") {
mediumRisks.push("🟡 Anti-whale mechanism is modifiable");
riskCount++;
}
else {
lowRisks.push("🟢 Anti-whale mechanism is enabled");
}
}
// Transfer pausable
if (contractData.transfer_pausable === "1") {
highRisks.push("🔴 Token transfers can be paused");
riskCount++;
}
// Trading cooldown
if (contractData.trading_cooldown === "1") {
mediumRisks.push("🟡 Trading cooldown mechanism is enabled");
riskCount++;
}
// Slippage modifiable
if (contractData.slippage_modifiable === "1") {
mediumRisks.push("🟡 Slippage can be modified");
riskCount++;
}
// Personal slippage modifiable
if (contractData.personal_slippage_modifiable === "1") {
mediumRisks.push("🟡 Personal slippage can be modified");
riskCount++;
}
// === Transaction security risk check ===
// Exchange support
if (contractData.is_in_dex === "1") {
lowRisks.push("🟢 Token is listed on decentralized exchange");
// Buy and sell tax rates
const buyTax = contractData.buy_tax ? parseFloat(contractData.buy_tax) : 0;
const sellTax = contractData.sell_tax ? parseFloat(contractData.sell_tax) : 0;
const transferTax = contractData.transfer_tax ? parseFloat(contractData.transfer_tax) : 0;
if (buyTax > 0 || sellTax > 0 || transferTax > 0) {
const taxInfo = {
buyTax: buyTax ? (buyTax * 100).toFixed(2) + '%' : '0%',
sellTax: sellTax ? (sellTax * 100).toFixed(2) + '%' : '0%',
transferTax: transferTax ? (transferTax * 100).toFixed(2) + '%' : '0%'
};
basicInfo.taxInfo = taxInfo;
if (buyTax >= 0.1 || sellTax >= 0.1 || transferTax >= 0.1) {
highRisks.push(`🔴 Token tax rate is too high: Buy ${taxInfo.buyTax}, Sell ${taxInfo.sellTax}, Transfer ${taxInfo.transferTax}`);
riskCount++;
}
else if (buyTax >= 0.05 || sellTax >= 0.05 || transferTax >= 0.05) {
mediumRisks.push(`🟡 Token tax rate is moderate: Buy ${taxInfo.buyTax}, Sell ${taxInfo.sellTax}, Transfer ${taxInfo.transferTax}`);
riskCount++;
}
else if (buyTax > 0 || sellTax > 0 || transferTax > 0) {
lowRisks.push(`🟢 Token tax rate is low: Buy ${taxInfo.buyTax}, Sell ${taxInfo.sellTax}, Transfer ${taxInfo.transferTax}`);
}
}
// Transaction status check
if (contractData.cannot_buy === "1") {
highRisks.push("🔴 Token cannot be purchased, may be a honeypot trap");
riskCount++;
}
if (contractData.cannot_sell_all === "1") {
highRisks.push("🔴 Token cannot be fully sold, may be a scam token");
riskCount++;
}
// Liquidity analysis
if (contractData.lp_holders && contractData.lp_holders.length > 0) {
const lpInfo = {
locked: false,
lockedPercent: 0,
totalLpHolders: contractData.lp_holders.length,
totalSupply: contractData.lp_total_supply
};
for (const holder of contractData.lp_holders) {
if (holder.is_locked === 1) {
lpInfo.locked = true;
lpInfo.lockedPercent += parseFloat(holder.percent || "0");
}
}
basicInfo.lpInfo = lpInfo;
if (lpInfo.locked && lpInfo.lockedPercent >= 0.8) {
lowRisks.push(`🟢 Liquidity is locked ${(lpInfo.lockedPercent * 100).toFixed(2)}%`);
}
else if (lpInfo.locked && lpInfo.lockedPercent >= 0.5) {
mediumRisks.push(`🟡 Liquidity is partially locked ${(lpInfo.lockedPercent * 100).toFixed(2)}%, some risk exists`);
riskCount++;
}
else {
highRisks.push(`🔴 Liquidity is not locked or locked proportion is too low ${(lpInfo.lockedPercent * 100).toFixed(2)}%, high risk`);
riskCount++;
}
}
// DEX information
if (contractData.dex && contractData.dex.length > 0) {
const dexList = contractData.dex.map((d) => `${d.name} (${d.liquidity_type})`).join(", ");
basicInfo.dexList = dexList;
lowRisks.push(`🟢 Listed on DEX: ${dexList}`);
}
}
else if (contractData.is_in_dex === "0") {
mediumRisks.push("🟡 Token is not listed on decentralized exchange, liquidity may be insufficient");
riskCount++;
}
// Honeypot detection
if (contractData.is_honeypot === "1") {
highRisks.push("🔴 Token is detected as honeypot, extremely high risk");
riskCount++;
}
// Honeypot with same creator
if (contractData.honeypot_with_same_creator === "1") {
highRisks.push("🔴 Creator has created honeypot tokens before, high risk");
riskCount++;
}
// Blacklist status
if (contractData.is_blacklisted === "1") {
highRisks.push("🔴 Token is blacklisted");
riskCount++;
}
// Whitelist status
if (contractData.is_whitelisted === "1") {
lowRisks.push("🟢 Token is whitelisted");
}
// Holder analysis
if (contractData.holders && contractData.holders.length > 0) {
const holderInfo = {
topHolders: contractData.holders.slice(0, 5),
creatorBalance: contractData.creator_balance || "0",
creatorPercent: contractData.creator_percent || "0",
ownerBalance: contractData.owner_balance || "0",
ownerPercent: contractData.owner_percent || "0"
};
basicInfo.holderInfo = holderInfo;
// Check creator/owner concentration
const creatorPercent = parseFloat(contractData.creator_percent || "0");
const ownerPercent = parseFloat(contractData.owner_percent || "0");
if (creatorPercent > 0.5 || ownerPercent > 0.5) {
highRisks.push("🔴 Creator/Owner holds more than 50% of tokens, high centralization risk");
riskCount++;
}
else if (creatorPercent > 0.2 || ownerPercent > 0.2) {
mediumRisks.push("🟡 Creator/Owner holds significant portion of tokens");
riskCount++;
}
}
// Calculate security score (0-100)
let securityScore = 100;
securityScore -= highRisks.length * 25;
securityScore -= mediumRisks.length * 10;
securityScore = Math.max(0, securityScore);
// Determine risk level
let riskLevel = "Low";
if (securityScore < 30) {
riskLevel = "Extremely High";
}
else if (securityScore < 50) {
riskLevel = "High";
}
else if (securityScore < 70) {
riskLevel = "Medium";
}
else if (securityScore < 85) {
riskLevel = "Low";
}
else {
riskLevel = "Very Low";
}
// Return structured results
return {
success: true,
contractAddress,
riskCount,
riskLevel,
securityScore,
basicInfo,
risks: {
high: highRisks,
medium: mediumRisks,
low: lowRisks
},
details: contractData
};
}
catch (error) {
return {
success: false,
error: error.message,
contractAddress
};
}
}
/**
* Register token security analysis tool to MCP server
* @param server MCP server instance
*/
function registerTokenSecurityTool(server) {
server.tool('token_security', 'Analyze token security for EVM-compatible blockchains (Ethereum, BSC, Polygon, Arbitrum, Optimism, Avalanche, Fantom, Cronos, Gnosis, Moonriver, Moonbeam, OKC, HECO, KCC, Metis, Polygon zkEVM, zkSync Era, Linea, Base, Mantle, Scroll)', {
chain_id: zod_1.z.string().describe('Blockchain ID (1=Ethereum, 56=BSC, 137=Polygon, 42161=Arbitrum, 204=opBNB, 324=zkSync Era, 59144=Linea, 8453=Base, 5000=Mantle, 130=Unichain, 534352=Scroll, 10=Optimism, 43114=Avalanche, 250=Fantom, 25=Cronos, 128=HECO, 100=Gnosis, 321=KCC, 201022=FON, 42766=ZKFair, 2818=Morph, 1868=Soneium, 1514=Story, 146=Sonic, 2741=Abstract, 177=Hashkey, 80094=Berachain, 10143=Monad, 480=World Chain, 81457=Blast, 1625=Gravity, 185=Mint, 48899=Zircuit, 196=X Layer, 810180=zkLink Nova, 200901=Bitlayer, 4200=Merlin, 169=Manta Pacific)'),
contract_addresses: zod_1.z.string().describe('Contract address(es) to analyze, separated by commas for multiple addresses')
}, async ({ chain_id, contract_addresses }) => {
try {
// Get authorization header
let authHeader = tokenManager_js_1.tokenManager.getAuthorizationHeader();
if (!authHeader) {
const newToken = await (0, api_js_1.getGoPlusAccessToken)();
tokenManager_js_1.tokenManager.setGoPlusToken(newToken);
authHeader = tokenManager_js_1.tokenManager.getAuthorizationHeader();
}
// Make API request
const response = await axios_1.default.get(`${index_js_1.API_BASE_URL}/token_security/${chain_id}`, {
params: {
contract_addresses: contract_addresses
},
headers: {
'Authorization': authHeader
},
timeout: 30000
});
if (response.data && response.data.code === 1) {
const results = response.data.result;
if (!results || Object.keys(results).length === 0) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: "No token security data found for the provided contract address(es)",
chain_id: chain_id,
contract_addresses: contract_addresses
}, null, 2)
}
]
};
}
// Process each contract address
const analysisResults = {};
for (const [contractAddress, contractData] of Object.entries(results)) {
if (contractData) {
analysisResults[contractAddress] = analyzeTokenSecurityData(contractData, contractAddress);
}
else {
analysisResults[contractAddress] = {
success: false,
error: "No data available for this contract address"
};
}
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
chain_id: chain_id,
analysis_results: analysisResults,
timestamp: new Date().toISOString()
}, null, 2)
}
]
};
}
else {
const errorMsg = response.data?.message || 'Unknown API error';
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: `API returned error: ${errorMsg}`,
chain_id: chain_id,
contract_addresses: contract_addresses
}, null, 2)
}
]
};
}
}
catch (error) {
let errorMessage = error.message;
if (error.response) {
errorMessage = `API Error (${error.response.status}): ${JSON.stringify(error.response.data)}`;
}
else if (error.request) {
errorMessage = `Network Error: No response from server - ${error.message}`;
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: errorMessage,
chain_id: chain_id,
contract_addresses: contract_addresses,
timestamp: new Date().toISOString()
}, null, 2)
}
]
};
}
});
}