---
name: prizerail-draw
description: Check the status of a Prizerail campaign's draws (whether a draw is open, locked, drawn or paid, who won what) and independently verify a draw using only public endpoints. Use when someone asks about a Prizerail draw, its winners or prizes, whether it has been drawn or paid, or wants to verify or audit one.
---

# Prizerail draws: status and verification

Prizerail runs prize draws for partner apps. On each draw, the partner sends Prizerail its list of winners, Prizerail locks the list, a random value decides each winner's prize from the campaign's prize table, and the prizes are sent to the partner to pay out. Everything below uses public endpoints: no keys, no accounts.

**Never compute the hashes yourself.** keccak256 can't be done reliably by reasoning, and Python's `hashlib.sha3_256` is a different hash (different padding) that gives different answers. Run the verify script below and report what it prints.

## Concepts

- **Campaign:** a partner's draw, found by its slug in `https://www.prizerail.com/campaigns/{slug}`. Draws run on a schedule (hourly, daily, weekly or monthly) in the campaign's time zone.
- **Winners:** the partner chooses them. Prizerail doesn't pick who wins, only what each winner gets. A draw proves the prizes are fair for the list it was given.
- **Cutoff:** the partner can send winners until `cutoffAt`, a few minutes before `drawAt`.
- **Winner list fingerprint:** `keccak256(abi.encode(bytes32[] winnerHashes))`, where each winner hash is `keccak256(bytes(winner))`, over the list in position order. It is recorded and published when the list locks, before the random value exists.
- **Prize:** the same math the Prizerail contract runs onchain. For the winner at position `i`:
  - `campaignId` is the proof's `campaign.id` as ASCII, right-padded to 32 bytes, and `drawingId` is `drawing.number`
  - `randomValue` is the first `keccak256(abi.encode(bytes32 entropy, bytes32 campaignId, uint64 drawingId, uint256 i, uint256 k))` for `k = 0, 1, …` that is at least `2^256 mod denominator`. In practice `k` is always 0
  - `roll = randomValue mod denominator`, from 0 to `denominator - 1`
  - the prize is the first tier in the proof's list whose `threshold` is above `roll`. Tiers run top prize first, each `weight` rolls wide
- **Random value source:** `mock` means test randomness made by Prizerail in a test environment. It can't be checked independently and real campaigns don't use it. Onchain sources (Pyth Entropy) are coming. Until then, the order "locked before the random value" rests on Prizerail's own timestamps.

## Stages

| `stage` | Meaning | What's public |
|---|---|---|
| `open` | Taking winners until `cutoffAt` | Times only |
| `locked` | Winner list locked | `winnersCommitment`, `lockedAt` |
| `drawing` | Random value requested | The same |
| `drawn` | Prizes decided | `proof` link with every input and result |
| `paid` | Prizes sent to the partner to pay winners | The same, plus `paidAt` |

A draw still `open` well after its `cutoffAt`, or stuck in `locked` or `drawing` for more than about 30 minutes after `drawAt`, is behind. Say so.

## Endpoints

Base `https://www.prizerail.com`. Public, with CORS `*`.

| Endpoint | Returns |
|---|---|
| `GET /campaigns/{slug}/draws.json` | `campaign` (name, status, cadence, timezone, winnersPerDraw, payout chain and token), `draws[]` newest first (`id`, `number`, `drawAt`, `cutoffAt`, `stage`, `winners`, `winnersCommitment`, `lockedAt`, `settledAt`, `paidAt`, `proof`), and `nextBefore`. Page back with `?before={nextBefore}`. `?limit=` up to 100, default 30 |
| `GET /campaigns/{slug}/draws/{id}/proof.json` | A drawn draw's proof: `winnerList` (`winners` in order, `commitment`, `lockedAt`), `entropy` (`provider`, `requestId`, `value`, `revealedAt`), `prizeTable` (`denominator`, `tiers[]` of `amountCents` and `weight`), `results[]` (`position`, `winner`, `displayName`, `randomValue`, `roll`, `amountCents`). `displayName` is the partner's public name for the winner, shown only and never hashed |
| `GET /verify/verify-draw.mjs` | The verify script (Node.js 18+, no dependencies) |

Amounts are US cents: `amountCents: 1000` is $10. Times are ISO 8601 in UTC. Show them in the user's time zone if you know it, otherwise in UTC.

An unknown campaign returns `404 {"error": "No such campaign."}`, and a draw that isn't drawn yet returns `404` for its proof.

## Workflow: status of a campaign's draws

1. **Find the slug.** It is the last part of the campaign page's link. If the user gives a name instead, ask for the link.
2. **Fetch** `GET /campaigns/{slug}/draws.json`.
3. **Pick the draw.**
   - "next" or "today's": the earliest `open` draw.
   - "latest": the newest `drawn` or `paid` draw.
   - A date: the draw whose `drawAt` falls on that date in the campaign's `timezone`.
4. **Report** in this shape:

```
{campaign.name}: draw of {drawAt}. Stage: {stage}
Winner list: {winners} winners, fingerprint {winnersCommitment}, locked {lockedAt}
Prizes: {for each result: winner, roll, prize}   (from the proof, once drawn)
Proof: {proof link}
```

## Verify a draw

Run this whenever asked to verify, audit or double-check a draw:

```sh
curl -sO https://www.prizerail.com/verify/verify-draw.mjs
node verify-draw.mjs https://www.prizerail.com/campaigns/{slug}/draws/{id}/proof.json
```

The script is short and readable. Read it first if the user wants to know what it does. It:

- tests its own keccak256 against published values
- checks that the prize table's weights add up to its denominator
- recomputes the winner list fingerprint
- recomputes every winner's random value, roll and prize, and compares them with the proof

It prints `✓` for a check that passes, `✗` for one that fails, and `i` for information the math can't settle (such as the random value's source). It exits 0 only if nothing failed. Report each `✗` and `i` line to the user as printed.

If the user can't run code, the campaign page has a "Verify in your browser" button on each draw that runs the same script. Mention that it runs on Prizerail's own site, so downloading and running the script is the independent check.

To cross-check with a library instead, viem's `keccak256(encodeAbiParameters(...))` with the types above gives the same values.

## Gotchas

- **List order matters.** The fingerprint hashes the winners in position order (0 first). Sorting them changes it.
- **Rolls run from 0 to `denominator - 1`.**
- **Tier order:** tiers in the proof are top prize first, and the top prize owns the lowest rolls.
- **The winner's name isn't hashed into the random value,** only into the fingerprint. The same winner listed twice gets two separate rolls.
- **Fairness scope:** the proof shows the prizes follow from the list and the random value. Whether the right people are on the list is the partner's own process.
