From ccab738ae2bd76d68b8875a154e7f333463ef067 Mon Sep 17 00:00:00 2001 From: defiQUG Date: Thu, 30 Apr 2026 03:48:53 -0700 Subject: [PATCH] Advance official DODO migration routing evidence --- ...official-protocol-integration-sources.json | 44 ++- package.json | 4 +- ...ICIAL_DODO_MIGRATION_EXECUTION_20260430.md | 27 ++ ...ild-official-protocol-integration-plan.mjs | 37 ++- ...build-remaining-official-routing-tasks.mjs | 158 ++++++++++ .../check-remaining-deployer-balances.mjs | 291 ++++++++++++++++++ .../status/discover-official-dodo-pools.mjs | 32 ++ .../execute-official-dodo-migration.mjs | 1 + 8 files changed, 581 insertions(+), 13 deletions(-) create mode 100644 reports/ALL_MAINNET_OFFICIAL_DODO_MIGRATION_EXECUTION_20260430.md create mode 100644 scripts/status/build-remaining-official-routing-tasks.mjs create mode 100644 scripts/status/check-remaining-deployer-balances.mjs diff --git a/config/official-protocol-integration-sources.json b/config/official-protocol-integration-sources.json index a5c99615..46a4e81c 100644 --- a/config/official-protocol-integration-sources.json +++ b/config/official-protocol-integration-sources.json @@ -21,6 +21,11 @@ "label": "DODO docs: Ethereum contract addresses", "url": "https://docs.dodoex.io/en/developer/contracts/dodo-v1-v2/contracts-address/ethereum", "retrievedAt": "2026-04-30" + }, + { + "label": "DODO docs: Base contract addresses", + "url": "https://docs.dodoex.io/en/developer/contracts/dodo-v1-v2/contracts-address/base", + "retrievedAt": "2026-04-30" } ], "poolDiscovery": { @@ -63,6 +68,11 @@ "network": "Avalanche C-Chain", "dvmFactory": "0xfF133A6D335b50bDAa6612D19E1352B049A8aE6a", "source": "DODO docs" + }, + "8453": { + "network": "Base", + "dvmFactory": "0x0226fCE8c969604C3A0AD19c37d1FAFac73e13c2", + "source": "DODO docs" } }, "unsupportedOrUnverifiedChains": { @@ -74,10 +84,6 @@ "network": "Gnosis Chain", "status": "official_dodo_dvm_factory_not_committed" }, - "8453": { - "network": "Base", - "status": "official_dodo_dvm_factory_not_committed" - }, "42220": { "network": "Celo", "status": "official_dodo_dvm_factory_not_committed" @@ -202,6 +208,16 @@ "label": "1inch developer portal", "url": "https://portal.1inch.dev/", "retrievedAt": "2026-04-30" + }, + { + "label": "1inch Help Center: supported networks", + "url": "https://help.1inch.com/en/articles/5528619-how-to-use-different-networks-on-1inch", + "retrievedAt": "2026-04-30" + }, + { + "label": "1inch Business: Swap API supported chains", + "url": "https://business.1inch.com/products/swap", + "retrievedAt": "2026-04-30" } ], "poolDiscovery": { @@ -210,6 +226,26 @@ "Use 1inch official API quote/swap response for current router tx data.", "Do not create pools; this protocol wires route access to external liquidity." ] + }, + "chainSupport": { + "8453": { + "network": "Base", + "status": "supported_by_official_1inch_network_list", + "source": "1inch Help Center / 1inch Business chain pages", + "supportedUse": "aggregated quote and swap route access for cW*/official stable pairs; requires token import/listing or direct contract-address query." + }, + "100": { + "network": "Gnosis Chain", + "status": "supported_by_official_1inch_network_list", + "source": "1inch Help Center / 1inch Business chain pages", + "supportedUse": "aggregated quote and swap route access for cW*/official stable pairs; requires token import/listing or direct contract-address query." + }, + "25": { + "network": "Cronos", + "status": "supported_by_official_1inch_business_swap_list", + "source": "1inch Business Swap API supported chains", + "supportedUse": "aggregated quote and swap route access for cW*/official stable pairs; requires token import/listing or direct contract-address query." + } } }, "sushiswap_v2": { diff --git a/package.json b/package.json index f93f3d85..6a8f8f3e 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,9 @@ "all-mainnet:record-canaries": "node scripts/status/record-all-mainnet-canary-evidence.mjs", "all-mainnet:official-protocol-plan": "node scripts/status/build-official-protocol-integration-plan.mjs", "all-mainnet:official-dodo-discovery": "node scripts/status/discover-official-dodo-pools.mjs", - "all-mainnet:official-dodo-migration": "node scripts/status/execute-official-dodo-migration.mjs" + "all-mainnet:official-dodo-migration": "node scripts/status/execute-official-dodo-migration.mjs", + "all-mainnet:remaining-routing-tasks": "node scripts/status/build-remaining-official-routing-tasks.mjs", + "all-mainnet:remaining-balances": "node scripts/status/check-remaining-deployer-balances.mjs" }, "keywords": [ "proxmox", diff --git a/reports/ALL_MAINNET_OFFICIAL_DODO_MIGRATION_EXECUTION_20260430.md b/reports/ALL_MAINNET_OFFICIAL_DODO_MIGRATION_EXECUTION_20260430.md new file mode 100644 index 00000000..83c71401 --- /dev/null +++ b/reports/ALL_MAINNET_OFFICIAL_DODO_MIGRATION_EXECUTION_20260430.md @@ -0,0 +1,27 @@ +# ALL Mainnet Official DODO Migration Execution - 2026-04-30 + +## Completed Broadcasts + +| Chain | Pool | Official Pool | Atomic Seeder | Transactions | +| --- | --- | --- | --- | --- | +| Optimism `10` | `10-dodo_pmm-cwusdc-usdc` | `0x2F27480932AF9Dc889668453522F988D0F16215A` | `0x2Bb4d9B882de6E7F1Cc6C437AD036324324fD0c6` | deploy seeder `0x5e4d9ee103aadd5aa4582436ccc0567db2dd5bc3c684604b4bb8f553940b32a1`; approve base `0xc4b0b4013925fb5c83a73b9e1e8ff95a70e732f788e5b78f032836408947f636`; approve quote `0x41a25d6445f17d83d23287d1e3c572fee1f36a1194cd0a6cd6ead0022dc263eb`; seed `0x48b983d72c78f2034a040d4d105d2b0dd9086e4ea54b0c72d34dcc27a7d4ebaa` | +| Optimism `10` | `10-dodo_pmm-cwusdt-usdt` | `0xf7DB4284D71B869d73924F8059fD241D0b31aa43` | `0x2Bb4d9B882de6E7F1Cc6C437AD036324324fD0c6` | approve base `0x89b531766835647133cea2cfcdb6af14b25e23b6b2217e9db7b39ed69e9a3681`; approve quote `0x0f8578bd3d3a11e9dc97c66b0df70379d0a96224d78fba843798b51796d70da2`; seed `0x08ed7e4bc2aea8d736a77a592f7179b0f8e30bcdd7b18691a6bbd130ceb11ffe` | +| Base `8453` | `8453-dodo_pmm-cwusdc-usdc` | `0xF28Ca783A7b8Ed44ca19f45F1EF0b8D7F6E7bdc6` | `0xFc2e6eED941D18998dAC029b9FD567744F4e8D86` | create pool `0xc150eb438b24882611e9b6bea649ae89e0f6d19547af26648d2c75af5d010618`; deploy seeder `0x8031e2488b07f0564fe4cc5040de3eb207276a22c88541c4728ba92d502c6652`; approve base `0xec9b1d58782bb4f8d155bda72c3f82bd1a74bb94f77312a395ebdaac42c11e9f`; approve quote `0x35d09e4c8d732a06667f6224faf4ef7e19c4bb8ff8847f0337dfccf7175dd5f0`; seed `0x1c01be26a6e103c88449ac4c3eb338cb0ee13025453ca1aa80e213da0ce68032` | +| Base `8453` | `8453-dodo_pmm-cwusdt-usdt` | `0xf6729883CbB93b10a1a1fc00EF5647819b65f65a` | `0xFc2e6eED941D18998dAC029b9FD567744F4e8D86` | create pool `0x3256cdef0a88323159bd91770dda72a28cd2f6d62fe0f12506c800fa2e802f2c`; approve base `0x1b7b1f3580699ae0a217e83482f4c67394789ed7e7aa4e3895f3ef66c65b0874`; approve quote `0x57903f95d5982fbd1295c67ba6c0d9f50503d3190410d117f3f61f16c600bd0b`; seed `0x5a98c991ac23f2eb806f8311b3014b2f5c8b2e1daa7fda993ce328873e180d9e` | + +Each completed pool was seeded through `DODOAtomicSeeder` with `0.01` raw token-unit depth per side and then rediscovered with nonzero base reserve, quote reserve, and LP total supply. + +## Remaining Blockers + +| Chain | Pool | Blocker | +| --- | --- | --- | +| Arbitrum `42161` | `42161-dodo_pmm-cwusdc-usdc` | Official DODO pool exists at `0x370F464B6e37978a0F838c41cd9EA73732bf25BE`, but seeding reverted with `MINT_AMOUNT_NOT_ENOUGH` at the deployer wallet's available `0.002545` USDC. Top up at least `0.007455` USDC to reach the current `0.01` minimum seed target before retrying. | +| Cronos `25` | `25-dodo_pmm-cwusdc-usdc`, `25-dodo_pmm-cwusdt-usdt` | No committed official DODO DVM factory source. 1inch official Swap support is recorded as the alternate official route profile. | +| Gnosis `100` | `100-dodo_pmm-cwusdc-usdc`, `100-dodo_pmm-cwusdt-usdt` | No committed official DODO DVM factory source. 1inch official network support is recorded as the alternate official route profile. | + +## Current Summary + +- Seeded official DODO replacement rows: `11` +- Remaining official migration rows: `5` +- Remaining unsupported-DODO rows with official 1inch alternate route path: `4` +- Remaining zero/unusable official DODO pools: `1` diff --git a/scripts/status/build-official-protocol-integration-plan.mjs b/scripts/status/build-official-protocol-integration-plan.mjs index 5cd29734..8550baf3 100644 --- a/scripts/status/build-official-protocol-integration-plan.mjs +++ b/scripts/status/build-official-protocol-integration-plan.mjs @@ -8,12 +8,13 @@ * unwind/repeg/depth work needed before treating liquidity as deep production. */ -import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +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 sourcePath = resolve(repoRoot, "config/official-protocol-integration-sources.json"); +const discoveryPath = resolve(repoRoot, "reports/status/all-mainnet-official-dodo-discovery-latest.json"); const outJson = resolve(repoRoot, "reports/status/all-mainnet-official-protocol-integration-plan-latest.json"); const outMd = resolve(repoRoot, "reports/status/all-mainnet-official-protocol-integration-plan-latest.md"); @@ -59,12 +60,25 @@ function dodoOfficialFactory(source, chainId) { return source?.chainFactories?.[key]?.dvmFactory || null; } -function replacementMigration(row, sources) { +function discoverySeeded(discoveryRow) { + return (discoveryRow?.poolEvidence || []).some((pool) => pool.seeded === true); +} + +function replacementMigration(row, sources, discoveryByPoolId) { const source = protocolSource(sources, row.protocol); const officialFactory = row.protocol === "dodo_pmm" ? dodoOfficialFactory(source, row.chainId) : null; const unsupported = row.protocol === "dodo_pmm" ? source?.unsupportedOrUnverifiedChains?.[String(row.chainId)]?.status || null : null; + const discovery = discoveryByPoolId.get(row.poolId) || null; + const seededOfficialPool = discoverySeeded(discovery); + const officialPools = (discovery?.poolEvidence || []).map((pool) => ({ + poolAddress: pool.poolAddress, + baseReserveRaw: pool.baseReserveRaw || null, + quoteReserveRaw: pool.quoteReserveRaw || null, + totalSupplyRaw: pool.totalSupplyRaw || null, + seeded: pool.seeded === true, + })); return { poolId: row.poolId, chainId: row.chainId, @@ -75,11 +89,15 @@ function replacementMigration(row, sources) { temporaryReplacement: Boolean(row.replacementEvidence), oldPoolAddress: row.replacementEvidence?.oldPoolAddress || null, officialFactory, - officialSourceStatus: officialFactory ? "official_factory_available" : unsupported || "official_source_not_committed", + officialSourceStatus: seededOfficialPool + ? "official_pool_seeded" + : officialFactory ? (discovery?.status || "official_factory_available") : unsupported || "official_source_not_committed", + officialPools, + migrationCompleteThroughSeed: seededOfficialPool, actions: [ - officialFactory ? "discover_existing_official_pool" : "confirm_official_factory_or_choose_alternate_protocol", - officialFactory ? "create_official_pool_if_absent" : "keep_replacement_pool_read_only_until_supported", - "seed_official_pool_to_target_depth", + seededOfficialPool ? "record_official_seed_evidence" : officialFactory ? "discover_existing_official_pool" : "confirm_official_factory_or_choose_alternate_protocol", + seededOfficialPool ? "run_protocol_native_canaries" : officialFactory ? "create_official_pool_if_absent" : "keep_replacement_pool_read_only_until_supported", + seededOfficialPool ? "switch_public_routing_to_official_pool_after_canary" : "seed_official_pool_to_target_depth", "run_oracle_normalized_repeg_check", "run_10_100_1000_canaries", "switch_public_routing_to_official_pool", @@ -146,11 +164,13 @@ function depthIntent(row) { const matrix = readJson(matrixPath); const sources = readJson(sourcePath); +const discovery = existsSync(discoveryPath) ? readJson(discoveryPath) : { rows: [] }; +const discoveryByPoolId = new Map((discovery.rows || []).map((row) => [row.poolId, row])); const generatedAt = new Date().toISOString(); const requiredRows = matrix.rows.filter((row) => row.requiredForSpend === true); const replacementRows = requiredRows.filter((row) => row.replacementEvidence); const optionalRows = matrix.rows.filter((row) => row.requiredForSpend !== true); -const officialMigrations = replacementRows.map((row) => replacementMigration(row, sources)); +const officialMigrations = replacementRows.map((row) => replacementMigration(row, sources, discoveryByPoolId)); const optionalProtocolRows = optionalRows .filter((row) => ["sushiswap_v2", "curve_stable", "balancer_weighted", "aave_backstop", "oneinch_aggregator", "uniswap_v2", "dodo_pmm"].includes(row.protocol)) .map((row) => optionalProtocolAction(row, sources)); @@ -179,7 +199,8 @@ const report = { matrixFile: "config/all-mainnet-pool-creation-matrix.json", summary: { requiredRows: requiredRows.length, - replacementRowsNeedingOfficialMigration: replacementRows.length, + replacementRowsNeedingOfficialMigration: officialMigrations.filter((row) => !row.migrationCompleteThroughSeed).length, + replacementRowsWithSeededOfficialPool: officialMigrations.filter((row) => row.migrationCompleteThroughSeed).length, optionalProtocolRowsNeedingOfficialEvidence: optionalProtocolRows.length, depthRows: depthIntents.length, unwindRows: unwindRows.length, diff --git a/scripts/status/build-remaining-official-routing-tasks.mjs b/scripts/status/build-remaining-official-routing-tasks.mjs new file mode 100644 index 00000000..d7d100b6 --- /dev/null +++ b/scripts/status/build-remaining-official-routing-tasks.mjs @@ -0,0 +1,158 @@ +#!/usr/bin/env node +/** + * Build the remaining task list for unsupported DODO rows and zero/unusable + * official DODO pools. + */ + +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 sourcesPath = resolve(repoRoot, "config/official-protocol-integration-sources.json"); +const discoveryPath = resolve(repoRoot, "reports/status/all-mainnet-official-dodo-discovery-latest.json"); +const outJson = resolve(repoRoot, "reports/status/all-mainnet-remaining-official-routing-tasks-latest.json"); +const outMd = resolve(repoRoot, "reports/status/all-mainnet-remaining-official-routing-tasks-latest.md"); + +function readJson(path) { + return JSON.parse(readFileSync(path, "utf8")); +} + +function table(headers, rows) { + return [ + `| ${headers.join(" | ")} |`, + `| ${headers.map(() => "---").join(" | ")} |`, + ...rows.map((row) => `| ${row.map((cell) => String(cell ?? "").replace(/\|/g, "\\|")).join(" | ")} |`), + ].join("\n"); +} + +function pair(row) { + return `${row.baseToken?.symbol || "?"}/${row.quoteToken?.symbol || "?"}`; +} + +const matrix = readJson(matrixPath); +const sources = readJson(sourcesPath); +const discovery = readJson(discoveryPath); +const generatedAt = new Date().toISOString(); + +const unsupportedDodoRows = matrix.rows.filter((row) => ( + row.requiredForSpend === true && + row.protocol === "dodo_pmm" && + [25, 100].includes(Number(row.chainId)) +)); + +const oneInchSupport = sources.protocols?.oneinch_aggregator?.chainSupport || {}; +const unsupportedRoutingTasks = unsupportedDodoRows.map((row) => { + const support = oneInchSupport[String(row.chainId)]; + const hasAggregatorSupport = Boolean(support); + return { + poolId: row.poolId, + chainId: row.chainId, + network: row.network, + pair: pair(row), + currentReplacementPool: row.poolAddress, + targetSupportProtocol: hasAggregatorSupport ? "oneinch_aggregator" : "official_alternate_required", + supportStatus: support?.status || "needs_official_source", + tasks: hasAggregatorSupport ? [ + "create_or_promote matching oneinch_aggregator row for this pair", + "wire official 1inch quote/swap API chain support evidence", + "verify token address import/direct contract-address quote for both directions", + "run dry-run quote and minimum-output route simulation", + "execute tiny canary only after route quote is nonzero and compliance policy passes", + "switch public routing from replacement DODO row to aggregator-backed route", + "deprecate replacement DODO row after canary and fallback route are recorded", + ] : [ + "select an official native DEX or aggregator source with public factory/router/API documentation", + "add protocol profile and official source registry entry", + "add or promote matrix rows for cWUSDC/USDC and cWUSDT/USDT", + "discover or create official pools through that protocol", + "seed with ISO-4217 oracle-normalized amounts", + "run canaries and switch routing only after nonzero reserves are proven", + "deprecate replacement DODO row after supported route is live", + ], + }; +}); + +const zeroPoolTasks = discovery.rows + .flatMap((row) => (row.poolEvidence || []) + .filter((pool) => pool.seeded === false) + .map((pool) => ({ + poolId: row.poolId, + chainId: row.chainId, + network: row.network, + pair: row.pair, + officialPool: pool.poolAddress, + baseReserveRaw: pool.baseReserveRaw, + quoteReserveRaw: pool.quoteReserveRaw, + totalSupplyRaw: pool.totalSupplyRaw, + tasks: [ + "keep publicRoutingEnabled=false for the official pool until reserves are nonzero and canary passes", + "read pool token balances and reserves from the same block to identify stranded transfers versus empty pool", + "confirm deployer quote balance meets DODO minimum mint threshold for the token decimals", + "top up short-side quote inventory by swap/bridge before retrying seed", + "seed through DODOAtomicSeeder only; do not use separate token transfers", + "rerun official DODO discovery and reserve evidence", + "run tiny canary in both directions where token transfer behavior allows", + "promote route only after ISO-4217 value-normalized peg and nonzero LP supply are recorded", + ], + }))); + +const report = { + generatedAt, + sources: { + matrix: "config/all-mainnet-pool-creation-matrix.json", + officialSources: "config/official-protocol-integration-sources.json", + dodoDiscovery: "reports/status/all-mainnet-official-dodo-discovery-latest.json", + }, + summary: { + unsupportedDodoRows: unsupportedRoutingTasks.length, + oneInchSupportableRows: unsupportedRoutingTasks.filter((row) => row.targetSupportProtocol === "oneinch_aggregator").length, + cronosRowsNeedingNativeDexProfile: unsupportedRoutingTasks.filter((row) => row.targetSupportProtocol === "cronos_native_dex_profile_required").length, + zeroOrUnusableOfficialPools: zeroPoolTasks.length, + }, + unsupportedRoutingTasks, + zeroPoolTasks, +}; + +const md = [ + "# ALL Mainnet Remaining Official Routing Tasks", + "", + `- Generated: \`${generatedAt}\``, + "", + table(["Metric", "Count"], Object.entries(report.summary)), + "", + "## Unsupported DODO Rows", + "", + table( + ["Pool", "Chain", "Pair", "Target Support", "Status", "First Task"], + unsupportedRoutingTasks.map((row) => [ + row.poolId, + row.chainId, + row.pair, + row.targetSupportProtocol, + row.supportStatus, + row.tasks[0], + ]), + ), + "", + "## Zero / Unusable Official Pools", + "", + table( + ["Pool", "Chain", "Pair", "Official Pool", "Base Reserve", "Quote Reserve", "First Task"], + zeroPoolTasks.map((row) => [ + row.poolId, + row.chainId, + row.pair, + row.officialPool, + row.baseReserveRaw, + row.quoteReserveRaw, + row.tasks[0], + ]), + ), + "", +].join("\n"); + +mkdirSync(resolve(repoRoot, "reports/status"), { recursive: true }); +writeFileSync(outJson, `${JSON.stringify(report, null, 2)}\n`); +writeFileSync(outMd, `${md}\n`); +console.log(`[OK] Remaining official routing tasks written: ${outJson}`); diff --git a/scripts/status/check-remaining-deployer-balances.mjs b/scripts/status/check-remaining-deployer-balances.mjs new file mode 100644 index 00000000..c6bcb309 --- /dev/null +++ b/scripts/status/check-remaining-deployer-balances.mjs @@ -0,0 +1,291 @@ +#!/usr/bin/env node +/** + * Read-only deployer balance report for the remaining official-routing blockers. + */ + +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { resolve } from "node:path"; +import { Contract, JsonRpcProvider, Wallet, formatEther, formatUnits, parseUnits } from "ethers"; + +const repoRoot = resolve(new URL("../..", import.meta.url).pathname); +const matrixPath = resolve(repoRoot, "config/all-mainnet-pool-creation-matrix.json"); +const tasksPath = resolve(repoRoot, "reports/status/all-mainnet-remaining-official-routing-tasks-latest.json"); +const discoveryPath = resolve(repoRoot, "reports/status/all-mainnet-official-dodo-discovery-latest.json"); +const outJson = resolve(repoRoot, "reports/status/all-mainnet-remaining-deployer-balances-latest.json"); +const outMd = resolve(repoRoot, "reports/status/all-mainnet-remaining-deployer-balances-latest.md"); + +const ERC20_ABI = [ + "function balanceOf(address) view returns (uint256)", + "function decimals() view returns (uint8)", + "function symbol() view returns (string)", +]; + +const chainRpcCandidates = { + 10: ["OPTIMISM_MAINNET_RPC", "OPTIMISM_RPC_URL", "OPTIMISM_RPC"], + 25: ["CRONOS_RPC_URL", "CRONOS_RPC", "CRONOS_MAINNET_RPC"], + 100: ["GNOSIS_MAINNET_RPC", "GNOSIS_RPC_URL", "GNOSIS_RPC"], + 8453: ["BASE_MAINNET_RPC", "BASE_RPC_URL", "BASE_RPC"], + 42161: ["ARBITRUM_MAINNET_RPC", "ARBITRUM_RPC_URL", "ARBITRUM_RPC"], +}; + +function readJson(path) { + return JSON.parse(readFileSync(path, "utf8")); +} + +function loadEnvFile(path, env) { + if (!existsSync(path)) return; + for (const line of readFileSync(path, "utf8").split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue; + const index = trimmed.indexOf("="); + const key = trimmed.slice(0, index).replace(/^export\s+/, "").trim(); + let value = trimmed.slice(index + 1).trim(); + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1); + value = value.replace(/\s+#.*$/, ""); + value = value.replace(/\$\{([^}:]+)(:-([^}]*))?\}/g, (_, name, _fallback, fallbackValue) => ( + env[name] ?? process.env[name] ?? fallbackValue ?? "" + )); + if (!value || value === "0x" || value.includes("${")) continue; + env[key] ??= value; + } +} + +function loadEnv() { + const env = { ...process.env }; + loadEnvFile(resolve(repoRoot, ".env"), env); + loadEnvFile(resolve(repoRoot, "smom-dbis-138/.env"), env); + loadEnvFile(resolve(homedir(), ".secure-secrets/private-keys.env"), env); + if (!env.PRIVATE_KEY && env.DEPLOYER_PRIVATE_KEY) env.PRIVATE_KEY = env.DEPLOYER_PRIVATE_KEY; + return env; +} + +function rpcForChain(chainId, env) { + for (const key of chainRpcCandidates[chainId] || []) { + if (env[key]) return { key, url: env[key] }; + } + return { key: null, url: null }; +} + +function table(headers, rows) { + return [ + `| ${headers.join(" | ")} |`, + `| ${headers.map(() => "---").join(" | ")} |`, + ...rows.map((row) => `| ${row.map((cell) => String(cell ?? "").replace(/\|/g, "\\|")).join(" | ")} |`), + ].join("\n"); +} + +function addToken(tokensByChain, chainId, token) { + if (!token?.address) return; + const key = String(chainId); + tokensByChain[key] ??= new Map(); + tokensByChain[key].set(token.address.toLowerCase(), { + symbol: token.symbol, + address: token.address, + }); +} + +function pair(row) { + return `${row.baseToken?.symbol || "?"}/${row.quoteToken?.symbol || "?"}`; +} + +function minDodoSeedRaw(decimals) { + // DODO V2 DVM rejected 1_000/2_000 raw but accepted 10_000 raw on 6-decimal quote rows. + return parseUnits("0.01", Number(decimals)); +} + +const env = loadEnv(); +const operator = env.DEPLOYER_ADDRESS || env.SIGNER_ADDRESS || (env.PRIVATE_KEY ? new Wallet(env.PRIVATE_KEY).address : null); +if (!operator) throw new Error("Set DEPLOYER_ADDRESS, SIGNER_ADDRESS, or PRIVATE_KEY."); + +const matrix = readJson(matrixPath); +const tasks = existsSync(tasksPath) ? readJson(tasksPath) : { unsupportedRoutingTasks: [], zeroPoolTasks: [] }; +const discovery = existsSync(discoveryPath) ? readJson(discoveryPath) : { rows: [] }; + +const unsupportedRows = matrix.rows.filter((row) => ( + row.requiredForSpend === true && + row.protocol === "dodo_pmm" && + [25, 100].includes(Number(row.chainId)) +)); + +const zeroRows = discovery.rows.filter((row) => ( + (row.poolEvidence || []).some((pool) => pool.seeded === false) +)); + +const tokensByChain = {}; +for (const row of [...unsupportedRows, ...zeroRows.map((zero) => matrix.rows.find((row) => row.poolId === zero.poolId)).filter(Boolean)]) { + addToken(tokensByChain, row.chainId, row.baseToken); + addToken(tokensByChain, row.chainId, row.quoteToken); +} + +const chainReports = []; +for (const chainId of Object.keys(tokensByChain).map(Number).sort((a, b) => a - b)) { + const rpc = rpcForChain(chainId, env); + const chainReport = { + chainId, + rpcEnvKey: rpc.key, + nativeBalanceWei: null, + nativeBalance: null, + tokenBalances: [], + errors: [], + }; + + if (!rpc.url) { + chainReport.errors.push("missing_rpc_url"); + chainReports.push(chainReport); + continue; + } + + const provider = new JsonRpcProvider(rpc.url, chainId, { staticNetwork: true }); + try { + const native = await provider.getBalance(operator); + chainReport.nativeBalanceWei = native.toString(); + chainReport.nativeBalance = formatEther(native); + } catch (error) { + chainReport.errors.push(`native_balance:${error.shortMessage || error.message}`); + } + + for (const token of tokensByChain[String(chainId)].values()) { + const entry = { + symbol: token.symbol, + address: token.address, + balanceRaw: null, + decimals: null, + balance: null, + minDodoSeedRaw: null, + minDodoSeed: null, + shortfallToMinDodoSeedRaw: null, + shortfallToMinDodoSeed: null, + error: null, + }; + try { + const contract = new Contract(token.address, ERC20_ABI, provider); + const [decimals, balance] = await Promise.all([ + contract.decimals(), + contract.balanceOf(operator), + ]); + const minSeed = minDodoSeedRaw(decimals); + const shortfall = balance >= minSeed ? 0n : minSeed - balance; + entry.decimals = Number(decimals); + entry.balanceRaw = balance.toString(); + entry.balance = formatUnits(balance, decimals); + entry.minDodoSeedRaw = minSeed.toString(); + entry.minDodoSeed = formatUnits(minSeed, decimals); + entry.shortfallToMinDodoSeedRaw = shortfall.toString(); + entry.shortfallToMinDodoSeed = formatUnits(shortfall, decimals); + } catch (error) { + entry.error = error.shortMessage || error.message; + } + chainReport.tokenBalances.push(entry); + } + + chainReports.push(chainReport); +} + +const unsupportedTaskRows = unsupportedRows.map((row) => { + const task = (tasks.unsupportedRoutingTasks || []).find((item) => item.poolId === row.poolId); + return { + poolId: row.poolId, + chainId: row.chainId, + network: row.network, + pair: pair(row), + currentReplacementPool: row.poolAddress, + targetSupportProtocol: task?.targetSupportProtocol || "pending_official_route_profile", + minimumSpendPlan: task?.targetSupportProtocol === "oneinch_aggregator" + ? "No pool seed required; minimum spend is gas plus a tiny route canary after nonzero official 1inch quote is proven." + : "Needs official protocol selection first; no safe spend amount can be calculated until factory/router is confirmed.", + }; +}); + +const zeroTaskRows = zeroRows.map((zero) => { + const matrixRow = matrix.rows.find((row) => row.poolId === zero.poolId); + const evidence = (zero.poolEvidence || []).find((pool) => pool.seeded === false); + const chainReport = chainReports.find((chain) => chain.chainId === zero.chainId); + const quoteBalance = chainReport?.tokenBalances.find((token) => token.symbol === matrixRow?.quoteToken?.symbol); + return { + poolId: zero.poolId, + chainId: zero.chainId, + network: zero.network, + pair: zero.pair, + officialPool: evidence?.poolAddress || null, + baseReserveRaw: evidence?.baseReserveRaw || null, + quoteReserveRaw: evidence?.quoteReserveRaw || null, + totalSupplyRaw: evidence?.totalSupplyRaw || null, + quoteSymbol: matrixRow?.quoteToken?.symbol || null, + deployerQuoteBalance: quoteBalance?.balance || null, + minimumQuoteNeeded: quoteBalance?.minDodoSeed || "0.01", + quoteShortfall: quoteBalance?.shortfallToMinDodoSeed || null, + minimumSpendPlan: "Top up quote to at least 0.01 token units, then seed equal base/quote through DODOAtomicSeeder.", + }; +}); + +const report = { + generatedAt: new Date().toISOString(), + mode: "read_only_deployer_balance_inventory", + operator, + summary: { + unsupportedDodoRows: unsupportedTaskRows.length, + zeroOrUnusableOfficialPools: zeroTaskRows.length, + chainsChecked: chainReports.length, + }, + chainReports, + unsupportedTaskRows, + zeroTaskRows, +}; + +const md = [ + "# ALL Mainnet Remaining Deployer Balances", + "", + `- Generated: \`${report.generatedAt}\``, + `- Operator: \`${operator}\``, + "", + table(["Metric", "Count"], Object.entries(report.summary)), + "", + "## Chain Balances", + "", + table( + ["Chain", "Native", "Token", "Balance", "Min DODO Seed", "Shortfall"], + chainReports.flatMap((chain) => chain.tokenBalances.map((token) => [ + chain.chainId, + chain.nativeBalance, + token.symbol, + token.balance ?? token.error, + token.minDodoSeed, + token.shortfallToMinDodoSeed, + ])), + ), + "", + "## Unsupported Rows", + "", + table( + ["Pool", "Chain", "Pair", "Target", "Minimum Spend Plan"], + unsupportedTaskRows.map((row) => [ + row.poolId, + row.chainId, + row.pair, + row.targetSupportProtocol, + row.minimumSpendPlan, + ]), + ), + "", + "## Zero / Unusable Official Pools", + "", + table( + ["Pool", "Chain", "Pair", "Official Pool", "Quote Balance", "Quote Shortfall", "Minimum Spend Plan"], + zeroTaskRows.map((row) => [ + row.poolId, + row.chainId, + row.pair, + row.officialPool, + row.deployerQuoteBalance, + row.quoteShortfall, + row.minimumSpendPlan, + ]), + ), + "", +].join("\n"); + +mkdirSync(resolve(repoRoot, "reports/status"), { recursive: true }); +writeFileSync(outJson, `${JSON.stringify(report, null, 2)}\n`); +writeFileSync(outMd, `${md}\n`); +console.log(`[OK] Remaining deployer balance report written: ${outJson}`); diff --git a/scripts/status/discover-official-dodo-pools.mjs b/scripts/status/discover-official-dodo-pools.mjs index 8d00b988..4762efe0 100644 --- a/scripts/status/discover-official-dodo-pools.mjs +++ b/scripts/status/discover-official-dodo-pools.mjs @@ -22,6 +22,11 @@ const DVM_FACTORY_ABI = [ "function getDODOPoolBidirection(address token0,address token1) view returns (address[] baseToken0Machines,address[] baseToken1Machines)", "function getDODOPool(address baseToken,address quoteToken) view returns (address[] machines)", ]; +const DVM_POOL_ABI = [ + "function getVaultReserve() view returns (uint256,uint256)", + "function totalSupply() view returns (uint256)", + "function getMidPrice() view returns (uint256)", +]; const ZERO = "0x0000000000000000000000000000000000000000"; @@ -30,6 +35,7 @@ const chainRpcCandidates = { 10: ["OPTIMISM_MAINNET_RPC", "OPTIMISM_RPC_URL"], 56: ["BSC_MAINNET_RPC", "BSC_RPC_URL"], 137: ["POLYGON_MAINNET_RPC", "POLYGON_RPC_URL"], + 8453: ["BASE_MAINNET_RPC", "BASE_RPC_URL", "BASE_RPC"], 42161: ["ARBITRUM_MAINNET_RPC", "ARBITRUM_RPC_URL"], 43114: ["AVALANCHE_MAINNET_RPC", "AVALANCHE_RPC_URL"], }; @@ -160,6 +166,31 @@ async function discoverRow(row, source, env) { const [forward, reverse] = await factoryContract.getDODOPoolBidirection(baseToken, quoteToken); const existingPools = [...forward].filter((address) => address !== ZERO); const reversePools = [...reverse].filter((address) => address !== ZERO); + const poolEvidence = []; + for (const address of [...existingPools, ...reversePools]) { + const pool = new Contract(address, DVM_POOL_ABI, provider); + try { + const [reserves, totalSupply, midPrice] = await Promise.all([ + pool.getVaultReserve(), + pool.totalSupply(), + pool.getMidPrice().catch(() => null), + ]); + poolEvidence.push({ + poolAddress: address, + baseReserveRaw: reserves[0].toString(), + quoteReserveRaw: reserves[1].toString(), + totalSupplyRaw: totalSupply.toString(), + midPriceRaw: midPrice?.toString() || null, + seeded: reserves[0] > 0n && reserves[1] > 0n && totalSupply > 0n, + }); + } catch (error) { + poolEvidence.push({ + poolAddress: address, + error: String(error?.shortMessage || error?.message || error), + seeded: false, + }); + } + } return { poolId: row.poolId, chainId, @@ -172,6 +203,7 @@ async function discoverRow(row, source, env) { status: existingPools.length || reversePools.length ? "official_pool_exists" : "official_pool_missing", existingPools, reversePools, + poolEvidence, recommendedAction: existingPools.length || reversePools.length ? "validate_reserves_canary_then_switch_routes" : "create_official_dvm_pool_then_seed_and_canary", diff --git a/scripts/status/execute-official-dodo-migration.mjs b/scripts/status/execute-official-dodo-migration.mjs index e00dbb4a..0addcb58 100644 --- a/scripts/status/execute-official-dodo-migration.mjs +++ b/scripts/status/execute-official-dodo-migration.mjs @@ -66,6 +66,7 @@ const chainRpcCandidates = { 10: ["OPTIMISM_MAINNET_RPC", "OPTIMISM_RPC_URL", "OPTIMISM_RPC"], 56: ["BSC_MAINNET_RPC", "BSC_RPC_URL", "BSC_RPC"], 137: ["POLYGON_MAINNET_RPC", "POLYGON_RPC_URL", "POLYGON_RPC"], + 8453: ["BASE_MAINNET_RPC", "BASE_RPC_URL", "BASE_RPC"], 42161: ["ARBITRUM_MAINNET_RPC", "ARBITRUM_RPC_URL", "ARBITRUM_RPC"], 43114: ["AVALANCHE_MAINNET_RPC", "AVALANCHE_RPC_URL", "AVALANCHE_RPC"], };