Transaction
A3241981B7186F…C8B5E8D7F21F
Block 26,070 · index 0 · indexed
Summary
- Hash
- A3241981B7186F263FD72E55AFF8A820A0D15AB94B7D9433471FC8B5E8D7F21F
- Block
- 26,070
- Size
- 7701 bytes
- Gas used
- 11,026,126 / 13,231,411
- Fee
- 1000000ugnot
- Memo
- gnopublish
- Status
- success
Messages
Arguments · 9
- #1guestbook
- #2README.md
- #3# 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, on-chain guestbook realm for gno.land. Anyone can leave a signed message; every entry records the signer's address, the message, and the block height at which it was signed. The realm's `Render` page shows all entries as a Markdown table, newest-first, with a running total. Built for the **sapphire** testnet (gno 0.9). ## Realm path ``` gno.land/r/REPLACE_ADDR/guestbook ``` (`REPLACE_ADDR` is substituted with the deployer's address at publish time.) ## Public API - `Sign(cur realm, message string)` — crossing tx. Appends an entry attributed to the immediate caller. Panics on an empty or over-long (>280 bytes) message. - `Count() int` — total number of signatures (read-only). - `Render(path string) string` — Markdown table of all entries, newest-first. ## Example calls Sign the guestbook (from an EOA via `gnokey`): ```sh gnokey maketx call \ -pkgpath "gno.land/r/REPLACE_ADDR/guestbook" \ -func Sign \ -args "gm gno.land!" \ -gas-fee 1000000ugnot -gas-wanted 2000000 \ -broadcast -chainid sapphire-1 -remote <rpc> mykey ``` From another realm, invoke it as a crossing call: ```go guestbook.Sign(cross(cur), "hello from my realm") ``` View the rendered guestbook: ```sh gnokey query vm/qrender --data "gno.land/r/REPLACE_ADDR/guestbook:" ``` ## Example render output ```markdown # Guestbook **Total signatures:** 2 | # | Who | Message | Height | |---|-----|---------|--------| | 2 | g1v9jxgu…0gh | hello from my realm | 4210 | | 1 | g1jg8mtu…qf5 | gm gno.land! | 4180 | ``` ## Notes - Caller identity uses the gno 0.9 interrealm convention: `Sign` is a crossing function that checks `cur.IsCurrent()` before deriving the author from `cur.Previous().Address()`. - Block height comes from `chain/runtime.ChainHeight()` — deterministic. - Entries are stored in a `gno.land/p/nt/avl/v0` tree keyed by a zero-padded sequence number so iteration order is deterministic and matches insertion order (reversed for newest-first rendering). ``` <!-- 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/guestbook/v0" gno = "0.9"
- #6guestbook.gno
- #7// Package guestbook is a public, on-chain guestbook realm for gno.land. // // Anyone can sign the guestbook with a short message via the Sign crossing // function. Every entry records the caller's address, the message, and the // block height at which it was signed. Render lists all entries newest-first // as a Markdown table. package guestbook import ( "strconv" "strings" "chain" "chain/runtime" "gno.land/p/nt/avl/v0" ) // Entry is a single signed guestbook line. type Entry struct { ID int // 1-based sequence number Author string // bech32 address of the signer Message string // the message left by the signer Height int64 // block height at which the entry was signed } const maxMessageLen = 280 var ( // entries is keyed by a zero-padded sequence so avl iteration is ordered. entries = avl.NewTree() count int ) // Sign appends a new entry to the guestbook attributed to the immediate caller. // // It is a crossing function (gno 0.9 interrealm convention): callers invoke it // as Sign(cross(cur), "hello"). The cur.IsCurrent() guard is the authentication // primitive — without it a stale/forged realm value could spoof the author. func Sign(cur realm, message string) { if !cur.IsCurrent() { panic("guestbook: spoofed realm; Sign must be called via cross(cur)") } author := cur.Previous().Address().String() e := addEntry(author, message, runtime.ChainHeight()) chain.Emit("Signed", "id", strconv.Itoa(e.ID), "author", e.Author, "height", strconv.FormatInt(e.Height, 10), ) } // addEntry validates the message and appends an entry. Non-crossing internal // helper so the append/validation logic is unit-testable without a realm frame. // Panics (aborting the tx, reverting state) on an invalid message. func addEntry(author, message string, height int64) *Entry { msg := strings.TrimSpace(message) if msg == "" { panic("guestbook: message must not be empty") } if len(msg) > maxMessageLen { panic("guestbook: message too long (max " + strconv.Itoa(maxMessageLen) + " bytes)") } count++ e := &Entry{ ID: count, Author: author, Message: msg, Height: height, } entries.Set(seqKey(count), e) return e } // Count returns the total number of signatures. Read-only, non-crossing. func Count() int { return count } // Render lists every entry newest-first as a Markdown table plus a total count. func Render(path string) string { var b strings.Builder b.WriteString("# Guestbook\n\n") if count == 0 { b.WriteString("_No one has signed yet. Be the first!_\n") return b.String() } b.WriteString("**Total signatures:** ") b.WriteString(strconv.Itoa(count)) b.WriteString("\n\n") b.WriteString("| # | Who | Message | Height |\n") b.WriteString("|---|-----|---------|--------|\n") // avl iterates ascending by key; ReverseIterate gives newest-first. entries.ReverseIterate("", "", func(_ string, v any) bool { e := v.(*Entry) b.WriteString("| ") b.WriteString(strconv.Itoa(e.ID)) b.WriteString(" | ") b.WriteString(shortAddr(e.Author)) b.WriteString(" | ") b.WriteString(escapeCell(e.Message)) b.WriteString(" | ") b.WriteString(strconv.FormatInt(e.Height, 10)) b.WriteString(" |\n") return false }) return b.String() } // seqKey renders a sequence number as a fixed-width, zero-padded string so // avl's lexicographic key order matches numeric order. func seqKey(n int) string { s := strconv.Itoa(n) const width = 16 if len(s) >= width { return s } return strings.Repeat("0", width-len(s)) + s } // shortAddr abbreviates a bech32 address for compact display. func shortAddr(addr string) string { if len(addr) <= 12 { return addr } return addr[:8] + "…" + addr[len(addr)-4:] } // escapeCell neutralizes characters that would break a Markdown table cell. func escapeCell(s string) string { s = strings.ReplaceAll(s, "\\", "\\\\") s = strings.ReplaceAll(s, "|", "\\|") s = strings.ReplaceAll(s, "\r\n", " ") s = strings.ReplaceAll(s, "\n", " ") s = strings.ReplaceAll(s, "\r", " ") return s }
- #8/gno.MemPackageType
- #9 MPUserProd
Result log
msg:0,success:true,log:,events:[]