Skip to main content

Provably Fair

Randomness provided by

Chainlink

This page is for developers and users who want to understand, inspect, and independently verify the fairness model used by GalaxyGrails.io.

It complements the high-level overview in the main GalaxyGrails page with concrete API details and example code.


Overview

GalaxyGrails.io uses a combination of:

  • Chainlink VRF (Verifiable Random Function) for on-chain randomness
  • A pre-committed deck snapshot and SHA-256 deck hash
  • A deterministic fairness algorithm that uses the VRF seed to drive card selection

The key idea:

Once an event starts, the outcomes are fully determined by the deck snapshot, the VRF seed, and the fairness algorithm. No one (including GalaxyGrails) can change the result afterwards without detection.

This guide walks through the public fairness endpoint and shows how to:

  • Fetch fairness data for a completed event
  • Recompute the deck hash from the snapshot
  • Use the VRF seed to drive a deterministic RNG
  • Align that RNG with the published pull timeline

In addition to the API, you can browse completed events and their fairness reports in the UI at https://galaxygrails.io/events.

For more details on how Chainlink VRF itself works under the hood, see the official Chainlink VRF documentation.


Fairness Algorithm

At a high level, each GalaxyGrails pack event follows this pattern:

  1. Fix the deck – Before sales start, the full list of cards for the event is locked into a snapshot and hashed (the deck_hash).
  2. Fix the randomness – Chainlink VRF generates a single, verifiable random seed (random_seed) for that event.
  3. Derive a sequence – A versioned fairness algorithm (see fairness_algo_version) combines the deck snapshot and seed to deterministically decide the order in which cards are pulled.
  4. Reveal over time – As packs are opened, the system walks that pre-determined sequence and reveals one card at a time.

Because the deck snapshot, deck hash, VRF seed, and pulls are all exposed via the fairness API once the event ends, anyone can:

  • Rebuild the same initial deck state.
  • Start from the same seed.
  • Implement the same deterministic selection rules.
  • Confirm that the resulting pull order matches the published pulls timeline.

That is what makes the system provably fair rather than just procedurally random.


Public Fairness Endpoint

URL

GET https://galaxygrails.io/api/pack-events/{PACK_EVENT_ID}/fairness
  • Authentication: Not required (public)
  • Path params:
    • PACK_EVENT_ID – UUID of the pack event (e.g. 11655b60-f6cc-40a8-b8a8-0282c4e5d9f1)

Behaviour: Active vs Completed Events

The endpoint behaves differently depending on whether the event is still live:

  • While the event is active (is_active = true):

    • The response includes VRF metadata and the deck hash, but hides:
      • The actual random_seed
      • The full deck_snapshot
      • The full pulls array
    • This prevents leaking information about future pulls while still letting users verify the on-chain commitments.
  • After the event ends (is_active = false):

    • The response includes the full fairness payload:
      • random_seed
      • random_seed_source
      • deck_snapshot
      • pulls (ordered timeline)

The rest of the fields (event name, price, deck hash, VRF metadata) are always present.

Example Response (Completed Event)

{
"pack_event_id": "11655b60-f6cc-40a8-b8a8-0282c4e5d9f1",
"alpha": 1.0,
"pack_price": "200.00",
"stop_ev_buffer": 0.99,
"random_seed": "6a5f...c9d3", // VRF seed (hex, no 0x prefix)
"random_seed_source": "vrf",
"deck_hash": "f712...3b8e", // SHA-256 hash of deck_snapshot
"deck_snapshot": [
{
"pack_card_id": "...",
"card_inventory_id": "...",
"initial_price": "220.00"
}
// ... one entry per card in the event deck
],
"fairness_algo_version": "v1",
"pulls": [
{
"pull_id": "...",
"sequence_index": 1,
"user_id": "...",
"username": "collector123",
"pack_card_id": "...",
"card_token_id": "...",
"card": {
"id": "...",
"title": "2021 POKEMON ...",
"front_image_url": "https://...",
"back_image_url": "https://...",
"market_price": "250.00"
},
"pulled_at": "2025-11-24T03:12:45.123456+00:00"
}
// ... one entry per pull, ordered by sequence_index
],
"is_active": false,
"vrf_event_id": "0x...", // Event ID used in the VRF consumer contract
"vrf_request_id": "0x...", // Transaction hash that requested randomness
"vrf_consumer_address": "0x...", // VRF consumer contract address
"vrf_network": "polygon-mainnet" // Network label
}

