Protocol reference · Solana devnet

Specification

On-chain proof of who won. What a verified result guarantees, how outcomes are resolved, the committed artifact formats, the replay rules and the public API surface.

solana devnet SDK 0.9.1 rng_v1 · rng_v2 SHA-256 replay by signature Markdown for agents
01

Overview

VRE commits the rules of an outcome on Solana before the result exists. Anyone can later recompute the result from the transaction signature, using only public RPC data, and compare it with what the program recorded.

What a MATCH proves

  • The rules did not change. Participants, weights or scores, formula and winner count are the exact bytes stored on-chain under the committed artifact hash, and that artifact was bound to the runtime before resolution.
  • The winners follow from the rules. Replaying the committed bytes with the randomness the program recorded yields the same winners, in the same order.
  • The records agree. Input, output, winner count, winner ids and effect digests match across the event, the resolution account and the replay.

What it does not prove

  • That the list was fair. VRE proves the list did not change after commit, not that it was complete or honest when committed.
  • That the operator could not foresee the result on every path. This depends on the randomness source; see §02.
  • That a prize was paid. Attestation paths prove the winners. Paying them is the integrator's step.

Network

Program ID
6QtLot4uztCfjUuPreoiYPc3e1gH2fg6pq8hKYyvCP6B
Cluster
Solana devnet. Mainnet is planned for this year.
Last program deploy
slot 497829503 · 2026-09-13
SDK
verifiable-outcome-sdk 0.9.1

A plain Markdown copy of this page for agents and tools lives at /spec.md, indexed from /llms.txt.

02

Resolution Model

Every path has the same shape: commit the rules, bind a runtime to them, resolve, replay.

  1. Commit. submit_compiled_artifact, init_compiled_artifact_chunk, write_compiled_artifact_chunk and finalize_compiled_artifact store the artifact bytes in chunk accounts keyed by their SHA-256. review_compiled_artifact, signed by the ProgramConfig admin, approves the artifact for binding.
  2. Bind. initialize_outcome_config creates a runtime, identified by runtime_id, bound to one artifact hash.
  3. Resolve. One of the paths in §06 writes an OutcomeResolution account and emits a resolution event.
  4. Replay. Any verifier rebuilds the artifact from its chunks, recomputes randomness and winners, and compares them with the records (§04).

Randomness sources

SourceWhere the entropy comes fromOperator can / cannotStatus
rng_v1A master_seed set by the operator when the runtime is created.Cannot change the rules or the recorded result. Can compute the result before sending the transaction.Legacy. No public endpoint uses it; not planned for mainnet.
rng_v2 · single phase (v1.2)Hashes of three Solana slots fixed at prepare, that do not exist yet.Cannot predict the result. Can decline to settle after the slots land; an unsettled draw stays on-chain and expires.Canonical. Default.
rng_v2 · two phase (v1.3.2)Same slot hashes; anyone may seal them in.Cannot predict and cannot abort: after seal the outcome is fixed, and skipping attest forfeits the stake.Canonical. Opt-in per draw.
Unicorn-G (B8)A VDF output verified with Groth16.Designed to remove residual validator influence on the seed.Implemented; not deployed to the canonical program; see §06.
randomness derivation
# rng_v1 randomness = SHA256(master_seed ‖ runtime_id ‖ resolve_id_u64le ‖ actor) # rng_v2 # offsets by path: draws { 8, 16, 24 } · sortition { 75, 90, 105 } · slot spins { 3, 4, 5 } target_slots = prepare_slot + offsets # each target resolves to itself, or to the next produced slot after the previous resolved one seed = SHA256("VRE_RNG_V2" ‖ runtime_id ‖ resolve_id_u64le ‖ compiled_artifact_hash ‖ snapshot_hash ‖ (slot_u64le ‖ slot_hash) × 3 [‖ operator_commit_hash]) randomness = SHA256(seed ‖ runtime_id ‖ resolve_id_u64le ‖ actor) expiry_slot = target_slots[2] + 480 # snapshot_hash is 32 zero bytes for compact artifacts

The resolved slots and slot hashes are written into the resolution account and the event, so replay never needs historical SlotHashes.

Which path the public endpoints use today

EndpointResolution path
POST /api/examples/draw
POST /api/partner/draw
POST /api/live-raffle
rng_v2 single phase, in one request: commit, prepare_rng_v2, wait for the target slots, settle_rng_v2, replay.
POST /api/partner/snapshot/finalizerng_v2 single phase on a format 7 snapshot artifact, in the background: publish the snapshot, commit, prepare_rng_v2, wait, settle_rng_v2 with the claimed winners, replay.
POST /api/partner/sortition/seal
POST /api/examples/sortition/seal
Sortition seal on future slot hashes.
POST /api/partner/conditions/resolveresolve_conditions: deterministic scoring of committed slips against revealed facts. No randomness.
03

Artifact Formats

Rules are serialized into a binary artifact that starts with the magic "W3O1" and a u16 format version. All integers are little-endian. An artifact is at most 8,192 bytes. Participant ids are printable ASCII, zero-padded to 64 bytes, and strictly ascending by bytes.

