#!/usr/bin/env node import { readFileSync } from "node:fs"; import { resolve } from "node:path"; const repoRoot = resolve(new URL("../..", import.meta.url).pathname); const sourcePath = resolve(repoRoot, "config/official-protocol-integration-sources.json"); const addressPattern = /^0x[a-fA-F0-9]{40}$/; function fail(message) { console.error(`[validate-official-protocol-sources] ERROR: ${message}`); process.exitCode = 1; } function isObject(value) { return value && typeof value === "object" && !Array.isArray(value); } let config; try { config = JSON.parse(readFileSync(sourcePath, "utf8")); } catch (error) { fail(`cannot read ${sourcePath}: ${error.message}`); process.exit(); } if (typeof config.schemaVersion !== "string") fail("schemaVersion must be a string"); if (typeof config.updated !== "string") fail("updated must be a string"); if (!Array.isArray(config.guardrails) || config.guardrails.length === 0) fail("guardrails must be a non-empty array"); if (!isObject(config.protocols)) fail("protocols must be an object"); const protocols = config.protocols || {}; for (const [protocol, profile] of Object.entries(protocols)) { if (!isObject(profile)) { fail(`protocol ${protocol} must be an object`); continue; } if (!Array.isArray(profile.officialSources) || profile.officialSources.length === 0) { fail(`protocol ${protocol} must declare officialSources[]`); } else { for (const [index, source] of profile.officialSources.entries()) { if (typeof source.label !== "string" || source.label.length === 0) { fail(`protocol ${protocol} officialSources[${index}].label must be a non-empty string`); } if (typeof source.url !== "string" || !/^https:\/\//.test(source.url)) { fail(`protocol ${protocol} officialSources[${index}].url must be https`); } if (typeof source.retrievedAt !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(source.retrievedAt)) { fail(`protocol ${protocol} officialSources[${index}].retrievedAt must be YYYY-MM-DD`); } } } if (!isObject(profile.poolDiscovery)) { fail(`protocol ${protocol} must declare poolDiscovery`); } for (const [chainId, chain] of Object.entries(profile.chainFactories || {})) { if (!/^\d+$/.test(chainId)) fail(`protocol ${protocol} chainFactories key ${chainId} must be numeric`); for (const key of ["dvmFactory", "factory", "router", "vault"]) { if (chain[key] && !addressPattern.test(chain[key])) { fail(`protocol ${protocol} chain ${chainId} ${key} must be an EVM address`); } } if (typeof chain.network !== "string" || chain.network.length === 0) { fail(`protocol ${protocol} chain ${chainId} network must be a non-empty string`); } if (typeof chain.source !== "string" || chain.source.length === 0) { fail(`protocol ${protocol} chain ${chainId} source must be a non-empty string`); } } for (const [chainId, chain] of Object.entries(profile.unsupportedOrUnverifiedChains || {})) { if (!/^\d+$/.test(chainId)) fail(`protocol ${protocol} unsupported key ${chainId} must be numeric`); if (typeof chain.network !== "string" || chain.network.length === 0) { fail(`protocol ${protocol} unsupported ${chainId} network must be a non-empty string`); } if (typeof chain.status !== "string" || chain.status.length === 0) { fail(`protocol ${protocol} unsupported ${chainId} status must be a non-empty string`); } } } if (process.exitCode) process.exit(); console.log(`[validate-official-protocol-sources] OK: ${Object.keys(protocols).length} protocol profile(s)`);