- Submodule pins: dbis_core, cross-chain-pmm-lps, mcp-proxmox (local, push may be pending), metamask-integration, smom-dbis-138 - Atomic swap + cross-chain-pmm-lops-publish, deploy-portal workflow, phoenix deploy-targets, routing/aggregator matrices - Docs, token-lists, forge proxy, phoenix API, runbooks, verify scripts Made-with: Cursor
453 lines
15 KiB
Python
453 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import math
|
|
import re
|
|
import subprocess
|
|
import time
|
|
from collections import deque
|
|
from dataclasses import dataclass
|
|
from decimal import Decimal, InvalidOperation, getcontext
|
|
from pathlib import Path
|
|
|
|
getcontext().prec = 50
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
DEPLOYMENT_STATUS = ROOT / "cross-chain-pmm-lps" / "config" / "deployment-status.json"
|
|
UNISWAP_DISCOVERY = ROOT / "reports" / "extraction" / "promod-uniswap-v2-live-pair-discovery-latest.json"
|
|
JSON_OUT = ROOT / "reports" / "status" / "cw-public-prices-latest.json"
|
|
DOC_OUT = ROOT / "docs" / "03-deployment" / "CW_PUBLIC_NETWORK_PRICES.md"
|
|
ROOT_ENV_PATH = ROOT / ".env"
|
|
SMOM_ENV_PATH = ROOT / "smom-dbis-138" / ".env"
|
|
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
|
|
UINT_RE = re.compile(r"\b\d+\b")
|
|
|
|
CHAIN_CONFIG = {
|
|
"1": {"rpc_keys": ["ETHEREUM_MAINNET_RPC"]},
|
|
"10": {"rpc_keys": ["OPTIMISM_RPC_URL", "OPTIMISM_MAINNET_RPC"]},
|
|
"25": {"rpc_keys": ["CRONOS_RPC_URL", "CRONOS_MAINNET_RPC"]},
|
|
"56": {"rpc_keys": ["BSC_RPC_URL", "BSC_MAINNET_RPC"]},
|
|
"100": {"rpc_keys": ["GNOSIS_RPC_URL", "GNOSIS_MAINNET_RPC", "GNOSIS_RPC"]},
|
|
"137": {"rpc_keys": ["POLYGON_MAINNET_RPC", "POLYGON_RPC_URL"]},
|
|
"1111": {"rpc_keys": ["WEMIX_RPC_URL", "WEMIX_MAINNET_RPC"]},
|
|
"8453": {"rpc_keys": ["BASE_RPC_URL", "BASE_MAINNET_RPC"]},
|
|
"42161": {"rpc_keys": ["ARBITRUM_RPC_URL", "ARBITRUM_MAINNET_RPC"]},
|
|
"42220": {"rpc_keys": ["CELO_RPC_URL", "CELO_MAINNET_RPC", "CELO_RPC"]},
|
|
"43114": {"rpc_keys": ["AVALANCHE_RPC_URL", "AVALANCHE_MAINNET_RPC"]},
|
|
}
|
|
|
|
STABLES = {"USDC": Decimal("1"), "USDT": Decimal("1")}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Edge:
|
|
src: str
|
|
dst: str
|
|
ratio: Decimal
|
|
venue: str
|
|
path_label: str
|
|
price_detail: str
|
|
liquidity_note: str
|
|
|
|
|
|
def now() -> str:
|
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
|
|
|
|
def load_json(path: Path) -> dict:
|
|
return json.loads(path.read_text())
|
|
|
|
|
|
def write_json(path: Path, payload: dict) -> 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 load_env_file(path: Path) -> dict[str, str]:
|
|
values: dict[str, str] = {}
|
|
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().strip('"').strip("'")
|
|
return values
|
|
|
|
|
|
def merged_env_values() -> dict[str, str]:
|
|
values: dict[str, str] = {}
|
|
values.update(load_env_file(ROOT_ENV_PATH))
|
|
values.update(load_env_file(SMOM_ENV_PATH))
|
|
values.update(load_env_from_shell())
|
|
return values
|
|
|
|
|
|
def load_env_from_shell() -> dict[str, str]:
|
|
loader = ROOT / "smom-dbis-138" / "scripts" / "load-env.sh"
|
|
if not loader.exists():
|
|
return {}
|
|
proc = subprocess.run(
|
|
["bash", "-lc", f"source {loader} >/dev/null 2>&1 && env"],
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=15,
|
|
check=False,
|
|
cwd=ROOT,
|
|
)
|
|
if proc.returncode != 0:
|
|
return {}
|
|
values: dict[str, str] = {}
|
|
for raw_line in proc.stdout.splitlines():
|
|
if "=" not in raw_line:
|
|
continue
|
|
key, value = raw_line.split("=", 1)
|
|
values[key.strip()] = value.strip()
|
|
return values
|
|
|
|
|
|
def resolve_env_value(key: str, env_values: dict[str, str], seen: set[str] | None = None) -> str:
|
|
if seen is None:
|
|
seen = set()
|
|
if key in seen:
|
|
return env_values.get(key, "")
|
|
seen.add(key)
|
|
value = env_values.get(key, "")
|
|
if value.startswith("${") and value.endswith("}"):
|
|
inner = value[2:-1]
|
|
target = inner.split(":-", 1)[0]
|
|
fallback = inner.split(":-", 1)[1] if ":-" in inner else ""
|
|
resolved = resolve_env_value(target, env_values, seen)
|
|
return resolved or fallback
|
|
return value.rstrip("\r\n")
|
|
|
|
|
|
def parse_uint(value: str) -> int:
|
|
cleaned = re.sub(r"\[[^\]]*\]", "", value)
|
|
matches = UINT_RE.findall(cleaned)
|
|
if matches:
|
|
return int(matches[0])
|
|
for line in value.splitlines():
|
|
token = line.strip().split(" ", 1)[0]
|
|
if token.isdigit():
|
|
return int(token)
|
|
raise ValueError(f"could not parse integer from {value!r}")
|
|
|
|
|
|
def parse_uints(value: str, count: int) -> list[int]:
|
|
cleaned = re.sub(r"\[[^\]]*\]", "", value)
|
|
matches = [int(match) for match in UINT_RE.findall(cleaned)]
|
|
if len(matches) >= count:
|
|
return matches[:count]
|
|
matches = []
|
|
for line in value.splitlines():
|
|
token = line.strip().split(" ", 1)[0]
|
|
if token.isdigit():
|
|
matches.append(int(token))
|
|
if len(matches) < count:
|
|
raise ValueError(f"expected {count} integers, got {value!r}")
|
|
return matches[:count]
|
|
|
|
|
|
def cast_call(rpc_url: str, target: str, signature: str, *args: str) -> str:
|
|
cmd = ["cast", "call", target, signature, *args, "--rpc-url", rpc_url]
|
|
proc = subprocess.run(cmd, text=True, capture_output=True, timeout=3, check=False)
|
|
if proc.returncode != 0:
|
|
stderr = proc.stderr.strip() or proc.stdout.strip() or "cast call failed"
|
|
raise RuntimeError(stderr)
|
|
return proc.stdout.strip()
|
|
|
|
|
|
def safe_decimal(value: str | int | float | Decimal | None) -> Decimal | None:
|
|
if value is None:
|
|
return None
|
|
try:
|
|
return Decimal(str(value))
|
|
except (InvalidOperation, ValueError):
|
|
return None
|
|
|
|
|
|
def format_decimal(value: Decimal | None, places: int = 8) -> str:
|
|
if value is None:
|
|
return "not found"
|
|
quant = Decimal(10) ** -places
|
|
try:
|
|
rounded = value.quantize(quant)
|
|
except InvalidOperation:
|
|
return str(value)
|
|
return format(rounded, "f")
|
|
|
|
|
|
def normalize_18(raw: int) -> Decimal:
|
|
return Decimal(raw) / (Decimal(10) ** 18)
|
|
|
|
|
|
def rpc_for_chain(chain_id: str, env_values: dict[str, str]) -> str:
|
|
if chain_id == "1":
|
|
infura_project_id = resolve_env_value("INFURA_PROJECT_ID", env_values)
|
|
if infura_project_id:
|
|
return f"https://mainnet.infura.io/v3/{infura_project_id}"
|
|
config = CHAIN_CONFIG.get(chain_id, {})
|
|
for key in config.get("rpc_keys", []):
|
|
value = resolve_env_value(key, env_values)
|
|
if value:
|
|
return value
|
|
return ""
|
|
|
|
|
|
def build_uniswap_edges(entry: dict) -> list[Edge]:
|
|
edges: list[Edge] = []
|
|
for row in entry.get("pairsChecked") or []:
|
|
if not row.get("live"):
|
|
continue
|
|
health = row.get("health") or {}
|
|
price = safe_decimal(health.get("priceQuotePerBase"))
|
|
if price is None or price <= 0:
|
|
continue
|
|
base = row["base"]
|
|
quote = row["quote"]
|
|
pair = f"{base}/{quote}"
|
|
addr = row.get("poolAddress") or ""
|
|
reserves = f"base={health.get('baseReserveUnits', '?')}, quote={health.get('quoteReserveUnits', '?')}"
|
|
liquidity_note = (
|
|
f"Uniswap V2 pair {addr}; healthy={health.get('healthy')}; "
|
|
f"depthOk={health.get('depthOk')}; parityOk={health.get('parityOk')}; {reserves}"
|
|
)
|
|
edges.append(
|
|
Edge(
|
|
src=base,
|
|
dst=quote,
|
|
ratio=price,
|
|
venue="uniswap_v2",
|
|
path_label=pair,
|
|
price_detail=f"reserve ratio from {pair}",
|
|
liquidity_note=liquidity_note,
|
|
)
|
|
)
|
|
edges.append(
|
|
Edge(
|
|
src=quote,
|
|
dst=base,
|
|
ratio=Decimal(1) / price,
|
|
venue="uniswap_v2",
|
|
path_label=pair,
|
|
price_detail=f"inverse reserve ratio from {pair}",
|
|
liquidity_note=liquidity_note,
|
|
)
|
|
)
|
|
return edges
|
|
|
|
|
|
def build_pmm_edges(chain: dict, rpc_url: str) -> tuple[list[Edge], list[dict]]:
|
|
edges: list[Edge] = []
|
|
snapshots: list[dict] = []
|
|
if not rpc_url:
|
|
return edges, snapshots
|
|
|
|
for row in chain.get("pmmPools") or []:
|
|
pool = row.get("poolAddress") or ""
|
|
base = row.get("base")
|
|
quote = row.get("quote")
|
|
if not pool or pool.lower() == ZERO_ADDRESS or not base or not quote:
|
|
continue
|
|
try:
|
|
mid_price = normalize_18(parse_uint(cast_call(rpc_url, pool, "getMidPrice()(uint256)")))
|
|
except Exception as exc:
|
|
snapshots.append(
|
|
{
|
|
"base": base,
|
|
"quote": quote,
|
|
"poolAddress": pool,
|
|
"venue": row.get("venue", "dodo_pmm"),
|
|
"error": str(exc),
|
|
}
|
|
)
|
|
continue
|
|
|
|
if mid_price <= 0:
|
|
continue
|
|
|
|
pair = f"{base}/{quote}"
|
|
liquidity_note = f"DODO PMM {pool}; midPrice={mid_price}"
|
|
edges.append(
|
|
Edge(
|
|
src=base,
|
|
dst=quote,
|
|
ratio=mid_price,
|
|
venue="dodo_pmm",
|
|
path_label=pair,
|
|
price_detail=f"PMM mid price from {pair}",
|
|
liquidity_note=liquidity_note,
|
|
)
|
|
)
|
|
edges.append(
|
|
Edge(
|
|
src=quote,
|
|
dst=base,
|
|
ratio=Decimal(1) / mid_price,
|
|
venue="dodo_pmm",
|
|
path_label=pair,
|
|
price_detail=f"inverse PMM mid price from {pair}",
|
|
liquidity_note=liquidity_note,
|
|
)
|
|
)
|
|
snapshots.append(
|
|
{
|
|
"base": base,
|
|
"quote": quote,
|
|
"poolAddress": pool,
|
|
"venue": row.get("venue", "dodo_pmm"),
|
|
"midPrice": str(mid_price),
|
|
}
|
|
)
|
|
return edges, snapshots
|
|
|
|
|
|
def best_prices_for_chain(chain: dict, edges: list[Edge]) -> dict[str, dict]:
|
|
adjacency: dict[str, list[Edge]] = {}
|
|
for edge in edges:
|
|
adjacency.setdefault(edge.src, []).append(edge)
|
|
|
|
best: dict[str, dict] = {}
|
|
queue: deque[tuple[str, Decimal, list[str], list[str], list[str], int]] = deque()
|
|
|
|
for stable, price in STABLES.items():
|
|
best[stable] = {
|
|
"price": price,
|
|
"steps": [],
|
|
"venues": [],
|
|
"notes": [f"{stable} anchored at 1 USD"],
|
|
"hops": 0,
|
|
}
|
|
queue.append((stable, price, [], [], [f"{stable} anchored at 1 USD"], 0))
|
|
|
|
while queue:
|
|
token, usd_price, steps, venues, notes, hops = queue.popleft()
|
|
for edge in adjacency.get(token, []):
|
|
next_price = usd_price / edge.ratio
|
|
next_steps = steps + [edge.path_label]
|
|
next_venues = venues + [edge.venue]
|
|
next_notes = notes + [edge.liquidity_note]
|
|
next_hops = hops + 1
|
|
current = best.get(edge.dst)
|
|
should_replace = current is None or next_hops < current["hops"]
|
|
if not should_replace and current is not None and next_hops == current["hops"]:
|
|
current_venue_score = 0 if "dodo_pmm" in current["venues"] else 1
|
|
next_venue_score = 0 if "dodo_pmm" in next_venues else 1
|
|
should_replace = next_venue_score < current_venue_score
|
|
if should_replace:
|
|
best[edge.dst] = {
|
|
"price": next_price,
|
|
"steps": next_steps,
|
|
"venues": next_venues,
|
|
"notes": next_notes,
|
|
"hops": next_hops,
|
|
}
|
|
queue.append((edge.dst, next_price, next_steps, next_venues, next_notes, next_hops))
|
|
|
|
out: dict[str, dict] = {}
|
|
for symbol in sorted((chain.get("cwTokens") or {}).keys()):
|
|
resolution = best.get(symbol)
|
|
if resolution is None:
|
|
out[symbol] = {
|
|
"priceUsd": None,
|
|
"derivedFrom": "not found",
|
|
"sourceType": "not_found",
|
|
"notes": ["No live direct or bridged price path was found from USDC/USDT anchors."],
|
|
}
|
|
continue
|
|
out[symbol] = {
|
|
"priceUsd": str(resolution["price"]),
|
|
"derivedFrom": " -> ".join(resolution["steps"]) if resolution["steps"] else "stable anchor",
|
|
"sourceType": resolution["venues"][0] if resolution["venues"] else "stable_anchor",
|
|
"notes": resolution["notes"],
|
|
}
|
|
return out
|
|
|
|
|
|
def build_report() -> dict:
|
|
env_values = merged_env_values()
|
|
deployment = load_json(DEPLOYMENT_STATUS)
|
|
discovery = load_json(UNISWAP_DISCOVERY)
|
|
discovery_by_chain = {str(entry["chain_id"]): entry for entry in discovery.get("entries") or []}
|
|
|
|
chains_out: list[dict] = []
|
|
for chain_id, chain in sorted((deployment.get("chains") or {}).items(), key=lambda item: int(item[0])):
|
|
if int(chain_id) == 138:
|
|
continue
|
|
rpc_url = rpc_for_chain(chain_id, env_values)
|
|
uniswap_edges = build_uniswap_edges(discovery_by_chain.get(chain_id, {}))
|
|
pmm_edges, pmm_snapshots = build_pmm_edges(chain, rpc_url)
|
|
price_rows = best_prices_for_chain(chain, uniswap_edges + pmm_edges)
|
|
chains_out.append(
|
|
{
|
|
"chainId": int(chain_id),
|
|
"network": chain.get("name", ""),
|
|
"activationState": chain.get("activationState", ""),
|
|
"rpcConfigured": bool(rpc_url),
|
|
"prices": price_rows,
|
|
"pmmSnapshots": pmm_snapshots,
|
|
}
|
|
)
|
|
|
|
return {
|
|
"generatedAt": now(),
|
|
"inputs": {
|
|
"deploymentStatus": str(DEPLOYMENT_STATUS),
|
|
"uniswapDiscovery": str(UNISWAP_DISCOVERY),
|
|
},
|
|
"chains": chains_out,
|
|
}
|
|
|
|
|
|
def render_markdown(payload: dict) -> str:
|
|
lines = [
|
|
"# cW Public Network Prices",
|
|
"",
|
|
f"- Generated: `{payload['generatedAt']}`",
|
|
f"- Deployment inventory: `{payload['inputs']['deploymentStatus']}`",
|
|
f"- Uniswap discovery snapshot: `{payload['inputs']['uniswapDiscovery']}`",
|
|
"- Price convention: USD per 1 token.",
|
|
"- `not found` means the generator could not reach the token from a live USDC/USDT anchor using the current public-pair snapshot plus live PMM mid-price reads.",
|
|
"",
|
|
"| Chain | Token | Price (USD) | Derived From | Source | Notes |",
|
|
"|---|---|---:|---|---|---|",
|
|
]
|
|
|
|
for chain in payload["chains"]:
|
|
first_row = True
|
|
prices = chain["prices"]
|
|
for symbol in sorted(prices.keys()):
|
|
row = prices[symbol]
|
|
chain_cell = f"`{chain['chainId']}` {chain['network']}" if first_row else ""
|
|
first_row = False
|
|
notes = "; ".join(row["notes"][:2])
|
|
lines.append(
|
|
f"| {chain_cell} | `{symbol}` | `{format_decimal(safe_decimal(row['priceUsd']))}` | "
|
|
f"`{row['derivedFrom']}` | `{row['sourceType']}` | {notes} |"
|
|
)
|
|
if prices:
|
|
lines.append(
|
|
f"| | | | | | Activation state: `{chain['activationState'] or 'active'}`; RPC configured: `{chain['rpcConfigured']}` |"
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> None:
|
|
payload = build_report()
|
|
write_json(JSON_OUT, payload)
|
|
write_text(DOC_OUT, render_markdown(payload))
|
|
print(JSON_OUT)
|
|
print(DOC_OUT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|