Polyrankdocs

Resolutions feed

Cursor-paged bulk read of on-chain condition resolutions, for mechanical settlement.

GET /v1/resolutions is the pull-side companion to the market.resolved webhook. It exists for one job: letting a bot settle resolved positions mechanically, with no dependence on having been online when the resolution happened.

If your bot learns about resolutions only from webhooks, it will eventually miss one — and then it carries that position forever, on mismarked equity, with the capital stuck in a slot that never frees. Persist a cursor and sweep this feed.

Quick start

curl -s "https://api.polyrank.app/v1/resolutions?limit=200&binary_only=true" \
  -H "authorization: Bearer $POLYRANK_API_KEY" | jq
{
  "rows": [
    {
      "condition_id": "0x5f65…",
      "question_id": "0x9c01…",
      "oracle": "0xa1b2…",
      "outcome_slot_count": 2,
      "payout_numerators": ["1", "0"],
      "winner_outcome": "YES",
      "winner_index": 0,
      "is_binary": true,
      "resolved_block": 78431022,
      "resolved_time": "2026-07-21T18:02:55Z",
      "source": "ctf",
      "tx_hash": "0xab12…",
      "log_index": 41,
      "updated_at": "2026-07-21T18:03:10Z"
    }
  ],
  "next_cursor": "Nzg0MzEwMjI6NDE",
  "has_more": true,
  "contract": { "ordering": "…", "backfill_caveat": "…", "recommended_pattern": "…" }
}

Parameters

ParamDefaultMeaning
cursorOpaque cursor from a previous response's next_cursor.
since_updated_atISO-8601. Pages by ingestion time instead. Mutually exclusive with cursor.
limit2001–1000.
binary_onlyfalseRestrict to 2-outcome conditions.

Paging contract

Ordering is a keyset over (resolved_block, log_index) ascending — the on-chain order, which is total and immutable. Paging is strictly forward and, over a stable dataset, never skips or repeats a row. Page while has_more is true.

next_cursor is null only when a page came back empty, which means you are caught up. Keep your previous cursor; do not persist the null.

The one thing that can bite you

The resolutions table also receives backfilled and re-org-corrected rows. Such a row can land with a block number older than a cursor you have already passed, so a pure cursor consumer will never see it.

That is why since_updated_at exists: it pages by ingestion time and does surface those rows, at the cost of re-delivering rows you already have — harmless, because settlement is idempotent.

// steady state: drain forward
let cursor = await store.get('resolutions_cursor');
for (;;) {
  const url = new URL('https://api.polyrank.app/v1/resolutions');
  url.searchParams.set('limit', '500');
  if (cursor) url.searchParams.set('cursor', cursor);
  const page = await get(url);
  if (page.rows.length === 0) break;          // caught up — keep the old cursor
  for (const r of page.rows) await settleIdempotently(r);
  cursor = page.next_cursor;
  await store.set('resolutions_cursor', cursor);
  if (!page.has_more) break;
}

// hourly, additionally: absorb backfilled corrections
const since = new Date(Date.now() - 3 * 86400e3).toISOString();
const sweep = await get(`…/v1/resolutions?since_updated_at=${since}&limit=1000`);
for (const r of sweep.rows) await settleIdempotently(r);

Pair this with the market.resolved webhook for low latency. The webhook makes you fast; this feed is what guarantees you never miss one.

Field notes

  • winner_outcome is YES / NO for binary conditions, may be INVALID, and is null (with winner_index: -1) on ties and multi-outcome payouts. Settle on payout_numerators when winner_index is -1.
  • payout_numerators are returned as strings — they are raw UInt256 values as emitted on-chain and do not fit a JSON number.
  • source is ctf or negrisk. One scan covers both binary and neg-risk sub-conditions.

On this page