diff --git a/config/all-mainnet-canary-evidence.json b/config/all-mainnet-canary-evidence.json index 450a414c..880a7925 100644 --- a/config/all-mainnet-canary-evidence.json +++ b/config/all-mainnet-canary-evidence.json @@ -37,6 +37,24 @@ "Tiny live canary swap executed on ALL Mainnet Uniswap V2 WALL/AUSDC." ] }, + { + "poolId": "651940-dodo_pmm-wall-ausdt", + "generatedAt": "2026-04-30T06:58:00Z", + "canaryTransactions": [ + { + "direction": "base_to_quote", + "txHash": "0x6b74c3b6816eb67409268a6e3b108fd631d278baf0fd57524a63c60c995bded3", + "fundingTransferTxHash": "0x2e1540c484abac9049e383a16fc35a5b6b81a33bd70183b80b20f94533dbe141", + "amountInRaw": "1000000", + "tokenIn": "WALL", + "tokenOut": "AUSDT", + "executor": "DODO_DVM.transfer_then_sellBase" + } + ], + "notes": [ + "Tiny live canary swap executed on ALL Mainnet DODO PMM WALL/AUSDT." + ] + }, { "poolId": "137-dodo_pmm-cwusdc-usdc", "generatedAt": "2026-04-29T04:41:13.993Z", diff --git a/config/all-mainnet-pool-creation-matrix.json b/config/all-mainnet-pool-creation-matrix.json index fd63b70c..decbdf8d 100644 --- a/config/all-mainnet-pool-creation-matrix.json +++ b/config/all-mainnet-pool-creation-matrix.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "generatedAt": "2026-04-29T06:18:00Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "description": "Canonical pool-creation matrix for ALL Mainnet public spend readiness. Pools must exist here before funding.", "lifecycle": [ "planned", @@ -23,9 +23,9 @@ ], "statusCounts": { "planned": 83, - "canary_passed": 5, + "canary_passed": 6, "created": 17, - "live_read": 8 + "live_read": 7 }, "protocolCounts": { "dodo_pmm": 49, @@ -292,7 +292,7 @@ "verification": "DODOPMMIntegration pools(base,quote) resolves to pool address; DVM clone has bytecode; initial reserves are zero until funding." }, "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#651940-dodo_pmm-wall-ausdc", "baseBalanceRaw": "1000000000001000000", "quoteBalanceRaw": "999999999998000001", @@ -444,10 +444,27 @@ "k": null, "singleSided": false, "publicRoutingEnabled": true, - "reserveSource": "all-mainnet-canonical-ausdt-correction", - "status": "live_read", + "reserveSource": "all-mainnet-required-pool-balance-check", + "status": "canary_passed", "requiredForSpend": true, - "canaryEvidence": null, + "canaryEvidence": { + "generatedAt": "2026-04-30T06:58:00Z", + "sourceFile": "config/all-mainnet-canary-evidence.json", + "canaryTransactions": [ + { + "direction": "base_to_quote", + "txHash": "0x6b74c3b6816eb67409268a6e3b108fd631d278baf0fd57524a63c60c995bded3", + "fundingTransferTxHash": "0x2e1540c484abac9049e383a16fc35a5b6b81a33bd70183b80b20f94533dbe141", + "amountInRaw": "1000000", + "tokenIn": "WALL", + "tokenOut": "AUSDT", + "executor": "DODO_DVM.transfer_then_sellBase" + } + ], + "notes": [ + "Tiny live canary swap executed on ALL Mainnet DODO PMM WALL/AUSDT." + ] + }, "fundingTiersUsd": { "seed": 10, "smoke": 100, @@ -464,7 +481,9 @@ "Canonical WALL/AUSDT DODO V2 DVM-backed pool created after quote asset correction from USDT to AUSDT.", "Vault assignments applied from explicit All Mainnet vault assignment map.", "Seeded with 1 WALL and 1 AUSDT from deployer wallet.", - "EnhancedSwapRouterV2 DODO route registered and quoteable." + "EnhancedSwapRouterV2 DODO route registered and quoteable.", + "Live reserve read recorded from required-pool balance checker.", + "Canary evidence recorded from explicit All Mainnet canary evidence file." ], "infrastructure": { "dvmFactory": "0x8a3403aef8d40c0F4AfaF6Dc2000A537EbC863c2", @@ -477,16 +496,12 @@ "verification": "DODOPMMIntegration pools(WALL,AUSDT) resolves to pool address; DVM clone has bytecode." }, "reserveEvidence": { - "generatedAt": "2026-04-29T05:48:00Z", - "baseBalanceRaw": "1000000000000000000", - "quoteBalanceRaw": "1000000000000000000", + "generatedAt": "2026-04-30T06:51:46.495Z", + "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#651940-dodo_pmm-wall-ausdt", + "baseBalanceRaw": "1000000000001000000", + "quoteBalanceRaw": "999999999998000001", "poolHasCode": true, - "liveReadStatus": "nonzero_base_and_quote", - "fundingTransactions": [ - "0x2aa8835e2506e1d8802f9d14c570abdad7e14e9dfe0d0261f7e19da019215f25", - "0xc3a6a7edb6d9e9422a78fafae53bab9b96746a6fff83ca607edff7793286f294", - "0xf9344211547bccadc1b433cf31f6f953f15b915b7d031c03411a114e180cf399" - ] + "liveReadStatus": "nonzero_base_and_quote" }, "routerRouteEvidence": { "routerAddress": "0xb905fEfA56b028221E2Bc248Bbcd41141dc7aeD3", @@ -815,10 +830,10 @@ "Canary evidence recorded from explicit All Mainnet canary evidence file." ], "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#651940-uniswap_v2-wall-ausdc", - "baseBalanceRaw": "222025888680628319105322384", - "quoteBalanceRaw": "130598623255778071651280", + "baseBalanceRaw": "224445209871270534969174163", + "quoteBalanceRaw": "129194376317333448505973", "poolHasCode": true, "liveReadStatus": "nonzero_base_and_quote" } @@ -895,10 +910,10 @@ "Vault assignments applied from explicit All Mainnet vault assignment map." ], "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#651940-uniswap_v2-wall-usdt", - "baseBalanceRaw": "119579763278558287348114878", - "quoteBalanceRaw": "96183323535791914791099", + "baseBalanceRaw": "116762468938410673082689864", + "quoteBalanceRaw": "98510599724849461923660", "poolHasCode": true, "liveReadStatus": "nonzero_base_and_quote" } @@ -975,10 +990,10 @@ "Vault assignments applied from explicit All Mainnet vault assignment map." ], "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#651940-uniswap_v2-usdt-ausdc", - "baseBalanceRaw": "128823453150848278870209", - "quoteBalanceRaw": "77642915975212836473965", + "baseBalanceRaw": "126950810303091320030551", + "quoteBalanceRaw": "78791103453769787415423", "poolHasCode": true, "liveReadStatus": "nonzero_base_and_quote" } @@ -1205,7 +1220,7 @@ "Vault assignments applied from explicit All Mainnet vault assignment map." ], "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#1-dodo_pmm-cwusdc-usdc", "baseBalanceRaw": "818811480", "quoteBalanceRaw": "233144600", @@ -1285,7 +1300,7 @@ "Vault assignments applied from explicit All Mainnet vault assignment map." ], "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#1-dodo_pmm-cwusdt-usdt", "baseBalanceRaw": "9845664", "quoteBalanceRaw": "2160199", @@ -2583,7 +2598,7 @@ "Canary evidence recorded from explicit All Mainnet canary evidence file." ], "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#137-dodo_pmm-cwusdc-usdc", "baseBalanceRaw": "39885", "quoteBalanceRaw": "39883", @@ -2681,7 +2696,7 @@ "Canary evidence recorded from explicit All Mainnet canary evidence file." ], "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#137-dodo_pmm-cwusdt-usdt", "baseBalanceRaw": "2605", "quoteBalanceRaw": "2603", @@ -3291,7 +3306,7 @@ "transactionHash": "0x6a63466a12d2ee71201be232db0af6d5f40f8d829dbe453c9f00abbe6df6fbb9" }, "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#8453-dodo_pmm-cwusdc-usdc", "baseBalanceRaw": "1000000", "quoteBalanceRaw": "1000000", @@ -3977,7 +3992,7 @@ "transactionHash": "0x4385161e41e88a560cc5107020d6099c540428c4e39f2415e238a37de16f4d31" }, "reserveEvidence": { - "generatedAt": "2026-04-29T04:41:20.353Z", + "generatedAt": "2026-04-30T06:51:46.495Z", "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#42161-dodo_pmm-cwusdc-usdc", "baseBalanceRaw": "100000", "quoteBalanceRaw": "100000", @@ -8677,7 +8692,7 @@ "k": null, "singleSided": false, "publicRoutingEnabled": true, - "reserveSource": "uniswap_v3_position_manager_seed", + "reserveSource": "all-mainnet-required-pool-balance-check", "status": "canary_passed", "requiredForSpend": true, "canaryEvidence": { @@ -8712,7 +8727,8 @@ "notes": [ "ALL Mainnet official Uniswap V3 pool stack deployed from upstream Uniswap V3 artifacts.", "AUSDT/WALL 0.30% pool initialized at 1:1 sqrtPriceX96 and seeded through NonfungiblePositionManager.", - "Standalone SwapRouter and Quoter are live and tested; EnhancedSwapRouterV2 V3 provider remains disabled until adapter quote compatibility is fixed." + "Standalone SwapRouter and Quoter are live and tested; EnhancedSwapRouterV2 V3 provider remains disabled until adapter quote compatibility is fixed.", + "Live reserve read recorded from required-pool balance checker." ], "infrastructure": { "nftDescriptorLibrary": "0xb53E8A0A19fB381537c6f28D37b7C2f7DC29EF02", @@ -8731,12 +8747,12 @@ "verification": "UniswapV3Factory.getPool(AUSDT,WALL,3000) resolves to pool address; pool has code and liquidity." }, "reserveEvidence": { - "generatedAt": "2026-04-29T06:14:00Z", - "liquidityRaw": "1000000000000000000", - "sqrtPriceX96": "79228162514264337593543950336", - "tick": 0, - "mintTxHash": "0x64660f089eac210ff94a1a01ffcdc1e3d9dc82739589b781d9ef18d8247a4754", - "liveReadStatus": "nonzero_liquidity" + "generatedAt": "2026-04-30T06:51:46.495Z", + "evidenceRef": "reports/status/all-mainnet-required-pool-balances-latest.json#651940-uniswap_v3-wall-ausdt", + "baseBalanceRaw": "1000000000001000000", + "quoteBalanceRaw": "999999999999003001", + "poolHasCode": true, + "liveReadStatus": "nonzero_base_and_quote" }, "routerRouteEvidence": { "routerAddress": "0xb905fEfA56b028221E2Bc248Bbcd41141dc7aeD3", diff --git a/docs/03-deployment/ALL_MAINNET_PROTOCOL_COMPLETION_RUNBOOK.md b/docs/03-deployment/ALL_MAINNET_PROTOCOL_COMPLETION_RUNBOOK.md new file mode 100644 index 00000000..fa4ece70 --- /dev/null +++ b/docs/03-deployment/ALL_MAINNET_PROTOCOL_COMPLETION_RUNBOOK.md @@ -0,0 +1,188 @@ +# ALL Mainnet Protocol Completion Runbook + +**Status:** Operator runbook +**Scope:** ALL Mainnet `651940` pool inventory, auto-rebalancing, and protocol support gates. +**Canonical inputs:** `config/all-mainnet-pool-creation-matrix.json`, `config/allmainnet-non-dodo-protocol-surface.json` + +## Current State + +The pool matrix already contains the intended inventory surface. Treat it as the source of truth for what must be created, funded, canaried, and eventually promoted. + +As of the current matrix: + +| Protocol | Rows | Required for spend | Main blocker | +|---|---:|---:|---| +| `dodo_pmm` | 49 | 24 | Remaining planned pools and non-production rows | +| `single_sided_pmm` | 3 | 3 | Pool creation and funding | +| `uniswap_v2` | 5 | 3 | One planned pool plus promotion evidence | +| `uniswap_v3` | 1 | 1 | Canary passed; keep adapter compatibility gated | +| `balancer_weighted` | 11 | 0 | Optional protocol inventory not deployed/imported | +| `curve_stable` | 11 | 0 | Optional protocol inventory not deployed/imported | +| `sushiswap_v2` | 11 | 0 | Optional protocol inventory not deployed/imported | +| `oneinch_aggregator` | 11 | 0 | Optional aggregator routes depend on live native liquidity first | +| `aave_backstop` | 11 | 0 | Optional backstop market not deployed/imported | + +`config/allmainnet-non-dodo-protocol-surface.json` currently publishes partial same-chain inventory and keeps only DODO enabled through `EnhancedSwapRouterV2`. Do not enable optional providers until their pool rows have real addresses, reserve reads, canary evidence, and route quotes. + +## Phase 1: Close Required Inventory + +Work required rows first. Optional protocols should not distract from getting the spend mesh healthy. + +1. Generate the live worklist: + +```bash +node scripts/status/generate-all-mainnet-readiness.mjs +``` + +Read: + +- `reports/status/all-mainnet-deployment-readiness-worklist-latest.json` +- `reports/status/all-mainnet-production-gate-latest.json` +- `reports/status/all-mainnet-spend-readiness-latest.json` + +2. For every required row with `status: planned`, create or import the pool address. + +Required blockers are usually one of: + +- `pool_not_created` +- `missing_base_address:*` +- `missing_quote_address:*` +- `missing_pool_address` + +For DODO rows, use the committed DODO infrastructure from the row: + +- `infrastructure.dvmFactory` +- `infrastructure.dvmFactoryAdapter` +- `infrastructure.dodoPmmIntegration` + +For Uniswap V2/V3 rows, use the committed factory/router/quoter or import the canonical address from verified on-chain deployment evidence. Do not invent placeholder pool addresses. + +3. After creating or importing pools, run reserve reads: + +```bash +node scripts/status/check-all-mainnet-required-pool-balances.mjs +node scripts/status/check-all-mainnet-required-pool-balances.mjs --update-matrix +``` + +Only use `--update-matrix` after reviewing the report. It promotes rows with live code and non-zero base/quote balances to `live_read`; it does not create, fund, or canary pools. + +## Phase 2: Fund and Canary + +Funding should move in tiers, not straight to production. + +Use each row's `fundingTiersUsd`: + +- `seed`: proves transferability and reserve reads +- `smoke`: proves route quoteability and tiny swaps +- `productionMinimum`: minimum operating reserve before promotion + +After funding, run: + +```bash +node scripts/status/check-all-mainnet-required-pool-balances.mjs +node scripts/status/preflight-all-mainnet-canaries.mjs +``` + +Record successful canaries with: + +```bash +node scripts/status/record-all-mainnet-canary-evidence.mjs +``` + +Promotion rule: + +- `planned` -> create/import pool +- `created` -> fund both sides +- `live_read` -> canary both directions where applicable +- `canary_passed` -> operator review +- `production` -> public route eligible + +## Phase 3: Auto-Rebalancing + +Auto-rebalancing should be conservative and evidence driven. It should not be allowed to create new exposure on rows that have not passed the production gate. + +Minimum policy per row: + +```json +{ + "maxPriceImpactBps": 100, + "minReserveUsd": 1000, + "refillTriggerBps": 200, + "pauseOnReserveReadFailure": true +} +``` + +Implementation shape: + +1. Watch only rows with `requiredForSpend: true` and status `live_read`, `canary_passed`, or `production`. +2. Read live base/quote balances using `check-all-mainnet-required-pool-balances.mjs`. +3. Convert reserves to oracle ISO value using the same ISO peg rules used by `scripts/verify/build-cw-public-price-table.py`. +4. If either side is below `minReserveUsd` or outside `refillTriggerBps`, generate a rebalance intent. +5. Execute only after dry-run quote checks pass and `maxPriceImpactBps` is respected. +6. Record every action back into a report before changing matrix status. + +Recommended first automation artifact: + +```text +scripts/status/plan-all-mainnet-rebalance.mjs +``` + +It should be read-only by default and write: + +```text +reports/status/all-mainnet-rebalance-plan-latest.json +``` + +The read-only planner is available now: + +```bash +node scripts/status/plan-all-mainnet-rebalance.mjs +``` + +Only after that report is stable should an execution wrapper be added, for example: + +```text +scripts/deployment/execute-all-mainnet-rebalance.mjs +``` + +The execution wrapper must require an explicit `--execute` flag. + +## Phase 4: Enable All Protocols + +Protocol enablement must follow native-liquidity order. Aggregators come last. + +1. **DODO PMM:** Finish required DODO and single-sided PMM rows first. Keep DODO as the default router provider until other providers pass canaries. +2. **Uniswap V2:** Promote committed V2 rows with live reserve reads and canaries. Add the remaining planned V2 pool address or mark it explicitly deferred. +3. **Uniswap V3:** Keep the standalone V3 route recorded, but do not enable the enhanced-router V3 provider until the adapter quote incompatibility is fixed and canary evidence exists through the enhanced router path. +4. **Balancer / Curve / Sushi:** Import or deploy canonical factories, routers/vaults, pools, and pool IDs. Add addresses to the pool matrix before funding. No route generation from config-only rows. +5. **1inch:** Enable only after DODO plus at least one native venue for the same pair is live. 1inch rows are aggregator-only and must not be the first source of liquidity truth. +6. **Aave backstop:** Treat as a reserve/backstop surface, not a swap venue. Enable after token markets, oracle config, caps, and pause controls are committed. + +After each protocol wave: + +```bash +bash scripts/verify/check-allmainnet-protocol-surface.sh +node scripts/status/generate-all-mainnet-readiness.mjs +bash scripts/validation/validate-config-files.sh +``` + +## Production Gate + +A row can become public-route eligible only when all of these are true: + +- Real token addresses +- Real pool address with bytecode +- Required vault assignments complete +- Non-zero base and quote reserves +- Canary evidence recorded +- Route quote check passes +- Protocol surface has the provider enabled +- Status is `production` + +The whole ALL Mainnet same-chain surface is production-ready only when: + +```bash +jq -e '.status == "production_ready"' reports/status/all-mainnet-production-gate-latest.json +``` + +Until then, keep public routing constrained to canary-passed rows and explicitly disabled historical rows out of route generation. diff --git a/scripts/status/build-all-mainnet-deployment-funding-packet.mjs b/scripts/status/build-all-mainnet-deployment-funding-packet.mjs new file mode 100644 index 00000000..2dc9a00b --- /dev/null +++ b/scripts/status/build-all-mainnet-deployment-funding-packet.mjs @@ -0,0 +1,164 @@ +#!/usr/bin/env node +/** + * Build the strict deployment/funding packet for ALL Mainnet completion. + * + * This is intentionally read-only. It converts the matrix, reserve report, + * canary preflight, and rebalance plan into an operator packet that separates + * executable work from rows that are blocked by missing addresses/evidence. + */ + +import { existsSync, 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 readinessPath = resolve(repoRoot, "reports/status/all-mainnet-deployment-readiness-worklist-latest.json"); +const reservePath = resolve(repoRoot, "reports/status/all-mainnet-required-pool-balances-latest.json"); +const canaryPath = resolve(repoRoot, "reports/status/all-mainnet-canary-preflight-latest.json"); +const rebalancePath = resolve(repoRoot, "reports/status/all-mainnet-rebalance-plan-latest.json"); +const jsonOut = resolve(repoRoot, "reports/status/all-mainnet-full-deployment-funding-packet-latest.json"); +const mdOut = resolve(repoRoot, "reports/status/all-mainnet-full-deployment-funding-packet-latest.md"); + +function readJson(path, fallback = null) { + return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : fallback; +} + +function table(headers, rows) { + return [ + `| ${headers.join(" | ")} |`, + `| ${headers.map(() => "---").join(" | ")} |`, + ...rows.map((row) => `| ${row.map((cell) => String(cell ?? "").replace(/\|/g, "\\|")).join(" | ")} |`), + ].join("\n"); +} + +function tokenPair(row) { + return `${row.baseToken?.symbol || "?"}/${row.quoteToken?.symbol || "?"}`; +} + +function poolBlockers(row) { + const blockers = []; + 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 (!row.poolAddress) blockers.push("missing_pool_address"); + if ((row.missingRequiredVaultRoles || []).length) blockers.push("missing_required_vault_assignments"); + return blockers; +} + +const matrix = readJson(matrixPath); +const readiness = readJson(readinessPath, { blockers: [] }); +const reserve = readJson(reservePath, { results: [] }); +const canary = readJson(canaryPath, { results: [] }); +const rebalance = readJson(rebalancePath, { intents: [] }); +const generatedAt = new Date().toISOString(); + +const reserveByPoolId = new Map(reserve.results.map((row) => [row.poolId, row])); +const canaryByPoolId = new Map(canary.results.map((row) => [row.poolId, row])); +const rebalanceByPoolId = new Map(rebalance.intents.map((row) => [row.poolId, row])); +const requiredRows = matrix.rows.filter((row) => row.requiredForSpend === true); + +const rows = requiredRows.map((row) => { + const reserveRow = reserveByPoolId.get(row.poolId); + const canaryRow = canaryByPoolId.get(row.poolId); + const rebalanceRow = rebalanceByPoolId.get(row.poolId); + const blockers = new Set(poolBlockers(row)); + const productionBlockers = new Set(); + const actions = []; + + if (row.status === "planned") { + actions.push(row.protocol === "dodo_pmm" ? "create_or_import_dodo_pool" : "create_or_import_pool"); + } + if (reserveRow?.liveReadStatus === "zero_balances") actions.push("fund_base_and_quote"); + if (reserveRow?.liveReadStatus === "partial_balance") actions.push("fund_low_side"); + if (["live_read"].includes(row.status)) actions.push("run_canary"); + if (canaryRow?.canaryPreflight === "ready") actions.push("execute_canary_swap"); + if (rebalanceRow?.executionStatus === "operator_review_required") actions.push(...(rebalanceRow.actions || [])); + + for (const blocker of canaryRow?.blockers || []) blockers.add(blocker); + if (["live_read", "canary_passed", "production"].includes(row.status)) { + for (const blocker of rebalanceRow?.blockers || []) blockers.add(blocker); + } + if (row.status !== "production") productionBlockers.add("not_production_status"); + + return { + poolId: row.poolId, + chainId: row.chainId, + network: row.network, + protocol: row.protocol, + pair: tokenPair(row), + status: row.status, + poolAddress: row.poolAddress, + publicRoutingEnabled: Boolean(row.publicRoutingEnabled), + reserveStatus: reserveRow?.liveReadStatus || "not_checked", + canaryPreflight: canaryRow?.canaryPreflight || "not_checked", + rebalanceStatus: rebalanceRow?.executionStatus || "not_planned", + actions: [...new Set(actions)], + blockers: [...blockers], + productionBlockers: [...productionBlockers], + }; +}); + +const blocked = rows.filter((row) => row.blockers.length > 0); +const executable = rows.filter((row) => row.blockers.length === 0 && row.actions.length > 0); +const readyNoop = rows.filter((row) => row.blockers.length === 0 && row.actions.length === 0); +const hardConfigBlockers = rows.filter((row) => + row.blockers.some((blocker) => blocker.startsWith("missing_") || blocker === "missing_pool_address"), +); + +const packet = { + generatedAt, + mode: "read_only_operator_packet", + summary: { + requiredRows: rows.length, + executableRows: executable.length, + blockedRows: blocked.length, + readyNoopRows: readyNoop.length, + hardConfigBlockers: hardConfigBlockers.length, + }, + operatorOrder: [ + "resolve_hard_config_blockers", + "create_or_import_missing_pools", + "fund_zero_balance_pools", + "run_reserve_reads", + "execute_canary_swaps_for_ready_rows", + "record_canary_evidence", + "promote_reviewed_rows_to_production", + "enable_optional_protocol_providers_after_native_pool_canaries", + ], + rows, +}; + +const md = [ + "# ALL Mainnet Full Deployment + Funding Packet", + "", + `- Generated: \`${generatedAt}\``, + "- Mode: read-only operator packet; no transactions were executed.", + "", + table( + ["Metric", "Count"], + Object.entries(packet.summary).map(([key, value]) => [key, value]), + ), + "", + "## Executable Rows", + "", + executable.length + ? table(["Pool", "Chain", "Protocol", "Pair", "Status", "Actions"], executable.map((row) => [row.poolId, row.chainId, row.protocol, row.pair, row.status, row.actions.join(", ")])) + : "_No rows are fully executable without first clearing blockers._", + "", + "## Hard Config Blockers", + "", + hardConfigBlockers.length + ? table(["Pool", "Chain", "Protocol", "Pair", "Status", "Blockers"], hardConfigBlockers.map((row) => [row.poolId, row.chainId, row.protocol, row.pair, row.status, row.blockers.join(", ")])) + : "_No hard config blockers._", + "", + "## Blocked Rows", + "", + blocked.length + ? table(["Pool", "Chain", "Protocol", "Pair", "Status", "Reserve", "Canary", "Blockers"], blocked.map((row) => [row.poolId, row.chainId, row.protocol, row.pair, row.status, row.reserveStatus, row.canaryPreflight, row.blockers.join(", ")])) + : "_No blocked rows._", + "", +].join("\n"); + +mkdirSync(resolve(repoRoot, "reports/status"), { recursive: true }); +writeFileSync(jsonOut, `${JSON.stringify(packet, null, 2)}\n`); +writeFileSync(mdOut, `${md}\n`); +console.log(`[OK] ALL Mainnet deployment/funding packet written: ${jsonOut}`); diff --git a/scripts/status/plan-all-mainnet-rebalance.mjs b/scripts/status/plan-all-mainnet-rebalance.mjs new file mode 100644 index 00000000..dc5d8664 --- /dev/null +++ b/scripts/status/plan-all-mainnet-rebalance.mjs @@ -0,0 +1,155 @@ +#!/usr/bin/env node +/** + * Build a read-only ALL Mainnet rebalance plan. + * + * This intentionally does not execute transfers or swaps. It consumes the + * canonical pool matrix plus the latest reserve-read report, then emits + * operator-reviewable intents for rows that are eligible for rebalancing. + */ + +import { existsSync, 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 reserveReportPath = resolve(repoRoot, "reports/status/all-mainnet-required-pool-balances-latest.json"); +const outPath = resolve(repoRoot, "reports/status/all-mainnet-rebalance-plan-latest.json"); + +const eligibleStatuses = new Set(["live_read", "canary_passed", "production"]); +const productionStatuses = new Set(["canary_passed", "production"]); + +function readJson(path) { + return JSON.parse(readFileSync(path, "utf8")); +} + +function asBigInt(value) { + try { + return BigInt(value || "0"); + } catch { + return 0n; + } +} + +function ratioBps(a, b) { + if (a === 0n && b === 0n) return null; + const high = a > b ? a : b; + const low = a > b ? b : a; + if (high === 0n) return null; + return Number(((high - low) * 10_000n) / high); +} + +function reserveEvidenceFor(row, reserveByPoolId) { + const live = reserveByPoolId.get(row.poolId); + if (live) { + return { + source: "latest_reserve_report", + liveReadStatus: live.liveReadStatus, + baseBalanceRaw: live.baseBalanceRaw, + quoteBalanceRaw: live.quoteBalanceRaw, + poolHasCode: live.poolHasCode, + errors: live.errors || [], + }; + } + if (row.reserveEvidence) { + return { + source: "matrix_reserve_evidence", + liveReadStatus: row.reserveEvidence.liveReadStatus, + baseBalanceRaw: row.reserveEvidence.baseBalanceRaw, + quoteBalanceRaw: row.reserveEvidence.quoteBalanceRaw, + poolHasCode: row.reserveEvidence.poolHasCode, + errors: [], + }; + } + return null; +} + +function planForRow(row, evidence) { + const policy = row.policy || {}; + const blockers = []; + const actions = []; + + if (!row.requiredForSpend) blockers.push("not_required_for_spend"); + if (!eligibleStatuses.has(row.status)) blockers.push(`status_not_rebalance_eligible:${row.status}`); + if (!row.poolAddress) blockers.push("missing_pool_address"); + 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 (!evidence) blockers.push("missing_reserve_evidence"); + + const base = asBigInt(evidence?.baseBalanceRaw); + const quote = asBigInt(evidence?.quoteBalanceRaw); + const imbalanceBps = ratioBps(base, quote); + + if (evidence) { + if (evidence.poolHasCode !== true) blockers.push("pool_code_not_confirmed"); + if (evidence.errors?.length) blockers.push("reserve_read_errors"); + if (evidence.liveReadStatus === "partial_balance") actions.push("refill_empty_or_low_side"); + if (evidence.liveReadStatus === "zero_balances") actions.push("fund_both_sides_before_rebalance"); + if (evidence.liveReadStatus === "missing_pool_address") blockers.push("reserve_report_missing_pool_address"); + if (evidence.liveReadStatus === "missing_token_address") blockers.push("reserve_report_missing_token_address"); + } + + if (base === 0n && quote > 0n) actions.push("fund_base_side"); + if (quote === 0n && base > 0n) actions.push("fund_quote_side"); + if (base > 0n && quote > 0n && imbalanceBps !== null && imbalanceBps >= (policy.refillTriggerBps || 200)) { + actions.push("quote_and_rebalance_to_target_inventory"); + } + if (actions.length === 0 && blockers.length === 0) actions.push("monitor_no_rebalance_required"); + + return { + poolId: row.poolId, + chainId: row.chainId, + network: row.network, + protocol: row.protocol, + status: row.status, + productionRouteEligible: productionStatuses.has(row.status) && blockers.length === 0, + baseToken: row.baseToken, + quoteToken: row.quoteToken, + poolAddress: row.poolAddress, + policy: { + maxPriceImpactBps: policy.maxPriceImpactBps ?? 100, + minReserveUsd: policy.minReserveUsd ?? 1000, + refillTriggerBps: policy.refillTriggerBps ?? 200, + pauseOnReserveReadFailure: policy.pauseOnReserveReadFailure ?? true, + }, + reserveEvidence: evidence, + imbalanceBps, + actions: [...new Set(actions)], + blockers: [...new Set(blockers)], + executionStatus: blockers.length ? "blocked" : actions.includes("monitor_no_rebalance_required") ? "no_action" : "operator_review_required", + }; +} + +const matrix = readJson(matrixPath); +const reserveReport = existsSync(reserveReportPath) ? readJson(reserveReportPath) : null; +const reserveByPoolId = new Map((reserveReport?.results || []).map((row) => [row.poolId, row])); +const generatedAt = new Date().toISOString(); + +const rows = matrix.rows.filter((row) => row.requiredForSpend === true); +const intents = rows.map((row) => planForRow(row, reserveEvidenceFor(row, reserveByPoolId))); +const actionable = intents.filter((intent) => intent.executionStatus === "operator_review_required"); +const blocked = intents.filter((intent) => intent.executionStatus === "blocked"); +const noAction = intents.filter((intent) => intent.executionStatus === "no_action"); + +const payload = { + generatedAt, + sourceMatrix: "config/all-mainnet-pool-creation-matrix.json", + sourceReserveReport: existsSync(reserveReportPath) ? "reports/status/all-mainnet-required-pool-balances-latest.json" : null, + mode: "read_only_plan", + executionGuardrails: [ + "This report never executes swaps or transfers.", + "An execution wrapper must require an explicit --execute flag.", + "Rows with reserve read errors or missing code must pause, not rebalance.", + "Optional protocol rows are excluded until they become requiredForSpend or production-route eligible.", + ], + summary: { + requiredRows: rows.length, + actionableRows: actionable.length, + blockedRows: blocked.length, + noActionRows: noAction.length, + }, + intents, +}; + +mkdirSync(resolve(repoRoot, "reports/status"), { recursive: true }); +writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}\n`); +console.log(`[OK] ALL Mainnet rebalance plan written: ${outPath}`);