252 lines
11 KiB
Python
252 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
from pathlib import Path
|
|
import json
|
|
import time
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
PROMOD_REPORT = ROOT / "reports" / "extraction" / "promod-uniswap-v2-liquidity-program-latest.json"
|
|
DEPLOYMENT_STATUS = ROOT / "cross-chain-pmm-lps" / "config" / "deployment-status.json"
|
|
ENV_PATH = ROOT / "smom-dbis-138" / ".env"
|
|
LIVE_DISCOVERY = ROOT / "reports" / "extraction" / "promod-uniswap-v2-live-pair-discovery-latest.json"
|
|
REPORT = ROOT / "reports" / "extraction" / "promod-uniswap-v2-promotion-gates-latest.json"
|
|
DOC = ROOT / "docs" / "03-deployment" / "PROMOD_UNISWAP_V2_PROMOTION_GATES.md"
|
|
|
|
UNIV2_CODE_SUPPORTED = {1, 10, 25, 56, 100, 137, 138, 1111, 8453, 42161, 42220, 43114, 651940}
|
|
|
|
|
|
def load(path: Path):
|
|
return json.loads(path.read_text())
|
|
|
|
|
|
def load_env_file(path: Path):
|
|
values = {}
|
|
if not path.exists():
|
|
return values
|
|
for raw_line in path.read_text().splitlines():
|
|
line = raw_line.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
values[key.strip()] = value.strip()
|
|
return values
|
|
|
|
|
|
def write_json(path: Path, payload):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2) + "\n")
|
|
|
|
|
|
def write_text(path: Path, text: str):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(text.rstrip() + "\n")
|
|
|
|
|
|
def now():
|
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
|
|
|
|
def build_env_vars(chain_id: int):
|
|
prefix = f"CHAIN_{chain_id}_UNISWAP_V2"
|
|
return [
|
|
{
|
|
"name": f"{prefix}_FACTORY",
|
|
"purpose": "Factory address used by token-aggregation to discover PairCreated events and pair addresses.",
|
|
},
|
|
{
|
|
"name": f"{prefix}_ROUTER",
|
|
"purpose": "Router address used for quote execution assumptions and operator documentation.",
|
|
},
|
|
{
|
|
"name": f"{prefix}_START_BLOCK",
|
|
"purpose": "Start block for indexer backfill so the first live pool is visible without scanning the full chain.",
|
|
},
|
|
]
|
|
|
|
|
|
def build_required_registry_records(chain_id: int, hub_stable: str, documented_cw_tokens: list[str], all_pairs: list[str]):
|
|
required_cw_tokens = sorted({pair.split("/")[0] for pair in all_pairs if pair.split("/")[0].startswith("cW")})
|
|
records = []
|
|
for symbol in required_cw_tokens:
|
|
records.append(
|
|
{
|
|
"path": f"chains[{chain_id}].cwTokens.{symbol}",
|
|
"purpose": f"Document the deployed {symbol} address on this chain.",
|
|
}
|
|
)
|
|
records.append(
|
|
{
|
|
"path": f"chains[{chain_id}].anchorAddresses.{hub_stable}",
|
|
"purpose": f"Document the hub stable address used by settlement-phase pairs on this chain.",
|
|
}
|
|
)
|
|
records.append(
|
|
{
|
|
"path": f"chains[{chain_id}].pmmPools[]",
|
|
"purpose": "Record each promoted Uniswap V2 pair as a source-of-truth pool entry using base, quote, and poolAddress until a dedicated uniswapV2Pools registry is introduced.",
|
|
"required_fields": ["base", "quote", "poolAddress"],
|
|
}
|
|
)
|
|
records.append(
|
|
{
|
|
"path": f"ai-mcp-pmm-controller/config/allowlist-{chain_id}.json",
|
|
"purpose": "Expose the promoted pool to MCP/API visibility after it is written to deployment-status.json.",
|
|
}
|
|
)
|
|
return records
|
|
|
|
|
|
def main():
|
|
promod = load(PROMOD_REPORT)
|
|
deployment = load(DEPLOYMENT_STATUS)
|
|
env_values = load_env_file(ENV_PATH)
|
|
live_discovery = load(LIVE_DISCOVERY) if LIVE_DISCOVERY.exists() else {"entries": []}
|
|
discovery_by_chain = {str(entry["chain_id"]): entry for entry in live_discovery.get("entries", [])}
|
|
|
|
entries = []
|
|
blocked_chain_count = 0
|
|
|
|
for entry in promod["entries"]:
|
|
chain_id = int(entry["chain_id"])
|
|
chain_status = deployment["chains"].get(str(chain_id), {})
|
|
all_pairs = entry["wrapped_depth_phase_pairs"] + entry["settlement_phase_pairs"]
|
|
code_support = chain_id in UNIV2_CODE_SUPPORTED
|
|
env_var_names = [item["name"] for item in build_env_vars(chain_id)]
|
|
env_present = all(env_values.get(name) for name in env_var_names)
|
|
discovered_live = any(row.get("live") for row in discovery_by_chain.get(str(chain_id), {}).get("pairsChecked", []))
|
|
recorded_rows = chain_status.get("uniswapV2Pools", [])
|
|
recorded_live = any(
|
|
row.get("base") in {pair.split("/")[0] for pair in all_pairs}
|
|
and row.get("quote") in {pair.split("/")[1] for pair in all_pairs}
|
|
for row in recorded_rows
|
|
)
|
|
blockers = []
|
|
if not code_support:
|
|
blockers.append(
|
|
"token-aggregation dex-factories.ts does not yet expose CHAIN_<id>_UNISWAP_V2_* wiring for this chain."
|
|
)
|
|
if not env_present:
|
|
blockers.append(
|
|
"Uniswap V2 factory/router/start-block env values are not fully documented in smom-dbis-138/.env for this chain."
|
|
)
|
|
if not discovered_live:
|
|
blockers.append("No live cW Uniswap V2-compatible pair is currently discoverable for this chain.")
|
|
if not recorded_live:
|
|
blockers.append("No live cW Uniswap V2-compatible pair is currently recorded in deployment-status.json for this chain.")
|
|
blockers.extend(
|
|
blocker
|
|
for blocker in entry.get("blockers", [])
|
|
if "Uniswap V2 factory/router addresses are not documented in-repo" not in blocker
|
|
and "New Uniswap V2 pools must be added to token-aggregation indexing and MCP/API visibility before promotion." not in blocker
|
|
)
|
|
|
|
promotion_ready = code_support and env_present and discovered_live and recorded_live
|
|
if blockers:
|
|
blocked_chain_count += 1
|
|
|
|
pool_templates = []
|
|
for pair in all_pairs:
|
|
base, quote = pair.split("/")
|
|
pool_templates.append(
|
|
{
|
|
"base": base,
|
|
"quote": quote,
|
|
"poolAddress": "0x...",
|
|
}
|
|
)
|
|
|
|
entries.append(
|
|
{
|
|
"chain_id": chain_id,
|
|
"network": entry["network"],
|
|
"tier": entry["tier"],
|
|
"hub_stable": entry["hub_stable"],
|
|
"code_support_status": "ready" if code_support else "blocked",
|
|
"env_values_present": env_present,
|
|
"exact_env_vars_to_fill": build_env_vars(chain_id),
|
|
"required_registry_records": build_required_registry_records(
|
|
chain_id,
|
|
entry["hub_stable"],
|
|
entry["documented_cw_tokens"],
|
|
all_pairs,
|
|
),
|
|
"pool_entry_templates": pool_templates,
|
|
"existing_documented_cw_tokens": entry["documented_cw_tokens"],
|
|
"existing_anchor_addresses": chain_status.get("anchorAddresses", {}),
|
|
"promotion_gate": {
|
|
"factory_router_env_present": env_present,
|
|
"indexer_support_present": code_support,
|
|
"pool_registry_recorded": recorded_live,
|
|
"mcp_or_api_visibility_added": env_present,
|
|
"promotion_ready": promotion_ready,
|
|
},
|
|
"blocking_items": blockers,
|
|
}
|
|
)
|
|
|
|
payload = {
|
|
"generated_at": now(),
|
|
"program_name": promod["program_name"],
|
|
"purpose": "Exact per-chain env vars and registry records required before any Mr. Promod Uniswap V2 pool can be promoted as live.",
|
|
"mainnet_funding_posture": promod["mainnet_funding_posture"],
|
|
"summary": {
|
|
"chain_count": len(entries),
|
|
"blocked_chain_count": blocked_chain_count,
|
|
"note": "All target public chains remain blocked for live promotion until Uniswap V2 env wiring, registry entries, and pool/indexer visibility are complete.",
|
|
},
|
|
"entries": entries,
|
|
"source_artifacts": [
|
|
"reports/extraction/promod-uniswap-v2-liquidity-program-latest.json",
|
|
"reports/extraction/promod-uniswap-v2-live-pair-discovery-latest.json",
|
|
"cross-chain-pmm-lps/config/deployment-status.json",
|
|
"smom-dbis-138/services/token-aggregation/src/config/dex-factories.ts",
|
|
],
|
|
}
|
|
|
|
write_json(REPORT, payload)
|
|
|
|
lines = [
|
|
"# Mr. Promod Uniswap V2 Promotion Gates",
|
|
"",
|
|
f"- Generated: `{payload['generated_at']}`",
|
|
"- Purpose: exact per-chain env vars and registry records required before the first live Uniswap V2 pool can be promoted.",
|
|
f"- Mainnet funding posture: `{payload['mainnet_funding_posture']['mode']}` via `{', '.join(payload['mainnet_funding_posture']['required_deployer_assets'])}`",
|
|
f"- Chains in scope: `{payload['summary']['chain_count']}`",
|
|
f"- Currently blocked: `{payload['summary']['blocked_chain_count']}`",
|
|
"",
|
|
"## Global Rule",
|
|
"",
|
|
"- Promotion requires all of the following on the target chain: Uniswap V2 factory/router/start-block env set, indexer code support present, a live pair discoverable on-chain, and the pool address recorded in `deployment-status.json`.",
|
|
"",
|
|
"## Operator Table",
|
|
"",
|
|
"| Chain | Network | Code Support | Exact Env Vars To Fill | Registry Records To Fill |",
|
|
"|---|---|---|---|---|",
|
|
]
|
|
|
|
for entry in entries:
|
|
env_vars = ", ".join(f"`{item['name']}`" for item in entry["exact_env_vars_to_fill"])
|
|
registry = ", ".join(f"`{item['path']}`" for item in entry["required_registry_records"][:3])
|
|
lines.append(
|
|
f"| `{entry['chain_id']}` | {entry['network']} | `{entry['code_support_status']}` | {env_vars} | {registry} |"
|
|
)
|
|
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"## First Live Pool Minimum Checklist",
|
|
"",
|
|
"1. Add `CHAIN_<id>_UNISWAP_V2_FACTORY`, `CHAIN_<id>_UNISWAP_V2_ROUTER`, and `CHAIN_<id>_UNISWAP_V2_START_BLOCK` for the target chain.",
|
|
"2. Extend token-aggregation code support for that chain if `code_support_status` is `blocked`.",
|
|
"3. Create the pool on-chain and record its `base`, `quote`, and `poolAddress` in `cross-chain-pmm-lps/config/deployment-status.json`.",
|
|
"4. Rebuild live-pair discovery and promotion-gate artifacts so the new pair is visible to operator tooling.",
|
|
"5. Only then promote the pair as live in operator-facing docs or routing artifacts.",
|
|
]
|
|
)
|
|
|
|
write_text(DOC, "\n".join(lines))
|
|
print(REPORT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|