fmtNameWinners selected byRandomnessShape
1W3O1 v1Program, from the on-chain tablerng_v1 · rng_v2Weighted single winner, per-outcome SOL effects.
2W3O1 v2Programrng_v1 · rng_v2Weighted multi-winner, without replacement.
3W3O1 v3Programrng_v1 · rng_v2Native formulas with signed scores and stable order.
4Snapshot v4Caller claim, checked by snapshot replayrng_v1Legacy snapshot line.
5Outcome Standard V1.1 compactProgramrng_v1 · rng_v2Named entries, one payout per winner, up to 103 entries.
6Outcome Standard V1.2 scaleCaller claim, snapshot replayrng_v1Snapshot hash and URI on-chain, list off-chain.
7Outcome Standard V1.2.1 scaleCaller claim bound by winner_claim_hash, snapshot and Merkle replayrng_v1 · rng_v2Large lists off-chain, optional Merkle root on-chain.
8composite_rank_descProgramrng_v1 · rng_v2 (accepted, not yet in the end-to-end suite)Multi-field weighted score, ranked descending.
9Conditions fc7Program scores slips against factsnoneresolve_conditions only.
10Slot SF1Program replays reelsfuture slot hashesprepare_slot_spin and settle_slot_spin.

Artifacts resolved through rng_v2 must be payout-free: the header payout and every effect amount are zero. The program then records who won, and the integrator pays.

Selection rules

  • weighted_random. Round r takes a u64 from the first 8 bytes of the randomness when r = 0, and from SHA-256 of a domain tag, the randomness and r otherwise. It selects the entry whose cumulative weight covers u64 mod total_weight among entries not yet chosen.
  • rank_desc. Score descending, ties by order ascending.
  • rank_asc. Score ascending, ties by order ascending.
  • first_n. order ascending.
  • closest_to. |score − target| ascending, computed in 128 bits, ties by order ascending.
  • Compact winner digest. On rng_v2 the program binds the full winner list as winner_claim_hash = SHA256("VRE_RNG_V2_WINNERS" ‖ count_u16le ‖ Σ(len_u8 ‖ id_bytes)).

W3O1 v1–v3 layout (fmt 1, 2, 3)

Header fieldTypeNotes
magicbytes[4]"W3O1"
format_versionu161, 2 or 3
min_input_lamports · max_input_lamportsu64 · u64Must equal the runtime bounds.
outcome_count · total_effect_countu16 · u16
v1: reservedbytes[8]Zero. One winner, weighted_random.
v2: winners_count · reservedu16 · bytes[6]weighted_random.
v3: winners_count · formula_code · reserved · target_scoreu16 · u8 · bytes[5] · i641 weighted_random, 2 rank_desc, 3 rank_asc, 4 first_n, 5 closest_to.
Outcome recordTypeNotes
outcome_id_len · outcome_idu8 · bytes[64]Printable ASCII, zero-padded.
weightu32Greater than zero.
v3 only: score · orderi64 · u16order values are unique.
first_effect_index · effect_countu16 · u16Slice of the effect table.
effectu8 · bytes[7] · u64Type 1 = transfer SOL, reserved zero, amount.

Compact V1.1 layout (fmt 5)

FieldTypeNotes
magic · format_versionbytes[4] · u16fmt 5
min_input_lamports · max_input_lamportsu64 · u64
winners_count · formula_code · reserved · target_scoreu16 · u8 · bytes[5] · i6448-byte header in total.
entry_count · payout_lamportsu16 · u64One payout for every winner.
entryu8 · bytes[64] · u32 · i64 · u16id_len · id · weight · score · order, 79 bytes each.

Scale layout (fmt 6, 7)

FieldTypeNotes
magic · format_versionbytes[4] · u16fmt 6 or 7
min_input_lamports · max_input_lamportsu64 · u64
winners_count · formula_code · flags · reservedu16 · u8 · u8 · bytes[4]Flag bit 0: Merkle root present.
target_score · snapshot_count · payout_lamportsi64 · u32 · u64
snapshot_hashbytes[32]SHA-256 of the canonical snapshot.
merkle_rootbytes[32]Only when flag bit 0 is set.
snapshot_uri_len · snapshot_uriu16 · bytes1–512 bytes, no control characters.

Composite, conditions and slot layouts are encoded and replayed by the SDK modules listed in §08.

04

Verification

Given a transaction signature and a program ID, the verifier:

  1. Fetches the transaction logs and finds the resolution event emitted by that program. Conditions and slot events go to their dedicated replays.
  2. Loads the runtime's OutcomeConfig, the OutcomeResolution, the ApprovedOutcomeArtifact header and, for rng_v2, the RngV2State.
  3. Checks the artifact hash is the same in the event, config, resolution and header, and that the artifact is finalized with a status that allows binding.
  4. Rebuilds the artifact from its chunk accounts and re-hashes it.
  5. Recomputes the randomness: from master_seed for rng_v1, from the recorded slots and slot hashes for rng_v2, checking any operator reveal against its commitment.
  6. Replays winner selection. Compact artifacts replay the on-chain table; scale artifacts fetch the snapshot from the committed URI or a hash-addressed mirror (see Snapshot sources in §08) and check its hash, count and Merkle root.
  7. Compares input, output, winner count, winner ids and their padding, winner digest and effect digest. All equal: MATCH / OK. Otherwise the first failing check is returned as the reason.

