Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

guestbook.gno

3.44 Kb · 105 lines
  1// Package guestbook is a public, on-chain guestbook realm for gno.land.
  2//
  3// Anyone can sign the guestbook with a short message via the Sign crossing
  4// function. Every entry records the caller's address, the message, and the
  5// block height at which it was signed. Render lists all entries newest-first
  6// as a Markdown table.
  7//
  8// v1 renders through [p/moul/kit/ui](/p/nym-moultest123/kit/ui/v0) instead of the
  9// hand-rolled shortAddr and escapeCell helpers v0 carried. The escaping is the
 10// substantive change: v0 replaced four characters, while ui.Cell also strips
 11// bidi and zero-width characters, which a message can otherwise use to reorder
 12// how the rest of the row reads.
 13package guestbook
 14
 15import (
 16	"strconv"
 17	"strings"
 18
 19	"chain"
 20	"chain/runtime"
 21
 22	"gno.land/p/nym-moultest123/kit/store/v0"
 23	"gno.land/p/nym-moultest123/kit/ui/v0"
 24)
 25
 26// Entry is a single signed guestbook line. It carries no ID field: the id
 27// belongs to the store, which hands it back on lookup and iteration.
 28type Entry struct {
 29	Author  string // bech32 address of the signer
 30	Message string // the message left by the signer
 31	Height  int64  // block height at which the entry was signed
 32}
 33
 34const maxMessageLen = 280
 35
 36// entries assigns the sequence numbers. v1 kept its own count plus a seqKey()
 37// that zero-padded to width 16, the widest of the six hand-rolled variants in
 38// this repo and still a ceiling; the store key has none.
 39var entries = store.Named("guestbook: entry")
 40
 41// Sign appends a new entry to the guestbook attributed to the immediate caller.
 42//
 43// It is a crossing function (gno 0.9 interrealm convention): callers invoke it
 44// as Sign(cross(cur), "hello"). The cur.IsCurrent() guard is the authentication
 45// primitive — without it a stale/forged realm value could spoof the author.
 46func Sign(cur realm, message string) {
 47	if !cur.IsCurrent() {
 48		panic("guestbook: spoofed realm; Sign must be called via cross(cur)")
 49	}
 50
 51	author := cur.Previous().Address().String()
 52	id, e := addEntry(author, message, runtime.ChainHeight())
 53
 54	chain.Emit("Signed",
 55		"id", id.String(),
 56		"author", e.Author,
 57		"height", strconv.FormatInt(e.Height, 10),
 58	)
 59}
 60
 61// addEntry validates the message and appends an entry. Non-crossing internal
 62// helper so the append/validation logic is unit-testable without a realm frame.
 63// Panics (aborting the tx, reverting state) on an invalid message.
 64func addEntry(author, message string, height int64) (store.ID, *Entry) {
 65	msg := strings.TrimSpace(message)
 66	if msg == "" {
 67		panic("guestbook: message must not be empty")
 68	}
 69	if len(msg) > maxMessageLen {
 70		panic("guestbook: message too long (max " + strconv.Itoa(maxMessageLen) + " bytes)")
 71	}
 72
 73	e := &Entry{
 74		Author:  author,
 75		Message: msg,
 76		Height:  height,
 77	}
 78	return entries.Add(e), e
 79}
 80
 81// Count returns the total number of signatures. Read-only, non-crossing.
 82func Count() int {
 83	return entries.Len()
 84}
 85
 86// Render lists every entry newest-first as a Markdown table plus a total count.
 87func Render(path string) string {
 88	if entries.Len() == 0 {
 89		return "# Guestbook\n\n" + ui.Empty("No one has signed yet. Be the first!")
 90	}
 91
 92	t := ui.NewTable("#", "Who", "Message", "Height")
 93	// Ids ascend with signing, so reverse iteration is newest-first.
 94	entries.EachReverse(func(id store.ID, v any) {
 95		e := v.(*Entry)
 96		t.Row(
 97			id.String(),
 98			ui.AddrOf(e.Author),
 99			ui.Cell(e.Message),
100			strconv.FormatInt(e.Height, 10),
101		)
102	})
103
104	return "# Guestbook\n\n**Total signatures:** " + strconv.Itoa(entries.Len()) + "\n\n" + t.String()
105}