247 lines
10 KiB
Python
247 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import time
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
PHASE_ORDER = ROOT / "reports" / "extraction" / "promod-uniswap-v2-phase-order-latest.json"
|
|
DEPLOYMENT_STATUS = ROOT / "cross-chain-pmm-lps" / "config" / "deployment-status.json"
|
|
REPORT = ROOT / "reports" / "extraction" / "promod-uniswap-v2-phase2-operator-sequence-latest.json"
|
|
DOC = ROOT / "docs" / "03-deployment" / "PROMOD_UNISWAP_V2_PHASE2_OPERATOR_SEQUENCE.md"
|
|
|
|
RPC_ENV_KEYS = {
|
|
1: ["ETHEREUM_MAINNET_RPC"],
|
|
10: ["OPTIMISM_RPC_URL", "OPTIMISM_MAINNET_RPC"],
|
|
25: ["CRONOS_RPC_URL", "CRONOS_MAINNET_RPC"],
|
|
56: ["BSC_RPC_URL", "BSC_MAINNET_RPC"],
|
|
100: ["GNOSIS_RPC_URL", "GNOSIS_MAINNET_RPC", "GNOSIS_RPC"],
|
|
137: ["POLYGON_MAINNET_RPC", "POLYGON_RPC_URL"],
|
|
8453: ["BASE_RPC_URL", "BASE_MAINNET_RPC"],
|
|
42161: ["ARBITRUM_RPC_URL", "ARBITRUM_MAINNET_RPC"],
|
|
42220: ["CELO_RPC_URL", "CELO_MAINNET_RPC", "CELO_RPC"],
|
|
43114: ["AVALANCHE_RPC_URL", "AVALANCHE_MAINNET_RPC"],
|
|
}
|
|
|
|
|
|
def now() -> str:
|
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
|
|
|
|
def load(path: Path):
|
|
return json.loads(path.read_text())
|
|
|
|
|
|
def write_json(path: Path, payload) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2) + "\n")
|
|
|
|
|
|
def write_text(path: Path, text: str) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(text.rstrip() + "\n")
|
|
|
|
|
|
def chain_entry(status: dict, chain_id: int) -> dict:
|
|
return status["chains"].get(str(chain_id)) or status["chains"].get(chain_id) or {}
|
|
|
|
|
|
def resolve_token(chain_status: dict, symbol: str) -> str:
|
|
cw = chain_status.get("cwTokens", {})
|
|
value = cw.get(symbol, "")
|
|
if isinstance(value, dict):
|
|
return value.get("address") or value.get("token") or ""
|
|
return value or ""
|
|
|
|
|
|
def sanitize(text: str) -> str:
|
|
return re.sub(r"[^A-Z0-9]+", "_", text.upper()).strip("_")
|
|
|
|
|
|
def amount_envs(chain_id: int, pair: str) -> tuple[str, str]:
|
|
token_a, token_b = pair.split("/")
|
|
prefix = f"PHASE2_{chain_id}_{sanitize(token_a)}_{sanitize(token_b)}"
|
|
return f"{prefix}_A_RAW", f"{prefix}_B_RAW"
|
|
|
|
|
|
def pair_commands(chain_id: int, pair: str, token_a_addr: str, token_b_addr: str, rpc_key: str) -> dict:
|
|
factory_var = f"CHAIN_{chain_id}_UNISWAP_V2_FACTORY"
|
|
router_var = f"CHAIN_{chain_id}_UNISWAP_V2_ROUTER"
|
|
amount_a_env, amount_b_env = amount_envs(chain_id, pair)
|
|
token_a, token_b = pair.split("/")
|
|
|
|
prelude = [
|
|
"source smom-dbis-138/scripts/load-env.sh >/dev/null",
|
|
f'export RPC_URL="${{{rpc_key}}}"',
|
|
f'export FACTORY="${{{factory_var}}}"',
|
|
f'export ROUTER="${{{router_var}}}"',
|
|
f'export TOKEN_A="{token_a_addr}"',
|
|
f'export TOKEN_B="{token_b_addr}"',
|
|
f'export AMOUNT_A_RAW="${{{amount_a_env}:-}}"',
|
|
f'export AMOUNT_B_RAW="${{{amount_b_env}:-}}"',
|
|
'export SIGNER="$(cast wallet address --private-key "$PRIVATE_KEY")"',
|
|
'export DEADLINE="$(( $(date +%s) + 3600 ))"',
|
|
'test -n "$AMOUNT_A_RAW" && test -n "$AMOUNT_B_RAW"',
|
|
]
|
|
probe = 'cast call "$FACTORY" \'getPair(address,address)(address)\' "$TOKEN_A" "$TOKEN_B" --rpc-url "$RPC_URL"'
|
|
create = "\n".join(
|
|
prelude
|
|
+ [
|
|
'PAIR="$(cast call "$FACTORY" \'getPair(address,address)(address)\' "$TOKEN_A" "$TOKEN_B" --rpc-url "$RPC_URL")"',
|
|
'if [[ "$PAIR" == "0x0000000000000000000000000000000000000000" ]]; then',
|
|
' cast send "$FACTORY" \'createPair(address,address)(address)\' "$TOKEN_A" "$TOKEN_B" \\',
|
|
' --private-key "$PRIVATE_KEY" --rpc-url "$RPC_URL"',
|
|
"fi",
|
|
]
|
|
)
|
|
deploy = "\n".join(
|
|
prelude
|
|
+ [
|
|
'cast send "$TOKEN_A" \'approve(address,uint256)(bool)\' "$ROUTER" "$AMOUNT_A_RAW" \\',
|
|
' --private-key "$PRIVATE_KEY" --rpc-url "$RPC_URL"',
|
|
"",
|
|
'cast send "$TOKEN_B" \'approve(address,uint256)(bool)\' "$ROUTER" "$AMOUNT_B_RAW" \\',
|
|
' --private-key "$PRIVATE_KEY" --rpc-url "$RPC_URL"',
|
|
"",
|
|
'cast send "$ROUTER" \\',
|
|
' \'addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256)\' \\',
|
|
' "$TOKEN_A" "$TOKEN_B" "$AMOUNT_A_RAW" "$AMOUNT_B_RAW" "$AMOUNT_A_RAW" "$AMOUNT_B_RAW" "$SIGNER" "$DEADLINE" \\',
|
|
' --private-key "$PRIVATE_KEY" --rpc-url "$RPC_URL"',
|
|
]
|
|
)
|
|
return {
|
|
"pair": pair,
|
|
"token_a": token_a,
|
|
"token_b": token_b,
|
|
"token_a_address": token_a_addr,
|
|
"token_b_address": token_b_addr,
|
|
"amount_env_a": amount_a_env,
|
|
"amount_env_b": amount_b_env,
|
|
"probe_block": probe,
|
|
"create_if_absent_block": create,
|
|
"deploy_block": deploy,
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
phase = load(PHASE_ORDER)
|
|
status = load(DEPLOYMENT_STATUS)
|
|
|
|
entries = []
|
|
for entry in phase["entries"]:
|
|
chain_id = entry["chain_id"]
|
|
chain_status = chain_entry(status, chain_id)
|
|
rpc_keys = RPC_ENV_KEYS.get(chain_id, [])
|
|
phase_2_pairs = entry.get("phase_2_full_cw_wrapped_mesh", [])
|
|
pair_entries = []
|
|
for pair in phase_2_pairs:
|
|
token_a, token_b = pair.split("/")
|
|
token_a_addr = resolve_token(chain_status, token_a)
|
|
token_b_addr = resolve_token(chain_status, token_b)
|
|
if not token_a_addr or not token_b_addr:
|
|
continue
|
|
pair_entries.append(
|
|
pair_commands(chain_id, pair, token_a_addr, token_b_addr, rpc_keys[0] if rpc_keys else "RPC_URL")
|
|
)
|
|
|
|
entries.append(
|
|
{
|
|
"chain_id": chain_id,
|
|
"network": entry["network"],
|
|
"tier": entry["tier"],
|
|
"phase_1_core_rail": entry["phase_1_core_rail"],
|
|
"phase_2_pair_count": len(pair_entries),
|
|
"phase_2_other_gru_v2_cw_tokens": entry.get("phase_2_other_gru_v2_cw_tokens", []),
|
|
"rpc_env_keys": rpc_keys,
|
|
"required_uniswap_v2_env_vars": [
|
|
f"CHAIN_{chain_id}_UNISWAP_V2_FACTORY",
|
|
f"CHAIN_{chain_id}_UNISWAP_V2_ROUTER",
|
|
f"CHAIN_{chain_id}_UNISWAP_V2_START_BLOCK",
|
|
],
|
|
"phase_2_pairs": pair_entries,
|
|
"post_pair_commands": [
|
|
"bash scripts/verify/build-promod-uniswap-v2-live-pair-discovery.sh",
|
|
"python3 scripts/lib/promod_uniswap_v2_live_pair_discovery.py --write-discovered",
|
|
"node cross-chain-pmm-lps/scripts/validate-deployment-status.cjs cross-chain-pmm-lps/config/deployment-status.json",
|
|
"bash scripts/verify/build-promod-uniswap-v2-promotion-gates.sh",
|
|
],
|
|
}
|
|
)
|
|
|
|
payload = {
|
|
"generated_at": now(),
|
|
"program_name": phase["program_name"],
|
|
"purpose": "Exact phase-2 live operator sequence for the full cW* wrapped mesh rollout, using per-pair amount envs rather than fixed sizing assumptions.",
|
|
"mainnet_funding_posture": phase["mainnet_funding_posture"],
|
|
"entries": entries,
|
|
"source_artifacts": [
|
|
"reports/extraction/promod-uniswap-v2-phase-order-latest.json",
|
|
"cross-chain-pmm-lps/config/deployment-status.json",
|
|
],
|
|
}
|
|
write_json(REPORT, payload)
|
|
|
|
lines = [
|
|
"# Mr. Promod Uniswap V2 Phase 2 Operator Sequence",
|
|
"",
|
|
f"- Generated: `{payload['generated_at']}`",
|
|
f"- Program: {payload['program_name']}",
|
|
f"- Mainnet funding posture: `{payload['mainnet_funding_posture']['mode']}` via `{', '.join(payload['mainnet_funding_posture']['required_deployer_assets'])}`",
|
|
"- Purpose: exact phase-2 live operator sequence for the full `cW*` wrapped mesh rollout.",
|
|
"- Funding rule: each pair uses explicit raw amount env vars because BTC, gold, FX, and fiat rails do not share a safe default seed size.",
|
|
"",
|
|
"| Chain | Network | Phase 2 Pair Count | RPC Keys | Required Env |",
|
|
"|---|---|---:|---|---|",
|
|
]
|
|
|
|
for entry in entries:
|
|
lines.append(
|
|
f"| `{entry['chain_id']}` | {entry['network']} | `{entry['phase_2_pair_count']}` | "
|
|
f"{', '.join(f'`{x}`' for x in entry['rpc_env_keys'])} | "
|
|
f"{', '.join(f'`{x}`' for x in entry['required_uniswap_v2_env_vars'])} |"
|
|
)
|
|
|
|
lines.extend(["", "## Per-Chain Sequence", ""])
|
|
for entry in entries:
|
|
lines.append(f"### Chain `{entry['chain_id']}` — {entry['network']}")
|
|
lines.append("")
|
|
lines.append(f"- Phase 1 prerequisite: `{entry['phase_1_core_rail']}`")
|
|
lines.append(f"- Phase 2 cW* count: `{entry['phase_2_pair_count']}`")
|
|
lines.append(f"- Other phase-2 cW* tokens: {', '.join(f'`{x}`' for x in entry['phase_2_other_gru_v2_cw_tokens']) or 'none'}")
|
|
lines.append("")
|
|
for pair_entry in entry["phase_2_pairs"]:
|
|
lines.append(f"#### `{pair_entry['pair']}`")
|
|
lines.append("")
|
|
lines.append(
|
|
f"- Amount envs: `{pair_entry['amount_env_a']}`, `{pair_entry['amount_env_b']}`"
|
|
)
|
|
lines.append(
|
|
f"- Token addresses: `{pair_entry['token_a']}={pair_entry['token_a_address']}`, `{pair_entry['token_b']}={pair_entry['token_b_address']}`"
|
|
)
|
|
lines.append("Probe:")
|
|
lines.append("```bash")
|
|
lines.append(pair_entry["probe_block"])
|
|
lines.append("```")
|
|
lines.append("Create if absent:")
|
|
lines.append("```bash")
|
|
lines.append(pair_entry["create_if_absent_block"])
|
|
lines.append("```")
|
|
lines.append("Deploy:")
|
|
lines.append("```bash")
|
|
lines.append(pair_entry["deploy_block"])
|
|
lines.append("```")
|
|
lines.append("")
|
|
lines.append("Post-pair refresh commands:")
|
|
for cmd in entry["post_pair_commands"]:
|
|
lines.append(f"- `{cmd}`")
|
|
lines.append("")
|
|
|
|
write_text(DOC, "\n".join(lines))
|
|
print(REPORT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|