HTTP response

POST /api/replay · devnet, 2026-09-13
curl -s https://verifiableoutcome.online/api/replay \ -H "content-type: application/json" \ -d '{"signature":"237xT8M5YE9drfMTdJKRCrgbWU6b1pZAB7YPHZkgy8A3pqi8Jtf3TrAVCBaLa6GfXDS5MaMNAEsSkjRyf5aCYEgw"}' → { "ok": true, "replay": { "verification_result": "MATCH", "verification_reason": "OK", "outcome_id": "entry_02", "outcome_ids": ["entry_02", "entry_03", "entry_04"], "winners_count": 3, "artifact_format_version": 5, "resolution_formula": "weighted_random", "runtime_id": "64bfee3c3b1bd09d6a6160bd46ed7b82", "resolve_id": "0", "compiled_artifact_hash": "bbd8ea03e8a3dfc00f8e921b88a50941a9d17ead5ddf4f228d8f77cedf3fc0c9", "program_id": "6QtLot4uztCfjUuPreoiYPc3e1gH2fg6pq8hKYyvCP6B", "outcomes": [ … ] }, "stdout": "…", "stderr": "…" }

Request fields: signature (or sig) and an optional programId, which defaults to the canonical program. RPC and local-artifact overrides are disabled on the public host.

SDK result

verifyOutcome returns the same data with status and reason in place of verification_result and verification_reason. Fields appear only when they apply.

GroupFields
Alwaysstatus, reason, outcome_id, resolve_id, compiled_artifact_hash, runtime_id, program_id
Winners and rulesoutcome_ids, winners_count, artifact_format_version, resolution_formula, target, standard_version, entry_count, outcomes[]
rng_v2rng_version, target_slots, resolved_slots, slot_hashes, operator_commit_hash, winner_claim_hash, entropy_source, protocol_revision
Scale snapshotsnapshot_hash, snapshot_count, snapshot_uri, merkle_root
Conditionsentrant_count, winning_score_sum, winners_digest, facts_ref_root
Slot spinactor, input_lamports, payout_lamports, raw_win_lamports, reel_stops, randomness, settled_at_slot

Reason codes

MATCH always carries OK. A MISMATCH carries one of these codes.

CodeMeaning
OKEvery transaction, account, artifact, randomness, winner and digest check passed.
ERR_TX_NOT_FOUND_OR_NO_LOGSThe RPC could not return the transaction or its logs.
ERR_PROGRAM_ID_MISMATCHThe requested program was not invoked, or the event came from another program.
ERR_EVENT_DISCRIMINATOR_MISMATCHProgram data was present but is not a supported resolution event.
ERR_EVENT_NOT_FOUND_FOR_PROGRAMNo resolution event from the requested program.
ERR_OUTCOME_CONFIG_NOT_FOUNDThe runtime's OutcomeConfig is missing or undecodable.
ERR_RESOLUTION_ACCOUNT_NOT_FOUNDThe OutcomeResolution account is missing or undecodable.
ERR_ARTIFACT_HEADER_NOT_FOUNDThe ApprovedOutcomeArtifact header is missing or undecodable.
ERR_ARTIFACT_CHUNK_MISSINGAn artifact chunk is missing, unreadable, misplaced or incomplete.
ERR_CONFIG_HASH_MISMATCHThe config's artifact hash differs from the event.
ERR_RESOLUTION_HASH_MISMATCHThe resolution account's hash, runtime or resolve id differs from the event.
ERR_ARTIFACT_HASH_MISMATCHThe header, rebuilt bytes or a supplied file do not hash to the event's artifact hash.
ERR_ARTIFACT_NOT_FINALIZEDThe artifact was never finalized.
ERR_ARTIFACT_STATUS_INVALIDThe artifact status does not allow binding under the program config.
ERR_ARTIFACT_FORMAT_VERSION_MISMATCHThe format version differs between event, resolution account and artifact bytes.
ERR_RANDOMNESS_MISMATCHRecomputed randomness differs from the event or the resolution account.
ERR_RNG_V2_EVIDENCE_MISSINGResolved slots, slot hashes or commit evidence are missing.
ERR_RNG_V2_SNAPSHOT_HASH_MISMATCHThe snapshot hash in the event differs from the artifact, or is not zero for a compact artifact.
ERR_SNAPSHOT_UNAVAILABLENo source — the committed URI, a hash-addressed mirror or a local copy — served the snapshot with the committed hash. The result is unverifiable for now, not disproven.
ERR_RNG_V2_STATE_MISMATCHThe event differs from the prepared RngV2State.
ERR_RNG_V2_RESOLUTION_MISMATCHrng_v2 evidence differs between the event and the resolution account.
ERR_RNG_V2_OPERATOR_REVEAL_MISMATCHThe operator reveal is missing or does not hash to its commitment.
ERR_INPUT_MISMATCHInput lamports differ between event, account and replay.
ERR_OUTPUT_MISMATCHOutput lamports differ between event, account and replay.
ERR_WINNER_COUNT_MISMATCHWinner count differs between event, account, artifact and replay.
ERR_OUTCOME_ID_MISMATCHA winner id, its length or its zero padding differs from the replay.
ERR_WINNER_CLAIM_HASH_MISMATCHThe recorded winner digest differs from the digest of the replayed winner list.
ERR_EFFECTS_DIGEST_MISMATCHEffect count or effect digest differs from the replay.
ERR_CONDITIONS_ENTRANT_COUNT_MISMATCHConditions: the entrant count differs from the committed artifact.
ERR_CONDITIONS_FACTS_ROOT_MISMATCHConditions: the facts root differs from the event.
ERR_CONDITIONS_SCORE_SUM_MISMATCHConditions: the recomputed winning score sum differs from the event.
ERR_CONDITIONS_WINNERS_DIGEST_MISMATCHConditions: the recomputed ranking digest differs from the event.
ERR_SLOT_RANDOMNESS_MISMATCHSlot spin: randomness from the frozen slot evidence differs from the event.
ERR_SLOT_REEL_STOPS_MISMATCHSlot spin: replayed reel stops differ from the event.
ERR_SLOT_WIN_MISMATCHSlot spin: the replayed raw win differs from the event.
ERR_SLOT_PAYOUT_MISMATCHSlot spin: min(raw win, max-win cap) differs from the payout.
ERR_REPLAY_UNHANDLEDInternal error or unsupported shape. On the public devnet RPC it can also mean a temporary rate limit: retry after 20–30 seconds before treating it as a failure.
05