Note: Field names and shapes are stable, but example values above are illustrative.

Key Fields Explained

  • alpha: Controls the weighting curve for card selection based on market value. Higher alpha = stronger preference for lower-value cards.
  • stop_ev_buffer: The threshold (as a ratio of pack price) below which the event auto-stops to maintain fairness.
  • fairness_algo_version: Version identifier for the selection algorithm. Currently v1.

Verifying the Deck Commitment

The deck_hash is a SHA-256 hash of the serialized deck_snapshot. You can recompute it as follows:

import hashlib
import json

# Given a fairness API response in `data` (e.g. from resp.json())

deck_snapshot = data["deck_snapshot"]

serialized = json.dumps(deck_snapshot, sort_keys=True, separators=(",", ":"))
local_deck_hash = hashlib.sha256(serialized.encode("utf-8")).hexdigest()

assert local_deck_hash == data["deck_hash"], "Deck hash mismatch!"

If this assertion passes, you know the deck used for the event is exactly the one that was committed via deck_hash at event creation.


Verifying VRF-Based Randomness

The random_seed field is the output of Chainlink VRF for that event, represented as a hex string without the 0x prefix.

A simple way to use it is to seed a local PRNG and walk through a deterministic sequence of random values:

import random

seed_hex = data["random_seed"]

rng = random.Random()
rng.seed(int(seed_hex, 16))

# Example: get the first few random numbers
for i in range(5):
print(i + 1, rng.random())

GalaxyGrails.io uses this seed (plus the deck snapshot and a versioned fairness algorithm) to drive card selection for each pull.

If you build your own deck model and fairness function, you can:

  1. Start from the same deck_snapshot and random_seed.
  2. Apply your own deterministic selection logic per pull.
  3. Compare the resulting sequence of cards to the published pulls timeline.

As long as you implement the same fairness algorithm and state updates, you should reproduce the exact same pull order.


Full End-to-End Verification Example

The UI includes a compact Python example, but here is a more explicit script you can adapt. It:

  1. Fetches fairness data for a completed event.
  2. Recomputes the deck hash and asserts it matches.
  3. Seeds a RNG from the VRF output.
  4. Prints the random sequence alongside each pulled card.
import hashlib
import json
import random

import requests

# 1) Configure your event ID
EVENT_ID = "11655b60-f6cc-40a8-b8a8-0282c4e5d9f1" # Replace with your event ID

# 2) Fetch fairness JSON
resp = requests.get(f"https://galaxygrails.io/api/pack-events/{EVENT_ID}/fairness")
resp.raise_for_status()

# This endpoint returns the fairness payload as the top-level JSON object
# (no nested {"data": ...} envelope)

data = resp.json()

# 3) Verify deck hash

deck_snapshot = data["deck_snapshot"]
serialized = json.dumps(deck_snapshot, sort_keys=True, separators=(",", ":"))
local_deck_hash = hashlib.sha256(serialized.encode("utf-8")).hexdigest()

assert local_deck_hash == data["deck_hash"], "Deck hash mismatch!"
print("Deck hash verified.")

# 4) Seed PRNG from Chainlink VRF seed

seed_hex = data["random_seed"]

rng = random.Random()
rng.seed(int(seed_hex, 16))

pulls = data["pulls"]

# 5) Walk through pulls with a deterministic RNG stream

for i, pull in enumerate(pulls, start=1):
r = rng.random()
card_title = None
card = pull.get("card") or {}
if isinstance(card, dict):
card_title = card.get("title")

print(i, r, card_title)

# At this point, you can plug `r` into your own fairness logic
# and compare the resulting card selection to `pull["card"]`.

Tip: This script is intentionally conservative and does not depend on any GalaxyGrails-specific client libraries. It only uses requests, hashlib, json, and Python's standard random module.


Summary

  • Use GET https://galaxygrails.io/api/pack-events/{PACK_EVENT_ID}/fairness to fetch fairness data.
  • Use deck_snapshot and deck_hash to verify the deck commitment.
  • Use random_seed with a deterministic algorithm to verify the pull order.
  • Use the published pulls timeline to compare your reconstruction against what actually happened.

Together, these tools let you independently confirm that each GalaxyGrails event is consistent with its pre-committed deck and Chainlink VRF-powered randomness.