- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains - Omit embedded publish git dirs and empty placeholders from index Made-with: Cursor
156 lines
5.0 KiB
JavaScript
156 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import https from 'https';
|
|
|
|
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
|
|
const packsRoot = path.join(repoRoot, 'reports', 'status', 'publication-packs');
|
|
const outJson = path.join(repoRoot, 'reports', 'status', 'publication-pack-explorer-status.json');
|
|
const outMd = path.join(repoRoot, 'docs', '11-references', 'PUBLICATION_PACK_EXPLORER_STATUS.md');
|
|
|
|
const chainIdToApi = {
|
|
'1': 'https://api.etherscan.io/v2/api',
|
|
'10': 'https://api.etherscan.io/v2/api',
|
|
'56': 'https://api.etherscan.io/v2/api',
|
|
'137': 'https://api.etherscan.io/v2/api',
|
|
'8453': 'https://api.etherscan.io/v2/api',
|
|
};
|
|
|
|
const apiKey = process.env.ETHERSCAN_API_KEY || '';
|
|
if (!apiKey) {
|
|
console.error('ETHERSCAN_API_KEY is required for pack explorer status checks.');
|
|
process.exit(1);
|
|
}
|
|
|
|
function ensureDir(filePath) {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
}
|
|
|
|
function getJson(url) {
|
|
return new Promise((resolve, reject) => {
|
|
https.get(url, {
|
|
headers: {
|
|
'user-agent': 'proxmox-publication-status-checker/1.0',
|
|
accept: 'application/json',
|
|
},
|
|
}, (res) => {
|
|
let data = '';
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
res.on('end', () => {
|
|
try {
|
|
resolve(JSON.parse(data));
|
|
} catch (err) {
|
|
reject(new Error(`Invalid JSON from ${url}: ${err.message}`));
|
|
}
|
|
});
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
function sleep(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function fetchStatus(chainId, address) {
|
|
const api = chainIdToApi[chainId];
|
|
if (!api) return { status: 'unsupported', detail: 'No configured explorer API' };
|
|
const url = `${api}?chainid=${chainId}&module=contract&action=getsourcecode&address=${address}&apikey=${apiKey}`;
|
|
|
|
for (let attempt = 1; attempt <= 4; attempt += 1) {
|
|
const json = await getJson(url);
|
|
const result = Array.isArray(json.result) ? json.result[0] : null;
|
|
if (result) {
|
|
const source = (result.SourceCode || '').trim();
|
|
const name = (result.ContractName || '').trim();
|
|
if (source || name) {
|
|
return {
|
|
status: 'verified',
|
|
contractName: name || null,
|
|
detail: result.CompilerVersion || 'verified',
|
|
};
|
|
}
|
|
return { status: 'unverified', detail: result.ABI || 'No source metadata' };
|
|
}
|
|
|
|
const message = typeof json.message === 'string' ? json.message : '';
|
|
const detail = typeof json.result === 'string' && json.result
|
|
? `${message}: ${json.result}`
|
|
: (message || 'No result');
|
|
const retryable = /rate limit|timeout|temporarily unavailable|busy/i.test(detail);
|
|
if (!retryable || attempt === 4) {
|
|
return { status: 'unknown', detail };
|
|
}
|
|
await sleep(400 * attempt);
|
|
}
|
|
|
|
return { status: 'unknown', detail: 'Status check exhausted retries' };
|
|
}
|
|
|
|
async function main() {
|
|
const packDirs = fs.readdirSync(packsRoot).sort();
|
|
const report = {
|
|
generatedAt: new Date().toISOString(),
|
|
packs: [],
|
|
};
|
|
|
|
for (const dir of packDirs) {
|
|
const packPath = path.join(packsRoot, dir, 'pack.json');
|
|
if (!fs.existsSync(packPath)) continue;
|
|
const pack = JSON.parse(fs.readFileSync(packPath, 'utf8'));
|
|
const entries = [];
|
|
for (const entry of pack.entries) {
|
|
try {
|
|
const status = await fetchStatus(entry.chainId, entry.address);
|
|
entries.push({ ...entry, explorerStatus: status.status, explorerDetail: status.detail, explorerContractName: status.contractName || null });
|
|
} catch (err) {
|
|
entries.push({ ...entry, explorerStatus: 'error', explorerDetail: err.message, explorerContractName: null });
|
|
}
|
|
}
|
|
const counts = entries.reduce((acc, entry) => {
|
|
acc[entry.explorerStatus] = (acc[entry.explorerStatus] || 0) + 1;
|
|
return acc;
|
|
}, {});
|
|
report.packs.push({
|
|
slug: dir,
|
|
chainId: pack.chainId,
|
|
chainName: pack.chainName,
|
|
explorer: pack.explorer,
|
|
counts,
|
|
entries,
|
|
});
|
|
}
|
|
|
|
const rows = report.packs.map((pack) => {
|
|
const verified = pack.counts.verified || 0;
|
|
const unverified = pack.counts.unverified || 0;
|
|
const unknown = pack.counts.unknown || 0;
|
|
const unsupported = pack.counts.unsupported || 0;
|
|
const error = pack.counts.error || 0;
|
|
return `| ${pack.chainId} | ${pack.chainName} | ${verified} | ${unverified} | ${unknown} | ${unsupported} | ${error} | ${pack.explorer} |`;
|
|
}).join('\n');
|
|
const md = `# Publication Pack Explorer Status
|
|
|
|
**Generated:** ${report.generatedAt}
|
|
|
|
Live explorer verification status for the grouped publication packs.
|
|
|
|
| Chain ID | Chain | Verified | Unverified | Unknown | Unsupported | Errors | Explorer |
|
|
| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |
|
|
${rows}
|
|
`;
|
|
|
|
ensureDir(outJson);
|
|
ensureDir(outMd);
|
|
fs.writeFileSync(outJson, JSON.stringify(report, null, 2) + '\n');
|
|
fs.writeFileSync(outMd, md + '\n');
|
|
console.log(`Wrote:\n- ${outJson}\n- ${outMd}`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|