Canonical Program

One program on devnet carries every path. Integrators use it directly; a dedicated instance is possible under a partner agreement.

Program ID
6QtLot4uztCfjUuPreoiYPc3e1gH2fg6pq8hKYyvCP6B
Upgrade authority
9Ei6bPifoK3UKkAPn5eMTHboFjCe6wF2jSRcrGBtqe4b
ProgramConfig admin
CJN7pVC8LCsia4QPy1yWiTX3iQMtVbnwjoUmyNK4ce2Y
Treasury
GQcdfnLVQn8Jur7SAyokBGL9uspveS2GbUo7r95kgLQm

Key separation

The upgrade authority can replace the code and nothing else. The ProgramConfig admin signs operational calls, including review_compiled_artifact, and cannot upgrade the program, so a compromised operator host cannot swap the code. Both are single keys on devnet. Mainnet governance is decided separately and is not implied by the devnet setup.

ProgramConfig

FieldTypeDevnet value
adminPubkeyCJN7pVC8LCsia4QPy1yWiTX3iQMtVbnwjoUmyNK4ce2Y
allow_unreviewed_bindingboolfalse
fee_lamportsu640
treasuryPubkeyGQcdfnLVQn8Jur7SAyokBGL9uspveS2GbUo7r95kgLQm

Account addresses

AccountPDA seeds
ProgramConfig["outcome_program_config"]
ApprovedOutcomeArtifact["approved_outcome_artifact", artifact_hash]
artifact chunk["approved_outcome_artifact_chunk", artifact_hash, index_u32le]
OutcomeConfig["outcome_config", runtime_id]
vault["outcome_vault", runtime_id]
OutcomeResolution["outcome_resolution", runtime_id, resolve_id_u64le]
RngV2State["rng_v2_state", runtime_id, resolve_id_u64le]

Protocol fee

fee_lamports is 0 on devnet and can be changed by the admin without an upgrade. When set, it is charged by resolve_outcome* and settle_rng_v2 from the calling operator, snapshotted at prepare_rng_v2 and taken from the stake at attest_winners, and charged on paid sortition registration. Verification is always free.

Instructions

GroupInstructions
Config and admininitialize_program_config, set_program_config, recover_program_config_admin, admin_pause, admin_withdraw
Artifact commitsubmit_compiled_artifact, init_compiled_artifact_chunk, write_compiled_artifact_chunk, finalize_compiled_artifact, review_compiled_artifact, initialize_outcome_config
rng_v1 resolveresolve_outcome, resolve_outcome_v4, resolve_outcome_v121, refresh_master_seed
rng_v2prepare_rng_v2, settle_rng_v2, seal_randomness, attest_winners, expire_rng_v2, expire_sealed_rng_v2
Sortitioncreate_sortition_draw, register_sortition_ticket, register_sortition_ticket_paid, seal_sortition_draw, claim_sortition_ticket, init_sortition_economics, close_sortition_ticket, sweep_sortition_tickets
Conditionsresolve_conditions
Slotprepare_slot_spin, settle_slot_spin
Unicorn-G (source only)initialize_unicorn_g_config, set_unicorn_g_config, prepare_rng_v2_unicorn, seal_randomness_unicorn

