chore: pnpm lockfile, info-defi-oracle-138 app, token-lists, OMNL discovery output
- Refresh pnpm-lock.yaml / workspace after prior merge - Add Chain 138 info hub SPA (info-defi-oracle-138) - Token list and validation script tweaks; path_b report; Hyperledger proxmox install notes - HYBX implementation roadmap and routing graph data model Note: transaction-composer is a nested git repo — convert to submodule before tracking. Made-with: Cursor
This commit is contained in:
@@ -4,16 +4,57 @@
|
||||
* Validates that all logoURI URLs are accessible and return image content
|
||||
*/
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { readFileSync, existsSync, statSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const PROJECT_ROOT = resolve(__dirname, '../..');
|
||||
|
||||
const MAX_LOGO_SIZE = 500 * 1024; // 500KB
|
||||
const IMAGE_MIME_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml', 'image/webp', 'image/gif'];
|
||||
|
||||
function inferContentTypeFromPath(filePath) {
|
||||
if (filePath.endsWith('.svg')) return 'image/svg+xml';
|
||||
if (filePath.endsWith('.png')) return 'image/png';
|
||||
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
|
||||
if (filePath.endsWith('.webp')) return 'image/webp';
|
||||
if (filePath.endsWith('.gif')) return 'image/gif';
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveLocalRepoAsset(logoURI) {
|
||||
try {
|
||||
const url = new URL(logoURI);
|
||||
if (url.protocol !== 'https:') return null;
|
||||
|
||||
if (url.hostname === 'raw.githubusercontent.com') {
|
||||
const segments = url.pathname.split('/').filter(Boolean);
|
||||
if (segments.length < 4) return null;
|
||||
const relativePath = segments.slice(3).join('/');
|
||||
const candidate = resolve(PROJECT_ROOT, relativePath);
|
||||
return existsSync(candidate) ? candidate : null;
|
||||
}
|
||||
|
||||
if (url.hostname === 'gitea.d-bis.org') {
|
||||
const marker = '/raw/branch/';
|
||||
const markerIndex = url.pathname.indexOf(marker);
|
||||
if (markerIndex === -1) return null;
|
||||
const afterMarker = url.pathname.slice(markerIndex + marker.length);
|
||||
const pathSegments = afterMarker.split('/').filter(Boolean);
|
||||
if (pathSegments.length < 2) return null;
|
||||
const relativePath = pathSegments.slice(1).join('/');
|
||||
const candidate = resolve(PROJECT_ROOT, relativePath);
|
||||
return existsSync(candidate) ? candidate : null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function validateLogo(logoURI, tokenInfo) {
|
||||
const issues = [];
|
||||
|
||||
@@ -24,6 +65,24 @@ async function validateLogo(logoURI, tokenInfo) {
|
||||
|
||||
// For HTTPS URLs, validate accessibility
|
||||
if (logoURI.startsWith('https://')) {
|
||||
const localAsset = resolveLocalRepoAsset(logoURI);
|
||||
if (localAsset) {
|
||||
try {
|
||||
const size = statSync(localAsset).size;
|
||||
const contentType = inferContentTypeFromPath(localAsset);
|
||||
if (!contentType || !IMAGE_MIME_TYPES.includes(contentType)) {
|
||||
issues.push(`Local repo asset has unsupported extension: ${localAsset}`);
|
||||
}
|
||||
if (size > MAX_LOGO_SIZE) {
|
||||
issues.push(`Local repo asset too large: ${(size / 1024).toFixed(2)}KB (max ${MAX_LOGO_SIZE / 1024}KB)`);
|
||||
}
|
||||
return issues;
|
||||
} catch (error) {
|
||||
issues.push(`Failed to stat local repo asset: ${error.message}`);
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(logoURI, { method: 'HEAD' });
|
||||
|
||||
@@ -132,4 +191,3 @@ validateLogos(filePath).then(exitCode => {
|
||||
console.error('Unexpected error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ async function getSchema() {
|
||||
return tokenLists.schema;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ @uniswap/token-lists package not available, fetching schema from URL...\n');
|
||||
console.log('ℹ️ Using fallback token list schema source\n');
|
||||
}
|
||||
|
||||
// Fallback: fetch schema from Uniswap
|
||||
@@ -69,10 +69,38 @@ function isChecksummed(address) {
|
||||
function enhancedValidation(tokenList) {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const infos = [];
|
||||
const seenAddresses = new Set();
|
||||
const seenSymbols = new Map(); // chainId -> Set of symbols
|
||||
const seenSymbols = new Map(); // chainId -> Map<symbol, token[]>
|
||||
let detectedChainId = null;
|
||||
|
||||
function isAllowedGruVersionDuplicate(existingToken, nextToken) {
|
||||
if (!existingToken?.extensions || !nextToken?.extensions) return false;
|
||||
|
||||
const existingVersion = existingToken.extensions.gruVersion;
|
||||
const nextVersion = nextToken.extensions.gruVersion;
|
||||
const existingCurrencyCode = existingToken.extensions.currencyCode;
|
||||
const nextCurrencyCode = nextToken.extensions.currencyCode;
|
||||
|
||||
if (typeof existingVersion !== 'string' || typeof nextVersion !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existingVersion === nextVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof existingCurrencyCode !== 'string' || typeof nextCurrencyCode !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existingCurrencyCode !== nextCurrencyCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Required fields
|
||||
if (!tokenList.name || typeof tokenList.name !== 'string') {
|
||||
errors.push('Missing or invalid "name" field');
|
||||
@@ -154,13 +182,27 @@ function enhancedValidation(tokenList) {
|
||||
// Symbol uniqueness per chainId
|
||||
const chainId = token.chainId || 0;
|
||||
if (!seenSymbols.has(chainId)) {
|
||||
seenSymbols.set(chainId, new Set());
|
||||
seenSymbols.set(chainId, new Map());
|
||||
}
|
||||
const symbolSet = seenSymbols.get(chainId);
|
||||
if (symbolSet.has(token.symbol.toUpperCase())) {
|
||||
warnings.push(`${prefix}: Duplicate symbol "${token.symbol}" on chainId ${chainId}`);
|
||||
const symbolMap = seenSymbols.get(chainId);
|
||||
const symbolKey = token.symbol.toUpperCase();
|
||||
const existingTokens = symbolMap.get(symbolKey) || [];
|
||||
if (existingTokens.length > 0) {
|
||||
const duplicateAllowed = existingTokens.every(existingToken =>
|
||||
isAllowedGruVersionDuplicate(existingToken, token)
|
||||
);
|
||||
|
||||
if (duplicateAllowed) {
|
||||
infos.push(
|
||||
`${prefix}: Allowed staged GRU duplicate symbol "${token.symbol}" on chainId ${chainId} ` +
|
||||
`(${existingTokens.map(existingToken => existingToken.extensions?.gruVersion).join(', ')} -> ${token.extensions?.gruVersion})`
|
||||
);
|
||||
} else {
|
||||
warnings.push(`${prefix}: Duplicate symbol "${token.symbol}" on chainId ${chainId}`);
|
||||
}
|
||||
}
|
||||
symbolSet.add(token.symbol.toUpperCase());
|
||||
existingTokens.push(token);
|
||||
symbolMap.set(symbolKey, existingTokens);
|
||||
}
|
||||
|
||||
if (typeof token.decimals !== 'number' || token.decimals < 0 || token.decimals > 255) {
|
||||
@@ -181,7 +223,7 @@ function enhancedValidation(tokenList) {
|
||||
}
|
||||
});
|
||||
|
||||
return { errors, warnings, valid: errors.length === 0 };
|
||||
return { errors, warnings, infos, valid: errors.length === 0 };
|
||||
}
|
||||
|
||||
async function validateTokenList(filePath) {
|
||||
@@ -226,6 +268,7 @@ async function validateTokenList(filePath) {
|
||||
validationResult = {
|
||||
errors: [...schemaErrors, ...enhanced.errors],
|
||||
warnings: enhanced.warnings,
|
||||
infos: enhanced.infos,
|
||||
valid: false
|
||||
};
|
||||
}
|
||||
@@ -275,6 +318,14 @@ async function validateTokenList(filePath) {
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (validationResult.infos.length > 0) {
|
||||
console.log('ℹ️ Notes:');
|
||||
validationResult.infos.forEach(info => {
|
||||
console.log(` - ${info}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} else {
|
||||
@@ -295,6 +346,14 @@ async function validateTokenList(filePath) {
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (validationResult.infos.length > 0) {
|
||||
console.log('Notes:');
|
||||
validationResult.infos.forEach(info => {
|
||||
console.log(` ℹ️ ${info}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -318,4 +377,3 @@ validateTokenList(filePath).catch(error => {
|
||||
console.error('Unexpected error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user