Advance ALL Mainnet production readiness planning
This commit is contained in:
@@ -90,6 +90,80 @@
|
||||
"notes": [
|
||||
"Tiny live canary swap executed on Polygon DODO PMM cWUSDT/USDT."
|
||||
]
|
||||
},
|
||||
{
|
||||
"poolId": "651940-uniswap_v2-usdt-ausdc",
|
||||
"generatedAt": "2026-04-30T07:30:00Z",
|
||||
"canaryTransactions": [
|
||||
{
|
||||
"direction": "base_to_quote",
|
||||
"txHash": "0xa5479400c203922b0e29a4c438daeeeef6f99b847d617f029e7978c1beba6b2b",
|
||||
"approvalTxHash": "0xe4e73ff7d9c4a998e4cca1fcbf847d351171c0c026b11e798a0c1dc2bb6b4f12",
|
||||
"fundingSwapTxHash": "0x39bddcca6160e2df5c7595350f9d911c73e0e7bfe59595fae2c3248576962d5b",
|
||||
"fundingApprovalTxHash": "0x238bae3818d16a95711c19657dbd08bc076ef3fde7fedb7291ed514d2e090684",
|
||||
"amountInRaw": "1000000",
|
||||
"tokenIn": "USDT",
|
||||
"tokenOut": "AUSDC",
|
||||
"executor": "UniswapV2Router.swapExactTokensForTokens"
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
"Tiny live canary swap executed on ALL Mainnet Uniswap V2 USDT/AUSDC after acquiring USDT inventory via AUSDC/USDT."
|
||||
]
|
||||
},
|
||||
{
|
||||
"poolId": "651940-uniswap_v2-wall-usdt",
|
||||
"generatedAt": "2026-04-30T07:30:00Z",
|
||||
"canaryTransactions": [
|
||||
{
|
||||
"direction": "base_to_quote",
|
||||
"txHash": "0x7b6a3d5dedc775e9b0f73de4e0a89bdd004f82c451d7cccf1e2178e5893d487c",
|
||||
"approvalTxHash": "0xdd7985b4535915523831f989e054e6333af6b84e589c67791e73cce7acbff6a6",
|
||||
"amountInRaw": "1000000",
|
||||
"tokenIn": "WALL",
|
||||
"tokenOut": "USDT",
|
||||
"executor": "UniswapV2Router.swapExactTokensForTokens"
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
"Tiny live canary swap executed on ALL Mainnet Uniswap V2 WALL/USDT."
|
||||
]
|
||||
},
|
||||
{
|
||||
"poolId": "42220-dodo_pmm-cwusdc-usdc",
|
||||
"generatedAt": "2026-04-30T00:00:00.000-07:00",
|
||||
"canaryTransactions": [
|
||||
{
|
||||
"direction": "base_to_quote",
|
||||
"txHash": "0x32d3869f987d558e392fd01aab77968d9cad1d90da71be1db90061f58d5c14b1",
|
||||
"fundingTransferTxHash": "0x8ecd2a68f5dc3f2ab471ccc7ea8fdce16064029b6792bce7a387d943326fa1d6",
|
||||
"amountInRaw": "100",
|
||||
"tokenIn": "cWUSDC",
|
||||
"tokenOut": "USDC",
|
||||
"executor": "DODO_DVM.transfer_then_sellBase"
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
"Tiny live canary swap executed on Celo DODO PMM cWUSDC/USDC after deployer-funded seed liquidity."
|
||||
]
|
||||
},
|
||||
{
|
||||
"poolId": "42220-dodo_pmm-cwusdt-usdt",
|
||||
"generatedAt": "2026-04-30T00:00:00.000-07:00",
|
||||
"canaryTransactions": [
|
||||
{
|
||||
"direction": "base_to_quote",
|
||||
"txHash": "0x0498a0484f6707e0f8019b8803a8aed932f33f587a57186cc37bcdc594393094",
|
||||
"fundingTransferTxHash": "0xd4182a6e116df30544097851892f379d67af2b33fc2374bb0204a6450dcecea4",
|
||||
"amountInRaw": "100",
|
||||
"tokenIn": "cWUSDT",
|
||||
"tokenOut": "USDT",
|
||||
"executor": "DODO_DVM.transfer_then_sellBase"
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
"Tiny live canary swap executed on Celo DODO PMM cWUSDT/USDT after deployer-funded seed liquidity."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ const readinessPath = resolve(repoRoot, "reports/status/all-mainnet-deployment-r
|
||||
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 dodoFundingPath = resolve(repoRoot, "reports/status/all-mainnet-dodo-funding-preflight-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");
|
||||
|
||||
@@ -49,17 +50,20 @@ const readiness = readJson(readinessPath, { blockers: [] });
|
||||
const reserve = readJson(reservePath, { results: [] });
|
||||
const canary = readJson(canaryPath, { results: [] });
|
||||
const rebalance = readJson(rebalancePath, { intents: [] });
|
||||
const dodoFunding = readJson(dodoFundingPath, { results: [] });
|
||||
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 dodoFundingByPoolId = new Map(dodoFunding.results.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 dodoFundingRow = dodoFundingByPoolId.get(row.poolId);
|
||||
const blockers = new Set(poolBlockers(row));
|
||||
const productionBlockers = new Set();
|
||||
const actions = [];
|
||||
@@ -67,11 +71,26 @@ const rows = requiredRows.map((row) => {
|
||||
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 (reserveRow?.liveReadStatus === "zero_balances") {
|
||||
if (row.protocol === "dodo_pmm" && dodoFundingRow?.status !== "ready") {
|
||||
for (const blocker of dodoFundingRow?.blockers || ["dodo_funding_preflight_missing"]) blockers.add(`funding_${blocker}`);
|
||||
} else {
|
||||
actions.push("fund_base_and_quote");
|
||||
}
|
||||
}
|
||||
if (reserveRow?.liveReadStatus === "partial_balance") {
|
||||
if (row.protocol === "dodo_pmm" && dodoFundingRow?.status !== "ready") {
|
||||
for (const blocker of dodoFundingRow?.blockers || ["dodo_funding_preflight_missing"]) blockers.add(`funding_${blocker}`);
|
||||
} else {
|
||||
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 || []));
|
||||
if (rebalanceRow?.executionStatus === "operator_review_required") {
|
||||
actions.push(...(rebalanceRow.actions || []));
|
||||
blockers.add("rebalance_operator_review_required");
|
||||
}
|
||||
|
||||
for (const blocker of canaryRow?.blockers || []) blockers.add(blocker);
|
||||
if (["live_read", "canary_passed", "production"].includes(row.status)) {
|
||||
@@ -90,6 +109,7 @@ const rows = requiredRows.map((row) => {
|
||||
publicRoutingEnabled: Boolean(row.publicRoutingEnabled),
|
||||
reserveStatus: reserveRow?.liveReadStatus || "not_checked",
|
||||
canaryPreflight: canaryRow?.canaryPreflight || "not_checked",
|
||||
dodoFundingPreflight: dodoFundingRow?.status || "not_checked",
|
||||
rebalanceStatus: rebalanceRow?.executionStatus || "not_planned",
|
||||
actions: [...new Set(actions)],
|
||||
blockers: [...blockers],
|
||||
|
||||
@@ -38,6 +38,29 @@ function ratioBps(a, b) {
|
||||
return Number(((high - low) * 10_000n) / high);
|
||||
}
|
||||
|
||||
function rebalanceHint(row, base, quote, imbalanceBps) {
|
||||
if (base === 0n && quote === 0n) return null;
|
||||
const highSide = base > quote ? "base" : quote > base ? "quote" : "balanced";
|
||||
const lowSide = base > quote ? "quote" : quote > base ? "base" : "balanced";
|
||||
const highSymbol = highSide === "base" ? row.baseToken?.symbol : row.quoteToken?.symbol;
|
||||
const lowSymbol = lowSide === "base" ? row.baseToken?.symbol : row.quoteToken?.symbol;
|
||||
return {
|
||||
highSide,
|
||||
lowSide,
|
||||
highSymbol,
|
||||
lowSymbol,
|
||||
imbalanceBps,
|
||||
conservativeAction:
|
||||
highSide === "balanced"
|
||||
? "monitor_no_rebalance_required"
|
||||
: `add_or_swap_into_${lowSide}_side`,
|
||||
note:
|
||||
highSide === "balanced"
|
||||
? "Reserves are balanced in raw units."
|
||||
: `Reserve skew is ${imbalanceBps} bps by raw token units; correct toward ${lowSymbol} before increasing ${highSymbol} exposure.`,
|
||||
};
|
||||
}
|
||||
|
||||
function reserveEvidenceFor(row, reserveByPoolId) {
|
||||
const live = reserveByPoolId.get(row.poolId);
|
||||
if (live) {
|
||||
@@ -78,6 +101,7 @@ function planForRow(row, evidence) {
|
||||
const base = asBigInt(evidence?.baseBalanceRaw);
|
||||
const quote = asBigInt(evidence?.quoteBalanceRaw);
|
||||
const imbalanceBps = ratioBps(base, quote);
|
||||
const hint = rebalanceHint(row, base, quote, imbalanceBps);
|
||||
|
||||
if (evidence) {
|
||||
if (evidence.poolHasCode !== true) blockers.push("pool_code_not_confirmed");
|
||||
@@ -113,6 +137,7 @@ function planForRow(row, evidence) {
|
||||
},
|
||||
reserveEvidence: evidence,
|
||||
imbalanceBps,
|
||||
rebalanceHint: hint,
|
||||
actions: [...new Set(actions)],
|
||||
blockers: [...new Set(blockers)],
|
||||
executionStatus: blockers.length ? "blocked" : actions.includes("monitor_no_rebalance_required") ? "no_action" : "operator_review_required",
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* promoted to live_read.
|
||||
*/
|
||||
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { resolve } from "node:path";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
@@ -16,17 +17,45 @@ const matrixPath = resolve(repoRoot, "config/all-mainnet-pool-creation-matrix.js
|
||||
const outDir = resolve(repoRoot, "reports/status");
|
||||
const reportPath = resolve(outDir, "all-mainnet-canary-preflight-latest.json");
|
||||
|
||||
function loadDotenvFile(path) {
|
||||
if (!existsSync(path)) return;
|
||||
const content = readFileSync(path, "utf8");
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
||||
if (!match) continue;
|
||||
const [, key, rawValue] = match;
|
||||
if (process.env[key] !== undefined) continue;
|
||||
let value = rawValue.trim();
|
||||
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
} else {
|
||||
value = value.replace(/\s+#.*$/, "");
|
||||
}
|
||||
if (value.includes("${")) continue;
|
||||
process.env[key] = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
loadDotenvFile(resolve(repoRoot, ".env"));
|
||||
loadDotenvFile(resolve(repoRoot, "smom-dbis-138/.env"));
|
||||
loadDotenvFile(resolve(homedir(), ".secure-secrets/private-keys.env"));
|
||||
if (!process.env.PRIVATE_KEY && process.env.DEPLOYER_PRIVATE_KEY) {
|
||||
process.env.PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY;
|
||||
}
|
||||
|
||||
const rpcByChain = {
|
||||
1: process.env.ETHEREUM_MAINNET_RPC || process.env.RPC_URL_1 || "https://ethereum.publicnode.com",
|
||||
10: process.env.OPTIMISM_RPC || process.env.RPC_URL_10 || "https://optimism.publicnode.com",
|
||||
25: process.env.CRONOS_RPC || process.env.RPC_URL_25 || "https://cronos-evm-rpc.publicnode.com",
|
||||
56: process.env.BSC_RPC || process.env.RPC_URL_56 || "https://bsc-rpc.publicnode.com",
|
||||
100: process.env.GNOSIS_RPC || process.env.RPC_URL_100 || "https://gnosis.publicnode.com",
|
||||
137: process.env.POLYGON_RPC || process.env.RPC_URL_137 || "https://polygon-bor-rpc.publicnode.com",
|
||||
8453: process.env.BASE_RPC || process.env.RPC_URL_8453 || "https://base-rpc.publicnode.com",
|
||||
42161: process.env.ARBITRUM_RPC || process.env.RPC_URL_42161 || "https://arbitrum-one-rpc.publicnode.com",
|
||||
42220: process.env.CELO_RPC || process.env.RPC_URL_42220 || "https://celo-rpc.publicnode.com",
|
||||
43114: process.env.AVALANCHE_RPC || process.env.RPC_URL_43114 || "https://avalanche-c-chain-rpc.publicnode.com",
|
||||
10: process.env.OPTIMISM_RPC || process.env.OPTIMISM_MAINNET_RPC || process.env.RPC_URL_10 || "https://optimism.publicnode.com",
|
||||
25: process.env.CRONOS_RPC || process.env.CRONOS_RPC_URL || process.env.RPC_URL_25 || "https://cronos-evm-rpc.publicnode.com",
|
||||
56: process.env.BSC_RPC || process.env.BSC_RPC_URL || process.env.RPC_URL_56 || "https://bsc-rpc.publicnode.com",
|
||||
100: process.env.GNOSIS_RPC || process.env.GNOSIS_MAINNET_RPC || process.env.RPC_URL_100 || "https://gnosis.publicnode.com",
|
||||
137: process.env.POLYGON_RPC || process.env.POLYGON_MAINNET_RPC || process.env.RPC_URL_137 || "https://polygon-bor-rpc.publicnode.com",
|
||||
8453: process.env.BASE_RPC || process.env.BASE_MAINNET_RPC || process.env.RPC_URL_8453 || "https://base-rpc.publicnode.com",
|
||||
42161: process.env.ARBITRUM_RPC || process.env.ARBITRUM_MAINNET_RPC || process.env.RPC_URL_42161 || "https://arbitrum-one-rpc.publicnode.com",
|
||||
42220: process.env.CELO_RPC || process.env.CELO_MAINNET_RPC || process.env.RPC_URL_42220 || "https://celo-rpc.publicnode.com",
|
||||
43114: process.env.AVALANCHE_RPC || process.env.AVALANCHE_RPC_URL || process.env.RPC_URL_43114 || "https://avalanche-c-chain-rpc.publicnode.com",
|
||||
651940: process.env.ALL_MAINNET_RPC || process.env.CHAIN_651940_RPC_URL || "https://mainnet-rpc.alltra.global",
|
||||
};
|
||||
|
||||
@@ -39,8 +68,13 @@ const uniV2RouterAbi = [
|
||||
"function getAmountsOut(uint256,address[]) view returns (uint256[])",
|
||||
];
|
||||
const dodoDvmAbi = [
|
||||
"function querySellBase(address,uint256) view returns (uint256)",
|
||||
"function querySellQuote(address,uint256) view returns (uint256)",
|
||||
"function querySellBase(address,uint256) view returns (uint256,uint256)",
|
||||
"function querySellQuote(address,uint256) view returns (uint256,uint256)",
|
||||
"function getMidPrice() view returns (uint256)",
|
||||
"function getPMMStateForCall() view returns (uint256,uint256,uint256,uint256,uint256,uint256,uint256)",
|
||||
"function getVaultReserve() view returns (uint256,uint256)",
|
||||
"function _BASE_RESERVE_() view returns (uint112)",
|
||||
"function _QUOTE_RESERVE_() view returns (uint112)",
|
||||
];
|
||||
|
||||
function operatorAddress() {
|
||||
@@ -123,19 +157,57 @@ async function quoteDodo(provider, row, base, quote, operator) {
|
||||
const pool = new ethers.Contract(normalizedAddress(row.poolAddress), dodoDvmAbi, provider);
|
||||
const baseAmount = smallQuoteAmount(base.decimals ?? 18);
|
||||
const quoteAmount = smallQuoteAmount(quote.decimals ?? 18);
|
||||
const [sellBase, sellQuote] = await Promise.all([
|
||||
const [sellBase, sellQuote, midPrice, pmmState, vaultReserve, baseReserve, quoteReserve] = await Promise.all([
|
||||
callOrError(() => pool.querySellBase(operator, baseAmount)),
|
||||
callOrError(() => pool.querySellQuote(operator, quoteAmount)),
|
||||
callOrError(() => pool.getMidPrice()),
|
||||
callOrError(() => pool.getPMMStateForCall()),
|
||||
callOrError(() => pool.getVaultReserve()),
|
||||
callOrError(() => pool._BASE_RESERVE_()),
|
||||
callOrError(() => pool._QUOTE_RESERVE_()),
|
||||
]);
|
||||
|
||||
return {
|
||||
supported: sellBase.ok || sellQuote.ok,
|
||||
poolAddress: row.poolAddress,
|
||||
sellBaseOutRaw: sellBase.ok ? sellBase.value.toString() : null,
|
||||
sellQuoteOutRaw: sellQuote.ok ? sellQuote.value.toString() : null,
|
||||
sampleAmounts: {
|
||||
baseRaw: baseAmount.toString(),
|
||||
quoteRaw: quoteAmount.toString(),
|
||||
},
|
||||
midPriceRaw: midPrice.ok ? midPrice.value.toString() : null,
|
||||
pmmState: pmmState.ok
|
||||
? {
|
||||
iRaw: pmmState.value[0].toString(),
|
||||
kRaw: pmmState.value[1].toString(),
|
||||
baseReserveRaw: pmmState.value[2].toString(),
|
||||
quoteReserveRaw: pmmState.value[3].toString(),
|
||||
baseTargetRaw: pmmState.value[4].toString(),
|
||||
quoteTargetRaw: pmmState.value[5].toString(),
|
||||
rState: pmmState.value[6].toString(),
|
||||
}
|
||||
: null,
|
||||
vaultReserve: vaultReserve.ok
|
||||
? {
|
||||
baseRaw: vaultReserve.value[0].toString(),
|
||||
quoteRaw: vaultReserve.value[1].toString(),
|
||||
}
|
||||
: null,
|
||||
storedReserve: {
|
||||
baseRaw: baseReserve.ok ? baseReserve.value.toString() : null,
|
||||
quoteRaw: quoteReserve.ok ? quoteReserve.value.toString() : null,
|
||||
},
|
||||
sellBaseOutRaw: sellBase.ok ? sellBase.value[0].toString() : null,
|
||||
sellBaseFeeRaw: sellBase.ok ? sellBase.value[1].toString() : null,
|
||||
sellQuoteOutRaw: sellQuote.ok ? sellQuote.value[0].toString() : null,
|
||||
sellQuoteFeeRaw: sellQuote.ok ? sellQuote.value[1].toString() : null,
|
||||
errors: [
|
||||
...(sellBase.ok ? [] : [`query_sell_base:${sellBase.error}`]),
|
||||
...(sellQuote.ok ? [] : [`query_sell_quote:${sellQuote.error}`]),
|
||||
...(midPrice.ok ? [] : [`get_mid_price:${midPrice.error}`]),
|
||||
...(pmmState.ok ? [] : [`get_pmm_state:${pmmState.error}`]),
|
||||
...(vaultReserve.ok ? [] : [`get_vault_reserve:${vaultReserve.error}`]),
|
||||
...(baseReserve.ok ? [] : [`get_base_reserve:${baseReserve.error}`]),
|
||||
...(quoteReserve.ok ? [] : [`get_quote_reserve:${quoteReserve.error}`]),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
182
scripts/status/preflight-all-mainnet-dodo-funding.mjs
Normal file
182
scripts/status/preflight-all-mainnet-dodo-funding.mjs
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Read-only funding preflight for required ALL Mainnet DODO rows.
|
||||
*
|
||||
* It checks whether created zero-reserve DODO pools can be seeded safely with
|
||||
* the operator's current base/quote inventory. No transactions are executed.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { resolve } from "node:path";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
const repoRoot = resolve(new URL("../..", import.meta.url).pathname);
|
||||
const matrixPath = resolve(repoRoot, "config/all-mainnet-pool-creation-matrix.json");
|
||||
const outDir = resolve(repoRoot, "reports/status");
|
||||
const outPath = resolve(outDir, "all-mainnet-dodo-funding-preflight-latest.json");
|
||||
|
||||
function loadDotenvFile(path) {
|
||||
if (!existsSync(path)) return;
|
||||
for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
||||
if (!match) continue;
|
||||
const [, key, rawValue] = match;
|
||||
if (process.env[key] !== undefined) continue;
|
||||
let value = rawValue.trim();
|
||||
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
} else {
|
||||
value = value.replace(/\s+#.*$/, "");
|
||||
}
|
||||
if (value.includes("${")) continue;
|
||||
process.env[key] = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
loadDotenvFile(resolve(repoRoot, ".env"));
|
||||
loadDotenvFile(resolve(repoRoot, "smom-dbis-138/.env"));
|
||||
loadDotenvFile(resolve(homedir(), ".secure-secrets/private-keys.env"));
|
||||
if (!process.env.PRIVATE_KEY && process.env.DEPLOYER_PRIVATE_KEY) process.env.PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY;
|
||||
|
||||
const rpcByChain = {
|
||||
10: process.env.OPTIMISM_RPC || process.env.OPTIMISM_MAINNET_RPC || "https://optimism.publicnode.com",
|
||||
25: process.env.CRONOS_RPC || process.env.CRONOS_RPC_URL || "https://cronos-evm-rpc.publicnode.com",
|
||||
56: process.env.BSC_RPC || process.env.BSC_RPC_URL || "https://bsc-rpc.publicnode.com",
|
||||
100: process.env.GNOSIS_RPC || process.env.GNOSIS_MAINNET_RPC || "https://gnosis.publicnode.com",
|
||||
8453: process.env.BASE_RPC || process.env.BASE_MAINNET_RPC || "https://base-rpc.publicnode.com",
|
||||
42161: process.env.ARBITRUM_RPC || process.env.ARBITRUM_MAINNET_RPC || "https://arbitrum-one-rpc.publicnode.com",
|
||||
42220: process.env.CELO_RPC || process.env.CELO_MAINNET_RPC || "https://celo-rpc.publicnode.com",
|
||||
43114: process.env.AVALANCHE_RPC || process.env.AVALANCHE_RPC_URL || "https://avalanche-c-chain-rpc.publicnode.com",
|
||||
};
|
||||
|
||||
const erc20Abi = [
|
||||
"function balanceOf(address) view returns (uint256)",
|
||||
"function decimals() view returns (uint8)",
|
||||
];
|
||||
const dvmAbi = [
|
||||
"function totalSupply() view returns (uint256)",
|
||||
"function getVaultReserve() view returns (uint256,uint256)",
|
||||
"function _BASE_TOKEN_() view returns (address)",
|
||||
"function _QUOTE_TOKEN_() view returns (address)",
|
||||
"function buyShares(address) returns (uint256,uint256,uint256)",
|
||||
];
|
||||
|
||||
function operatorAddress() {
|
||||
if (process.env.DEPLOYER_ADDRESS && ethers.isAddress(process.env.DEPLOYER_ADDRESS)) return ethers.getAddress(process.env.DEPLOYER_ADDRESS);
|
||||
if (process.env.SIGNER_ADDRESS && ethers.isAddress(process.env.SIGNER_ADDRESS)) return ethers.getAddress(process.env.SIGNER_ADDRESS);
|
||||
if (process.env.PRIVATE_KEY) return new ethers.Wallet(process.env.PRIVATE_KEY).address;
|
||||
throw new Error("Set DEPLOYER_ADDRESS, SIGNER_ADDRESS, or PRIVATE_KEY for DODO funding preflight.");
|
||||
}
|
||||
|
||||
function normalized(address) {
|
||||
return ethers.getAddress(String(address).toLowerCase());
|
||||
}
|
||||
|
||||
function seedAmount(decimals) {
|
||||
return 10n ** BigInt(Math.min(Number(decimals ?? 18), 6));
|
||||
}
|
||||
|
||||
async function callOrError(fn) {
|
||||
try {
|
||||
return { ok: true, value: await fn() };
|
||||
} catch (error) {
|
||||
return { ok: false, error: error.shortMessage || error.message };
|
||||
}
|
||||
}
|
||||
|
||||
const matrix = JSON.parse(readFileSync(matrixPath, "utf8"));
|
||||
const operator = operatorAddress();
|
||||
const rows = matrix.rows.filter((row) => row.requiredForSpend === true && row.protocol === "dodo_pmm" && row.status === "created");
|
||||
const results = [];
|
||||
|
||||
for (const row of rows) {
|
||||
const result = {
|
||||
poolId: row.poolId,
|
||||
chainId: row.chainId,
|
||||
network: row.network,
|
||||
poolAddress: row.poolAddress,
|
||||
operator,
|
||||
baseToken: row.baseToken,
|
||||
quoteToken: row.quoteToken,
|
||||
status: "blocked",
|
||||
fundingPlan: null,
|
||||
blockers: [],
|
||||
};
|
||||
|
||||
const rpcUrl = rpcByChain[row.chainId];
|
||||
if (!rpcUrl) result.blockers.push("missing_rpc");
|
||||
if (!row.poolAddress) result.blockers.push("missing_pool_address");
|
||||
if (!row.baseToken?.address) result.blockers.push(`missing_base_address:${row.baseToken?.symbol || "unknown"}`);
|
||||
if (!row.quoteToken?.address) result.blockers.push(`missing_quote_address:${row.quoteToken?.symbol || "unknown"}`);
|
||||
if (result.blockers.length) {
|
||||
results.push(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl, row.chainId, { staticNetwork: true });
|
||||
const base = new ethers.Contract(normalized(row.baseToken.address), erc20Abi, provider);
|
||||
const quote = new ethers.Contract(normalized(row.quoteToken.address), erc20Abi, provider);
|
||||
const pool = new ethers.Contract(normalized(row.poolAddress), dvmAbi, provider);
|
||||
|
||||
const [baseDecimals, quoteDecimals, baseBalance, quoteBalance, totalSupply, reserves, actualBase, actualQuote, dryRunBuyShares] = await Promise.all([
|
||||
callOrError(() => base.decimals()),
|
||||
callOrError(() => quote.decimals()),
|
||||
callOrError(() => base.balanceOf(operator)),
|
||||
callOrError(() => quote.balanceOf(operator)),
|
||||
callOrError(() => pool.totalSupply()),
|
||||
callOrError(() => pool.getVaultReserve()),
|
||||
callOrError(() => pool._BASE_TOKEN_()),
|
||||
callOrError(() => pool._QUOTE_TOKEN_()),
|
||||
callOrError(() => pool.buyShares.staticCall(operator)),
|
||||
]);
|
||||
|
||||
if (!baseDecimals.ok) result.blockers.push(`base_decimals:${baseDecimals.error}`);
|
||||
if (!quoteDecimals.ok) result.blockers.push(`quote_decimals:${quoteDecimals.error}`);
|
||||
if (!baseBalance.ok) result.blockers.push(`base_balance:${baseBalance.error}`);
|
||||
if (!quoteBalance.ok) result.blockers.push(`quote_balance:${quoteBalance.error}`);
|
||||
if (!totalSupply.ok) result.blockers.push(`dvm_total_supply:${totalSupply.error}`);
|
||||
if (!reserves.ok) result.blockers.push(`dvm_reserves:${reserves.error}`);
|
||||
if (!actualBase.ok || normalized(actualBase.value) !== normalized(row.baseToken.address)) result.blockers.push("dvm_base_token_mismatch");
|
||||
if (!actualQuote.ok || normalized(actualQuote.value) !== normalized(row.quoteToken.address)) result.blockers.push("dvm_quote_token_mismatch");
|
||||
|
||||
const baseSeed = baseDecimals.ok ? seedAmount(baseDecimals.value) : 0n;
|
||||
const quoteSeed = quoteDecimals.ok ? seedAmount(quoteDecimals.value) : 0n;
|
||||
if (baseBalance.ok && baseBalance.value < baseSeed) result.blockers.push(`insufficient_operator_base:${row.baseToken.symbol}`);
|
||||
if (quoteBalance.ok && quoteBalance.value < quoteSeed) result.blockers.push(`insufficient_operator_quote:${row.quoteToken.symbol}`);
|
||||
|
||||
result.fundingPlan = {
|
||||
method: "transfer_base_and_quote_then_buyShares",
|
||||
baseSeedRaw: baseSeed.toString(),
|
||||
quoteSeedRaw: quoteSeed.toString(),
|
||||
operatorBaseRaw: baseBalance.ok ? baseBalance.value.toString() : null,
|
||||
operatorQuoteRaw: quoteBalance.ok ? quoteBalance.value.toString() : null,
|
||||
totalSupplyRaw: totalSupply.ok ? totalSupply.value.toString() : null,
|
||||
reserveBaseRaw: reserves.ok ? reserves.value[0].toString() : null,
|
||||
reserveQuoteRaw: reserves.ok ? reserves.value[1].toString() : null,
|
||||
buySharesBeforeTransferStaticCall: dryRunBuyShares.ok
|
||||
? dryRunBuyShares.value.map((value) => value.toString())
|
||||
: null,
|
||||
buySharesBeforeTransferError: dryRunBuyShares.ok ? null : dryRunBuyShares.error,
|
||||
};
|
||||
result.status = result.blockers.length === 0 ? "ready" : "blocked";
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
const statusCounts = results.reduce((counts, result) => {
|
||||
counts[result.status] = (counts[result.status] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
writeFileSync(outPath, `${JSON.stringify({
|
||||
generatedAt: new Date().toISOString(),
|
||||
sourceMatrix: "config/all-mainnet-pool-creation-matrix.json",
|
||||
mode: "read_only_funding_preflight",
|
||||
operator,
|
||||
statusCounts,
|
||||
results,
|
||||
}, null, 2)}\n`);
|
||||
console.log(`[OK] ALL Mainnet DODO funding preflight written: ${statusCounts.ready || 0} ready, ${statusCounts.blocked || 0} blocked.`);
|
||||
73
scripts/status/promote-all-mainnet-production-ready.mjs
Normal file
73
scripts/status/promote-all-mainnet-production-ready.mjs
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Promote already-proven ALL Mainnet required rows to production.
|
||||
*
|
||||
* This script does not execute transactions. It only advances rows that already
|
||||
* have a pool address, live nonzero reserves, complete vault assignments, public
|
||||
* routing enabled, and recorded canary evidence.
|
||||
*/
|
||||
|
||||
import { 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 dryRun = process.argv.includes("--dry-run");
|
||||
|
||||
function eligible(row) {
|
||||
const reserve = row.reserveEvidence || {};
|
||||
return (
|
||||
row.requiredForSpend === true &&
|
||||
row.status === "canary_passed" &&
|
||||
row.publicRoutingEnabled === true &&
|
||||
Boolean(row.poolAddress) &&
|
||||
Boolean(row.canaryEvidence) &&
|
||||
(row.missingRequiredVaultRoles || []).length === 0 &&
|
||||
row.vaultAssignmentStatus === "ready" &&
|
||||
reserve.poolHasCode === true &&
|
||||
reserve.liveReadStatus === "nonzero_base_and_quote" &&
|
||||
BigInt(reserve.baseBalanceRaw || "0") > 0n &&
|
||||
BigInt(reserve.quoteBalanceRaw || "0") > 0n
|
||||
);
|
||||
}
|
||||
|
||||
const matrix = JSON.parse(readFileSync(matrixPath, "utf8"));
|
||||
const promoted = [];
|
||||
const generatedAt = new Date().toISOString();
|
||||
|
||||
for (const row of matrix.rows) {
|
||||
if (!eligible(row)) continue;
|
||||
row.status = "production";
|
||||
row.productionEvidence = {
|
||||
generatedAt,
|
||||
basis: [
|
||||
"requiredForSpend=true",
|
||||
"publicRoutingEnabled=true",
|
||||
"poolAddress present",
|
||||
"vaultAssignmentStatus=ready",
|
||||
"reserveEvidence.liveReadStatus=nonzero_base_and_quote",
|
||||
"canaryEvidence present",
|
||||
],
|
||||
};
|
||||
if (!row.notes.includes("Promoted to production after reserve and canary evidence checks.")) {
|
||||
row.notes.push("Promoted to production after reserve and canary evidence checks.");
|
||||
}
|
||||
promoted.push(row.poolId);
|
||||
}
|
||||
|
||||
matrix.generatedAt = generatedAt;
|
||||
matrix.statusCounts = matrix.rows.reduce((counts, row) => {
|
||||
counts[row.status] = (counts[row.status] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
matrix.protocolCounts = matrix.rows.reduce((counts, row) => {
|
||||
counts[row.protocol] = (counts[row.protocol] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
|
||||
if (!dryRun) {
|
||||
writeFileSync(matrixPath, `${JSON.stringify(matrix, null, 2)}\n`);
|
||||
}
|
||||
|
||||
console.log(`[OK] ALL Mainnet production promotion ${dryRun ? "dry run" : "complete"}: ${promoted.length} row(s) promoted.`);
|
||||
for (const poolId of promoted) console.log(` - ${poolId}`);
|
||||
Reference in New Issue
Block a user