Transaction
8343232F35C68F…71EEA120F55B
Block 26,009 · index 0 · indexed
Summary
- Hash
- 8343232F35C68FD150C7943CAA2CC06532086BF2E034B72E88F671EEA120F55B
- Block
- 26,009
- Size
- 8171 bytes
- Gas used
- 12,648,816 / 15,178,639
- Fee
- 1000000ugnot
- Memo
- gnopublish
- Status
- success
Messages
Arguments · 9
- #1dice
- #2README.md
- #3# 🎲 Provably-Fair Dice > ⚠️ **Experimental — generated with no human supervision.** This realm was > produced automatically by an MCP-driven agent to exercise the gno MCP server > and tooling, and to generate test content for gno compilers, linters and > formatters. **Not audited. Not for production.** Full context & folder README: > [r/moul/x/daily](https://github.com/moul/gno-contracts/blob/main/r/moul/x/daily/README.md) --- A dice game realm for gno.land where every roll is verifiable. There is no true randomness on-chain, so each roll's 1-6 face is derived **deterministically** from public block entropy (the block height) mixed with the caller's address and their running roll count. Anyone can recompute a roll from public data and confirm it was never tampered with — that is what "provably fair" means here. The realm keeps a per-caller roll history and a global distribution across the six faces. - **Realm path:** `gno.land/r/REPLACE_ADDR/dice` - **Network:** sapphire (gno 0.9) ## Example calls ```sh # Roll the dice (state-changing crossing call, returns the 1-6 face) gnokey maketx call -pkgpath "gno.land/r/REPLACE_ADDR/dice" \ -func Roll -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast ... # Query the current view (distribution chart + recent rolls) gnokey query "vm/qrender" -data "gno.land/r/REPLACE_ADDR/dice:" # Read one account's roll history (read-only helper) gnokey query "vm/qeval" \ -data 'gno.land/r/REPLACE_ADDR/dice.RollsOf("g1...")' ``` `Render(path)` output shows the total rolls, a `▓░` distribution bar chart for faces 1-6 with percentages, and the most recent rolls. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:**  > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
- #4dice.gno
- #5// Package dice implements a provably-fair on-chain dice game for gno.land. // // There is no true randomness on-chain: every node must reach the same result // deterministically. Instead, each roll derives its 1-6 face from public block // entropy (chain height) mixed with the caller address and the caller's roll // count. The derivation is fully reproducible from public data, which is what // "provably fair" means here — anyone can recompute a roll and verify it. package dice import ( "chain" "chain/runtime" "strconv" "strings" "gno.land/p/nt/avl/v0" ) // history holds every roll made by a single caller, most recent last. type history struct { rolls []int // each value in [1,6] } var ( // count[i] is the total number of times face (i+1) has come up globally. count [6]int // total is the total number of rolls across all callers. total int // players maps caller address string -> *history (deterministic iteration). players = avl.NewTree() ) // deriveFace turns public entropy into a face in [1,6]. It is deterministic: // the same (height, addr, n) always yields the same face, so any observer can // recompute and verify a roll. Uses a simple FNV-1a-style hash over the mixed // entropy bytes so different callers at the same height get different faces. func deriveFace(height int64, addr string, n int) int { const ( offset = uint64(1469598103934665603) prime = uint64(1099511628211) ) h := offset mix := func(v uint64) { for i := 0; i < 8; i++ { h ^= v & 0xff h *= prime v >>= 8 } } mix(uint64(height)) mix(uint64(n)) for i := 0; i < len(addr); i++ { h ^= uint64(addr[i]) h *= prime } return int(h%6) + 1 } // Roll performs one dice roll for the calling account and records it. // // It is a crossing function (gno 0.9 interrealm convention: `cur realm` first // parameter). Callers invoke it as Roll(cross(cur)). The result is derived // deterministically from the current block height, the caller address, and the // caller's prior roll count — no stored secret, fully verifiable. func Roll(cur realm) int { if !cur.IsCurrent() { panic("dice: spoofed realm value") } addr := cur.Previous().Address().String() var h *history if v := players.Get(addr); v != nil { h = v.(*history) } else { h = &history{} players.Set(addr, h) } face := deriveFace(runtime.ChainHeight(), addr, len(h.rolls)) h.rolls = append(h.rolls, face) count[face-1]++ total++ chain.Emit("Roll", "player", addr, "face", strconv.Itoa(face)) return face } // RollsOf returns the recorded roll history for an address (oldest first). // Read-only helper, safe to call as a query. func RollsOf(addr string) []int { v := players.Get(addr) if v == nil { return nil } out := v.(*history).rolls cp := make([]int, len(out)) copy(cp, out) return cp } // Total returns the global roll count. func Total() int { return total } // Distribution returns the global per-face counts, faces 1-6. func Distribution() [6]int { return count } // bar renders a proportional ▓/░ bar of fixed width for a face's share. func bar(n, max, width int) string { if max <= 0 { return strings.Repeat("░", width) } filled := n * width / max if filled > width { filled = width } return strings.Repeat("▓", filled) + strings.Repeat("░", width-filled) } // Render returns the realm's gnoweb Markdown view. // // Render is NOT a crossing function (no `cur realm` param) — it is a read-only // query surface. It shows the total roll count, a distribution bar chart for // faces 1-6, and the most recent rolls. func Render(path string) string { var b strings.Builder b.WriteString("# 🎲 Provably-Fair Dice\n\n") b.WriteString("Every roll is derived deterministically from the block height, ") b.WriteString("the caller address, and their roll count — no hidden randomness, ") b.WriteString("fully reproducible from public data.\n\n") b.WriteString("**Total rolls:** " + strconv.Itoa(total) + "\n\n") // Distribution bar chart. b.WriteString("## Distribution\n\n") max := 0 for _, c := range count { if c > max { max = c } } for face := 1; face <= 6; face++ { c := count[face-1] pct := 0 if total > 0 { pct = c * 100 / total } b.WriteString("`" + strconv.Itoa(face) + "` ") b.WriteString(bar(c, max, 20)) b.WriteString(" " + strconv.Itoa(c)) b.WriteString(" (" + strconv.Itoa(pct) + "%)\n\n") } // Recent rolls: walk players, collect last rolls, show most recent globally. b.WriteString("## Recent rolls\n\n") if total == 0 { b.WriteString("_No rolls yet. Call `Roll` to play._\n") return b.String() } type entry struct { addr string face int } recent := []entry{} players.Iterate("", "", func(key string, value interface{}) bool { h := value.(*history) // take up to the last 3 rolls of each player start := len(h.rolls) - 3 if start < 0 { start = 0 } for _, f := range h.rolls[start:] { recent = append(recent, entry{addr: key, face: f}) } return false }) // show at most the last 10 collected start := len(recent) - 10 if start < 0 { start = 0 } for _, e := range recent[start:] { short := e.addr if len(short) > 12 { short = short[:8] + "…" + short[len(short)-4:] } b.WriteString("- 🎲 **" + strconv.Itoa(e.face) + "** — `" + short + "`\n") } return b.String() }
- #6gnomod.toml
- #7module = "gno.land/r/moul/x/daily/dice/v0" gno = "0.9"
- #8/gno.MemPackageType
- #9 MPUserProd
Result log
msg:0,success:true,log:,events:[]