> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sesameterminal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0016. Combo ids: the pmc: namespace, legs keyed by Gamma position ids, eligibility from Gamma

# 0016. Combo ids: the `pmc:` namespace, legs keyed by Gamma position ids, eligibility from Gamma

* **Status:** Proposed
* **Date:** 2026-10-01

## Context

A combo is its own market with its own condition and positions, separate from the
Predictions CLOB. Its legs are ordinary Predictions outcomes, but the Combos APIs name them
differently from the CLOB ([venue notes §16, "Legs"](../venue/polymarket.md#legs)):

* The RFQ keys legs by Protocol V2 **position ids**, Gamma's `positionIds`, not by CLOB
  token ids (`clobTokenIds`) or condition ids.
* The Data API's `leg_condition_id` on combo positions is not always the Gamma
  `conditionId` (the fixtures show a `0x01…` form).
* Two sources say which markets can be legs: Gamma `comboStatus` on each market, and the
  public RFQ catalog (`combos-rfq-api.polymarket.com/v1/rfq/combo-markets`).

IDs that cross the venue boundary are namespaced ([ADR 0004](0004-venue-capability-boundary.md)),
and every signal has one source (PLAN §3.7).

## Decision

* **Namespace `pmc:`** for the Combos venue: `pmc:<combo conditionId>` for a combo market
  and `pmc:<combo YES positionId>` for a combo position, which is also the Accept order's
  token. **Legs stay `pm:<tokenId>`**, the outcomes the rest of the app already shows,
  prices and links to.
* **Legs are keyed by Gamma position id only.** The metadata cache stores each outcome's
  `positionIds` entry in `outcomes.position_id` (`''` when Gamma sends none), indexed. The
  browser sends leg token ids; `engine/orders` maps each to its position id through the
  catalog, and the adapter sends them as canonical decimals in ascending order. A leg the
  venue reports (a combo position's legs) maps back to an outcome by position id: from the
  cache, or, for a closed market the cache does not hold, from Gamma by `position_ids`
  with `closed=true`, which is then cached. A leg is never matched by condition id or by
  the Data API's `leg_condition_id`.
* **Eligibility comes from Gamma `comboStatus` only**, refreshed with the rest of the
  market metadata into `markets.combo_eligible`: `enabled` is eligible; `pending`,
  `disabled` and any unknown value are not. A market with an outcome whose position id is
  missing is not eligible either. The public RFQ catalog is never read.
* `engine/risk` refuses a quote request with a leg whose market is not eligible, or is
  unknown to the catalog, with `combo_ineligible`, and a closed market with
  `market_closed`. Two outcomes of one market, or of one neg-risk event, are
  `combo_contradictory_legs`.
* The adapter checks that the venue's quote echoes the request (legs by position id, side,
  size, wallet) and names a combo condition before the quote is shown; a mismatch is
  `no_quote` with `combo_quote_mismatch`.

## Consequences

* Leg prices, links and names come from the existing `pm:` outcomes and books; the combo
  itself has no book and no price but the quote ([ADR 0007](0007-prices-only-from-market-feed.md)
  and PLAN §3.7).
* Combo positions and combo trades are told apart from Predictions ones by the `pmc:`
  prefix; Data API trade rows with `is_combo` are mapped to `pmc:` ids.
* Eligibility is as fresh as the catalog's Gamma refresh. A market the venue enables
  shows as eligible after the next refresh; one it disables in between is refused by the
  venue on the quote request. The app does not fill the gap from the catalog endpoint.
* A leg the cache has never seen needs one Gamma request before a combo position can show
  its names.
* Open: security finding P8-05. Gamma sends `outcomes`, `clobTokenIds` and `positionIds`
  as separate arrays, and the cache pairs them by index. The proposed fix marks a market
  ineligible unless its position ids are consecutive from the first, and compares the
  filled position's legs with the Accept's; the live run checks it (row 16.42).

## Alternatives considered

* **Key legs by CLOB token id and let the adapter translate at the edge.** Rejected: the
  translation still needs the position id, and storing it once in the cache keeps one
  mapping.
* **Match legs by condition id.** Rejected: the Data API's leg condition ids do not match
  Gamma's.
* **Read the RFQ catalog for eligibility, or merge it with Gamma.** Rejected: two sources
  for one signal; when one goes stale the merged result still looks right.
* **Reuse `pm:` for combos.** Rejected: combo positions would look like Predictions
  positions to every consumer of `pm:` ids, including the books and the portfolio.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.