Compute and heap

rng_v2 prepare_rng_v2, settle_rng_v2 and attest_winners on a compact artifact parse the whole outcome table on-chain. Add ComputeBudgetProgram.requestHeapFrame up to 256 KiB and a compute limit of about 1.4M units. Measured on 103 entries: settle 174k–266k CU, attest 142k–231k CU. Other instructions keep the default 32 KiB heap.

06

Resolution Paths

VersionPathStatus on the canonical program
rng_v1Operator-seed resolveLegacy · retained
v1.2Single-phase settleCanonical · default
v1.3.2Two-phase seal and attestCanonical · opt-in
2026-09-09Compact artifacts on rng_v2Canonical
v1.4.0Sortition-by-ClaimCanonical
v1.4.1Sortition economicsCanonical
fc7ConditionsCanonical · attestation only
SF1Slot spinsInstructions on the canonical program; demo game on its own program
B8Unicorn-G entropyResearch · not in the current build

Single-phase settle (v1.2) · default

prepare_rng_v2 fixes the rules, the stake and three future target slots, 8, 16 and 24 slots ahead. A Solana leader produces four slots in a row, so the first target is always at least two leader groups after the slot that included prepare, and the three targets fall in three different leader groups. Groups are not validators: one validator can lead several groups, and a skipped target moves to the next produced slot after the previous resolved one. A devnet draw on 2026-09-13 shows both: eight slots were skipped, targets ending 680, 688, 696 resolved to 688, 689, 696, and one validator produced all three. Measured over 5,000 recent slots on 2026-09-13: on mainnet one validator produced two of the three hashes in 3.1% of draws with these offsets, against 2.3% with 75/90/105, and never all three; on devnet, with 12 leaders and a 22.5% skip rate, one validator produced two or more in about 73% of draws and all three in about 10%, whatever the offsets. After the last target slot and before expiry, the same operator calls settle_rng_v2, which reads SlotHashes, derives the randomness, resolves the artifact and writes the resolution. The operator cannot predict the result. It can still choose not to settle; the prepared draw then remains visible on-chain and expires through expire_rng_v2, which refunds the stake. The public draw endpoints run this path in one request.

Two-phase seal and attest (v1.3.2)

  1. prepare — the operator opens the draw with a stake and a bounty.
  2. seal — seal_randomness is permissionless. Once the target slots land, anyone can seal, fixing the outcome and earning the bounty.
  3. attest — attest_winners, by the operator or the configured attester, records the resolution, takes the fee and refunds the rest of the stake.
  4. expire — a never-sealed draw expires and its bounty goes to the treasury; a sealed draw not attested by the deadline, 480 slots after expiry, can be closed by anyone with expire_sealed_rng_v2.

Use it when the operator has an interest in the result and must not be able to back out.

Compact artifacts on rng_v2

Since 2026-09-09 both rng_v2 lifecycles accept compact formats 1, 2, 3, 5 and 8, not only scale format 7. For compact artifacts the program selects the winners itself, so callers pass empty winner arguments; non-empty ones fail with RngV2CompactArgsMustBeEmpty. The resolution stores winner_claim_hash as the digest of the winner list, and the list is emitted in OutcomeResolvedV5. Scale artifacts keep caller-claimed winners proved by snapshot replay and emit OutcomeResolvedV4. Both must be payout-free. SDK 0.9.1 verifies these draws, and the public draw endpoints resolve through this path.

Sortition-by-Claim (v1.4.0)

Entrants register sha256(secret ‖ ticket_id ‖ draw_id) before a deadline. seal_sortition_draw fixes omega from three future slot hashes, 75, 90 and 105 slots after the draw was created; registration must close before the first of them. Each entrant claims by revealing the secret and wins when sha256(secret ‖ omega) < threshold.

  • Cost. A claim uses about 8,200 CU, under the 10k budget.
  • Coverage. 5 of 5 negative cases rejected.
  • Distribution. 200 draws of 100 tickets with 10 expected winners: mean 9.7, variance 8.76, against a binomial mean of 10 and variance 9.

Winners claim for themselves, so the operator pays nothing per winner. Fits airdrops, allowlists and mass rewards.

Sortition economics (v1.4.1)

An optional fee layer, byte-compatible with 1.4.0 draws. fee_mode selects an entry_fee_lamports at registration and a close_skim_bps skim at close. close_split(rent, skim_bps) guarantees refund + skim == rent. Measured: paid register 18,096 CU, init economics 5,234 CU, close 9,643 CU, sweep of two tickets 18,459 CU; 9 of 9 negative cases rejected.

Conditions (fc7)

Scores committed prediction slips, each a set of legs with odds, against revealed match facts, and ranks entrants. The off-chain scorer at POST /api/conditions/score is byte-identical to on-chain resolve_conditions, which uses about 15,700 CU. The event ConditionsResolvedV1 carries winners_digest, facts_ref_root and winning_score_sum. VRE proves the ranking; payout stays with the partner.

Slot spins (SF1)

