Transaction
57AD2946D879B2…61216F83DBB2
Block 26,405 · index 0 · indexed
Summary
- Hash
- 57AD2946D879B2F9D534C55DB2D76319296BFBB8840A09E08BE961216F83DBB2
- Block
- 26,405
- Size
- 8073 bytes
- Gas used
- 12,295,851 / 14,755,081
- Fee
- 1000000 ugnot
- Memo
- gnopublish
- Status
- success
Messages
Arguments · 9
- #1timecapsule
- #2README.md
- #3# Time Capsule Guestbook > ⚠️ **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 public guestbook with a twist: every message is sealed until a future block height, chosen by its author when they leave it. Until then it's just a locked entry showing who sealed it and when it opens; once the chain reaches that height, the message unlocks forever for anyone to read. Realm: `gno.land/r/g12cs4cehujpffpjpywmkqj43m6u5ya53nj69sjz/timecapsule` ## Example calls - `Leave("see you in 100 blocks", 100)` — seal a message that opens 100 blocks from now. Returns the capsule id. Only direct EOA (`MsgCall`) calls can seal one; empty messages, messages over 500 bytes, and delays outside `(0, 1_000_000]` blocks are rejected. - `Render("")` — home page: counts + every already-unlocked message. - `Render("sealed")` — every still-sealed capsule with its unlock height and blocks remaining (message hidden). - `Render("capsule/<id>")` — a single capsule's detail page; shows the message only once unlocked. ## Toolchain status Tested locally against a `gnolang/gno` master checkout (`~/p/gh/gnoland/gno`) using its own `gno` binary as GNOROOT, resolving `/p/nt/*` deps through its `examples/` workspace. All 5 unit tests pass (`gno test -v .`), and `gno lint .` reports no issues. This is newer than sapphire (gno 0.9), but the code only uses APIs documented as current in the project's gno skill (`chain`, `chain/runtime`, `gno.land/p/nt/avl/v0`, `gno.land/p/nt/markdown/sanitize/v0`), so it should be on-chain-valid for sapphire as well. <!-- 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 -->
- #4gnomod.toml
- #5module = "gno.land/r/moul/x/daily/timecapsule/v0" gno = "0.9"
- #6timecapsule.gno
- #7// Package timecapsule is a public guestbook where every message is sealed // until a future block height, then permanently unlocked for anyone to read. package timecapsule import ( "strconv" "strings" "chain" "chain/runtime" "gno.land/p/nt/avl/v0" "gno.land/p/nt/markdown/sanitize/v0" ) type capsule struct { id int64 author address message string createdAt int64 unlockHeight int64 } var ( capsules avl.Tree // key: zero-padded id -> *capsule nextID int64 ) const ( maxDelayBlocks int64 = 1_000_000 maxMessageLen int = 500 ) // Leave seals a new message that stays hidden until `delayBlocks` blocks // from now, then becomes publicly readable forever. Returns the capsule id. func Leave(cur realm, message string, delayBlocks int64) int64 { if !cur.Previous().IsUserCall() { panic("only direct user calls can leave a capsule") } message = strings.TrimSpace(message) if message == "" { panic("message must not be empty") } if len(message) > maxMessageLen { panic("message too long") } if delayBlocks <= 0 || delayBlocks > maxDelayBlocks { panic("delayBlocks out of range") } now := runtime.ChainHeight() id := nextID nextID++ c := &capsule{ id: id, author: cur.Previous().Address(), message: message, createdAt: now, unlockHeight: now + delayBlocks, } capsules.Set(idKey(id), c) chain.Emit("CapsuleSealed", "id", strconv.FormatInt(id, 10), "unlockHeight", strconv.FormatInt(c.unlockHeight, 10), ) return id } func idKey(id int64) string { s := strconv.FormatInt(id, 10) return strings.Repeat("0", 12-len(s)) + s } func Render(path string) string { switch { case path == "": return renderHome() case path == "sealed": return renderSealed() case strings.HasPrefix(path, "capsule/"): return renderCapsule(strings.TrimPrefix(path, "capsule/")) default: return "> [!WARNING]\n> Path not found\n" } } func renderHome() string { now := runtime.ChainHeight() revealed := 0 sealed := 0 capsules.Iterate("", "", func(_ string, value any) bool { if value.(*capsule).unlockHeight <= now { revealed++ } else { sealed++ } return false }) var out strings.Builder out.WriteString("# ⏳ Time Capsule Guestbook\n\n") out.WriteString("Leave a message for the future. It stays sealed until its unlock block height, then anyone can read it — call `Leave(message, delayBlocks)`.\n\n") out.WriteString(strconv.Itoa(revealed+sealed) + " capsule(s) total · " + strconv.Itoa(revealed) + " unlocked · " + strconv.Itoa(sealed) + " still sealed") if sealed > 0 { out.WriteString(" (see [sealed](sealed))") } out.WriteString(".\n\n## Unlocked messages\n\n") if revealed == 0 { out.WriteString("_None yet. Be the first to leave one that will open later._\n") return out.String() } capsules.Iterate("", "", func(_ string, value any) bool { c := value.(*capsule) if c.unlockHeight <= now { out.WriteString(renderEntry(c)) } return false }) return out.String() } func renderSealed() string { now := runtime.ChainHeight() var out strings.Builder out.WriteString("# Sealed Capsules\n\n") found := false capsules.Iterate("", "", func(_ string, value any) bool { c := value.(*capsule) if c.unlockHeight > now { found = true out.WriteString("- capsule [#" + strconv.FormatInt(c.id, 10) + "](capsule/" + strconv.FormatInt(c.id, 10) + ") by `" + c.author.String() + "` — unlocks at block " + strconv.FormatInt(c.unlockHeight, 10) + " (" + strconv.FormatInt(c.unlockHeight-now, 10) + " blocks left)\n") } return false }) if !found { out.WriteString("_No sealed capsules right now._\n") } return out.String() } func renderCapsule(idStr string) string { id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { return "> [!WARNING]\n> Invalid capsule id\n" } v := capsules.Get(idKey(id)) if v == nil { return "> [!WARNING]\n> Capsule not found\n" } c := v.(*capsule) now := runtime.ChainHeight() var out strings.Builder out.WriteString("# Capsule #" + strconv.FormatInt(c.id, 10) + "\n\n") out.WriteString("Sealed by `" + c.author.String() + "` at block " + strconv.FormatInt(c.createdAt, 10) + ".\n\n") if c.unlockHeight > now { out.WriteString("\U0001f512 Still sealed. Unlocks at block " + strconv.FormatInt(c.unlockHeight, 10) + " (" + strconv.FormatInt(c.unlockHeight-now, 10) + " blocks left).\n") return out.String() } out.WriteString("\U0001f513 Unlocked at block " + strconv.FormatInt(c.unlockHeight, 10) + ":\n\n> " + sanitize.InlineText(c.message) + "\n") return out.String() } func renderEntry(c *capsule) string { return "- **#" + strconv.FormatInt(c.id, 10) + "** by `" + c.author.String() + "` (sealed at block " + strconv.FormatInt(c.createdAt, 10) + ", opened at block " + strconv.FormatInt(c.unlockHeight, 10) + "):\n > " + sanitize.InlineText(c.message) + "\n\n" }
- #8/gno.MemPackageType
- #9 MPUserProd
Result log
msg:0,success:true,log:,events:[]