Advance official DODO migration routing evidence
All checks were successful
Deploy to Phoenix / validate (push) Successful in 1m21s
Deploy to Phoenix / deploy (push) Successful in 48s
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Successful in 1m20s
phoenix-deploy Deployed to cloudflare-sync
Deploy to Phoenix / cloudflare (push) Successful in 40s
All checks were successful
Deploy to Phoenix / validate (push) Successful in 1m21s
Deploy to Phoenix / deploy (push) Successful in 48s
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Successful in 1m20s
phoenix-deploy Deployed to cloudflare-sync
Deploy to Phoenix / cloudflare (push) Successful in 40s
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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`
|
||||
@@ -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,
|
||||
|
||||
158
scripts/status/build-remaining-official-routing-tasks.mjs
Normal file
158
scripts/status/build-remaining-official-routing-tasks.mjs
Normal file
@@ -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}`);
|
||||
291
scripts/status/check-remaining-deployer-balances.mjs
Normal file
291
scripts/status/check-remaining-deployer-balances.mjs
Normal file
@@ -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}`);
|
||||
@@ -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",
|
||||
|
||||
@@ -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"],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user