prepare_slot_spin binds the player, the bet and the committed game before the entropy exists. settle_slot_spin derives randomness from future slot hashes, replays the reels from the artifact, applies the max-win cap and pays the bound player from the vault. The public demo game runs on its own devnet program, 6R5n5uPTj1bg4xz6P3xCmVkyorTySbLQQZuENe8SSaKK; verify its spins with the SDK and that program ID.

Unicorn-G entropy (B8) · implemented, not deployed

An alternative entropy source: a VDF output verified on-chain with an embedded Groth16 verifier. It was measured on devnet in June 2026: seal with verification 247,476 CU, attest 61,466 CU, and a wrong proof rejected as InvalidUnicornGProof. Its instructions sit behind a build feature that the canonical program is not built with. Before mainnet it needs the circuit soundness fix and a trusted-setup ceremony.

07

HTTP API

Base URL https://verifiableoutcome.online. JSON in, JSON out. Draw endpoints sign with the server's operator key, so callers never hold one. A draw resolves through rng_v2 inside the request: the server commits the rules, waits for the future target slots, about 10 seconds, then settles and replays, so a draw call takes 12–45 seconds in total.

The canonical program binds only artifacts approved by the ProgramConfig admin, so a draw cannot be resolved there with your own wallet; the API runs it with the operator key. Sortition instructions need no admin and can be sent from any funded wallet (§08).

Limits and statuses

LimitDefault
General API, per IP60 requests / 60 s
POST /api/replay, per IP12 requests / 60 s
POST /api/examples/draw, per IP1 draw / 15 s
POST /api/live-raffle1 draw / 10 s
POST /api/partner/draw, per key1 draw / 10 s
POST /api/examples/sortition/draw, per IP1 draw / 15 s

Statuses: 200 success; 400 validation error; 401 missing or unknown key; 403 key without the needed permission; 404 unknown conditions round, or an account that does not exist (AccountNotInitialized); 409 a scale-shaped body sent to a draw endpoint, a duplicate snapshot chunk, or a program state conflict (SortitionRegistrationOpen, SortitionRegistrationClosed, SortitionClaimWindowClosed, SortitionAlreadyClaimed, AccountAlreadyInUse for a reused id); 413 participant table too large for a compact artifact; 422 the program rejected the input (SortitionPredicateNotMet for a losing ticket, InvalidSortitionSecret, other InvalidSortition*); 429 rate limit, counted from the start of the previous draw; 500 server-side failure; 503 partner config absent on the host; 504 devnet timeout, retry.

When the program rejects an instruction the body carries error_code (the program error name), error_number and a readable error: { "ok": false, "error": "Sortition predicate not met", "error_code": "SortitionPredicateNotMet", "error_number": 6045 }.

Public, no key

EndpointRequestResponse
POST /api/replaysignature, programId?Verdict, see §04.
POST /api/timelinesignature, compiledArtifactHash, programId?{ ok, artifact_slot, resolution_slot, gap_slots }
GET /api/recent-resolutions?limit={ ok, resolutions: [{ signature, outcome_id, outcome_ids, winners_count, artifact_format_version, standard_version, rng_version, resolution_formula, target, verification_result, verification_reason, runtime_id, resolve_id, compiled_artifact_hash, participants_count, snapshot_count, snapshot_hash, snapshot_uri, merkle_root, commit_slot, resolve_slot, updated_utc, source }] }
GET /api/blessed-signatures{ ok, data }
GET /api/health{ ok, program_id, rpc, operator: { mode, program_id }, world_id: { enabled, environment, … }, blessed_signatures_count }
GET /api/spec{ ok, summary, blessed }
POST /api/conditions/scorefixtures, entrantsScores and ranking, see below.
POST /api/examples/drawSame body as partner draw.Same response as partner draw. Runs on the server's demo key.
POST /api/examples/sortition/drawSame body as partner sortition draw.Creates a sortition draw on the demo key.
POST /api/examples/sortition/registerdraw_id, ticket_id, commitmentRegisters a ticket commitment.
POST /api/examples/sortition/sealdraw_idSeals omega from future slot hashes.
POST /api/examples/sortition/claimdraw_id, ticket_id, secretReveals and claims a ticket.

Scoring conditions without a key

POST /api/conditions/score
curl -s -X POST https://verifiableoutcome.online/api/conditions/score \ -H "content-type: application/json" -d '{ "fixtures": [ { "fixture_id": 1, "stats": [{"stat_key":1,"value":2},{"stat_key":2,"value":0}] }, { "fixture_id": 2, "stats": [{"stat_key":1,"value":1},{"stat_key":2,"value":1}] } ], "entrants": [ { "wallet": "HvVt…UUava", "tier": 50, "entered_at": 100, "fixture_index": [0,1], "legs": [ {"stat_key_a":1,"stat_key_b":2,"op":2,"comparison":0,"odds_milli":2150}, {"stat_key_a":1,"stat_key_b":2,"op":1,"comparison":1,"line_x2":5,"odds_milli":1850} ]} ] }' → { "ok": true, "formula": "conditions", "formula_code": 7, "format_version": 9, "winners_count": 1, "winning_score_sum": 39775, "ranking": [{ "rank": 1, "wallet": "HvVt…", "all_hit": true, "score_fixed": 39775, "slip_hash": "4dc5…" }], "artifact": { "hash": "a084be42…", "bytes": 148, "hex": "57334f31…" } }

