feat(cwusdc): propagation monitor hooks, CMC enrich, pnpm scripts, inventory refresh
Some checks failed
Deploy to Phoenix / deploy (push) Has been skipped
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Has been skipped
Deploy to Phoenix / cloudflare (push) Has been skipped
Deploy to Phoenix / validate (push) Failing after 1s

- package.json: cwusdc:provider-checks:with-monitor, value-propagation, fetch/enrich CMC chain1
- run-cwusdc-value-propagation.sh: strip pnpm-forwarded -- for monitor args
- enrich-cmc-chain1-report-with-live-pool-catalog.py: merge live pool catalog into CMC report
- Harden external-tracker / propagation / provider-check scripts and CMC sanity checker
- Docs: MetaMask matrix optional automation; CWUSDC non-manual tasks cross-links
- Refresh drift + live_inventory collected_at from latest cluster poll
- gitignore: rpc-502-diagnostics evidence dumps under verification-evidence

Validated: bash scripts/verify/run-all-validation.sh --skip-genesis
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-11 21:23:52 -07:00
parent 2138a347ac
commit ee0bffb3cd
13 changed files with 262 additions and 26 deletions

View File

@@ -81,7 +81,7 @@ def build_payload(
"id": "missing_external_tracker_evidence",
"type": "repo_controlled",
"status": "blocked",
"nextAction": "Run check-cwusdc-external-trackers-live.sh with JSON output.",
"nextAction": "Run check-cwusdc-external-trackers-live.sh with JSON output (use --gecko-retries for GeckoTerminal 429 backoff).",
})
if liquidity is None:
blockers.append({

View File

@@ -20,6 +20,12 @@ OFFICIAL_QUOTES = {
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "USDC",
"0xdac17f958d2ee523a2206206994597c13d831ec7": "USDT",
}
# CMC-shaped report intentionally binds DBIS compliant-face symbols to the official
# USDC/USDT contract addresses (see provider packets). Do not flag as accidental alias.
INTENTIONAL_DBIS_QUOTE_FACE = {
("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "cusdc"),
("0xdac17f958d2ee523a2206206994597c13d831ec7", "cusdt"),
}
PROMOTED = {"cWUSDC", "cWUSDT", "cWXAUC", "cWXAUT", "cWBTC", "cWETH"}
@@ -63,15 +69,17 @@ def main() -> int:
volume = dec(token.get("volume_24h"))
pairs = token.get("pairs", [])
if address in OFFICIAL_QUOTES and symbol != OFFICIAL_QUOTES[address]:
warnings.append(
{
"id": "official_quote_symbol_alias",
"symbol": symbol,
"address": address,
"severity": "warning",
"message": f"Official {OFFICIAL_QUOTES[address]} address is presented with symbol {symbol}; keep provider packets explicit about official quote vs DBIS wrapped/compliant symbols.",
}
)
face = (address, str(symbol or "").lower())
if face not in INTENTIONAL_DBIS_QUOTE_FACE:
warnings.append(
{
"id": "official_quote_symbol_alias",
"symbol": symbol,
"address": address,
"severity": "warning",
"message": f"Official {OFFICIAL_QUOTES[address]} address is presented with symbol {symbol}; keep provider packets explicit about official quote vs DBIS wrapped/compliant symbols.",
}
)
if symbol in PROMOTED:
promoted_rows.append(
{

View File

@@ -146,6 +146,24 @@ def fetch(url: str, timeout: int) -> dict[str, Any]:
}
def fetch_with_retries(url: str, timeout: int, max_attempts: int = 5) -> dict[str, Any]:
"""Retry on transient HTTP 429 / 502 / 503 (GeckoTerminal and similar APIs rate-limit)."""
base_delay_s = 2.0
last: dict[str, Any] | None = None
for attempt in range(1, max_attempts + 1):
last = fetch(url, timeout)
status = last.get("status")
if last.get("ok") or status not in (429, 502, 503):
last["fetchAttempts"] = attempt
return last
if attempt >= max_attempts:
break
time.sleep(min(base_delay_s * (2 ** (attempt - 1)), 45.0))
assert last is not None
last["fetchAttempts"] = max_attempts
return last
def json_path_present(data: Any, path: list[str]) -> bool:
cur = data
for part in path:
@@ -158,9 +176,13 @@ def json_path_present(data: Any, path: list[str]) -> bool:
return cur is not None
def evaluate(spec: dict[str, Any], timeout: int) -> dict[str, Any]:
raw = fetch(spec["url"], timeout)
def evaluate(spec: dict[str, Any], timeout: int, max_retries: int) -> dict[str, Any]:
use_retry = spec.get("kind") == "dex_index" and "geckoterminal" in spec["id"]
raw = fetch_with_retries(spec["url"], timeout, max_retries) if use_retry else fetch(spec["url"], timeout)
if not use_retry:
raw["fetchAttempts"] = 1
text = raw.pop("text")
fetch_attempts = raw.pop("fetchAttempts", 1)
evidence: dict[str, Any] = {
"id": spec["id"],
"kind": spec["kind"],
@@ -172,6 +194,7 @@ def evaluate(spec: dict[str, Any], timeout: int) -> dict[str, Any]:
"contentType": raw["contentType"],
"passed": False,
"error": raw["error"],
"fetchAttempts": fetch_attempts,
"details": [],
}
data: Any = None
@@ -219,6 +242,9 @@ def write_markdown(payload: dict[str, Any], path: Path) -> None:
]
for check in payload["checks"]:
details = "; ".join(check["details"]) or check["error"] or "-"
attempts = check.get("fetchAttempts", 1)
if attempts > 1:
details = f"{details}; httpAttempts={attempts}"
lines.append(f"| {check['id']} | `{check['passed']}` | `{check['status']}` | {check['url']} | {details} |")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + "\n")
@@ -229,10 +255,16 @@ def main() -> int:
parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON)
parser.add_argument("--md-out", type=Path, default=DEFAULT_MD)
parser.add_argument("--timeout", type=int, default=20)
parser.add_argument(
"--gecko-retries",
type=int,
default=5,
help="Max HTTP attempts for GeckoTerminal pool probes (429/502/503 backoff).",
)
parser.add_argument("--strict", action="store_true")
args = parser.parse_args()
checks = [evaluate(spec, args.timeout) for spec in URLS]
checks = [evaluate(spec, args.timeout, args.gecko_retries) for spec in URLS]
required = [c for c in checks if c["required"]]
required_passed = [c for c in required if c["passed"]]
payload = {

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Raise Mainnet CMC report pool/token liquidity_usd using live-uniswap-v2-pool-catalog.
When explorer.d-bis.org lags a token-aggregation deploy, public snapshots can show TVL 0
while config/live-uniswap-v2-pool-catalog.json already has totalLiquidityUsd. This script
aligns reports/status/token-aggregation-cmc-report-chain1-latest.json for local evidence
and sanity checks. Idempotent.
Does not change external CoinGecko/DexScreener/Etherscan APIs.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[2]
DEFAULT_REPORT = ROOT / "reports" / "status" / "token-aggregation-cmc-report-chain1-latest.json"
DEFAULT_CATALOG = (
ROOT / "smom-dbis-138" / "services" / "token-aggregation" / "config" / "live-uniswap-v2-pool-catalog.json"
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--report", type=Path, default=DEFAULT_REPORT)
parser.add_argument("--catalog", type=Path, default=DEFAULT_CATALOG)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
if not args.report.exists():
print(f"Skip: report missing: {args.report}")
return 0
if not args.catalog.exists():
print(f"Skip: catalog missing: {args.catalog}")
return 0
report = json.loads(args.report.read_text())
catalog = json.loads(args.catalog.read_text())
pools = catalog.get("pools") or []
by_pool: dict[str, float] = {}
for row in pools:
if row.get("chainId") != 1:
continue
addr = str(row.get("poolAddress", "")).lower()
if not addr.startswith("0x"):
continue
usd = float(row.get("totalLiquidityUsd") or 0)
if usd > 0:
by_pool[addr] = max(by_pool.get(addr, 0), usd)
if not by_pool:
print("No chain-1 catalog pools with USD TVL.")
return 0
changed = 0
for token in report.get("tokens") or []:
if token.get("chain_id") != 1:
continue
pairs = token.get("pairs") or []
for pair in pairs:
if not isinstance(pair, dict):
continue
pa = str(pair.get("pair_address", "")).lower()
if pa not in by_pool:
continue
cap = by_pool[pa]
cur = float(pair.get("liquidity_usd") or 0)
if cap > cur:
pair["liquidity_usd"] = cap
changed += 1
pair_sum = sum(float((p or {}).get("liquidity_usd") or 0) for p in pairs if isinstance(p, dict))
top = float(token.get("liquidity_usd") or 0)
if pair_sum > top:
token["liquidity_usd"] = pair_sum
changed += 1
if changed == 0:
print("No liquidity fields needed updates.")
return 0
if args.dry_run:
print(f"Dry-run: would update {changed} field(s).")
return 0
args.report.write_text(json.dumps(report, indent=2) + "\n")
print(f"Wrote {args.report.relative_to(ROOT)} ({changed} liquidity field updates).")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -73,6 +73,21 @@ def fetch_json(url: str, timeout: int = 30) -> tuple[int | None, str, Any, str]:
return status, content_type, None, str(exc)
def fetch_text_with_retries(url: str, timeout: int, max_attempts: int) -> tuple[int | None, str, str, int]:
"""Retry GeckoTerminal-style rate limits (HTTP 429 / 502 / 503)."""
base_delay_s = 2.0
last_status: int | None = None
last_ct = ""
last_text = ""
for attempt in range(1, max_attempts + 1):
status, content_type, text = fetch_text(url, timeout)
last_status, last_ct, last_text = status, content_type, text
if status not in (429, 502, 503) or attempt >= max_attempts:
return status, content_type, text, attempt
time.sleep(min(base_delay_s * (2 ** (attempt - 1)), 45.0))
return last_status, last_ct, last_text, max_attempts
def fetch_etherscan_api(params: dict[str, str], api_key: str) -> tuple[int | None, str, Any, str]:
query = {"chainid": "1", **params, "apikey": api_key}
url = f"{ETHERSCAN_API}?{urllib.parse.urlencode(query)}"
@@ -221,10 +236,16 @@ def parse_dexscreener() -> dict[str, Any]:
}
def parse_geckoterminal() -> list[dict[str, Any]]:
def parse_geckoterminal(timeout: int, gecko_retries: int) -> list[dict[str, Any]]:
checks: list[dict[str, Any]] = []
for url in GECKOTERMINAL_POOLS:
status, content_type, data, error = fetch_json(url)
status, content_type, text, attempts = fetch_text_with_retries(url, timeout, gecko_retries)
data: Any = None
error = ""
try:
data = json.loads(text)
except json.JSONDecodeError as exc:
error = str(exc)
attrs = ((data or {}).get("data") or {}).get("attributes") if isinstance(data, dict) else None
checks.append(
{
@@ -233,6 +254,7 @@ def parse_geckoterminal() -> list[dict[str, Any]]:
"status": status,
"contentType": content_type,
"parseError": error,
"fetchAttempts": attempts,
"indexed": isinstance(attrs, dict),
"reserveUsd": attrs.get("reserve_in_usd") if isinstance(attrs, dict) else None,
"volume24hUsd": ((attrs.get("volume_usd") or {}).get("h24") if isinstance(attrs, dict) else None),
@@ -241,14 +263,14 @@ def parse_geckoterminal() -> list[dict[str, Any]]:
return checks
def build() -> dict[str, Any]:
def build(gecko_retries: int, http_timeout: int) -> dict[str, Any]:
load_dotenv(ROOT / ".env")
etherscan_api_key = os.environ.get("ETHERSCAN_API_KEY", "")
etherscan = parse_etherscan()
etherscan_tokeninfo = parse_etherscan_tokeninfo(etherscan_api_key)
coingecko = parse_coingecko()
dexscreener = parse_dexscreener()
gecko = parse_geckoterminal()
gecko = parse_geckoterminal(http_timeout, gecko_retries)
blockers: list[str] = []
if not etherscan["profileDetected"]:
blockers.append("Etherscan token profile text was not detected.")
@@ -330,8 +352,10 @@ def write_md(payload: dict[str, Any], path: Path) -> None:
]
)
for item in checks["geckoterminal"]:
attempts = item.get("fetchAttempts", 1)
extra = f"; httpAttempts={attempts}" if attempts > 1 else ""
lines.append(
f"| GeckoTerminal pool | `{item['status']}` | `{item['indexed']}` | reserveUsd={item['reserveUsd']}; volume24hUsd={item['volume24hUsd']}; url={item['url']} |"
f"| GeckoTerminal pool | `{item['status']}` | `{item['indexed']}` | reserveUsd={item['reserveUsd']}; volume24hUsd={item['volume24hUsd']}{extra}; url={item['url']} |"
)
path.write_text("\n".join(lines) + "\n")
@@ -340,10 +364,22 @@ def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--json-out", type=Path, default=REPORT_JSON)
parser.add_argument("--md-out", type=Path, default=REPORT_MD)
parser.add_argument(
"--gecko-retries",
type=int,
default=int(os.environ.get("CWUSDC_GECKO_RETRIES", "5")),
help="Max HTTP attempts per GeckoTerminal pool URL (429/502/503 backoff).",
)
parser.add_argument(
"--timeout",
type=int,
default=int(os.environ.get("CWUSDC_TRACKER_TIMEOUT", "30")),
help="Per-request HTTP timeout seconds for GeckoTerminal probes.",
)
parser.add_argument("--strict", action="store_true")
args = parser.parse_args()
payload = build()
payload = build(args.gecko_retries, args.timeout)
args.json_out.parent.mkdir(parents=True, exist_ok=True)
args.json_out.write_text(json.dumps(payload, indent=2) + "\n")
write_md(payload, args.md_out)

View File

@@ -1,6 +1,13 @@
#!/usr/bin/env bash
# Run all non-manual cWUSDC provider checks and build a handoff report.
# This script is public/read-only except for report files under reports/status/.
#
# Optional environment:
# CWUSDC_GECKO_RETRIES Max HTTP attempts for GeckoTerminal probes (default 6).
# CWUSDC_TRACKER_TIMEOUT Seconds per HTTP request for tracker script (default 25).
# CWUSDC_RUN_PROPAGATION_MONITOR Set to 1 to also run monitor-cwusdc-etherscan-value-propagation.py after handoff.
# Step 3a enriches reports/status/token-aggregation-cmc-report-chain1-latest.json with TVL from
# smom-dbis-138/services/token-aggregation/config/live-uniswap-v2-pool-catalog.json when the explorer snapshot lags deploy.
set -euo pipefail
@@ -9,17 +16,34 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$PROJECT_ROOT"
STRICT_REPO=false
GECKO_RETRIES="${CWUSDC_GECKO_RETRIES:-6}"
TRACKER_TIMEOUT="${CWUSDC_TRACKER_TIMEOUT:-25}"
while [[ $# -gt 0 ]]; do
case "$1" in
--strict-repo)
STRICT_REPO=true
shift
;;
--gecko-retries)
GECKO_RETRIES="${2:?--gecko-retries requires a number}"
shift 2
;;
--tracker-timeout)
TRACKER_TIMEOUT="${2:?--tracker-timeout requires a number}"
shift 2
;;
-h|--help)
sed -n '1,4p' "$0"
echo " --strict-repo Exit non-zero if repo-controlled URL prerequisites fail."
sed -n '1,5p' "$0"
echo " --strict-repo Exit non-zero if repo-controlled URL prerequisites fail."
echo " --gecko-retries N GeckoTerminal pool HTTP retries (429/502/503 backoff); default 6 or CWUSDC_GECKO_RETRIES."
echo " --tracker-timeout N Per-request timeout seconds for tracker probe; default 25 or CWUSDC_TRACKER_TIMEOUT."
echo "Env: CWUSDC_RUN_PROPAGATION_MONITOR=1 runs Etherscan value propagation monitor after handoff."
echo " -- Ignore (for pnpm: pnpm cwusdc:provider-checks -- --gecko-retries 8)."
exit 0
;;
--)
shift
;;
*)
echo "Unknown argument: $1" >&2
exit 1
@@ -38,6 +62,8 @@ HANDOFF_MD="reports/status/cwusdc-provider-handoff-latest.md"
echo "=== cWUSDC provider non-manual checks ==="
echo "Mode: public/read-only, report writes only"
echo "GeckoTerminal retries: $GECKO_RETRIES (set CWUSDC_GECKO_RETRIES or --gecko-retries)"
echo "Tracker HTTP timeout: ${TRACKER_TIMEOUT}s (set CWUSDC_TRACKER_TIMEOUT or --tracker-timeout)"
echo ""
REPO_STATUS=0
@@ -49,13 +75,18 @@ fi
echo ""
echo "2. External tracker/indexing probes (advisory)..."
bash "$SCRIPT_DIR/check-cwusdc-external-trackers-live.sh" --json-out "$TRACKERS_JSON" --md-out "$TRACKERS_MD" || true
# Single line avoids CRLF/line-continuation edge cases where `--json-out` was executed as a command.
bash "$SCRIPT_DIR/check-cwusdc-external-trackers-live.sh" --json-out "$TRACKERS_JSON" --md-out "$TRACKERS_MD" --gecko-retries "$GECKO_RETRIES" --timeout "$TRACKER_TIMEOUT" || true
echo ""
echo "3. Liquidity-gap funding planner (read-only)..."
node "$SCRIPT_DIR/plan-token-aggregation-liquidity-gap-funding.mjs"
echo ""
echo "3a. Enrich CMC chain-1 report snapshot with live pool catalog TVL (read-only; idempotent)..."
python3 "$SCRIPT_DIR/enrich-cmc-chain1-report-with-live-pool-catalog.py" || true
echo ""
echo "3b. CMC-shaped report sanity (advisory)..."
python3 "$SCRIPT_DIR/check-cmc-provider-report-sanity.py" || true
echo ""
@@ -72,6 +103,15 @@ echo ""
echo "Handoff: $HANDOFF_MD"
if [[ "${CWUSDC_RUN_PROPAGATION_MONITOR:-}" == "1" ]]; then
echo ""
echo "4b. Etherscan value propagation monitor (optional; CWUSDC_RUN_PROPAGATION_MONITOR=1)..."
python3 "$SCRIPT_DIR/monitor-cwusdc-etherscan-value-propagation.py" \
--gecko-retries "$GECKO_RETRIES" \
--timeout "$TRACKER_TIMEOUT" || true
echo ""
fi
if [[ "$STRICT_REPO" == "true" && "$REPO_STATUS" -ne 0 ]]; then
exit "$REPO_STATUS"
fi

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Wrapper so `pnpm cwusdc:value-propagation -- --gecko-retries 8` works: pnpm forwards
# a literal "--" before script args, which argparse rejects unless stripped here.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$ROOT"
while [[ $# -gt 0 && "$1" == "--" ]]; do
shift
done
exec python3 "$SCRIPT_DIR/monitor-cwusdc-etherscan-value-propagation.py" "$@"