Root cause and fix for a silent bug in crates/ingest/src/polymarket.rs that caused discover_active_market to return Ok(None) on every single poll, keeping the emit market-cache empty and producing zero emitted events.

Symptoms

  • discover_active_market returned Ok(None) on every poll.
  • Emit market-cache remained empty.
  • Zero emitted events — no signal fired, no NATS publishes.
  • No panic or error log: the bug was a silent guard failure.

Root Cause

try_build_market validated Polymarket CLOB token IDs using token.parse::<u128>().

Polymarket token IDs are 256-bit (77-decimal-digit) values. The example live token ID at the time:

73086175040291113731614083457106176109233409549997382340708254660069213796683

That value overflows u128 (max ~3.4 × 10³⁸, vs a 77-digit decimal which can reach ~10⁷⁶). The parse fails on every real market. The validation guard therefore rejected every valid token, so try_build_market returned None for every market response, and discover_active_market propagated Ok(None) indefinitely.

Design trap

Polymarket CLOB token IDs look like large integers but are opaque 256-bit decimal strings. They must never be parsed into any fixed-width integer type (u128, u64, i64, etc.). Always treat them as digit strings.

A secondary issue: the GammaMarket struct had two serde field-name mismatches that silently produced default/zero values for order sizing fields:

Struct fieldWrong serde nameCorrect serde nameLive value
min_sizeminSizeorderMinSize5
tick_sizetickSizeorderPriceMinTickSize0.01

Fix

Commit 7bfe3a0, branch main.

1. Token ID validation — u256-safe digit-string check

Replaced token.parse::<u128>() with:

!s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())

This validates that the token ID is a non-empty decimal string without imposing any width limit.

2. Serde field-name corrections on GammaMarket

// before
#[serde(rename = "minSize")]
pub min_size: f64,
#[serde(rename = "tickSize")]
pub tick_size: f64,
 
// after
#[serde(rename = "orderMinSize")]
pub min_size: f64,
#[serde(rename = "orderPriceMinTickSize")]
pub tick_size: f64,

3. Regression tests added

Three tests cover the fix:

  • accepts_u256_token_id — passes the 77-digit live token ID; must return Some(market).
  • invalid_token_id_empty_returns_none — empty string; must return None.
  • gamma_market_deserializes_order_field_names — deserializes a minimal Gamma JSON payload with orderMinSize/orderPriceMinTickSize; asserts min_size == 5.0 and tick_size == 0.01.

Lesson

For Agents

Polymarket CLOB token IDs are opaque 256-bit decimal strings — field names clobTokenIds in CLOB API, nested in Gamma tokens[].token_id. Never parse into any fixed-width integer. The canonical safe check: !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()). Any u128/u64/i64 parse on a Polymarket token ID is wrong and will silently reject all real markets.

When deserializing GammaMarket (Gamma REST API /markets), the relevant field names are:

ConceptGamma JSON key
Minimum order sizeorderMinSize
Minimum tick sizeorderPriceMinTickSize
CLOB token IDsclobTokenIds (array of strings)
Condition IDconditionId

These differ from the shorter camelCase names (minSize, tickSize) that appear in some Polymarket documentation and SDK examples.

Affected File

crates/ingest/src/polymarket.rstry_build_market, GammaMarket struct.

How It Was Found

Root cause was found via a live Gamma probe (no VM logs needed): emit fired 0 times in ~2 days of dry-validate. The probe deserialized a real Gamma /markets response, which exposed both the u128 overflow on the 77-digit token id and the wrong serde field names. The condition_id-absent hypothesis was REFUTED — Gamma supplies a real 0x conditionId. The tickSize mistake was a 10× error: Gamma’s real tick is 0.01 (orderPriceMinTickSize) but the wrong tickSize rename defaulted to 0.001.

Separate Issue — observe went ~5h+ stale

Distinct from the emit bug: observe also went ~5h+ stale (latest window lagging), a separate feeds issue, likely the shared Alchemy creds drain (@polymarket #269). This MUST also clear — if observe has no live windows, emit has nothing to fire on even after the token fix. Pending @deploy.