Leg encoding: op 1 adds, for totals, and 2 subtracts, for 1x2; comparison 0 greater, 1 less, 2 equal; line_x2 is the line times two, totals only. For full-match goals stat_key 1 is side one and 2 is side two.

Live raffle with a wallet

EndpointRequestResponse
POST /api/live-raffleaddress, requireWorldId?, worldId?{ ok, signature, outcome, outcome_ids, runtimeId, resolveId, artifactHash, programId, participantCount, world_id?, rng_version, entropy_source, prepare_signature, target_slots, resolved_slots, winner_claim_hash }
POST /api/world-id/rp-context{ ok, enabled, …, rp_context }

With requireWorldId, the caller sends a World ID 4.0 proof; the server verifies it and reserves its nullifier, so one human enters once. The public host runs World ID in staging, through the simulator. Wallet pages read only the address through the Wallet Standard and never ask to sign.

Partner API

Every partner endpoint takes x-api-key: vresk_<32 hex>. Keys are issued on request in Telegram and stored server-side only. Draws additionally require draw_enabled on the key.

EndpointRequestResponse
POST /api/partner/drawformula, participants, winners_count?, target?, label?, use_case?{ ok, signature, outcome_id, outcome_ids, replay_url, artifact_slot, resolution_slot, rng_version, entropy_source, prepare_signature, target_slots, resolved_slots, winner_claim_hash }
GET /api/partner/draw/:signature/proof?address=Membership proof for a scale draw: included, winner, participant, merkle_root, leaf_hash, proof, snapshot_uri, publication_url.
POST /api/partner/snapshot/initlabel?, use_case?A snapshot session id, valid for 24 hours.
POST /api/partner/snapshot/chunksession_id, chunk_index, participantsUp to 5,000 participants per chunk, 2 MB per body.
POST /api/partner/snapshot/finalizesession_id, formula, winners_count, target?, wait?202 with status_url; the draw runs in the background. wait: true answers 200 with the result instead.
GET /api/partner/snapshot/status?session_id=Status open, finalizing, finalized or failed, with stage, result (signature, winners, snapshot_uri, snapshot_mirror_url, merkle_root, rng_v2 fields), or error and retryable.
POST /api/partner/sortition/drawdraw_id?, threshold, registration_duration_slots, claim_duration_slots, unclaimed_ruleCreates a sortition draw.
POST /api/partner/sortition/registerdraw_id, ticket_id, commitmentRegisters a ticket commitment.
POST /api/partner/sortition/sealdraw_idSeals omega from future slot hashes.
POST /api/partner/sortition/claimdraw_id, ticket_id, secretReveals and claims a ticket.
POST /api/partner/conditions/commitround_id, fixtures, entrants{ commitment_ref, commit_signature, committed_at_slot }
POST /api/partner/conditions/resolveround_id, fixtures (with stats, event_stat_root){ signature, verify_url, winners, ranking, winning_score_sum, artifact }
GET /api/resolutions?limit=Recent resolutions on the canonical program.
GET /api/participant?address=Draws where the address participated and whether it won.

Partner draw fields

  • formula. weighted_random, rank_desc, rank_asc, first_n or closest_to.
  • participants. At least 2 objects with a unique id of up to 64 printable ASCII bytes, plus weight or score as the formula needs. The compact artifact holds about 100 entries; use the snapshot flow above for more.
  • winners_count. 1 to 10, not more than the participants.
  • target. Required for closest_to; a signed safe integer.
  • use_case. raffle, airdrop or competition; informational.
  • Scale-shaped body. mode: "scale", scale: true, public_proof: true or standard_version: "1.2" on this endpoint returns 409 with route: "scale_snapshot" and the snapshot endpoints.
  • artifact_slot. The slot where these exact rules were first committed; identical rules reuse one artifact, so it can predate the draw.
POST /api/partner/draw
curl -s -X POST https://verifiableoutcome.online/api/partner/draw \ -H "content-type: application/json" \ -H "x-api-key: vresk_YOUR_KEY" \ -d '{"formula":"rank_desc","winners_count":2,"participants":[{"id":"alice","score":10},{"id":"bob","score":30},{"id":"carol","score":20}]}' → { "ok": true, "signature": "36oi8jNb…", "outcome_id": "bob", "outcome_ids": ["bob", "carol"], "replay_url": "https://verifiableoutcome.online/verify?sig=36oi8jNb…", "artifact_slot": …, "resolution_slot": …, "rng_version": 2, "entropy_source": "slot_hash", "target_slots": [ … ], "resolved_slots": [ … ] }
08

SDK

verifiable-outcome-sdk is the TypeScript library for building artifacts and verifying outcomes. It is an ES module for Node 18 and later.

