All checks were successful
Deploy to Phoenix / validate (push) Successful in 1m19s
Deploy to Phoenix / deploy (push) Successful in 52s
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Successful in 2m33s
phoenix-deploy Deployed to cloudflare-sync
Deploy to Phoenix / cloudflare (push) Successful in 39s
389 lines
16 KiB
JavaScript
Executable File
389 lines
16 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Generate ALL Mainnet readiness reports from the canonical matrix and surface.
|
|
*
|
|
* The reports are intentionally conservative: rows are not promoted by this
|
|
* script. It only summarizes what is already proven in config and what remains
|
|
* gated by missing addresses, vault assignments, funding, canaries, or protocol
|
|
* surface evidence.
|
|
*/
|
|
|
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
const repoRoot = resolve(new URL("../..", import.meta.url).pathname);
|
|
const matrixPath = resolve(repoRoot, "config/all-mainnet-pool-creation-matrix.json");
|
|
const surfacePath = resolve(repoRoot, "config/allmainnet-non-dodo-protocol-surface.json");
|
|
const outDir = resolve(repoRoot, "reports/status");
|
|
|
|
const statusesReadyForFunding = new Set(["created", "funded", "live_read", "canary_passed", "production"]);
|
|
const statusesReadyForCanary = new Set(["funded", "live_read", "canary_passed", "production"]);
|
|
const productionStatuses = new Set(["production"]);
|
|
|
|
function readJson(path) {
|
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
}
|
|
|
|
function countBy(items, keyFn) {
|
|
const counts = {};
|
|
for (const item of items) {
|
|
const key = keyFn(item);
|
|
counts[key] = (counts[key] || 0) + 1;
|
|
}
|
|
return Object.fromEntries(Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)));
|
|
}
|
|
|
|
function tokenRef(token) {
|
|
if (!token) return "MISSING";
|
|
return `${token.symbol || "MISSING"} ${token.address || "MISSING"}`;
|
|
}
|
|
|
|
function rowRef(row) {
|
|
return {
|
|
poolId: row.poolId,
|
|
chainId: row.chainId,
|
|
network: row.network,
|
|
protocol: row.protocol,
|
|
status: row.status,
|
|
requiredForSpend: Boolean(row.requiredForSpend),
|
|
publicRoutingEnabled: Boolean(row.publicRoutingEnabled),
|
|
baseToken: row.baseToken,
|
|
quoteToken: row.quoteToken,
|
|
poolAddress: row.poolAddress,
|
|
vaultAddress: row.vaultAddress,
|
|
missingRequiredVaultRoles: row.missingRequiredVaultRoles || [],
|
|
canaryEvidence: row.canaryEvidence,
|
|
notes: row.notes || [],
|
|
};
|
|
}
|
|
|
|
function rowBlockers(row, surface) {
|
|
const blockers = [];
|
|
if (row.status === "planned") blockers.push("pool_not_created");
|
|
if (!row.baseToken?.address) blockers.push(`missing_base_address:${row.baseToken?.symbol || "unknown"}`);
|
|
if (!row.quoteToken?.address) blockers.push(`missing_quote_address:${row.quoteToken?.symbol || "unknown"}`);
|
|
if (statusesReadyForFunding.has(row.status) && !row.poolAddress) blockers.push("missing_pool_address");
|
|
if ((row.missingRequiredVaultRoles || []).length > 0) blockers.push("missing_required_vault_assignments");
|
|
if (row.status === "created") blockers.push("pool_created_but_not_funded");
|
|
if (statusesReadyForCanary.has(row.status) && !row.canaryEvidence) blockers.push("missing_canary_evidence");
|
|
if (!productionStatuses.has(row.status)) blockers.push("not_production_status");
|
|
if (row.chainId === 651940 && surface.summary?.sameChainSwapInventoryPublished !== true) {
|
|
blockers.push("all_mainnet_same_chain_inventory_not_published");
|
|
}
|
|
return blockers;
|
|
}
|
|
|
|
function makeSummary(matrix, surface) {
|
|
const rows = matrix.rows;
|
|
const requiredRows = rows.filter((row) => row.requiredForSpend === true);
|
|
const rowsMissingVaults = rows.filter((row) => (row.missingRequiredVaultRoles || []).length > 0);
|
|
const requiredRowsMissingVaults = requiredRows.filter((row) => (row.missingRequiredVaultRoles || []).length > 0);
|
|
const rowsMissingTokenAddresses = rows.filter((row) => !row.baseToken?.address || !row.quoteToken?.address);
|
|
const requiredRowsMissingTokenAddresses = requiredRows.filter((row) => !row.baseToken?.address || !row.quoteToken?.address);
|
|
const rowsMissingCanary = requiredRows.filter((row) => statusesReadyForCanary.has(row.status) && !row.canaryEvidence);
|
|
const productionRows = requiredRows.filter((row) => row.status === "production");
|
|
|
|
return {
|
|
generatedAt: new Date().toISOString(),
|
|
matrixVersion: matrix.version,
|
|
matrixGeneratedAt: matrix.generatedAt,
|
|
totalRows: rows.length,
|
|
requiredForSpendRows: requiredRows.length,
|
|
statusCounts: countBy(rows, (row) => row.status),
|
|
protocolCounts: countBy(rows, (row) => row.protocol),
|
|
chainStatusCounts: Object.fromEntries(
|
|
Object.entries(countBy(rows, (row) => `${row.chainId} ${row.network}`)).sort(([a], [b]) => a.localeCompare(b)),
|
|
),
|
|
requiredStatusCounts: countBy(requiredRows, (row) => row.status),
|
|
plannedRows: rows.filter((row) => row.status === "planned").length,
|
|
createdUnfundedRows: rows.filter((row) => row.status === "created").length,
|
|
fundedRows: rows.filter((row) => row.status === "funded").length,
|
|
liveReadRows: rows.filter((row) => row.status === "live_read").length,
|
|
productionRows: productionRows.length,
|
|
rowsMissingVaultAssignments: rowsMissingVaults.length,
|
|
requiredRowsMissingVaultAssignments: requiredRowsMissingVaults.length,
|
|
rowsMissingTokenAddresses: rowsMissingTokenAddresses.length,
|
|
requiredRowsMissingTokenAddresses: requiredRowsMissingTokenAddresses.length,
|
|
requiredRowsMissingCanaryEvidence: rowsMissingCanary.length,
|
|
bridgeOnlyLive: surface.summary?.bridgeOnlyLive === true,
|
|
sameChainSwapInventoryPublished: surface.summary?.sameChainSwapInventoryPublished === true,
|
|
productionReady:
|
|
requiredRows.length > 0 &&
|
|
productionRows.length === requiredRows.length &&
|
|
requiredRowsMissingVaults.length === 0 &&
|
|
rowsMissingCanary.length === 0 &&
|
|
surface.summary?.sameChainSwapInventoryPublished === true,
|
|
};
|
|
}
|
|
|
|
function makeReadiness(matrix, surface, summary) {
|
|
const requiredRows = matrix.rows.filter((row) => row.requiredForSpend === true);
|
|
const allBlockers = requiredRows
|
|
.map((row) => ({
|
|
...rowRef(row),
|
|
blockers: rowBlockers(row, surface),
|
|
}))
|
|
.filter((row) => row.blockers.length > 0);
|
|
|
|
const protocolSurfaceBlockers = (surface.protocols || [])
|
|
.filter((protocol) => protocol.status !== "live" && (!protocol.factoryAddress || !protocol.routerAddress))
|
|
.map((protocol) => ({
|
|
name: protocol.name,
|
|
family: protocol.family,
|
|
status: protocol.status,
|
|
factoryAddress: protocol.factoryAddress,
|
|
routerAddress: protocol.routerAddress,
|
|
blockers: [
|
|
!protocol.factoryAddress ? "missing_factory_address" : null,
|
|
!protocol.routerAddress ? "missing_router_address" : null,
|
|
protocol.status !== "live" ? "protocol_surface_not_live" : null,
|
|
].filter(Boolean),
|
|
}));
|
|
|
|
return {
|
|
generatedAt: summary.generatedAt,
|
|
status: summary.productionReady ? "production_ready" : "blocked",
|
|
summary,
|
|
executionOrder: [
|
|
"commit_or_confirm_missing_token_and_accounting_addresses",
|
|
"assign_required_vaults_and_pause_controls",
|
|
"create_remaining_planned_pools",
|
|
"fund_created_and_new_pools",
|
|
"run_live_reserve_reads",
|
|
"run_bridge_fee_and_destination_settlement_preflights",
|
|
"run_10_100_1000_canary_swaps",
|
|
"publish_same_chain_inventory_and_verification_artifacts",
|
|
"enable_public_routing_only_for_canary_passed_or_production_rows",
|
|
],
|
|
blockers: allBlockers,
|
|
protocolSurfaceBlockers,
|
|
externalActionsRequired: [
|
|
"operator_signing_required_for_pool_creation_or_funding",
|
|
"vault_addresses_required_from_treasury_or_security_owner",
|
|
"canary_swap_transactions_required_on_live_networks",
|
|
"same_chain_factory_router_pool_inventory_required_before_public_route_generation",
|
|
],
|
|
};
|
|
}
|
|
|
|
function makeProductionGate(summary, readiness, surface) {
|
|
return {
|
|
generatedAt: summary.generatedAt,
|
|
status: summary.productionReady ? "production_ready" : "blocked",
|
|
gates: {
|
|
bridgeConfigOk: surface.bridgeSurface?.adapter?.status === "live",
|
|
sameChainSwapInventoryPublished: summary.sameChainSwapInventoryPublished,
|
|
requiredPoolsCreated: summary.requiredStatusCounts.planned === undefined,
|
|
requiredPoolsFundedOrBetter: !["planned", "created"].some((status) => summary.requiredStatusCounts[status] > 0),
|
|
vaultAssignmentsComplete: summary.requiredRowsMissingVaultAssignments === 0,
|
|
canaryEvidenceComplete: summary.requiredRowsMissingCanaryEvidence === 0,
|
|
productionStatusesComplete: summary.productionRows === summary.requiredForSpendRows,
|
|
},
|
|
counts: summary,
|
|
blockers: readiness.blockers.map((row) => ({
|
|
poolId: row.poolId,
|
|
chainId: row.chainId,
|
|
protocol: row.protocol,
|
|
status: row.status,
|
|
blockers: row.blockers,
|
|
})),
|
|
};
|
|
}
|
|
|
|
function mdTable(headers, rows) {
|
|
const header = `| ${headers.join(" | ")} |`;
|
|
const sep = `| ${headers.map((h) => (h.match(/count|rows|chain/i) ? "---:" : "---")).join(" | ")} |`;
|
|
const body = rows.map((row) => `| ${row.join(" | ")} |`);
|
|
return [header, sep, ...body].join("\n");
|
|
}
|
|
|
|
function escapeCell(value) {
|
|
return String(value ?? "-").replace(/\|/g, "\\|").replace(/\n/g, "<br>");
|
|
}
|
|
|
|
function writeReports(matrix, surface, summary, readiness, productionGate) {
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
writeFileSync(resolve(outDir, "all-mainnet-deployment-readiness-worklist-latest.json"), `${JSON.stringify(readiness, null, 2)}\n`);
|
|
writeFileSync(resolve(outDir, "all-mainnet-production-gate-latest.json"), `${JSON.stringify(productionGate, null, 2)}\n`);
|
|
writeFileSync(
|
|
resolve(outDir, "all-mainnet-spend-readiness-latest.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
generatedAt: summary.generatedAt,
|
|
version: matrix.version,
|
|
description: "Derived spend-readiness view for required ALL Mainnet pool rows.",
|
|
status: summary.productionReady ? "ready" : "blocked",
|
|
summary,
|
|
statusCounts: countBy(readiness.blockers, (row) => row.status),
|
|
routes: readiness.blockers.map((row) => ({
|
|
routeId: row.poolId,
|
|
chainId: row.chainId,
|
|
network: row.network,
|
|
protocol: row.protocol,
|
|
status: row.blockers.includes("pool_not_created")
|
|
? "missing_pool"
|
|
: row.blockers.includes("pool_created_but_not_funded")
|
|
? "pool_created_unfunded"
|
|
: row.blockers.includes("missing_canary_evidence")
|
|
? "missing_canary"
|
|
: "blocked",
|
|
path: `${row.baseToken?.symbol || "?"} -> ${row.quoteToken?.symbol || "?"}`,
|
|
blockers: row.blockers,
|
|
})),
|
|
assumptions: [
|
|
"Spend readiness is blocked until every required pool has vault assignments, funding/reserve evidence, canary evidence, and production status.",
|
|
"ALL Mainnet same-chain routing remains blocked while config/allmainnet-non-dodo-protocol-surface.json has sameChainSwapInventoryPublished=false.",
|
|
],
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
);
|
|
|
|
const poolMatrixReport = {
|
|
generatedAt: summary.generatedAt,
|
|
summary,
|
|
requiredPools: matrix.rows.filter((row) => row.requiredForSpend === true).map(rowRef),
|
|
rowsByStatus: countBy(matrix.rows, (row) => row.status),
|
|
rowsByProtocol: countBy(matrix.rows, (row) => row.protocol),
|
|
};
|
|
writeFileSync(resolve(outDir, "all-mainnet-pool-creation-matrix-latest.json"), `${JSON.stringify(poolMatrixReport, null, 2)}\n`);
|
|
|
|
const summaryRows = [
|
|
["totalRows", summary.totalRows],
|
|
["requiredForSpendRows", summary.requiredForSpendRows],
|
|
["plannedRows", summary.plannedRows],
|
|
["createdUnfundedRows", summary.createdUnfundedRows],
|
|
["fundedRows", summary.fundedRows],
|
|
["liveReadRows", summary.liveReadRows],
|
|
["productionRows", summary.productionRows],
|
|
["rowsMissingVaultAssignments", summary.rowsMissingVaultAssignments],
|
|
["requiredRowsMissingVaultAssignments", summary.requiredRowsMissingVaultAssignments],
|
|
["requiredRowsMissingCanaryEvidence", summary.requiredRowsMissingCanaryEvidence],
|
|
["sameChainSwapInventoryPublished", summary.sameChainSwapInventoryPublished],
|
|
["productionReady", summary.productionReady],
|
|
];
|
|
|
|
const blockersRows = readiness.blockers.map((row) => [
|
|
row.chainId,
|
|
`\`${escapeCell(row.poolId)}\``,
|
|
escapeCell(row.protocol),
|
|
escapeCell(row.status),
|
|
escapeCell(tokenRef(row.baseToken)),
|
|
escapeCell(tokenRef(row.quoteToken)),
|
|
escapeCell(row.poolAddress || "-"),
|
|
escapeCell(row.blockers.join(", ")),
|
|
]);
|
|
|
|
const readinessMd = [
|
|
"# ALL Mainnet Deployment Readiness Worklist",
|
|
"",
|
|
`Generated: \`${summary.generatedAt}\``,
|
|
"",
|
|
"## Summary",
|
|
"",
|
|
mdTable(["Item", "Count"], summaryRows),
|
|
"",
|
|
"## Execution Order",
|
|
"",
|
|
...readiness.executionOrder.map((step, index) => `${index + 1}. \`${step}\``),
|
|
"",
|
|
"## Required Pool Blockers",
|
|
"",
|
|
blockersRows.length
|
|
? mdTable(["Chain", "Pool", "Protocol", "Status", "Base", "Quote", "Pool Address", "Blockers"], blockersRows)
|
|
: "No required pool blockers remain.",
|
|
"",
|
|
"## Protocol Surface Blockers",
|
|
"",
|
|
readiness.protocolSurfaceBlockers.length
|
|
? mdTable(
|
|
["Protocol", "Family", "Status", "Factory", "Router", "Blockers"],
|
|
readiness.protocolSurfaceBlockers.map((protocol) => [
|
|
escapeCell(protocol.name),
|
|
escapeCell(protocol.family),
|
|
escapeCell(protocol.status),
|
|
escapeCell(protocol.factoryAddress || "-"),
|
|
escapeCell(protocol.routerAddress || "-"),
|
|
escapeCell(protocol.blockers.join(", ")),
|
|
]),
|
|
)
|
|
: "No protocol surface blockers remain.",
|
|
"",
|
|
].join("\n");
|
|
writeFileSync(resolve(outDir, "all-mainnet-deployment-readiness-worklist-latest.md"), readinessMd);
|
|
|
|
const spendRows = readiness.blockers.map((row) => [
|
|
`\`${escapeCell(row.poolId)}\``,
|
|
row.chainId,
|
|
escapeCell(row.protocol),
|
|
escapeCell(`${row.baseToken?.symbol || "?"} -> ${row.quoteToken?.symbol || "?"}`),
|
|
escapeCell(
|
|
row.blockers.includes("pool_not_created")
|
|
? "missing_pool"
|
|
: row.blockers.includes("pool_created_but_not_funded")
|
|
? "pool_created_unfunded"
|
|
: row.blockers.includes("missing_canary_evidence")
|
|
? "missing_canary"
|
|
: "blocked",
|
|
),
|
|
escapeCell(row.blockers.join(", ")),
|
|
]);
|
|
const spendMd = [
|
|
"# ALL Mainnet Spend Readiness",
|
|
"",
|
|
`Generated: \`${summary.generatedAt}\``,
|
|
"",
|
|
"## Summary",
|
|
"",
|
|
mdTable(["Item", "Count"], summaryRows),
|
|
"",
|
|
"## Required Route Gates",
|
|
"",
|
|
spendRows.length
|
|
? mdTable(["Route", "Chain", "Protocol", "Path", "Status", "Blockers"], spendRows)
|
|
: "All required spend routes are production-ready.",
|
|
"",
|
|
"## Direct Mapping Policy",
|
|
"",
|
|
"Direct ALL Mainnet -> public-network mappings remain inventory-only until verified bridge adapters, fee paths, vault assignments, live reserve reads, and canary evidence are recorded.",
|
|
"",
|
|
].join("\n");
|
|
writeFileSync(resolve(outDir, "all-mainnet-spend-readiness-latest.md"), spendMd);
|
|
|
|
const requiredPoolRows = poolMatrixReport.requiredPools.map((row) => [
|
|
row.chainId,
|
|
`\`${escapeCell(row.poolId)}\``,
|
|
escapeCell(row.protocol),
|
|
escapeCell(row.status),
|
|
escapeCell(row.poolAddress || "-"),
|
|
escapeCell((row.missingRequiredVaultRoles || []).join(", ") || "-"),
|
|
]);
|
|
const poolMatrixMd = [
|
|
"# ALL Mainnet Pool Creation Matrix",
|
|
"",
|
|
`Generated: \`${summary.generatedAt}\``,
|
|
"",
|
|
"## Summary",
|
|
"",
|
|
mdTable(["Status", "Count"], Object.entries(summary.statusCounts)),
|
|
"",
|
|
"## Required Pools",
|
|
"",
|
|
mdTable(["Chain", "Pool", "Protocol", "Status", "Address", "Missing Vault Roles"], requiredPoolRows),
|
|
"",
|
|
].join("\n");
|
|
writeFileSync(resolve(outDir, "all-mainnet-pool-creation-matrix-latest.md"), poolMatrixMd);
|
|
}
|
|
|
|
const matrix = readJson(matrixPath);
|
|
const surface = readJson(surfacePath);
|
|
const summary = makeSummary(matrix, surface);
|
|
const readiness = makeReadiness(matrix, surface, summary);
|
|
const productionGate = makeProductionGate(summary, readiness, surface);
|
|
|
|
writeReports(matrix, surface, summary, readiness, productionGate);
|
|
|
|
console.log(`[OK] ALL Mainnet readiness reports generated (${readiness.status}).`);
|