chore(cwusdc): optional DexScreener probes and Etherscan-value readiness subset
- Treat DexScreener token v1 APIs as optional; document in non-manual tasks. - Align tracker checks, handoff/dossier builders, CMC sanity, monitors, and CI shell wrapper with ETHERSCAN_VALUE_PATH_READY_IDS and summary fields. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,8 +14,8 @@ Latest evidence source: [../../reports/status/cwusdc-provider-handoff-latest.md]
|
||||
| CMC DEX visibility | Passing | External tracker probe sees the CMC DEX token page. |
|
||||
| GeckoTerminal V3 pool visibility | Passing | V3 pool API returns `data`. |
|
||||
| GeckoTerminal V2 pool visibility | Mitigated in automation | Probes use exponential backoff on HTTP **429** / **502** / **503** for GeckoTerminal pool URLs (`scripts/verify/check-cwusdc-external-trackers-live.py`, `--gecko-retries`). Same backoff for GeckoTerminal in `monitor-cwusdc-etherscan-value-propagation.py`. If all attempts fail, treat as advisory and rerun later. |
|
||||
| CoinGecko price API | External blocker | API responds but does not include the cWUSDC contract key. |
|
||||
| DexScreener token APIs | External blocker | Token-pairs and tokens endpoints return empty arrays. |
|
||||
| CoinGecko price API | External blocker | API responds but does not include the cWUSDC contract key (primary listing gate). |
|
||||
| DexScreener token APIs | Optional / usually empty pre-indexing | Same JSON: `summary.dexScreenerTokenApisIndexed`; not counted in `failedRequiredIds`. |
|
||||
| EVM liquidity-gap planner rows | No current rows | Latest planner summary shows `rows = 0`. |
|
||||
| Non-EVM funding requirements | Open, operator-bound | Solana, Tron, XRPL, and other non-EVM requirements need wallet/asset/target binding before claims. |
|
||||
| Mainnet cWUSDC supply attestation | Refreshed | `reports/status/cwusdc-supply-circulating-attestation-latest.md`; circulating supply `10451316981.309788`. |
|
||||
@@ -49,7 +49,7 @@ Latest evidence source: [../../reports/status/cwusdc-provider-handoff-latest.md]
|
||||
| Task | Command | Output | Current interpretation |
|
||||
|---|---|---|---|
|
||||
| Check Etherscan prerequisite website URLs | `bash scripts/verify/check-cwusdc-etherscan-prereq-urls.sh --json-out reports/status/cwusdc-etherscan-prereq-urls-latest.json --md-out reports/status/cwusdc-etherscan-prereq-urls-latest.md` | URL evidence JSON/Markdown with HTTP status, attempts, and curl status. | Passing; rerun before Etherscan profile submission or CI. |
|
||||
| Probe external tracker/indexing surfaces | `bash scripts/verify/check-cwusdc-external-trackers-live.sh --gecko-retries 6` (optional; defaults apply when run via `run-cwusdc-provider-nonmanual-checks.sh`) | `reports/status/cwusdc-external-trackers-live-latest.{json,md}` | Etherscan, CMC DEX, and GeckoTerminal V3 pass; CoinGecko and DexScreener remain external blockers; Gecko V2 uses 429 backoff. |
|
||||
| Probe external tracker/indexing surfaces | `bash scripts/verify/check-cwusdc-external-trackers-live.sh --gecko-retries 6` (optional; defaults apply when run via `run-cwusdc-provider-nonmanual-checks.sh`) | `reports/status/cwusdc-external-trackers-live-latest.{json,md}` | Required probes: Etherscan token page, CoinGecko price API, CMC DEX page, GeckoTerminal V2/V3 pools. DexScreener token v1 APIs are **optional** (supplementary indexing); see `summary.dexScreenerTokenApisIndexed` in the JSON. |
|
||||
| Re-run liquidity-gap funding planner | `node scripts/verify/plan-token-aggregation-liquidity-gap-funding.mjs` | `reports/status/token-aggregation-liquidity-gap-funding-plan-latest.{json,md}` | Read-only; latest EVM gap rows are `0`; non-EVM funding requirements remain open. |
|
||||
| Build cWUSDC Etherscan Value dossier | `pnpm cwusdc:etherscan-dossier` | `reports/status/cwusdc-etherscan-value-dossier-latest.{json,md}` | Read-only dossier generation for the Etherscan USD Value path. |
|
||||
| Generate Mainnet cWUSDC supply/circulating attestation | `python3 scripts/verify/generate-cwusdc-supply-circulating-attestation.py` | `reports/status/cwusdc-supply-circulating-attestation-latest.json` and related report output. | Safe when supply/exclusion evidence needs refresh. |
|
||||
|
||||
@@ -325,6 +325,7 @@ def build(args: argparse.Namespace) -> dict[str, Any]:
|
||||
unsupported_family_chain_ids = [chain_id for chain_id in family_chain_ids if chain_id not in chainlist.get("statusByChainId", {})]
|
||||
|
||||
blockers = list(((propagation.get("summary") or {}).get("blockers") or []))
|
||||
propagation_advisory = list(((propagation.get("summary") or {}).get("advisoryNotes") or []))
|
||||
command_failures = [item for item in commands if not item["ok"]]
|
||||
for item in command_failures:
|
||||
blockers.append("Command failed: " + " ".join(item["command"]))
|
||||
@@ -378,6 +379,7 @@ def build(args: argparse.Namespace) -> dict[str, Any]:
|
||||
"etherscanValueReady": (propagation.get("summary") or {}).get("etherscanValueReady"),
|
||||
"coinGeckoPriceReady": (propagation.get("summary") or {}).get("coingeckoPriceReady"),
|
||||
"blockers": blockers,
|
||||
"advisoryNotes": propagation_advisory,
|
||||
},
|
||||
"evidence": {
|
||||
"artifacts": {key: rel(path) for key, path in ARTIFACTS.items()},
|
||||
@@ -424,6 +426,10 @@ def write_md(payload: dict[str, Any], path: Path) -> None:
|
||||
lines.extend(f"- {item}" for item in readiness["blockers"])
|
||||
else:
|
||||
lines.append("- None detected by this dossier.")
|
||||
advisory = readiness.get("advisoryNotes") or []
|
||||
if advisory:
|
||||
lines.extend(["", "## Advisory (non-gating)", ""])
|
||||
lines.extend(f"- {item}" for item in advisory)
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
@@ -519,6 +525,9 @@ def main() -> int:
|
||||
print(f"readyForExternalSubmission={payload['readiness']['readyForExternalSubmission']}")
|
||||
if payload["readiness"]["blockers"]:
|
||||
print("Blockers: " + "; ".join(payload["readiness"]["blockers"]))
|
||||
adv = payload["readiness"].get("advisoryNotes") or []
|
||||
if adv:
|
||||
print("Advisory (non-gating): " + "; ".join(adv))
|
||||
if args.strict and payload["readiness"]["blockers"]:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@@ -67,7 +67,8 @@ def build_payload(
|
||||
tracker_summary = first(trackers, ["summary"], {})
|
||||
failed_required = tracker_summary.get("failedRequiredIds") if isinstance(tracker_summary, dict) else []
|
||||
liquidity_summary = first(liquidity, ["summary"], {})
|
||||
blockers = []
|
||||
blockers: list[dict[str, Any]] = []
|
||||
advisory_open_items: list[dict[str, Any]] = []
|
||||
|
||||
if prereq is None:
|
||||
blockers.append({
|
||||
@@ -115,14 +116,14 @@ def build_payload(
|
||||
})
|
||||
|
||||
if first(liquidity, ["summary", "nonEvmFundingRequirementRows"], 0):
|
||||
blockers.append({
|
||||
advisory_open_items.append({
|
||||
"id": "non_evm_funding_requirements",
|
||||
"type": "operator_bound",
|
||||
"status": "open",
|
||||
"nextAction": "Bind non-EVM wallets, asset IDs, and minimum funding targets before making non-EVM liquidity claims.",
|
||||
})
|
||||
if first(cmc_sanity, ["summary", "warningCount"], 0):
|
||||
blockers.append({
|
||||
advisory_open_items.append({
|
||||
"id": "cmc_report_sanity_warnings",
|
||||
"type": "repo_advisory",
|
||||
"status": "open",
|
||||
@@ -145,6 +146,7 @@ def build_payload(
|
||||
"repoControlledPrereqsPassed": repo_ready,
|
||||
"externalTrackersAllLive": bool(first(trackers, ["summary", "allTrackersLive"], False)),
|
||||
"readyForEtherscanUsdValue": ready_for_etherscan_value,
|
||||
"dexScreenerTokenApisIndexed": first(trackers, ["summary", "dexScreenerTokenApisIndexed"], None),
|
||||
"externalRequiredPassed": first(trackers, ["summary", "requiredPassedCount"], None),
|
||||
"externalRequiredCount": first(trackers, ["summary", "requiredCount"], None),
|
||||
"liquidityRows": first(liquidity, ["summary", "rows"], None),
|
||||
@@ -152,8 +154,10 @@ def build_payload(
|
||||
"cmcSanityWarningCount": first(cmc_sanity, ["summary", "warningCount"], None),
|
||||
"cmcPromotedTokenCount": first(cmc_sanity, ["summary", "promotedTokenCount"], None),
|
||||
"blockerCount": len(blockers),
|
||||
"advisoryOpenItemCount": len(advisory_open_items),
|
||||
},
|
||||
"blockers": blockers,
|
||||
"advisoryOpenItems": advisory_open_items,
|
||||
}
|
||||
|
||||
|
||||
@@ -169,8 +173,11 @@ def write_markdown(payload: dict[str, Any], prereq: Any, trackers: Any, liquidit
|
||||
"",
|
||||
f"- Generated: `{payload['generatedAt']}`",
|
||||
f"- Repo-controlled prerequisites passed: `{payload['summary']['repoControlledPrereqsPassed']}`",
|
||||
f"- External trackers all live: `{payload['summary']['externalTrackersAllLive']}`",
|
||||
f"- Ready for Etherscan USD Value path: `{payload['summary']['readyForEtherscanUsdValue']}`",
|
||||
f"- Required external probes all pass: `{payload['summary']['externalTrackersAllLive']}` (same as tracker `summary.allTrackersLive`; DexScreener token v1 probes are optional)",
|
||||
f"- Ready for Etherscan USD Value path (strict subset): `{payload['summary']['readyForEtherscanUsdValue']}`",
|
||||
f"- DexScreener token APIs indexed (optional): `{payload['summary'].get('dexScreenerTokenApisIndexed', 'n/a')}`",
|
||||
f"- Hard blocker count: `{payload['summary']['blockerCount']}`",
|
||||
f"- Advisory open items: `{payload['summary'].get('advisoryOpenItemCount', 0)}`",
|
||||
"",
|
||||
"## Inputs",
|
||||
"",
|
||||
@@ -214,12 +221,19 @@ def write_markdown(payload: dict[str, Any], prereq: Any, trackers: Any, liquidit
|
||||
[[w.get("id"), w.get("symbol", "-"), w.get("severity"), w.get("message")] for w in cmc_warnings],
|
||||
) if cmc_warnings else "No CMC sanity warnings.",
|
||||
"",
|
||||
"## Blockers",
|
||||
"## Blockers (hard)",
|
||||
"",
|
||||
table(
|
||||
["ID", "Type", "Status", "Next action"],
|
||||
[[b["id"], b["type"], b["status"], b["nextAction"]] for b in payload["blockers"]],
|
||||
) if payload["blockers"] else "No current blockers detected.",
|
||||
) if payload["blockers"] else "No hard blockers detected.",
|
||||
"",
|
||||
"## Advisory open items",
|
||||
"",
|
||||
table(
|
||||
["ID", "Type", "Status", "Next action"],
|
||||
[[b["id"], b["type"], b["status"], b["nextAction"]] for b in payload.get("advisoryOpenItems", [])],
|
||||
) if payload.get("advisoryOpenItems") else "No advisory items.",
|
||||
"",
|
||||
"## Submission Boundary",
|
||||
"",
|
||||
|
||||
@@ -29,6 +29,18 @@ INTENTIONAL_DBIS_QUOTE_FACE = {
|
||||
PROMOTED = {"cWUSDC", "cWUSDT", "cWXAUC", "cWXAUT", "cWBTC", "cWETH"}
|
||||
|
||||
|
||||
def token_discards_listing_liquidity_claim(token: dict[str, Any]) -> bool:
|
||||
"""True when the CMC-shaped row already tells reviewers not to use liquidity as listing evidence."""
|
||||
prov = token.get("supply_proof_provenance") or {}
|
||||
status = str(prov.get("status") or "")
|
||||
source = str(prov.get("source") or "")
|
||||
if status in {"proof_required", "non_reportable_until_erc20_deployed"}:
|
||||
return True
|
||||
if source == "deterministic-placeholder-address":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text()) if path.exists() else {}
|
||||
|
||||
@@ -90,7 +102,7 @@ def main() -> int:
|
||||
"pairCount": len(pairs),
|
||||
}
|
||||
)
|
||||
if liquidity <= 0:
|
||||
if liquidity <= 0 and not token_discards_listing_liquidity_claim(token):
|
||||
warnings.append(
|
||||
{
|
||||
"id": "zero_reported_liquidity",
|
||||
@@ -105,7 +117,14 @@ def main() -> int:
|
||||
for check in trackers.get("checks", []):
|
||||
if not str(check.get("id", "")).startswith("geckoterminal"):
|
||||
continue
|
||||
attrs = (((check.get("jsonPreview") or {}).get("data") or {}).get("attributes") or {})
|
||||
preview = check.get("jsonPreview")
|
||||
if not isinstance(preview, dict):
|
||||
continue
|
||||
data = preview.get("data")
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
attrs_raw = data.get("attributes")
|
||||
attrs = attrs_raw if isinstance(attrs_raw, dict) else {}
|
||||
gecko_reserves.append(
|
||||
{
|
||||
"id": check.get("id"),
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Probe cWUSDC public tracker/indexing surfaces and write evidence JSON."""
|
||||
"""Probe cWUSDC public tracker/indexing surfaces and write evidence JSON.
|
||||
|
||||
DexScreener token-pairs/tokens v1 probes are supplementary DEX-terminal signals
|
||||
(often empty pre-indexing). Etherscan USD value readiness follows the stricter
|
||||
subset in ETHERSCAN_VALUE_PATH_READY_IDS (see summary.readyForEtherscanUsdValue).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
@@ -23,6 +28,16 @@ POOLS = [
|
||||
"0xc28706f899266b36bc43cc072b3a921bdf2c48d9",
|
||||
]
|
||||
|
||||
# CoinGecko + public pair evidence surfaces aligned with CWUSDC_ETHERSCAN_VALUE_EXECUTION_PLAN.md;
|
||||
# excludes DexScreener token-level v1 APIs (supplementary indexing, not primary price gate).
|
||||
ETHERSCAN_VALUE_PATH_READY_IDS = frozenset({
|
||||
"etherscan_token_page",
|
||||
"coingecko_token_price_api",
|
||||
"coinmarketcap_dex_token",
|
||||
"geckoterminal_v3_pool",
|
||||
"geckoterminal_v2_pool",
|
||||
})
|
||||
|
||||
URLS = [
|
||||
{
|
||||
"id": "etherscan_token_page",
|
||||
@@ -49,14 +64,14 @@ URLS = [
|
||||
"id": "dexscreener_token_pairs_v1",
|
||||
"kind": "dex_index",
|
||||
"url": f"https://api.dexscreener.com/token-pairs/v1/ethereum/{CWUSDC}",
|
||||
"required": True,
|
||||
"required": False,
|
||||
"jsonRootMinLength": 1,
|
||||
},
|
||||
{
|
||||
"id": "dexscreener_tokens_v1",
|
||||
"kind": "dex_index",
|
||||
"url": f"https://api.dexscreener.com/tokens/v1/ethereum/{CWUSDC}",
|
||||
"required": True,
|
||||
"required": False,
|
||||
"jsonRootMinLength": 1,
|
||||
},
|
||||
{
|
||||
@@ -235,6 +250,8 @@ def write_markdown(payload: dict[str, Any], path: Path) -> None:
|
||||
"",
|
||||
f"- Generated: `{payload['generatedAt']}`",
|
||||
f"- All trackers live: `{payload['summary']['allTrackersLive']}`",
|
||||
f"- Ready for Etherscan USD value path (subset): `{payload['summary']['readyForEtherscanUsdValue']}`",
|
||||
f"- DexScreener token APIs indexed (optional): `{payload['summary']['dexScreenerTokenApisIndexed']}`",
|
||||
f"- Required passed: `{payload['summary']['requiredPassedCount']} / {payload['summary']['requiredCount']}`",
|
||||
"",
|
||||
"| Surface | Passed | HTTP | URL | Details |",
|
||||
@@ -267,13 +284,21 @@ def main() -> int:
|
||||
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"]]
|
||||
passed_ids = {c["id"] for c in checks if c["passed"]}
|
||||
dex_pair = next((c for c in checks if c["id"] == "dexscreener_token_pairs_v1"), None)
|
||||
dex_tok = next((c for c in checks if c["id"] == "dexscreener_tokens_v1"), None)
|
||||
dex_indexed = bool(
|
||||
dex_pair and dex_pair["passed"] and dex_tok and dex_tok["passed"],
|
||||
)
|
||||
ready_for_etherscan = ETHERSCAN_VALUE_PATH_READY_IDS.issubset(passed_ids)
|
||||
payload = {
|
||||
"schema": "cwusdc-external-trackers-live/v1",
|
||||
"generatedAt": datetime.now(timezone.utc).isoformat(),
|
||||
"token": {"chainId": 1, "network": "ethereum", "address": CWUSDC, "symbol": "cWUSDC"},
|
||||
"summary": {
|
||||
"allTrackersLive": len(required_passed) == len(required),
|
||||
"readyForEtherscanUsdValue": len(required_passed) == len(required),
|
||||
"readyForEtherscanUsdValue": ready_for_etherscan,
|
||||
"dexScreenerTokenApisIndexed": dex_indexed,
|
||||
"requiredCount": len(required),
|
||||
"requiredPassedCount": len(required_passed),
|
||||
"failedRequiredIds": [c["id"] for c in required if not c["passed"]],
|
||||
|
||||
@@ -32,6 +32,7 @@ doc_links = json.loads(doc_link_path.read_text())
|
||||
repo_ok = bool(handoff["summary"]["repoControlledPrereqsPassed"])
|
||||
doc_links_ok = doc_links.get("status") == "pass"
|
||||
external_blockers = [b for b in handoff.get("blockers", []) if b.get("type") != "repo_controlled"]
|
||||
advisory_open = handoff.get("advisoryOpenItems") or []
|
||||
payload = {
|
||||
"schema": "cwusdc-provider-readiness-ci/v1",
|
||||
"generatedAt": datetime.now(timezone.utc).isoformat(),
|
||||
@@ -42,6 +43,8 @@ payload = {
|
||||
"institutionalDocLinksReport": str(doc_link_path),
|
||||
"externalBlockersAdvisoryCount": len(external_blockers),
|
||||
"externalBlockersAdvisory": external_blockers,
|
||||
"advisoryOpenItemCount": len(advisory_open),
|
||||
"advisoryOpenItems": advisory_open,
|
||||
"handoffReport": str(handoff_path),
|
||||
}
|
||||
json_out.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -55,6 +58,7 @@ lines = [
|
||||
f"- Base provider prerequisites passed: `{repo_ok}`",
|
||||
f"- Institutional doc links passed: `{doc_links_ok}`",
|
||||
f"- External blockers advisory count: `{len(external_blockers)}`",
|
||||
f"- Handoff advisory open items: `{len(advisory_open)}`",
|
||||
f"- Handoff report: `{handoff_path}`",
|
||||
"",
|
||||
"External provider blockers are advisory in this CI gate. They require provider acceptance or operator action and should not fail repo-controlled CI.",
|
||||
|
||||
@@ -272,6 +272,7 @@ def build(gecko_retries: int, http_timeout: int) -> dict[str, Any]:
|
||||
dexscreener = parse_dexscreener()
|
||||
gecko = parse_geckoterminal(http_timeout, gecko_retries)
|
||||
blockers: list[str] = []
|
||||
advisory_notes: list[str] = []
|
||||
if not etherscan["profileDetected"]:
|
||||
blockers.append("Etherscan token profile text was not detected.")
|
||||
if etherscan["onchainMarketCapMissing"]:
|
||||
@@ -288,7 +289,9 @@ def build(gecko_retries: int, http_timeout: int) -> dict[str, Any]:
|
||||
if not coingecko["priceReady"]:
|
||||
blockers.append("CoinGecko contract price API does not return a positive USD price.")
|
||||
if not dexscreener["indexed"]:
|
||||
blockers.append("DexScreener token-pairs API still does not index cWUSDC pairs.")
|
||||
advisory_notes.append(
|
||||
"DexScreener token-pairs API has not indexed cWUSDC yet (supplementary DEX-terminal signal; not the same gate as CoinGecko for Etherscan Value)."
|
||||
)
|
||||
|
||||
return {
|
||||
"schema": "cwusdc-etherscan-value-propagation/v1",
|
||||
@@ -306,6 +309,7 @@ def build(gecko_retries: int, http_timeout: int) -> dict[str, Any]:
|
||||
"coingeckoPriceReady": coingecko["priceReady"],
|
||||
"readyForEtherscanValuePropagation": etherscan["valueReady"] or coingecko["priceReady"],
|
||||
"blockers": blockers,
|
||||
"advisoryNotes": advisory_notes,
|
||||
},
|
||||
"checks": {
|
||||
"etherscan": etherscan,
|
||||
@@ -338,6 +342,10 @@ def write_md(payload: dict[str, Any], path: Path) -> None:
|
||||
lines.extend(f"- {item}" for item in summary["blockers"])
|
||||
else:
|
||||
lines.append("- None detected by this monitor.")
|
||||
advisory = summary.get("advisoryNotes") or []
|
||||
if advisory:
|
||||
lines.extend(["", "## Advisory (non-gating)", ""])
|
||||
lines.extend(f"- {item}" for item in advisory)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
@@ -388,6 +396,9 @@ def main() -> int:
|
||||
print(f"etherscanValueReady={payload['summary']['etherscanValueReady']}")
|
||||
if payload["summary"]["blockers"]:
|
||||
print("Blockers: " + "; ".join(payload["summary"]["blockers"]))
|
||||
adv = payload["summary"].get("advisoryNotes") or []
|
||||
if adv:
|
||||
print("Advisory (non-gating): " + "; ".join(adv))
|
||||
if args.strict and not payload["summary"]["etherscanValueReady"]:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@@ -46,6 +46,7 @@ payload = {
|
||||
"coinGeckoPriceReady": dossier_readiness.get("coinGeckoPriceReady"),
|
||||
"repoControlledPrereqsPassed": provider_ci.get("repoControlledPrereqsPassed"),
|
||||
"externalBlockersAdvisory": provider_ci.get("externalBlockersAdvisory", []),
|
||||
"advisoryOpenItems": provider_ci.get("advisoryOpenItems", []),
|
||||
"docLinkStatus": links.get("status"),
|
||||
"roleEventCount": role_appendix.get("eventCount"),
|
||||
"artifacts": {
|
||||
@@ -69,6 +70,7 @@ lines = [
|
||||
f"- Institutional doc link status: `{payload['docLinkStatus']}`",
|
||||
f"- Role event count: `{payload['roleEventCount']}`",
|
||||
f"- External advisory blockers: `{len(payload['externalBlockersAdvisory'])}`",
|
||||
f"- Handoff advisory open items: `{len(payload.get('advisoryOpenItems', []))}`",
|
||||
]
|
||||
md_out.write_text("\n".join(lines) + "\n")
|
||||
print(f"Wrote {json_out}")
|
||||
|
||||
Reference in New Issue
Block a user