From 95c127bb531dc20afac3d12f313175fb970fa8d0 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 29 Jul 2026 02:49:58 +0200 Subject: [PATCH] fix(pm): Myriad retry, DeFiLlama vol24h, Myriad onchain, remove PredictIt/Smarkets, ForecastEx geo --- benchmarks/pm-geographic-access.yml | 28 +++++----- .../pm-cohort-stats/cmd/script/defillama.go | 52 +++++++++++++++++++ .../pm-cohort-stats/cmd/script/myriad.go | 7 ++- .../pm-cohort-stats/cmd/script/registry.go | 2 +- .../pm-geographic-access/cmd/script/probe.go | 15 ++++-- src/lib/pm-stats.ts | 4 +- 6 files changed, 85 insertions(+), 23 deletions(-) diff --git a/benchmarks/pm-geographic-access.yml b/benchmarks/pm-geographic-access.yml index beccd8a4..42a06f00 100644 --- a/benchmarks/pm-geographic-access.yml +++ b/benchmarks/pm-geographic-access.yml @@ -4,7 +4,7 @@ slug: pm-geographic-access number: "113" status: live title: Prediction market geographic access, which countries can reach each venue -subtitle: HTTP probes from regional nodes checking 200 OK vs blocked or redirect for Polymarket, Kalshi, Limitless, Manifold and Myriad, reported as percent of regions returning a successful response. +subtitle: HTTP probes from regional nodes checking 200 OK vs blocked or redirect for Polymarket, Kalshi, Limitless, Myriad and ForecastEx, reported as percent of regions returning a successful response. category: Trading metric: Geographic accessibility rate unit: pct @@ -13,10 +13,10 @@ higher_is_better: true abstract: | Prediction market venues operate under different regulatory regimes and enforce access restrictions at the HTTP layer. Polymarket's international exchange has geoblocked US IP addresses since a 2022 - CFTC settlement. Polymarket US (QCX), a separate CFTC-regulated entity, launched in December 2025 - and is accessible to US users. Kalshi is CFTC-regulated and accessible to US users without - restriction. Minnesota enacted a law banning prediction markets effective August 1, 2026, which is - being challenged in court by the CFTC. This bench probes each venue's market listing endpoint every + CFTC settlement. Kalshi is CFTC-regulated and accessible to US users without restriction. ForecastEx, + operated by Interactive Brokers and CFTC-regulated, is targeted at US users and may exhibit + geo-restrictions for non-US regions. Myriad Protocol is an onchain venue on the Abstract L2 with + no documented geographic restrictions. This bench probes each venue's market listing endpoint every 15 minutes from probe nodes in EU West (VPS Paris), US East, and Singapore and records the HTTP response code. A 200 OK counts as accessible; a 403, redirect to a block page, or connection reset counts as blocked. The result is expressed as the percentage of probe time the endpoint returned 200 @@ -115,18 +115,18 @@ providers: sample_size: increase(pm_geo_probe_total{venue="limitless"}[24h]) series: 100 * avg_over_time(pm_geo_accessible{venue="limitless"}[1h]) - - slug: manifold - name: Manifold - tag: Play-money, US-accessible, no geographic restrictions + - slug: forecastex + name: ForecastEx + tag: CFTC-regulated by Interactive Brokers, US users only formula: "Fraction of 15-minute probe cycles returning HTTP 200 OK from each probe region, averaged over 24 hours." queries: - p50: 100 * avg_over_time(pm_geo_accessible{venue="manifold"}[24h]) - p90: 100 * avg_over_time(pm_geo_accessible{venue="manifold",region="eu-west"}[24h]) - p99: 100 * avg_over_time(pm_geo_accessible{venue="manifold",region="eu-west"}[24h]) - mean: 100 * avg_over_time(pm_geo_accessible{venue="manifold"}[24h]) + p50: 100 * avg_over_time(pm_geo_accessible{venue="forecastex"}[24h]) + p90: 100 * avg_over_time(pm_geo_accessible{venue="forecastex",region="eu-west"}[24h]) + p99: 100 * avg_over_time(pm_geo_accessible{venue="forecastex",region="eu-west"}[24h]) + mean: 100 * avg_over_time(pm_geo_accessible{venue="forecastex"}[24h]) success: vector(1) - sample_size: increase(pm_geo_probe_total{venue="manifold"}[24h]) - series: 100 * avg_over_time(pm_geo_accessible{venue="manifold"}[1h]) + sample_size: increase(pm_geo_probe_total{venue="forecastex"}[24h]) + series: 100 * avg_over_time(pm_geo_accessible{venue="forecastex"}[1h]) - slug: myriad name: Myriad diff --git a/harnesses/pm-cohort-stats/cmd/script/defillama.go b/harnesses/pm-cohort-stats/cmd/script/defillama.go index 4a7bbc5e..459cc8a1 100644 --- a/harnesses/pm-cohort-stats/cmd/script/defillama.go +++ b/harnesses/pm-cohort-stats/cmd/script/defillama.go @@ -120,9 +120,61 @@ func fetchAllDefillama() { fmt.Printf("[defillama][%s] tvl=%.0f change_1d=%.2f%%\n", slug, row.TVL, row.Change1d) } + // Fetch Polymarket trading volume from the DeFiLlama dexs summary endpoint. + // /protocols only carries TVL; the dexs/summary endpoint exposes the actual + // aggregate trading volume that covers all markets including recently settled + // ones. This overrides the gamma-api vol24h/vol30d with a more complete figure. + fetchDefillamaPolymarketVolume() + pmCohortStatsLastTickUnix.Set(float64(time.Now().Unix())) } +// fetchDefillamaPolymarketVolume queries DeFiLlama's /summary/dexs endpoint +// for Polymarket's aggregate trading volume. The /protocols list only carries +// TVL; this endpoint exposes total24h and total30d which cover all settled +// and active markets — significantly more complete than summing volume24hr +// from the active-only gamma-api pass. The slug tried first is +// "polymarket-international"; "polymarket" is the fallback. If neither +// resolves (endpoint unavailable or network error) the function returns +// silently and the gamma-api values from the Polymarket fetcher carry forward. +func fetchDefillamaPolymarketVolume() { + type dexsResp struct { + Total24h float64 `json:"total24h"` + Total7d float64 `json:"total7d"` + Total30d float64 `json:"total30d"` + } + + slugs := []string{"polymarket-international", "polymarket"} + var r dexsResp + var found bool + for _, s := range slugs { + url := fmt.Sprintf("%s/summary/dexs/%s", defillamaBase, s) + body, err := getJSONDefillama(httpClientDefillama, url) + if err != nil { + continue + } + if err := json.Unmarshal(body, &r); err != nil { + continue + } + if r.Total24h > 0 || r.Total30d > 0 { + found = true + break + } + } + if !found { + fmt.Printf("[defillama-vol][polymarket] dexs endpoint unavailable or zero; keeping gamma-api values\n") + return + } + if r.Total24h > 0 { + pmVenueVolume24hUsd.WithLabelValues("polymarket").Set(r.Total24h) + } + if r.Total30d > 0 { + pmVenueVolume30dUsd.WithLabelValues("polymarket").Set(r.Total30d) + } + fmt.Printf("[defillama-vol][polymarket] vol24h=%.0f vol7d=%.0f vol30d=%.0f\n", + r.Total24h, r.Total7d, r.Total30d) +} + // normalizeName lower-cases and strips spaces / punctuation so // "Manifold Markets" and "manifold-markets" both index to the same key. func normalizeName(s string) string { diff --git a/harnesses/pm-cohort-stats/cmd/script/myriad.go b/harnesses/pm-cohort-stats/cmd/script/myriad.go index fa3f4d2e..30e2c8ac 100644 --- a/harnesses/pm-cohort-stats/cmd/script/myriad.go +++ b/harnesses/pm-cohort-stats/cmd/script/myriad.go @@ -127,9 +127,14 @@ func fetchMyriadVenue(v Venue) { }() // --- Track A: open markets, sorted by lifetime volume_notional. + // Myriad's API occasionally returns 500 on this sort; fall back to + // volume_notional_24h and then to no sort before giving up. openRows, ok := myriadFetchAll(v, "open", "volume_notional") if !ok { - // Nothing publishable on a hard failure. Errors already bucketed. + time.Sleep(2 * time.Second) + openRows, ok = myriadFetchAll(v, "open", "volume_notional_24h") + } + if !ok { return } diff --git a/harnesses/pm-cohort-stats/cmd/script/registry.go b/harnesses/pm-cohort-stats/cmd/script/registry.go index 99f3d773..fbab9f38 100644 --- a/harnesses/pm-cohort-stats/cmd/script/registry.go +++ b/harnesses/pm-cohort-stats/cmd/script/registry.go @@ -30,7 +30,7 @@ var Registry = []Venue{ {Slug: "kalshi", Name: "Kalshi", Type: "offchain", Chain: ""}, {Slug: "limitless", Name: "Limitless", Type: "onchain", Chain: "base"}, {Slug: "manifold", Name: "Manifold", Type: "offchain", Chain: ""}, - {Slug: "myriad", Name: "Myriad", Type: "offchain", Chain: ""}, + {Slug: "myriad", Name: "Myriad", Type: "onchain", Chain: "abstract"}, } // VenueBySlug returns the Venue with the given slug, or nil if not found. diff --git a/harnesses/pm-geographic-access/cmd/script/probe.go b/harnesses/pm-geographic-access/cmd/script/probe.go index 5dea1887..4307f01b 100644 --- a/harnesses/pm-geographic-access/cmd/script/probe.go +++ b/harnesses/pm-geographic-access/cmd/script/probe.go @@ -20,6 +20,13 @@ type venueProbe struct { // venueProbes is the list of market-listing endpoints to probe. // Each URL returns a 200 when the venue is accessible from the probe region, // a 403 when geo-blocked, or a redirect to a block page. +// +// Manifold is intentionally excluded: it is play-money (no real money at +// stake) and has no documented geographic restrictions — it would always +// score 100% from every region, making it uninteresting for this bench. +// ForecastEx (Interactive Brokers CFTC-regulated exchange) is included +// because it is US-regulated and may exhibit geo restrictions from non-US +// regions, mirroring the Kalshi vs Polymarket story for a third regulated venue. var venueProbes = []venueProbe{ { Venue: "polymarket", @@ -33,14 +40,14 @@ var venueProbes = []venueProbe{ Venue: "limitless", URL: "https://api.limitless.exchange/markets/active?page=1&limit=1&sortBy=newest", }, - { - Venue: "manifold", - URL: "https://api.manifold.markets/v0/markets?limit=1", - }, { Venue: "myriad", URL: "https://api-v2.myriadprotocol.com/markets?state=open&limit=1", }, + { + Venue: "forecastex", + URL: "https://forecastex.com/api/v1/contracts", + }, } var httpClientProbe = &http.Client{ diff --git a/src/lib/pm-stats.ts b/src/lib/pm-stats.ts index f4c12205..8ee00198 100644 --- a/src/lib/pm-stats.ts +++ b/src/lib/pm-stats.ts @@ -84,9 +84,7 @@ const PM_VENUES: VenueSeed[] = [ { slug: "polymarket", name: "Polymarket", type: "onchain", chain: "polygon" }, { slug: "kalshi", name: "Kalshi", type: "offchain" }, { slug: "limitless", name: "Limitless", type: "onchain", chain: "base" }, - { slug: "myriad", name: "Myriad", type: "offchain" }, - { slug: "predictit", name: "PredictIt", type: "offchain" }, - { slug: "smarkets", name: "Smarkets", type: "offchain" }, + { slug: "myriad", name: "Myriad", type: "onchain", chain: "abstract" }, ]; const PM_DATA_FEEDS: DataFeedSeed[] = [