The first real-data run of solv1-alpha produced no verdict — it hit a fatal ingestion blocker, not a code bug. Two independent problems killed the RPC-indexing data path at once: the Chainstack node is non-archival, and the raw GMGN 7d-PnL leaderboard is bot/dead-polluted. The fix pivots ingestion off RPC entirely: harvest per-wallet trades from GMGN’s wallet_activity endpoint through the claude-in-chrome browser (which carries a valid Cloudflare cf_clearance), then file-ingest into Rust.

For Agents

This is a data-source change, not an analysis change. Every downstream stage of the harness (score → filters → cohort → decay → mirror → benchmarks → verdict, i.e. tasks A8–A16) is untouched and reused — they only read solv1_alpha_trades. Only the source that fills that table changes: RPC indexing (A4–A7) is out, browser-harvest + file-ingest is in.

Two hard facts to carry forward: (1) Cloudflare blocks non-browser clients — headless curl/reqwest get 403; only a real browser context with a live cf_clearance cookie passes, so the fetch must run inside claude-in-chrome. (2) The GMGN payload has no slot number, so the ingester synthesizes a slot from the event timestamp (ordering-only, not a real slot height).

The chosen increment that implements this fix is solv1-alpha-gmgn-ingester-increment-2026-07-04.

Symptoms

  • The alpha harness built green (48 tests) but the first run against real data never reached a verdict — it stalled in the index stage.
  • The Chainstack RPC getSignaturesForAddress calls stopped returning history far short of the multi-week window the walk-forward split requires: an active wallet paged out at ~2.9h of history.
  • Separately, spot-checking the wallet universe showed the leaderboard was full of wallets that are uncopyable by construction (bots, dead accounts, mega-influencers).

Root Cause 1 — the Chainstack node is non-archival

getSignaturesForAddress on the Chainstack endpoint retains only ~3h of wallet history — an active wallet’s signatures page out at ~2.9h. A multi-week walk-forward backtest needs weeks of per-wallet fills to build a training window, so it is structurally impossible to index from this node. This is a node-tier property (pruned vs archival history), not a query bug — no amount of pagination recovers history the node no longer holds.

Non-archival RPC cannot backfill a walk-forward window

Any historical backtest keyed on per-wallet trade history needs an archival RPC (full signature history) or an off-chain history source. A standard pruned node silently truncates to a few hours, which looks like “the wallet just stopped trading” rather than an error.

Root Cause 2 — the raw GMGN 7d-PnL leaderboard is polluted

Sampling 24 wallets off the raw GMGN 7d-PnL leaderboard gave a copyability breakdown of roughly:

BucketShareWhy unusable as a copy target
HFT bots~21%Sub-second churn; a follower can never mirror them
Dead / inactive~29%No recent activity — nothing left to copy
Steady, copyable humans~0%The actual target population is essentially absent from the raw list

Some entries are also famous mega-accounts that are poor copy targets regardless of PnL — e.g. CyaE1V… is “Cented”, a wallet with 609k followers (copying a wallet everyone already front-runs is self-defeating). Conclusion: the leaderboard cannot seed a cohort directly — it needs a bot/dead pre-filter first. That filter is folded into task G1 of the ingester increment (activity-rate + GMGN smartmoney flag).

Fix — GMGN wallet_activity via browser same-origin fetch

Pull per-wallet trades directly from GMGN’s activity API instead of reconstructing them from RPC:

Endpoint + pagination

GET /api/v1/wallet_activity/sol?wallet=<addr>&cursor=<data.next>

Called via same-origin fetch() inside the claude-in-chrome browser. The browser context carries a valid Cloudflare cf_clearance cookie, so the request clears Cloudflare; the same call from curl/reqwest returns 403. Deep history is walked by feeding data.next from the previous page back in as cursor (~20 events/page).

graph LR
    subgraph dead["RPC path — DEAD"]
        CS["Chainstack RPC<br/>getSignaturesForAddress"] -->|"~2.9h history only<br/>(non-archival)"| X((✗))
    end
    subgraph chosen["GMGN path — CHOSEN"]
        BR["claude-in-chrome<br/>same-origin fetch<br/><b>cf_clearance</b>"] -->|"wallet_activity/sol<br/>cursor = data.next"| J["harvested JSON<br/>~20 events/page"]
        J --> ING["Rust file-ingester<br/>synthetic slot ← timestamp"]
        ING --> T[("solv1_alpha_trades")]
    end
    HL["headless curl / reqwest"] -->|403| X
    style X fill:#3d2020,stroke:#c1666b,color:#fff
    style BR fill:#264653,stroke:#2a9d8f,color:#fff
    style T fill:#2d2d2d,stroke:#888,color:#fff

Payload schema is rich (per event) — more than RPC swap-parsing recovered:

FieldMeaning
tx_hashtransaction signature
timestampevent time (→ synthetic slot)
event_typebuy / sell / launch
token.addresstraded token mint
token_amounttoken quantity
quote_amountSOL value of the trade
priceexecution price
priority_feeSolana priority fee
tip_feeJito tip
dexvenue (pump.fun / Raydium / PumpSwap / …)
is_open_or_closeposition open vs close marker

No slot → synthetic slot

The wallet_activity payload carries a timestamp but no slot number. The ingester derives a synthetic, monotonic slot from the timestamp so the harness’s ordering/matching logic keeps working. This is an ordering key only — it is not a real slot height and must not be treated as one.

Consequences for the harness

  • Replaced: RPC-indexing tasks A4–A7 (rpc.rs / index.rs / swap.rs / universe indexing) — the whole “reconstruct trades from chain history” path.
  • Reused unchanged: the analysis stack A8–A16 (score → filters → cohort → decay → mirror → benchmarks → verdict), because it depends only on solv1_alpha_trades being populated, not on how.
  • Ingestion becomes semi-manual and two-phase (an agent harvests JSON between Rust stages) — full design in solv1-alpha-gmgn-ingester-increment-2026-07-04.