| #!/usr/bin/env node |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 'use strict'; |
|
|
| const { execSync, spawnSync } = require('child_process'); |
| const fs = require('fs'); |
| const path = require('path'); |
|
|
| |
| let notifier; |
| try { |
| notifier = require('../prover/notifier'); |
| } catch (_) { |
| |
| } |
|
|
| const ROOT = path.resolve(__dirname, '..'); |
| const FORGE = path.join(ROOT, '.config', '.foundry', 'bin', 'forge'); |
|
|
| const SCRIPTS = { |
| cb: 'script/DeployCircuitBreaker.s.sol:DeployCircuitBreaker', |
| full: 'script/DeployFull.s.sol:DeployFull', |
| }; |
|
|
| const ADDRESS_PATTERNS = { |
| CircuitBreaker: /CircuitBreaker[^\n]*?(0x[0-9a-fA-F]{40})/, |
| AssetRegistry: /AssetRegistry[^\n]*?(0x[0-9a-fA-F]{40})/, |
| TEEVerifier: /TEEVerifier[^\n]*?(0x[0-9a-fA-F]{40})/, |
| }; |
|
|
| |
|
|
| const argv = process.argv.slice(2); |
| const flag = (f) => argv.includes(f); |
| const param = (f, d) => { const i = argv.indexOf(f); return i !== -1 ? argv[i + 1] : d; }; |
|
|
| const target = param('--target', 'full'); |
| const dryRun = flag('--dry-run') || flag('--simulate'); |
| const rpcUrl = param('--rpc', process.env.POLYGON_AMOY_RPC_URL || ''); |
|
|
| if (!SCRIPTS[target]) { |
| die(`Unknown target "${target}". Use --target cb or --target full.`); |
| } |
|
|
| |
|
|
| function die(msg) { |
| console.error(`\n[deploy] ERROR: ${msg}\n`); |
| process.exit(1); |
| } |
|
|
| function ok(msg) { console.log(` β ${msg}`); } |
| function info(msg) { console.log(` Β· ${msg}`); } |
| function step(msg) { console.log(`\n[deploy] ${msg}`); } |
|
|
| function hr() { console.log('\n' + 'β'.repeat(62)); } |
|
|
| |
|
|
| step('Pre-flight checks'); |
|
|
| if (!fs.existsSync(FORGE)) { |
| if (notifier) notifier.deploymentFailure({ |
| target, |
| error: 'forge not found', |
| detail: 'Install Foundry', |
| }); |
| die( |
| `forge not found at ${FORGE}.\n` + |
| ' Install Foundry: curl -L https://foundry.paradigm.xyz | bash && foundryup\n' + |
| ' Or run the project setup once to install it locally.' |
| ); |
| } |
| ok(`forge found at ${FORGE}`); |
|
|
| const required = ['PRIVATE_KEY', 'POLYGON_AMOY_RPC_URL', 'ORACLE_ADDRESS']; |
| if (target === 'full') required.push('ENCLAVE_ADDRESS'); |
|
|
| const missing = required.filter(v => !process.env[v]); |
| if (missing.length) { |
| die( |
| `Missing required environment variables:\n` + |
| missing.map(v => ` β’ ${v}`).join('\n') + '\n\n' + |
| ' Set them in Replit Secrets (or export them locally) before deploying.' |
| ); |
| } |
| ok(`All required env vars present (${required.join(', ')})`); |
|
|
| if (!rpcUrl) { |
| if (notifier) notifier.deploymentFailure({ target, error: 'Empty RPC URL' }); |
| die('POLYGON_AMOY_RPC_URL is empty.'); |
| } |
| ok(`RPC: ${rpcUrl.replace(/\/\/.+@/, '//***@')}`); |
|
|
| |
| if (notifier) { |
| notifier.deploymentStart({ |
| target: target === 'full' |
| ? 'CircuitBreaker + AssetRegistry + TEEVerifier' |
| : 'CircuitBreaker', |
| network: 'Polygon Amoy', |
| rpc: rpcUrl, |
| }); |
| } |
|
|
| |
|
|
| step('Compiling contracts'); |
|
|
| const buildResult = spawnSync( |
| FORGE, ['build', '--silent'], |
| { cwd: ROOT, encoding: 'utf8', env: process.env } |
| ); |
|
|
| if (buildResult.status !== 0) { |
| console.error(buildResult.stderr); |
| die('forge build failed. Fix compilation errors first.'); |
| } |
| ok('All contracts compiled successfully'); |
|
|
| |
|
|
| const scriptPath = SCRIPTS[target]; |
| step(`Deploying: ${scriptPath} ${dryRun ? '(DRY-RUN / simulate)' : 'to Polygon Amoy'}`); |
|
|
| const forgeArgs = [ |
| 'script', scriptPath, |
| '--rpc-url', rpcUrl, |
| '--private-key', process.env.PRIVATE_KEY, |
| '--gas-price', '30000000000', |
| '-vvv', |
| ]; |
|
|
| if (!dryRun) { |
| forgeArgs.push('--broadcast'); |
| info('Broadcasting transactions on-chain (--broadcast)'); |
| } else { |
| info('Simulating only β no transactions will be sent'); |
| } |
|
|
| info(`Running: forge ${forgeArgs.filter(a => a !== process.env.PRIVATE_KEY).join(' ')}`); |
|
|
| const deployResult = spawnSync(FORGE, forgeArgs, { |
| cwd: ROOT, |
| encoding: 'utf8', |
| env: { |
| ...process.env, |
| ORACLE_ADDRESS: process.env.ORACLE_ADDRESS, |
| ENCLAVE_ADDRESS: process.env.ENCLAVE_ADDRESS || '', |
| }, |
| maxBuffer: 4 * 1024 * 1024, |
| }); |
|
|
| const stdout = deployResult.stdout || ''; |
| const stderr = deployResult.stderr || ''; |
| const combined = stdout + '\n' + stderr; |
|
|
| if (deployResult.status !== 0) { |
| console.error('\nββ forge output ββββββββββββββββββββββββββββββββββββββ'); |
| console.error(combined); |
| die('forge script failed. See output above.'); |
| } |
|
|
| console.log('\nββ forge output ββββββββββββββββββββββββββββββββββββββ'); |
| console.log(combined.trim()); |
|
|
| |
|
|
| step('Parsing deployed addresses'); |
|
|
| const deployed = {}; |
| for (const [name, pattern] of Object.entries(ADDRESS_PATTERNS)) { |
| const m = combined.match(pattern); |
| if (m) { |
| deployed[name] = m[1]; |
| ok(`${name.padEnd(18)} ${m[1]}`); |
| } else { |
| console.log(`[debug] pattern for ${name} did not match. pattern=${pattern}`); |
| } |
| } |
|
|
| if (!Object.keys(deployed).length) { |
| info('No addresses parsed (simulation may not emit them β check forge output above)'); |
| } |
|
|
| |
|
|
| if (!dryRun && Object.keys(deployed).length) { |
| step('Writing .env.deployed'); |
|
|
| const envLines = [ |
| `# ProofBridge Liner β deployed addresses`, |
| `# Generated by scripts/deploy.js on ${new Date().toISOString()}`, |
| `# Network: Polygon Amoy (chainId 80002)`, |
| '', |
| ]; |
|
|
| if (deployed.CircuitBreaker) envLines.push(`CIRCUIT_BREAKER_ADDRESS=${deployed.CircuitBreaker}`); |
| if (deployed.AssetRegistry) envLines.push(`ASSET_REGISTRY_ADDRESS=${deployed.AssetRegistry}`); |
| if (deployed.TEEVerifier) envLines.push(`TEE_VERIFIER_ADDRESS=${deployed.TEEVerifier}`); |
| if (process.env.ORACLE_ADDRESS) envLines.push(`ORACLE_ADDRESS=${process.env.ORACLE_ADDRESS}`); |
| if (process.env.ENCLAVE_ADDRESS) envLines.push(`ENCLAVE_ADDRESS=${process.env.ENCLAVE_ADDRESS}`); |
| envLines.push(''); |
|
|
| const envFile = path.join(ROOT, '.env.deployed'); |
| fs.writeFileSync(envFile, envLines.join('\n')); |
| ok(`.env.deployed written to ${envFile}`); |
| info('Copy these values into Replit Secrets so the dashboard picks them up.'); |
| } |
|
|
| |
|
|
| hr(); |
| console.log('\n DEPLOYMENT SUMMARY\n'); |
| console.log(` Target : ${target === 'full' ? 'Full suite (CB + Registry + TEE)' : 'CircuitBreaker only'}`); |
| console.log(` Mode : ${dryRun ? 'Simulate (dry-run)' : 'Live broadcast'}`); |
| console.log(` Network : Polygon Amoy (chainId 80002)`); |
| console.log(` RPC : ${rpcUrl.replace(/\/\/.+@/, '//***@')}`); |
| console.log(''); |
|
|
| if (Object.keys(deployed).length) { |
| if (notifier) notifier.deploymentSuccess({ |
| target, |
| network: 'Polygon Amoy', |
| deployed, |
| }); |
| for (const [name, addr] of Object.entries(deployed)) { |
| const scanUrl = `https://amoy.polygonscan.com/address/${addr}`; |
| console.log(` ${name.padEnd(18)} ${addr}`); |
| console.log(` ${''.padEnd(18)} ${scanUrl}`); |
| console.log(''); |
| } |
| } else { |
| console.log(' No addresses extracted (check forge output above)'); |
| } |
|
|
| if (!dryRun) { |
| console.log(' Next steps:'); |
| console.log(' 1. Copy addresses from .env.deployed into Replit Secrets'); |
| console.log(' 2. Restart the dashboard (npm run start) to reflect new addresses'); |
| if (target === 'full') { |
| console.log(' 3. Call AssetRegistry.registerAsset() for each monitored deed'); |
| console.log(' 4. Run node prover/submitter.js to push first proof hashes'); |
| } |
| } |
|
|
| hr(); |
| console.log(''); |
|
|