2026-03-02 12:14:07 -08:00
#!/usr/bin/env node
/ * *
* Validates config / deployment - status . json for "minimum viable deployed graph" .
* Use in CI so deployment - realistic sim cannot run with half - filled state .
*
* Rules :
* - If bridgeAvailable === true on a chain , cwTokens must include at least cWUSDT and cWUSDC ( phase 1 ) .
2026-04-07 22:56:16 -07:00
* - For each pmmPool / pmmPoolsVolatile [ ] : role in { defense , public _routing , truu _routing } ,
* feeBps and k present , base / quote ( or tokenIn / tokenOut ) exist in cwTokens or anchorAddresses .
* TRUU must be listed under anchorAddresses when used as quote ( e . g . mainnet chain 1 ) .
2026-04-14 07:13:17 -07:00
* - Any row marked live / routingVisible / publicRoutingEnabled must use a native protocol contract ,
* not a placeholder scaffold or aggregator - only / non - native lane .
2026-03-02 12:14:07 -08:00
*
* Exit code : 0 if valid , 1 if invalid ( and prints errors to stderr ) .
* /
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const CONFIG _DIR = path . join ( _ _dirname , '..' , 'config' ) ;
2026-04-14 07:13:17 -07:00
let DEPLOYMENT _STATUS _PATH = path . join ( CONFIG _DIR , 'deployment-status.json' ) ;
2026-03-02 12:14:07 -08:00
const PHASE1 _CW = [ 'cWUSDT' , 'cWUSDC' ] ;
2026-04-07 22:56:16 -07:00
const VALID _ROLES = [ 'defense' , 'public_routing' , 'truu_routing' ] ;
const VALID _REFERENCE _PROTOCOLS = [ 'uniswap_v3' , 'balancer' , 'curve' , '1inch' ] ;
2026-04-14 07:13:17 -07:00
const VALID _NATIVE _REFERENCE _PROTOCOLS = [ 'uniswap_v3' , 'balancer' , 'curve' ] ;
2026-04-21 22:00:54 -07:00
const HOME _CHAIN _CANONICAL _PREFIXES = [ 'c' ] ;
2026-04-14 07:13:17 -07:00
function looksPlaceholderAddress ( address ) {
if ( ! address || typeof address !== 'string' ) return false ;
const normalized = address . toLowerCase ( ) ;
if ( ! /^0x[0-9a-f]{40}$/ . test ( normalized ) ) return false ;
if ( normalized === '0x0000000000000000000000000000000000000000' ) return true ;
const body = normalized . slice ( 2 ) ;
const placeholderPrefixes = [ 'd0' , '71' , 'ba' , 'c7' ] ;
const zeroCount = body . split ( '0' ) . length - 1 ;
return placeholderPrefixes . some ( ( prefix ) => body . startsWith ( prefix ) ) && zeroCount >= 24 ;
}
function isLiveRow ( row ) {
return row ? . live === true || row ? . routingVisible === true || row ? . publicRoutingEnabled === true ;
}
2026-03-02 12:14:07 -08:00
function loadJson ( p ) {
return JSON . parse ( fs . readFileSync ( p , 'utf8' ) ) ;
}
2026-04-07 22:56:16 -07:00
function validatePoolEntries ( chainId , pools , listLabel , knownTokens , errors ) {
for ( let i = 0 ; i < pools . length ; i ++ ) {
const pool = pools [ i ] ;
const base = pool . base ? ? pool . tokenIn ;
const quote = pool . quote ? ? pool . tokenOut ;
if ( ! VALID _ROLES . includes ( pool . role ) ) {
errors . push ( ` Chain ${ chainId } ${ listLabel } [ ${ i } ]: role must be one of ${ VALID _ROLES . join ( ', ' ) } ` ) ;
}
if ( pool . feeBps == null || pool . k == null ) {
errors . push ( ` Chain ${ chainId } ${ listLabel } [ ${ i } ]: feeBps and k required ` ) ;
}
if ( base && ! knownTokens . has ( base ) ) {
errors . push ( ` Chain ${ chainId } ${ listLabel } [ ${ i } ]: base/tokenIn " ${ base } " not in cwTokens or anchorAddresses ` ) ;
}
if ( quote && ! knownTokens . has ( quote ) ) {
errors . push ( ` Chain ${ chainId } ${ listLabel } [ ${ i } ]: quote/tokenOut " ${ quote } " not in cwTokens or anchorAddresses ` ) ;
}
const addr = pool . poolAddress ;
if ( addr != null && addr !== '' ) {
const z = String ( addr ) . toLowerCase ( ) ;
if ( z === '0x0000000000000000000000000000000000000000' ) {
errors . push ( ` Chain ${ chainId } ${ listLabel } [ ${ i } ]: poolAddress must not be zero when set ` ) ;
}
2026-04-14 07:13:17 -07:00
if ( pool . publicRoutingEnabled === true && looksPlaceholderAddress ( z ) ) {
errors . push ( ` Chain ${ chainId } ${ listLabel } [ ${ i } ]: live public routing poolAddress must use a native protocol contract, not a placeholder scaffold ( ${ addr } ) ` ) ;
}
}
if ( pool . publicRoutingEnabled === true && pool . venue && pool . venue !== 'dodo_pmm' ) {
errors . push ( ` Chain ${ chainId } ${ listLabel } [ ${ i } ]: public routing rows must use the native dodo_pmm venue, not " ${ pool . venue } " ` ) ;
2026-04-07 22:56:16 -07:00
}
}
}
2026-04-18 12:05:17 -07:00
function validateUniswapV2Entries ( chainId , pools , knownTokens , errors ) {
for ( let i = 0 ; i < pools . length ; i ++ ) {
const pool = pools [ i ] ;
const base = pool . base ;
const quote = pool . quote ;
if ( ! base || ! knownTokens . has ( base ) ) {
errors . push ( ` Chain ${ chainId } uniswapV2Pools[ ${ i } ]: base " ${ base } " not in cwTokens or anchorAddresses ` ) ;
}
if ( ! quote || ! knownTokens . has ( quote ) ) {
errors . push ( ` Chain ${ chainId } uniswapV2Pools[ ${ i } ]: quote " ${ quote } " not in cwTokens or anchorAddresses ` ) ;
}
if ( ! pool . poolAddress || looksPlaceholderAddress ( String ( pool . poolAddress ) . toLowerCase ( ) ) ) {
errors . push ( ` Chain ${ chainId } uniswapV2Pools[ ${ i } ]: poolAddress must use a real deployed pair address ` ) ;
}
if ( pool . venue && pool . venue !== 'uniswap_v2_pair' ) {
errors . push ( ` Chain ${ chainId } uniswapV2Pools[ ${ i } ]: venue must be "uniswap_v2_pair" when set ` ) ;
}
if ( pool . publicRoutingEnabled === true && ( ! pool . factoryAddress || ! pool . routerAddress ) ) {
errors . push ( ` Chain ${ chainId } uniswapV2Pools[ ${ i } ]: publicRoutingEnabled rows require factoryAddress and routerAddress ` ) ;
}
}
}
2026-03-02 12:14:07 -08:00
function main ( ) {
const status = loadJson ( DEPLOYMENT _STATUS _PATH ) ;
const chains = status . chains || { } ;
2026-04-21 22:00:54 -07:00
const homeChainId = String ( status . homeChainId || '' ) ;
2026-03-02 12:14:07 -08:00
const errors = [ ] ;
for ( const [ chainId , chain ] of Object . entries ( chains ) ) {
const cwTokens = chain . cwTokens || { } ;
2026-04-07 22:56:16 -07:00
const gasMirrors = chain . gasMirrors || { } ;
2026-03-02 12:14:07 -08:00
const anchorAddresses = chain . anchorAddresses || { } ;
2026-04-07 22:56:16 -07:00
const gasQuoteAddresses = chain . gasQuoteAddresses || { } ;
2026-03-02 12:14:07 -08:00
const pmmPools = chain . pmmPools || [ ] ;
2026-04-18 12:05:17 -07:00
const uniswapV2Pools = chain . uniswapV2Pools || [ ] ;
2026-04-07 22:56:16 -07:00
const pmmPoolsVolatile = chain . pmmPoolsVolatile || [ ] ;
const gasPmmPools = chain . gasPmmPools || [ ] ;
const gasReferenceVenues = chain . gasReferenceVenues || [ ] ;
2026-03-02 12:14:07 -08:00
const bridgeAvailable = chain . bridgeAvailable ;
2026-04-21 22:00:54 -07:00
const isHomeChain = chainId === homeChainId ;
const skipPhase1Requirement = isHomeChain || chainId === '651940' ;
if ( bridgeAvailable === true && ! skipPhase1Requirement ) {
2026-03-02 12:14:07 -08:00
for ( const sym of PHASE1 _CW ) {
if ( ! cwTokens [ sym ] || typeof cwTokens [ sym ] !== 'string' || ! cwTokens [ sym ] . trim ( ) ) {
errors . push ( ` Chain ${ chainId } ( ${ chain . name } ): bridgeAvailable=true but cwTokens. ${ sym } missing or empty ` ) ;
}
}
}
2026-04-07 22:56:16 -07:00
const knownTokens = new Set ( [
... Object . keys ( cwTokens ) ,
... Object . keys ( gasMirrors ) ,
... Object . keys ( anchorAddresses ) ,
... Object . keys ( gasQuoteAddresses ) ,
] ) ;
2026-03-02 12:14:07 -08:00
2026-04-21 22:00:54 -07:00
if ( isHomeChain ) {
for ( const pool of [ ... pmmPools , ... pmmPoolsVolatile ] ) {
const base = pool . base ? ? pool . tokenIn ;
const quote = pool . quote ? ? pool . tokenOut ;
if ( typeof base === 'string' && HOME _CHAIN _CANONICAL _PREFIXES . some ( ( prefix ) => base . startsWith ( prefix ) ) ) {
knownTokens . add ( base ) ;
}
if ( typeof quote === 'string' && HOME _CHAIN _CANONICAL _PREFIXES . some ( ( prefix ) => quote . startsWith ( prefix ) ) ) {
knownTokens . add ( quote ) ;
}
}
}
2026-04-07 22:56:16 -07:00
validatePoolEntries ( chainId , pmmPools , 'pmmPools' , knownTokens , errors ) ;
2026-04-18 12:05:17 -07:00
validateUniswapV2Entries ( chainId , uniswapV2Pools , knownTokens , errors ) ;
2026-04-07 22:56:16 -07:00
validatePoolEntries ( chainId , pmmPoolsVolatile , 'pmmPoolsVolatile' , knownTokens , errors ) ;
validatePoolEntries ( chainId , gasPmmPools , 'gasPmmPools' , knownTokens , errors ) ;
2026-03-02 12:14:07 -08:00
2026-04-07 22:56:16 -07:00
const gasPoolsByFamily = new Map ( ) ;
for ( const pool of gasPmmPools ) {
if ( ! pool . familyKey || typeof pool . familyKey !== 'string' ) {
errors . push ( ` Chain ${ chainId } gasPmmPools entry is missing familyKey ` ) ;
continue ;
2026-03-02 12:14:07 -08:00
}
2026-04-07 22:56:16 -07:00
if ( ! gasPoolsByFamily . has ( pool . familyKey ) ) gasPoolsByFamily . set ( pool . familyKey , [ ] ) ;
gasPoolsByFamily . get ( pool . familyKey ) . push ( pool ) ;
}
for ( const [ familyKey , pools ] of gasPoolsByFamily . entries ( ) ) {
const poolTypes = new Set ( pools . map ( ( pool ) => pool . poolType ) ) ;
if ( ! poolTypes . has ( 'wrapped_native' ) ) {
errors . push ( ` Chain ${ chainId } gas family ${ familyKey } : missing wrapped_native DODO pool ` ) ;
}
if ( ! poolTypes . has ( 'stable_quote' ) ) {
errors . push ( ` Chain ${ chainId } gas family ${ familyKey } : missing stable_quote DODO pool ` ) ;
}
}
const referenceVenuesByFamily = new Map ( ) ;
for ( let i = 0 ; i < gasReferenceVenues . length ; i ++ ) {
const venue = gasReferenceVenues [ i ] ;
if ( ! VALID _REFERENCE _PROTOCOLS . includes ( venue . protocol ) ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: protocol must be one of ${ VALID _REFERENCE _PROTOCOLS . join ( ', ' ) } ` ) ;
2026-03-02 12:14:07 -08:00
}
2026-04-18 12:05:17 -07:00
if ( typeof venue . supported !== 'boolean' ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: supported must be set explicitly to true or false ` ) ;
}
2026-04-07 22:56:16 -07:00
if ( ! venue . familyKey || typeof venue . familyKey !== 'string' ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: familyKey required ` ) ;
continue ;
2026-03-02 12:14:07 -08:00
}
2026-04-07 22:56:16 -07:00
if ( ! referenceVenuesByFamily . has ( venue . familyKey ) ) referenceVenuesByFamily . set ( venue . familyKey , [ ] ) ;
referenceVenuesByFamily . get ( venue . familyKey ) . push ( venue ) ;
2026-04-18 12:05:17 -07:00
if ( venue . aggregatorOnly === true && venue . protocol !== '1inch' ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: aggregatorOnly rows must use protocol "1inch" ` ) ;
}
2026-04-14 07:13:17 -07:00
if ( isLiveRow ( venue ) ) {
if ( ! VALID _NATIVE _REFERENCE _PROTOCOLS . includes ( venue . protocol ) ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: live/routingVisible rows must use a native protocol contract, not " ${ venue . protocol } " ` ) ;
}
if ( looksPlaceholderAddress ( venue . venueAddress ) ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: live/routingVisible venueAddress must use a native protocol contract, not a placeholder scaffold ( ${ venue . venueAddress } ) ` ) ;
}
}
2026-04-18 12:05:17 -07:00
if ( venue . supported === false && isLiveRow ( venue ) ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: supported=false rows cannot be live/routingVisible ` ) ;
}
if ( venue . supported === false && venue . protocol === '1inch' && venue . aggregatorOnly !== true ) {
errors . push ( ` Chain ${ chainId } gasReferenceVenues[ ${ i } ]: unsupported 1inch rows must be marked aggregatorOnly=true ` ) ;
}
2026-04-07 22:56:16 -07:00
}
for ( const [ familyKey , venues ] of referenceVenuesByFamily . entries ( ) ) {
const protocols = new Set ( venues . map ( ( venue ) => venue . protocol ) ) ;
if ( ! protocols . has ( 'uniswap_v3' ) ) {
errors . push ( ` Chain ${ chainId } gas family ${ familyKey } : missing uniswap_v3 reference venue ` ) ;
}
const oneInch = venues . find ( ( venue ) => venue . protocol === '1inch' ) ;
if ( oneInch ? . routingVisible === true || oneInch ? . live === true ) {
const hasUniswap = venues . some ( ( venue ) => venue . protocol === 'uniswap_v3' && venue . live === true ) ;
const hasDodo = ( gasPoolsByFamily . get ( familyKey ) || [ ] ) . some ( ( pool ) => pool . publicRoutingEnabled === true ) ;
if ( ! hasUniswap || ! hasDodo ) {
errors . push ( ` Chain ${ chainId } gas family ${ familyKey } : 1inch cannot be live/routingVisible before DODO and Uniswap venues are live ` ) ;
}
2026-03-02 12:14:07 -08:00
}
}
}
if ( errors . length > 0 ) {
errors . forEach ( ( e ) => process . stderr . write ( e + '\n' ) ) ;
process . exit ( 1 ) ;
}
process . exit ( 0 ) ;
}
2026-04-14 07:13:17 -07:00
function resolveInputPath ( argv ) {
const candidate = argv [ 2 ] ;
if ( ! candidate || candidate === '-h' || candidate === '--help' ) {
return DEPLOYMENT _STATUS _PATH ;
}
return path . isAbsolute ( candidate ) ? candidate : path . join ( process . cwd ( ) , candidate ) ;
}
function printUsage ( ) {
process . stdout . write ( 'Usage: node scripts/validate-deployment-status.cjs [path/to/deployment-status.json]\n' ) ;
}
if ( require . main === module ) {
const argv = process . argv ;
if ( argv [ 2 ] === '-h' || argv [ 2 ] === '--help' ) {
printUsage ( ) ;
process . exit ( 0 ) ;
}
if ( argv [ 2 ] ) {
DEPLOYMENT _STATUS _PATH = resolveInputPath ( argv ) ;
}
main ( ) ;
}
module . exports = {
looksPlaceholderAddress ,
isLiveRow ,
validatePoolEntries ,
main ,
} ;