check-ethereum-scanner
Version:
Safety checks for new Ethereum tokens
323 lines (275 loc) • 9.56 kB
JavaScript
const axios = require('axios');
const readline = require('readline');
const sdk = require('api')('@tokensniffer/v2.0#uea1oalm9dy116');
const fs = require('fs');
let processedAddresses = []; // Add this array to keep track of processed addresses
let counter = 0;
let tokenRetryAttempts = {}; // Object to track retry attempts for each token
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const API_KEY_DEX = 'ADDAPIKEYHERE';
const API_KEY_SNIFFER = 'ADDAPIKEYHERE';
const CHAIN = 'ethereal';
const ownershipRenouncedTracker = require('ownership-renounced-tracker');
const runSafetyChecks = (address, {
liquidity,
marketCap,
isContractRenounced,
txCount,
holders,
isFlagged,
exploits,
isSourceVerified,
hasMint,
hasProxy,
isSellable,
buyFee,
sellFee,
burnBalance,
lockBalance
}) => {
let safetyMessages = [];
let safeCount = 0;
const totalChecks = 10; // Updated number of total checks
if (liquidity && liquidity > 10000) {
safetyMessages.push("SAFE: Liquidity is $10,000+");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Liquidity is < $10,000");
}
if (marketCap && marketCap < 100000) {
safetyMessages.push("SAFE: Market cap is less than $100,000");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Market cap is more than $100,000");
}
if (isFlagged === false) {
safetyMessages.push("SAFE: Not flagged");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Is flagged");
}
if (exploits === "None") {
safetyMessages.push("SAFE: No known exploits");
safeCount++;
} else {
safetyMessages.push(`NOT SAFE: Known exploits - ${exploits}`);
}
//if (isSourceVerified === true) {
//safetyMessages.push("SAFE: Source is verified");
//safeCount++;
//} else {
//safetyMessages.push("NOT SAFE: Source is not verified");
// }
if (hasMint === false) {
safetyMessages.push("SAFE: No mint function");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Has mint function");
}
if (hasProxy === false) {
safetyMessages.push("SAFE: No proxy");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Has proxy");
}
if (isSellable === true) {
safetyMessages.push("SAFE: Is sellable");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Not sellable");
}
if (buyFee !== null && buyFee <= 15) {
safetyMessages.push("SAFE: Buy fee is 15% or less");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Buy fee is more than 15%");
}
if (sellFee !== null && sellFee <= 15) {
safetyMessages.push("SAFE: Sell fee is 15% or less");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Sell fee is more than 15%");
}
if (lockBalance && lockBalance > 0) {
safetyMessages.push("SAFE: Adequate lock balance");
safeCount++;
} else {
safetyMessages.push("NOT SAFE: Inadequate lock balance");
}
// Print all safety messages
safetyMessages.forEach((msg) => console.log(msg));
// Check if all are safe
if (safeCount === totalChecks) {
console.log("\nBUY");
fs.appendFile('buy.txt', address + '\n', (err) => {
if (err) {
console.error('Failed to save to file', err);
} else {
console.log(`Saved ${address} to buy.txt`);
}
});
} else {
console.log("\nNO BUY");
}
};
const fetchTokenInfoSniffer = (address, safetyChecks) => {
sdk
.getTokenResults({
apikey: API_KEY_SNIFFER,
include_metrics: 'true',
include_tests: 'false',
block_until_ready: 'true',
chain_id: '1',
address: address,
})
.then(({ data }) => {
console.log(`Is Flagged: ${data.is_flagged}`);
const exploits = data.exploits?.length > 0 ? data.exploits.join(', ') : 'None';
console.log(`Exploits: ${exploits}`);
// Existing Contract Outputs
for (const [key, value] of Object.entries(data.contract)) {
console.log(` ${key}: ${value}`);
}
console.log('Swap Simulation:');
for (const [key, value] of Object.entries(data.swap_simulation || {})) {
console.log(` ${key}: ${value}`);
}
if (data.pools && data.pools.length > 0) {
console.log('Pools:');
data.pools.forEach((pool) => {
for (const [key, value] of Object.entries(pool)) {
console.log(` ${key}: ${value}`);
}
});
} else {
console.log('Pools: None');
}
// Assuming that burnBalance and lockBalance are in the data object
const burnBalance = data.pools && data.pools[0] ? data.pools[0].burn_balance : null;
const lockBalance = data.pools && data.pools[0] ? data.pools[0].lock_balance : null;
console.log('Burn Balance:', burnBalance);
console.log('Lock Balance:', lockBalance);
runSafetyChecks(address, {
...safetyChecks,
txCount: parseInt(data.metrics?.txCount ?? '0', 10),
holders: parseInt(data.metrics?.holders ?? '0', 10),
isFlagged: data.is_flagged,
exploits: exploits,
isSourceVerified: data.contract?.is_source_verified ?? false,
hasMint: data.contract?.has_mint ?? false,
hasProxy: data.contract?.has_proxy ?? false,
isSellable: data.swap_simulation?.is_sellable ?? false,
buyFee: data.swap_simulation?.buy_fee ?? null,
sellFee: data.swap_simulation?.sell_fee ?? null,
burnBalance: burnBalance,
lockBalance: lockBalance
});
// promptForTokenAddress();
})
.catch(err => {
console.error(`An error occurred for token ${address}: ${err.message}. Skipping...`);
});
};
const fetchPairInfo = async (pair) => {
const response = await axios.get(`https://api.dextools.io/v1/pair`, {
params: {
chain: CHAIN,
address: pair
},
headers: {
'X-API-Key': API_KEY_DEX,
'Accept': 'application/json'
}
});
return response.data.data?.metrics?.liquidity ?? null;
};
const fetchTokenInfo = async (address) => {
try {
const response = await axios.get(`https://api.dextools.io/v1/token`, {
params: {
chain: CHAIN,
address
},
headers: {
'X-API-Key': API_KEY_DEX,
'Accept': 'application/json'
}
});
const data = response.data.data ?? {};
console.log(`Name: ${data.name ?? 'None'}`);
console.log(`Symbol: ${data.symbol ?? 'None'}`);
console.log(`Token Address: ${address}`);
console.log(`Is Contract Renounced: ${data.audit?.is_contract_renounced ?? 'None'}`);
console.log(`Telegram: ${data.links?.telegram ?? 'None'}`);
console.log(`Twitter: ${data.links?.twitter ?? 'None'}`);
console.log(`Website: ${data.links?.website ?? 'None'}`);
console.log(`Holders: ${data.metrics?.holders ?? 'None'}`);
console.log(`Transaction Count: ${data.metrics?.txCount ?? 'None'}`);
const currentPrice = data.reprPair?.price ?? 'None';
const totalSupply = data.metrics?.totalSupply ?? 'None';
const marketCap = (currentPrice !== 'None' && totalSupply !== 'None') ? (currentPrice * totalSupply) : 'None';
console.log(`Current Price: ${currentPrice}`);
console.log(`Market Cap: ${marketCap}`);
const pair = data.reprPair?.id?.pair ?? 'None';
let liquidity = null;
if (pair !== 'None') {
liquidity = await fetchPairInfo(pair);
}
fetchTokenInfoSniffer(address, { liquidity, marketCap: marketCap });
tokenRetryAttempts[address] = 0; // Reset retry attempts when successful
} catch (error) {
console.error('An error occurred for token', address, ':', error);
handleTokenRetry(address); // Handle the retry logic
}
};
const handleTokenRetry = (address) => {
if (tokenRetryAttempts[address] === undefined) {
tokenRetryAttempts[address] = 1; // Initialize if not already present
} else {
tokenRetryAttempts[address]++;
}
if (tokenRetryAttempts[address] <= 20) { // Retry for up to 20 minutes
setTimeout(() => fetchTokenInfo(address), 60000); // Retry after 1 minute
} else {
console.error(`NOBUY: Max retry attempts reached for token ${address}`);
}
};
const promptForTokenAddress = () => {
console.log();
rl.question('Token Address: ', (address) => {
fetchTokenInfo(address);
});
};
const processTokensFromFile = () => {
fs.readFile('renounced', 'utf-8', (err, data) => {
if (err) {
console.error('Error reading renounced:', err);
return;
}
const addresses = data.split('\n').filter(addr => !!addr); // filter out any empty lines
const newAddresses = addresses.filter(addr => !processedAddresses.includes(addr));
if (newAddresses.length === 0) {
counter++;
process.stdout.write(`Checking renounced.txt... ${counter} \r`);
return;
}
newAddresses.forEach((address) => {
processedAddresses.push(address); // Mark this address as processed
fetchTokenInfo(address);
// Remove processed token from 'renounced' file
setTimeout(() => {
fs.writeFile('renounced', addresses.filter(a => a !== address).join('\n') + '\n', 'utf-8', err => {
if (err) {
console.error('Error writing to renounced:', err);
} else {
console.log(`Removed ${address} from renounced`);
}
});
}, 15000);
});
});
};
setInterval(processTokensFromFile, 1000); // It was 30000, now it's 1000 for 1 second