nautechsystems/nautilus_trader · error

Expected 2 token IDs, received {}

Error message

Expected 2 token IDs, received {}

What it means

parse_gamma_market requires Polymarket binary markets to have exactly two CLOB token IDs (one per outcome). The raw clob_token_ids JSON string is deserialized; if it yields a count other than 2 the parse fails. This invariant underpins mapping token IDs to YES/NO outcome instruments.

Source

Thrown at crates/adapters/polymarket/src/http/parse.rs:118

/// and one for the No outcome.
pub fn parse_gamma_market(market: &GammaMarket) -> anyhow::Result<Vec<PolymarketInstrumentDef>> {
    let game_id = market.game_id.clone().or_else(|| {
        market
            .events
            .as_ref()?
            .iter()
            .find_map(|event| event.game_id.clone())
    });

    let token_ids: Vec<String> = serde_json::from_str(&market.clob_token_ids).map_err(|e| {
        anyhow::anyhow!(
            "Failed to parse clob_token_ids '{}': {e}",
            market.clob_token_ids
        )
    })?;

    if token_ids.len() != 2 {
        anyhow::bail!("Expected 2 token IDs, received {}", token_ids.len());
    }

    let outcomes: Vec<String> = serde_json::from_str(&market.outcomes)
        .map_err(|e| anyhow::anyhow!("Failed to parse outcomes '{}': {e}", market.outcomes))?;

    if outcomes.len() != 2 {
        anyhow::bail!("Expected 2 outcomes, received {}", outcomes.len());
    }

    let tick_size = market
        .order_price_min_tick_size
        .unwrap_or(DEFAULT_TICK_SIZE);
    let price_precision = POLYMARKET_PRICE_PRECISION;

    // Polymarket charges fees using `feeSchedule.rate` on the Gamma market.
    // Only takers pay; makers are always zero.
    // Reference: https://docs.polymarket.com/trading/fees
    let maker_fee: Option<Decimal> = market.fee_schedule.as_ref().map(|_| Decimal::ZERO);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter out markets with non-binary clob_token_ids before calling parse_markets_to_instruments.
  2. Check whether the market belongs to a neg-risk/multi-outcome event and handle those markets separately.
  3. Log the offending market.condition_id and clob_token_ids payload to confirm the API response shape.
  4. Update the parsing code if Polymarket introduced a new market type requiring more than two tokens.

Example fix

// before
let markets: Vec<GammaMarket> = fetch_all(event);
// after
let markets: Vec<GammaMarket> = fetch_all(event)
    .into_iter()
    .filter(|m| serde_json::from_str::<Vec<String>>(&m.clob_token_ids).map(|t| t.len() == 2).unwrap_or(false))
    .collect();
Defensive patterns

Strategy: validation

Validate before calling

fn has_two_token_ids(m: &GammaMarket) -> bool {
    serde_json::from_str::<Vec<String>>(&m.clob_token_ids)
        .map(|t| t.len() == 2)
        .unwrap_or(false)
}
// filter: markets.retain(has_two_token_ids);

Type guard

fn is_binary_token_count(raw: &str) -> bool {
    matches!(serde_json::from_str::<Vec<String>>(raw), Ok(v) if v.len() == 2)
}

Try / catch

match parse_gamma_market(&market, ts_init) {
    Ok(instrument) => instruments.push(instrument),
    Err(e) if e.to_string().starts_with("Expected 2 token IDs") => {
        log::warn!("skipping non-binary market {}: {e}", market.condition_id);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A Gamma market record whose clob_token_ids field deserializes to 0, 1, or 3+ token IDs (e.g. neg-risk multi-outcome markets, empty clob_token_ids, malformed JSON that still parses to a wrong-length array).

Common situations: Querying multi-outcome/neg-risk events where markets legitimately have more than two tokens; new/unresolved markets with empty token arrays; Gamma API schema changes.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/96e8fbfbd343b851. Report an issue: GitHub.