{"record":{"id":"96e8fbfbd343b851","repo":"nautechsystems/nautilus_trader","slug":"expected-2-token-ids-received","errorCode":null,"errorMessage":"Expected 2 token IDs, received {}","messagePattern":"Expected 2 token IDs, received (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/polymarket/src/http/parse.rs","lineNumber":118,"sourceCode":"/// and one for the No outcome.\npub fn parse_gamma_market(market: &GammaMarket) -> anyhow::Result<Vec<PolymarketInstrumentDef>> {\n    let game_id = market.game_id.clone().or_else(|| {\n        market\n            .events\n            .as_ref()?\n            .iter()\n            .find_map(|event| event.game_id.clone())\n    });\n\n    let token_ids: Vec<String> = serde_json::from_str(&market.clob_token_ids).map_err(|e| {\n        anyhow::anyhow!(\n            \"Failed to parse clob_token_ids '{}': {e}\",\n            market.clob_token_ids\n        )\n    })?;\n\n    if token_ids.len() != 2 {\n        anyhow::bail!(\"Expected 2 token IDs, received {}\", token_ids.len());\n    }\n\n    let outcomes: Vec<String> = serde_json::from_str(&market.outcomes)\n        .map_err(|e| anyhow::anyhow!(\"Failed to parse outcomes '{}': {e}\", market.outcomes))?;\n\n    if outcomes.len() != 2 {\n        anyhow::bail!(\"Expected 2 outcomes, received {}\", outcomes.len());\n    }\n\n    let tick_size = market\n        .order_price_min_tick_size\n        .unwrap_or(DEFAULT_TICK_SIZE);\n    let price_precision = POLYMARKET_PRICE_PRECISION;\n\n    // Polymarket charges fees using `feeSchedule.rate` on the Gamma market.\n    // Only takers pay; makers are always zero.\n    // Reference: https://docs.polymarket.com/trading/fees\n    let maker_fee: Option<Decimal> = market.fee_schedule.as_ref().map(|_| Decimal::ZERO);","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/polymarket/src/http/parse.rs#L100-L136","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Filter out markets with non-binary clob_token_ids before calling parse_markets_to_instruments.","Check whether the market belongs to a neg-risk/multi-outcome event and handle those markets separately.","Log the offending market.condition_id and clob_token_ids payload to confirm the API response shape.","Update the parsing code if Polymarket introduced a new market type requiring more than two tokens."],"exampleFix":"// before\nlet markets: Vec<GammaMarket> = fetch_all(event);\n// after\nlet markets: Vec<GammaMarket> = fetch_all(event)\n    .into_iter()\n    .filter(|m| serde_json::from_str::<Vec<String>>(&m.clob_token_ids).map(|t| t.len() == 2).unwrap_or(false))\n    .collect();","handlingStrategy":"validation","validationCode":"fn has_two_token_ids(m: &GammaMarket) -> bool {\n    serde_json::from_str::<Vec<String>>(&m.clob_token_ids)\n        .map(|t| t.len() == 2)\n        .unwrap_or(false)\n}\n// filter: markets.retain(has_two_token_ids);","typeGuard":"fn is_binary_token_count(raw: &str) -> bool {\n    matches!(serde_json::from_str::<Vec<String>>(raw), Ok(v) if v.len() == 2)\n}","tryCatchPattern":"match parse_gamma_market(&market, ts_init) {\n    Ok(instrument) => instruments.push(instrument),\n    Err(e) if e.to_string().starts_with(\"Expected 2 token IDs\") => {\n        log::warn!(\"skipping non-binary market {}: {e}\", market.condition_id);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Skip neg-risk/multi-outcome events when requesting binary instruments.","Log skipped market condition_ids for observability.","Pin/verify Gamma API response shape in integration tests."],"tags":["parsing","polymarket","invariant","json"],"backgroundTag":"schema-validation-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}