Provably Fair
Verify every game outcome with full transparency
Fairness Verification
Enter the details below to verify your game result. You can find these values in your game history or by clicking the Fairness button in any game.
This is a passphrase or random string set by the player or browser, which can be changed anytime.
To reveal the hashed server seed, the player must rotate it, replacing it with a new seed. This allows verification of any bets made with the previous seed using the provided encrypted hash.
After entering the information above and clicking Apply, proceed to the next section.
package main
import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
"strconv"
"fmt"
)
const CASE_ROLL_TICKETS_NUM uint64 = 1000000
type CaseItem struct {
ItemID int64 `json:"item_id"`
ItemName string `json:"item_name"` // not required for rolling
MarketHashName string `json:"market_hash_name"` // not required for rolling
ItemCategory string `json:"item_cat,omitempty"` // not required for rolling
Color string `json:"color,omitempty"` // not required for rolling
MinRand uint32 `json:"min_rand"`
MaxRand uint32 `json:"max_rand"`
Price float64 `json:"price"`
ImageURL string `json:"image_url,omitempty"` // not required for rolling
}
func getWinningTicket(serverSeed, externalSeed string, nonce int) (uint32, uint64) {
seed := serverSeed + externalSeed + "#" + strconv.FormatInt(int64(nonce), 10)
hash := sha256.Sum256([]byte(seed))
rolledUInt64 := binary.LittleEndian.Uint64(hash[:])
return uint32(rolledUInt64%CASE_ROLL_TICKETS_NUM) + 1, rolledUInt64
}
func rollItem(serverSeed, externalSeed string, nonce int, itemsMap map[int64]*CaseItem) (*CaseItem, uint32, uint64, error) {
rolledTicket, rolledUInt64 := getWinningTicket(serverSeed, externalSeed, nonce)
if itemsMap == nil {
return nil, rolledTicket, rolledUInt64, nil
}
for _, item := range itemsMap {
if item.MinRand <= rolledTicket && rolledTicket <= item.MaxRand {
return item, rolledTicket, rolledUInt64, nil
}
}
return nil, rolledTicket, rolledUInt64, errors.New("rollItem: case item not found")
}
func GetDroppedItems(serverSeed, clientSeed string, nonce int, caseItems string) (*CaseItem, uint32, error) {
if len(serverSeed) != 64 {
return nil, 0, errors.New("error: serverSeed must have length 64")
}
var itemsMap map[int64]*CaseItem
if len(caseItems) > 0 {
if err := json.Unmarshal([]byte(caseItems), &itemsMap); err != nil {
return nil, 0, err
}
}
caseItem, winningTicket, _, err := rollItem(serverSeed, clientSeed, nonce, itemsMap)
return caseItem, winningTicket, err
}
func main() {
serverSeed := ""
clientSeed := ""
nonce := 0
caseItems := ``
caseItem, winningTicket, err := GetDroppedItems(serverSeed, clientSeed, nonce, caseItems)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("ticket:", winningTicket)
if len(caseItems) > 0 {
fmt.Println("item:", caseItem.ItemName)
fmt.Printf("price: %.2f", caseItem.Price)
}
}package main
import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math"
"strings"
)
type MinesGamePublic struct {
Width int `json:"width"`
Height int `json:"height"`
MinesCount int `json:"mines"`
ClientSeed string `json:"clientSeed"`
Nonce int `json:"nonce"`
ProvablyFairVersion int `json:"provablyFairVersion"`
ServerSeedPublic string `json:"serverSeed,omitempty"`
serverSeed string
field []byte
}
// returns the index of the new mine, when enumerating all remaining tiles
func getRandomMine(game *MinesGamePublic, remainingTiles uint64) int {
maxValidRoll := uint64(math.MaxUint64)
extraVals := (maxValidRoll % remainingTiles) + 1
if extraVals != remainingTiles {
maxValidRoll -= extraVals
}
for attempt := 0; ; attempt++ {
seed := game.serverSeed + game.ClientSeed + fmt.Sprintf("#%d#%d#%d", game.Nonce, remainingTiles, attempt)
if game.ProvablyFairVersion > 0 {
seed = "mines" + seed
}
hash := sha256.Sum256([]byte(seed))
rolledUInt64 := binary.LittleEndian.Uint64(hash[:])
if rolledUInt64 <= maxValidRoll {
return int(rolledUInt64 % remainingTiles)
}
}
}
func populateField(game *MinesGamePublic) {
tiles := make([]int, game.Width*game.Height)
for i := range tiles {
tiles[i] = i
}
for mine := 0; mine < game.MinesCount; mine++ {
// select random remaining tile and place it at index `mine` in list of tiles:
mineOffset := getRandomMine(game, uint64(game.Width*game.Height-mine))
tiles[mine], tiles[mine+mineOffset] = tiles[mine+mineOffset], tiles[mine]
}
game.field = []byte(strings.Repeat("*", game.Width*game.Height))
// use first `game.MinesCount` tiles in list as mine positions:
for _, minePos := range tiles[:game.MinesCount] {
game.field[minePos] = 'x'
}
}
func isValidFieldConfig(width, height, minesCount int) error {
if width <= 0 || height <= 0 || width > 100 || height > 100 {
return errors.New("invalid width/height")
}
if minesCount < 1 || minesCount >= width*height {
return errors.New("invalid mines count")
}
return nil
}
type tile struct {
row int
column int
}
func initFromJSONandGetMines(gameJSON string) (mines []tile, err error) {
var game MinesGamePublic
if err = json.Unmarshal(json.RawMessage(gameJSON), &game); err != nil {
return nil, err
}
if err = isValidFieldConfig(game.Width, game.Height, game.MinesCount); err != nil {
return nil, err
}
game.serverSeed = game.ServerSeedPublic
if len(game.serverSeed) != 64 {
return nil, errors.New("server seed must be specified an have length 64")
}
populateField(&game)
for i, t := range game.field {
if t == 'x' {
mines = append(mines, tile{i / game.Width, i % game.Width})
}
}
return
}
func main() {
gameJSON := "{\"serverSeed\":\"\",\"clientSeed\":\"\",\"nonce\":0,\"width\":5,\"height\":5,\"mines\":3,\"provablyFairVersion\":0}"
mines, err := initFromJSONandGetMines(gameJSON)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Mine positions:")
for _, m := range mines {
fmt.Printf("Row: %d, Column: %d\n", m.row, m.column)
}
}package main
import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math"
"slices"
)
const (
totalTiles = 40
drawnTiles = 10
)
type KenoGamePublic struct {
ClientSeed string `json:"clientSeed"`
Nonce int `json:"nonce"`
ProvablyFairVersion int `json:"provablyFairVersion"`
ServerSeed string `json:"serverSeed,omitempty"`
serverSeed string
}
func getRandomTile(tileIndex int, game *KenoGamePublic) int {
remainingTiles := uint64(totalTiles - tileIndex)
maxValidRoll := uint64(math.MaxUint64)
extraVals := (maxValidRoll % remainingTiles) + 1
if extraVals != remainingTiles {
maxValidRoll -= extraVals
}
for attempt := 0; ; attempt++ {
seed := game.serverSeed + game.ClientSeed + fmt.Sprintf("#%d#%d#%d", game.Nonce, tileIndex, attempt)
if game.ProvablyFairVersion > 0 {
seed = "keno" + seed
}
hash := sha256.Sum256([]byte(seed))
rolledUInt64 := binary.LittleEndian.Uint64(hash[:])
if rolledUInt64 <= maxValidRoll {
return int(rolledUInt64 % remainingTiles)
}
}
}
func getTiles(game *KenoGamePublic) []int {
tiles := make([]int, totalTiles)
for i := range tiles {
tiles[i] = i
}
for tileIndex := 0; tileIndex < drawnTiles; tileIndex++ {
tileOffset := getRandomTile(tileIndex, game)
tiles[tileIndex], tiles[tileIndex+tileOffset] = tiles[tileIndex+tileOffset], tiles[tileIndex]
}
slices.Sort(tiles[:drawnTiles])
return tiles[:drawnTiles]
}
func initFromJSONandGetTiles(gameJSON string) ([]int, error) {
var game KenoGamePublic
if err := json.Unmarshal(json.RawMessage(gameJSON), &game); err != nil {
return nil, err
}
game.serverSeed = game.ServerSeed
if len(game.serverSeed) != 64 {
return nil, errors.New("server seed must be specified and have length 64")
}
return getTiles(&game), nil
}
func main() {
gameJSON := "{\"serverSeed\":\"\",\"clientSeed\":\"\",\"nonce\":0,\"provablyFairVersion\":0}"
tiles, err := initFromJSONandGetTiles(gameJSON)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Server tiles: %v\n", tiles)
}package main
import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math"
"slices"
"strconv"
"strings"
)
const battleProvablyFairVersion = 2
type CaseItem struct {
ItemID int64 `json:"item_id"`
ItemName string `json:"item_name"` // not required for rolling
MarketHashName string `json:"market_hash_name"` // not required for rolling
ItemCategory string `json:"item_cat,omitempty"` // not required for rolling
Color string `json:"color,omitempty"` // not required for rolling
MinRand uint32 `json:"min_rand"`
MaxRand uint32 `json:"max_rand"`
Price float64 `json:"price"`
ImageURL string `json:"image_url,omitempty"` // not required for rolling
}
type Case struct {
ID int64 `json:"id"` // not required for rolling
Name string `json:"name"` // not required for rolling
Type string `json:"type"` // not required for rolling
Color string `json:"color,omitempty"` // not required for rolling
Price float64 `json:"price"`
PublishStatus bool `json:"publish_status"` // not required for rolling
RiskLevel *string `json:"risk_level,omitempty"` // not required for rolling
Level *int `json:"level,omitempty"` // not required for rolling
ImageURL *string `json:"image_url,omitempty"` // not required for rolling
Items map[int64]*CaseItem `json:"items"`
}
type BattleStep struct {
Slot string `json:"slot"`
ItemID int64 `json:"itemId"`
Price float64 `json:"price"`
JackpotCurrent float64 `json:"jackpotCurrent"`
}
type WinnerSummary struct {
Slots []string `json:"Slots,omitempty"`
SlotPayout float64 `json:"SlotPrizes"`
TotalPayout float64 `json:"TotalPrizes"`
}
type BattleSummary struct {
Steps map[int][]BattleStep `json:"steps"`
Winners WinnerSummary `json:"winners"`
DropValue map[string]float64 `json:"prizes"`
Jackpot map[string]float64 `json:"jackpot,omitempty"`
JackpotWinner string `json:"jackpotWinner,omitempty"`
rawRolls []uint64
jackpotRawRoll uint64
}
type PFair struct {
ServerSeed string `json:"serverSeed,omitempty"`
ServerSeedHash string `json:"serverSeedHash"`
DrandRoundID uint64 `json:"drandRoundId,omitzero"`
DrandSeed string `json:"drandSeed,omitempty"`
ClientSeed string `json:"clientSeed,omitempty"`
Nonce int `json:"nonce"`
LogicVersion int `json:"logicVersion"`
}
type playerTypeInfo struct {
numPlayers int
playersPerTeam int
}
var playerTypeInfos = map[string]playerTypeInfo{
"solo": {1, 1},
"1v1": {2, 1},
"1v1v1": {3, 1},
"1v1v1v1": {4, 1},
"1v5": {6, 1}, // means "1v1v1v1v1v1"
"2v2": {4, 2},
"2v2v2": {6, 2},
"3v3": {6, 3},
}
func (ptInfo playerTypeInfo) slotIndexToTeam(slotIdx int) int {
return slotIdx / ptInfo.playersPerTeam
}
type BattleInfo struct {
PlayerType string `json:"playerType"`
ptInfo playerTypeInfo
Options []string `json:"options"`
jackpot bool
madness bool
equality bool
CaseSequence []int64 `json:"cases"`
CasesSnapshot map[int64]*Case `json:"casesSnapshot"`
PFair PFair `json:"pFair"`
}
const CASE_ROLL_TICKETS_NUM uint64 = 1000000
func getWinningTicket(serverSeed, externalSeed string, nonce int) (uint32, uint64) {
seed := serverSeed + externalSeed + "#" + strconv.FormatInt(int64(nonce), 10)
hash := sha256.Sum256([]byte(seed))
rolledUInt64 := binary.LittleEndian.Uint64(hash[:])
return uint32(rolledUInt64%CASE_ROLL_TICKETS_NUM) + 1, rolledUInt64
}
func rollItem(serverSeed, externalSeed string, nonce int, itemsMap map[int64]*CaseItem) (*CaseItem, uint32, uint64, error) {
rolledTicket, rolledUInt64 := getWinningTicket(serverSeed, externalSeed, nonce)
if itemsMap == nil {
return nil, rolledTicket, rolledUInt64, nil
}
for _, item := range itemsMap {
if item.MinRand <= rolledTicket && rolledTicket <= item.MaxRand {
return item, rolledTicket, rolledUInt64, nil
}
}
return nil, rolledTicket, rolledUInt64, errors.New("rollItem: case item not found")
}
func roundPrice(val float64) float64 {
return math.Round(val*100) / 100
}
func RollCasesPublic(battle *BattleInfo) (*BattleSummary, error) {
var extraSeed string
// use drand seed for multi-player and client seed for single-player
if battle.PlayerType != "solo" {
extraSeed = battle.PFair.DrandSeed
} else {
extraSeed = battle.PFair.ClientSeed
}
var summary BattleSummary
var slotTotals []float64
var curJackpotWeights []float64 // updated to current jackpot weights after every round (case)
summary.Steps = make(map[int][]BattleStep)
slotTotals = make([]float64, battle.ptInfo.numPlayers)
nonce := battle.PFair.Nonce
for caseIndex, caseID := range battle.CaseSequence {
curSteps := make([]BattleStep, battle.ptInfo.numPlayers)
for slotIdx := 0; slotIdx < battle.ptInfo.numPlayers; slotIdx++ {
item, _, rawRoll, err := rollItem(battle.PFair.ServerSeed, extraSeed, nonce, battle.CasesSnapshot[caseID].Items)
nonce++
if err != nil {
return nil, err
}
summary.rawRolls = append(summary.rawRolls, rawRoll)
curSteps[slotIdx] = BattleStep{
Slot: fmt.Sprintf("s%d", slotIdx+1),
ItemID: item.ItemID,
Price: item.Price,
}
slotTotals[slotIdx] += item.Price
}
if battle.jackpot {
curJackpotWeights = slices.Clone(slotTotals)
totalWeight := 0.0
for i, v := range curJackpotWeights {
if battle.madness {
curJackpotWeights[i] = 1.0 / v
}
totalWeight += curJackpotWeights[i]
}
for i := range curSteps {
curJackpotWeights[i] = 100.0 * (curJackpotWeights[i] / totalWeight)
curSteps[i].JackpotCurrent = curJackpotWeights[i]
}
}
summary.Steps[caseIndex] = curSteps
}
for i := range slotTotals {
slotTotals[i] = roundPrice(slotTotals[i]) // for accurate comparison
}
winnerTeam := -1
// determine winning team and check whether it is unique (if no modifiers or madness only)
if !battle.jackpot && !battle.equality {
bestTotal := -1.0
better := func(total float64) bool {
if bestTotal == -1.0 {
return true
}
if battle.madness {
return total < bestTotal
}
return total > bestTotal
}
bestTeams := []int{}
curTotal := 0.0
for i, v := range slotTotals {
curTotal += v
if (i+1)%battle.ptInfo.playersPerTeam == 0 { // if last player in team
if better(curTotal) {
bestTotal = curTotal
bestTeams = []int{battle.ptInfo.slotIndexToTeam(i)}
} else if bestTotal == curTotal {
bestTeams = append(bestTeams, battle.ptInfo.slotIndexToTeam(i))
}
curTotal = 0.0
}
}
if len(bestTeams) > 1 { // set up tie-breaker jackpot to uniformly choose any player from best teams
curJackpotWeights = make([]float64, battle.ptInfo.numPlayers)
weight := 100.0 / float64(len(bestTeams)*battle.ptInfo.playersPerTeam)
for i := range curJackpotWeights {
if slices.Contains(bestTeams, battle.ptInfo.slotIndexToTeam(i)) {
curJackpotWeights[i] = weight
}
}
} else {
winnerTeam = bestTeams[0]
}
}
// run jackpot
var jackpotWinner int
if len(curJackpotWeights) > 0 {
baseSeedHash := sha256.Sum256([]byte(battle.PFair.ServerSeed + extraSeed))
summary.jackpotRawRoll = binary.LittleEndian.Uint64(baseSeedHash[:])
randVal := 100.0 * (float64(summary.jackpotRawRoll) / float64(1<<64))
tmpWeight := 0.0
for i, v := range curJackpotWeights {
tmpWeight += v
if winnerTeam == -1 && (tmpWeight >= randVal || i+1 == len(curJackpotWeights)) {
jackpotWinner = i
winnerTeam = battle.ptInfo.slotIndexToTeam(i)
}
}
summary.Jackpot = make(map[string]float64)
}
// populate summary.DropValue and summary.Winners
summary.DropValue = make(map[string]float64)
for i, v := range slotTotals {
slotName := fmt.Sprintf("s%d", i+1)
summary.DropValue[slotName] = v
summary.Winners.TotalPayout += v
if battle.equality || battle.ptInfo.slotIndexToTeam(i) == winnerTeam {
summary.Winners.Slots = append(summary.Winners.Slots, slotName)
}
if len(curJackpotWeights) > 0 {
summary.Jackpot[slotName] = curJackpotWeights[i]
if i == jackpotWinner {
summary.JackpotWinner = slotName
}
}
}
summary.Winners.SlotPayout = roundPrice(summary.Winners.TotalPayout / float64(len(summary.Winners.Slots)))
return &summary, nil
}
const (
optionJackpot = "jackpot"
optionMadness = "madness"
optionEquality = "equality"
)
func InitFromJSONandRoll(battleJSON string) (*BattleSummary, error) {
var battle BattleInfo
if err := json.Unmarshal(json.RawMessage(battleJSON), &battle); err != nil {
return nil, err
}
if battle.PFair.LogicVersion != battleProvablyFairVersion {
return nil, errors.New(fmt.Sprintf("wrong battle provably fair logic version: expected %d, got %d", battleProvablyFairVersion, battle.PFair.LogicVersion))
}
var ok bool
battle.ptInfo, ok = playerTypeInfos[battle.PlayerType]
if !ok {
return nil, errors.New("invalid playerType")
}
for _, opt := range battle.Options {
switch strings.TrimSpace(strings.ToLower(opt)) {
case optionJackpot:
battle.jackpot = true
case optionMadness:
battle.madness = true
case optionEquality:
battle.equality = true
}
}
return RollCasesPublic(&battle)
}
func main() {
battleJSON := ``
summary, err := InitFromJSONandRoll(battleJSON)
if err != nil {
fmt.Println("error:", err)
return
}
summaryJSON, _ := json.Marshal(summary)
fmt.Println(string(summaryJSON))
// fmt.Println(summary.rawRolls, summary.jackpotRawRoll)
fmt.Println("Winners:")
for _, winnerSlot := range summary.Winners.Slots {
fmt.Printf("%s: %.2f\n", winnerSlot, summary.Winners.SlotPayout)
}
}package main
import (
"crypto/sha256"
"encoding/binary"
"fmt"
)
func houseEdge(roundId int) float64 {
if roundId >= 223777 {
return 0.05
}
return 0.077
}
func CalculateCrashMultiplier(serverSeed, drandSeed string, roundId int) float64 {
combinedSeedHash := sha256.Sum256([]byte(serverSeed + drandSeed))
const MAX = float64(1 << 32)
value := float64(binary.LittleEndian.Uint32(combinedSeedHash[:]))
result := (MAX / (value + 1)) * (1 - houseEdge(roundId))
return max(result, 1.0)
}
func main() {
serverSeed := ""
drandSeed := ""
roundId := 0
if len(serverSeed) != 64 {
fmt.Println("error: serverSeed must have length 64")
return
}
if len(drandSeed) != 64 {
fmt.Println("error: drandSeed must have length 64")
return
}
if roundId <= 0 {
fmt.Println("error: roundId must be positive")
return
}
fmt.Println(CalculateCrashMultiplier(serverSeed, drandSeed, roundId))
}package main
import (
"crypto/sha256"
"encoding/binary"
"math"
"fmt"
)
type Card string // Card identifies a bettable outcome on the roulette wheel.
const (
CardSilver Card = "Silver"
CardGold Card = "Gold"
CardCS2 Card = "CS2"
cs2Prob = 1.0 / 17
goldOrSilverProb = 1.0 - cs2Prob
silverProb = 0.5 * goldOrSilverProb
cs2SuperProb = 0.009375
silverSuperProb = 0.01875
)
var superMinGameID int64 = 671222
func toSuper(card Card) Card {
return "Super" + card
}
func pickResultFromSeed(serverSeed, drandSeed string, gameID int64) Card {
combinedSeedHash := sha256.Sum256([]byte(serverSeed + drandSeed))
num := binary.LittleEndian.Uint64(combinedSeedHash[:])
ratio := float64(num) / float64(math.MaxUint64)
var card Card
switch {
case ratio < silverProb:
card = CardSilver
case ratio < goldOrSilverProb:
card = CardGold
default:
card = CardCS2
}
if gameID >= superMinGameID {
isSuper := false
num = binary.LittleEndian.Uint64(combinedSeedHash[8:])
ratio = float64(num) / float64(math.MaxUint64)
if card == CardCS2 {
isSuper = (ratio < cs2SuperProb)
} else {
isSuper = (ratio < silverSuperProb)
}
if isSuper {
card = toSuper(card)
}
}
return card
}
func main() {
serverSeed := ""
drandSeed := ""
gameID := 0
if len(serverSeed) != 64 {
fmt.Println("error: serverSeed must have length 64")
return
}
if len(drandSeed) != 64 {
fmt.Println("error: drandSeed must have length 64")
return
}
if gameID <= 0 {
fmt.Println("error: gameID must be positive")
return
}
fmt.Println(pickResultFromSeed(serverSeed, drandSeed, int64(gameID)))
}package main
import (
"cmp"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"fmt"
"slices"
"strconv"
)
const giveawayProvablyFairVersion = 1
// GiveawayPublic contains the inputs needed to reproduce the original draw.
// Royale registration can subsequently skip selected participants without drawing replacements.
type GiveawayPublic struct {
NumWinners int `json:"num_winners"`
ParticipantCount int `json:"participant_count"`
ServerSeed string `json:"server_seed,omitempty"`
DrandSeed string `json:"drand_seed,omitempty"`
ProvablyFairVersion int `json:"provably_fair_version"`
}
// DrawWinners returns distinct participant indices, starting at 1, in draw order.
func DrawWinners(giveaway *GiveawayPublic) ([]int, error) {
if giveaway.ProvablyFairVersion != giveawayProvablyFairVersion {
return nil, fmt.Errorf("unsupported provably fair version: %d", giveaway.ProvablyFairVersion)
}
if giveaway.NumWinners <= 0 || giveaway.ParticipantCount < 0 {
return nil, fmt.Errorf("num_winners must be positive; participant_count must be non-negative")
}
if len(giveaway.ServerSeed) != 64 || len(giveaway.DrandSeed) != 64 {
return nil, fmt.Errorf("server_seed and drand_seed must be specified and have length 64")
}
type scoredParticipant struct {
index int
score int64
}
participants := make([]scoredParticipant, giveaway.ParticipantCount)
for i := range participants {
index := i + 1
hash := sha256.Sum256([]byte(giveaway.ServerSeed + giveaway.DrandSeed + strconv.Itoa(index)))
participants[i] = scoredParticipant{index: index, score: int64(binary.LittleEndian.Uint64(hash[:8]))}
}
slices.SortFunc(participants, func(a, b scoredParticipant) int {
if order := cmp.Compare(a.score, b.score); order != 0 {
return order
}
return cmp.Compare(a.index, b.index)
})
winners := make([]int, min(giveaway.NumWinners, giveaway.ParticipantCount))
for i := range winners {
winners[i] = participants[i].index
}
return winners, nil
}
func initFromJSONandDraw(giveawayJSON string) ([]int, error) {
var giveaway GiveawayPublic
if err := json.Unmarshal(json.RawMessage(giveawayJSON), &giveaway); err != nil {
return nil, err
}
return DrawWinners(&giveaway)
}
func main() {
giveawayJSON := "{\"num_winners\":1,\"participant_count\":0,\"server_seed\":\"\",\"drand_seed\":\"\",\"provably_fair_version\":0}"
winners, err := initFromJSONandDraw(giveawayJSON)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Winners: %v\n", winners)
}After copying the code above, go to https://go.dev/play, paste the code there, and click the Run button to see the result.
Provably Fair FAQs
What does "Provably Fair" mean ?

It's a cryptographic system that guarantees every game result is transparent and cannot be manipulated by us.
How does it work ?

- Before each round, we generate a secret Server Seed and show you its SHA-256 hash.
- Your browser contributes a Client Seed.
- A counter called Nonce increases with every bet.
- These values are combined with a public formula to generate the final result.
Can I verify the result myself ?

Yes. After the round ends, we reveal the Server Seed. You can re-calculate the outcome using the seeds and nonce. If your calculation matches the game result, you know it was 100% fair.
Why should I trust this ?

Because the hash of the Server Seed is published before you play. That means the result is locked in advance, and we cannot alter it after seeing your bet.
What's the benefit for me ?

Peace of mind — you know every spin, every crash, every case is fair, transparent, and verifiable forever.