npm install verifiable-outcome-sdk # 0.9.1

Verify from a terminal

npx -p verifiable-outcome-sdk vre verify --sig <signature> [--rpc <url>] [--program-id <id>] [--json] # exit code 0 on MATCH, 1 otherwise

Verify a transaction

import { verifyOutcome } from "verifiable-outcome-sdk"; const result = await verifyOutcome({ signature: "<transaction signature>", rpcUrl: "https://api.devnet.solana.com", programId: "6QtLot4uztCfjUuPreoiYPc3e1gH2fg6pq8hKYyvCP6B", }); // { status: "MATCH", reason: "OK", outcome_id, outcome_ids, … }

Build an artifact

import { buildArtifact } from "verifiable-outcome-sdk"; const bytes = buildArtifact({ type: "formula_draw_v5", formula: "rank_desc", input_lamports: 10, payout_lamports: 0, winners_count: 2, participants: [{ id: "alice", score: 10 }, { id: "bob", score: 30 }], }); // Buffer with the fmt 5 artifact; its SHA-256 is the compiled_artifact_hash

Exports

AreaExports
VerificationverifyOutcome
ArtifactsbuildArtifact, buildArtifactV4, buildArtifactV5, buildArtifactV12, buildArtifactV121, parseArtifactV5, parseArtifactV12, parseArtifactV121
Snapshots and MerklebuildSnapshotHash, buildSnapshotManifest, buildWinnerClaimHash, buildMerkleLevels, buildMerkleProof, buildSnapshotMerkleProofFromFile, hashSnapshotLeafLine; since 0.9.0: canonicalSnapshotLine, verifySnapshotMerkleProof, computeSnapshotMerkleRootFromProof, fetchSnapshotCommitment, fetchCommittedSnapshot, snapshotSourceCandidates, SnapshotUnavailableError
CompositebuildCompositeRankDescArtifact, parseCompositeRankDescArtifact, replayCompositeRankDescArtifact
SortitioncreateSortitionClient, sortitionCreateDrawInline, sortitionRegisterTicketInline, sortitionSealDrawInline, sortitionClaimTicketInline, sortitionCommitment, sortitionWins, sortitionThresholdFromProbability, build*SortitionInstruction helpers
Conditions and slotScoring, artifact encoders and replay helpers for fc7 and SF1.

The operator resolve client and the rng_v2 prepare-and-settle client are not in the published package: draws run through the HTTP API. verifyOutcome and vre verify do not replay sortition transactions; the claim instruction enforces the sortition predicate on-chain.

Snapshot sources

snapshot_uri is opaque to the program. It is part of the hashed artifact, so a draw commits to where the list was published, and the file itself is checked against snapshot_hash. The verifier tries, in order:

  1. a local copy (--snapshot-file);
  2. the committed URI: https:// as is, irys-devnet://<id> through devnet.irys.xyz and gateway.irys.xyz, irys:// through gateway.irys.xyz, ar:// through arweave.net and turbo-gateway.com;
  3. mirrors by hash, <mirror>/sha256/<snapshot_hash>.jsonl: https://verifiableoutcome.online by default, override with --snapshot-mirror or VRE_SNAPSHOT_MIRRORS.

A source whose bytes do not hash to snapshot_hash is skipped. If no source serves the file, the result is ERR_SNAPSHOT_UNAVAILABLE. Moving to other storage needs no program upgrade: new draws commit a different URI, and older draws keep verifying through mirrors. Irys devnet drops uploads after about 60 days; the VRE mirror keeps every snapshot it published.

File format: one JSON object per line, {{"id","order","weight"}} for weighted_random, {{"id","order"}} for first_n, {{"id","order","score"}} otherwise, each ending in a newline, sorted by id bytes. Leaf = SHA-256(SHA-256(line)); node = SHA-256(left ‖ right), the last node is duplicated on odd levels; a proof is a list of {{ position, sibling }} from the leaf upward, where position is the side the sibling sits on. verifySnapshotMerkleProof checks a line and proof against the on-chain merkle_root.

Measured on devnet on 2026-09-15: 10,001 participants, finalized in 23 s, replayed to MATCH from the committed URI alone. On a local validator, 100,001 participants (8 MB) took 24 s.

09

Use Cases

Use caseMechanismWhat is committed before the result
Raffle or NFT dropweighted_randomEvery ticket holder with a weight equal to their tickets.
Trading competitionrank_descFinal scores; the top N follow deterministically.
Lowest-bid contestrank_ascBids; no late entries or edits after commit.
Guess the valueclosest_toGuesses and the target value.
First-come airdropfirst_nRegistration order.
Pack break or loot tableweighted_randomEvery slot and its weight, before the stream.
Mass airdrop or allowlistSortitionTicket commitments; winners claim themselves.
Large leaderboardScale snapshotA snapshot hash and Merkle root; each wallet proves inclusion.
Prediction slipsConditionsEvery slip with its legs and odds; facts are revealed later.
Slot gameSF1The game math, before each bet's entropy exists.