Complete markdown files cleanup and organization
- Organized 252 files across project - Root directory: 187 → 2 files (98.9% reduction) - Moved configuration guides to docs/04-configuration/ - Moved troubleshooting guides to docs/09-troubleshooting/ - Moved quick start guides to docs/01-getting-started/ - Moved reports to reports/ directory - Archived temporary files - Generated comprehensive reports and documentation - Created maintenance scripts and guides All files organized according to established standards.
This commit is contained in:
225
scripts/validate-token-list.js
Executable file
225
scripts/validate-token-list.js
Executable file
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Token List Validator
|
||||
* Validates token lists against the Uniswap Token Lists JSON schema
|
||||
* Based on: https://github.com/Uniswap/token-lists
|
||||
* Schema: https://uniswap.org/tokenlist.schema.json
|
||||
*/
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Fetch schema from Uniswap
|
||||
const SCHEMA_URL = 'https://uniswap.org/tokenlist.schema.json';
|
||||
|
||||
async function fetchSchema() {
|
||||
try {
|
||||
const response = await fetch(SCHEMA_URL);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch schema: ${response.statusText}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching schema:', error.message);
|
||||
console.error('Falling back to basic validation...');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Basic validation when schema is unavailable
|
||||
function basicValidation(tokenList) {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
|
||||
// Required fields
|
||||
if (!tokenList.name || typeof tokenList.name !== 'string') {
|
||||
errors.push('Missing or invalid "name" field');
|
||||
}
|
||||
|
||||
if (!tokenList.version) {
|
||||
errors.push('Missing "version" field');
|
||||
} else {
|
||||
if (typeof tokenList.version.major !== 'number') {
|
||||
errors.push('version.major must be a number');
|
||||
}
|
||||
if (typeof tokenList.version.minor !== 'number') {
|
||||
errors.push('version.minor must be a number');
|
||||
}
|
||||
if (typeof tokenList.version.patch !== 'number') {
|
||||
errors.push('version.patch must be a number');
|
||||
}
|
||||
}
|
||||
|
||||
if (!tokenList.tokens || !Array.isArray(tokenList.tokens)) {
|
||||
errors.push('Missing or invalid "tokens" array');
|
||||
return { errors, warnings, valid: false };
|
||||
}
|
||||
|
||||
// Validate each token
|
||||
tokenList.tokens.forEach((token, index) => {
|
||||
const prefix = `Token[${index}]`;
|
||||
|
||||
// Required token fields
|
||||
if (typeof token.chainId !== 'number') {
|
||||
errors.push(`${prefix}: Missing or invalid "chainId"`);
|
||||
}
|
||||
|
||||
if (!token.address || typeof token.address !== 'string') {
|
||||
errors.push(`${prefix}: Missing or invalid "address"`);
|
||||
} else {
|
||||
// Validate Ethereum address format
|
||||
if (!/^0x[a-fA-F0-9]{40}$/.test(token.address)) {
|
||||
errors.push(`${prefix}: Invalid Ethereum address format: ${token.address}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!token.name || typeof token.name !== 'string') {
|
||||
errors.push(`${prefix}: Missing or invalid "name"`);
|
||||
}
|
||||
|
||||
if (!token.symbol || typeof token.symbol !== 'string') {
|
||||
errors.push(`${prefix}: Missing or invalid "symbol"`);
|
||||
}
|
||||
|
||||
if (typeof token.decimals !== 'number' || token.decimals < 0 || token.decimals > 255) {
|
||||
errors.push(`${prefix}: Invalid "decimals" (must be 0-255)`);
|
||||
}
|
||||
|
||||
// Optional fields (warnings)
|
||||
if (!token.logoURI) {
|
||||
warnings.push(`${prefix}: Missing "logoURI" (optional but recommended)`);
|
||||
} else if (typeof token.logoURI !== 'string' || (!token.logoURI.startsWith('http://') && !token.logoURI.startsWith('https://') && !token.logoURI.startsWith('ipfs://'))) {
|
||||
warnings.push(`${prefix}: Invalid "logoURI" format (should be HTTP/HTTPS/IPFS URL)`);
|
||||
}
|
||||
});
|
||||
|
||||
return { errors, warnings, valid: errors.length === 0 };
|
||||
}
|
||||
|
||||
async function validateTokenList(filePath) {
|
||||
console.log(`\n🔍 Validating token list: ${filePath}\n`);
|
||||
|
||||
// Read token list file
|
||||
let tokenList;
|
||||
try {
|
||||
const fileContent = readFileSync(filePath, 'utf-8');
|
||||
tokenList = JSON.parse(fileContent);
|
||||
} catch (error) {
|
||||
console.error('❌ Error reading or parsing token list file:');
|
||||
console.error(` ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Try to fetch and use Uniswap schema
|
||||
const schema = await fetchSchema();
|
||||
let validationResult;
|
||||
|
||||
if (schema) {
|
||||
// Use AJV if available, otherwise fall back to basic validation
|
||||
try {
|
||||
// Try to use dynamic import for ajv (if installed)
|
||||
const { default: Ajv } = await import('ajv');
|
||||
const addFormats = (await import('ajv-formats')).default;
|
||||
|
||||
const ajv = new Ajv({ allErrors: true, verbose: true });
|
||||
addFormats(ajv);
|
||||
const validate = ajv.compile(schema);
|
||||
const valid = validate(tokenList);
|
||||
|
||||
if (valid) {
|
||||
validationResult = { errors: [], warnings: [], valid: true };
|
||||
} else {
|
||||
const errors = validate.errors?.map(err => {
|
||||
const path = err.instancePath || err.schemaPath || '';
|
||||
return `${path}: ${err.message}`;
|
||||
}) || [];
|
||||
validationResult = { errors, warnings: [], valid: false };
|
||||
}
|
||||
} catch (importError) {
|
||||
// AJV not available, use basic validation
|
||||
console.log('⚠️ AJV not available, using basic validation');
|
||||
validationResult = basicValidation(tokenList);
|
||||
}
|
||||
} else {
|
||||
// Schema fetch failed, use basic validation
|
||||
validationResult = basicValidation(tokenList);
|
||||
}
|
||||
|
||||
// Display results
|
||||
if (validationResult.valid) {
|
||||
console.log('✅ Token list is valid!\n');
|
||||
|
||||
// Display token list info
|
||||
console.log('📋 Token List Info:');
|
||||
console.log(` Name: ${tokenList.name}`);
|
||||
if (tokenList.version) {
|
||||
console.log(` Version: ${tokenList.version.major}.${tokenList.version.minor}.${tokenList.version.patch}`);
|
||||
}
|
||||
if (tokenList.timestamp) {
|
||||
console.log(` Timestamp: ${tokenList.timestamp}`);
|
||||
}
|
||||
console.log(` Tokens: ${tokenList.tokens.length}`);
|
||||
console.log('');
|
||||
|
||||
// List tokens
|
||||
console.log('🪙 Tokens:');
|
||||
tokenList.tokens.forEach((token, index) => {
|
||||
console.log(` ${index + 1}. ${token.symbol} (${token.name})`);
|
||||
console.log(` Address: ${token.address}`);
|
||||
console.log(` Chain ID: ${token.chainId}`);
|
||||
console.log(` Decimals: ${token.decimals}`);
|
||||
if (token.logoURI) {
|
||||
console.log(` Logo: ${token.logoURI}`);
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
|
||||
if (validationResult.warnings.length > 0) {
|
||||
console.log('⚠️ Warnings:');
|
||||
validationResult.warnings.forEach(warning => {
|
||||
console.log(` - ${warning}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error('❌ Token list validation failed!\n');
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
console.error('Errors:');
|
||||
validationResult.errors.forEach(error => {
|
||||
console.error(` ❌ ${error}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (validationResult.warnings.length > 0) {
|
||||
console.log('Warnings:');
|
||||
validationResult.warnings.forEach(warning => {
|
||||
console.log(` ⚠️ ${warning}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
const filePath = process.argv[2] || resolve(__dirname, '../docs/METAMASK_TOKEN_LIST.json');
|
||||
|
||||
if (!filePath) {
|
||||
console.error('Usage: node validate-token-list.js [path/to/token-list.json]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
validateTokenList(filePath).catch(error => {
|
||||
console.error('Unexpected error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user