PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidators

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/guilds

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
guilds
Namespace
g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr
Files
2 (gnomod.toml)
Exported functions
5
Module
gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/guilds
gno
0.9

Files (2)

  • gnomod.tomltoml
  • guilds.gnogno
guilds.gnogno
1// Package guilds records which Discord server a court's moderators chose.2//3// WHY A SEPARATE REALM. kourtv2 is deployed and knows nothing about Discord, and4// a realm on chain is not edited — adding a field there means a new realm and a5// migration for every court that exists. This holds one string per court and6// asks kourtv2 who may set it, so the court's own moderator list stays the only7// authority and this file never has one of its own.8//9// WHY IT EXISTS AT ALL, when the service could verify a signed message and did.10// It could, and the signature had to be made at a terminal, because a wallet11// signs TRANSACTIONS and not arbitrary strings. So the last step of standing up12// a court's Discord server was a command nobody would ever discover, and the13// court page said so and stopped. A realm call IS a transaction, which is14// exactly what a wallet already does — the same button every other action on the15// page uses.16//17// WHAT IT DOES NOT DO. It does not check that the guild exists, that the bot is18// in it, or that anybody can join it. Those are the service's to know and it19// already does; this records a CHOICE, made by an address the court recognises,20// at a height anybody can read back. A guild id here with no bot behind it is a21// court pointing at nothing, which is visible rather than dangerous.22package guilds2324import (25	"chain"26	"strings"2728	bptree "gno.land/p/nt/bptree/v0"2930	kourt "gno.land/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt"31)3233// choice is what a court's moderators decided: which server, and who said so.34//35// THE SETTER IS STORED, not only emitted. The event carries it too, and the36// event is the history — but kourt.xyz reads this realm over qeval and has no37// indexer, so a service that recorded "listed, signed by" from the event alone38// would need one. Reading it back is one lookup on a page that already does one.39type choice struct {40	guildID string41	by      address42}4344// guildOf is court slug -> choice. One server per court: a court with two is a45// court whose readers are told different things by the same page.46//47// A bptree rather than an avl tree because kourtv2 uses one for every ordered48// map it keeps, and Courts() wants slug order.49var guildOf = bptree.NewBPTree32()5051// Set records the guild for a court, or clears it when guildID is empty.52//53// THE CALLER IS THE MODERATOR, NOT THIS REALM. cur.Previous().Address() is who54// asked, and kourt.IsCourtMod is the only thing consulted — so a moderator55// removed by the court loses this at the same moment, with no state here to keep56// in step. IsCourtMod panics on a court that does not exist, which is the answer57// we want: there is nothing to bind.58func Set(cur realm, courtSlug, guildID string) {59	if !cur.IsCurrent() {60		panic("guilds: stale realm")61	}62	who := cur.Previous().Address()63	if !kourt.IsCourtMod(courtSlug, who) {64		panic("guilds: only a moderator of this court may set its server")65	}66	id := strings.TrimSpace(guildID)67	if id == "" {68		if _, removed := guildOf.Remove(courtSlug); removed {69			chain.Emit("GuildCleared", "court", courtSlug, "by", who.String())70		}71		return72	}73	// A Discord snowflake is decimal digits, and every id minted since 2015 is74	// 17 to 20 of them. Bounded here so a mistyped paste is refused at the call75	// rather than stored and puzzled over on the page.76	if len(id) < 17 || len(id) > 20 {77		panic("guilds: a Discord server id is 17 to 20 digits")78	}79	for i := 0; i < len(id); i++ {80		if id[i] < '0' || id[i] > '9' {81			panic("guilds: a Discord server id is digits only")82		}83	}84	guildOf.Set(courtSlug, choice{guildID: id, by: who})85	chain.Emit("GuildSet", "court", courtSlug, "guild", id, "by", who.String())86}8788// GuildOf is the court's chosen server, or "" if it has none. Never panics: the89// service asks this about every court it knows, including ones that never chose.90func GuildOf(courtSlug string) string {91	c, ok := guildOf.Get(courtSlug).(choice)92	if !ok {93		return ""94	}95	return c.guildID96}9798// Chosen is the whole record: the server, and the address that chose it.99//100// ONE READ AND NOT TWO, and this exists for that reason alone. kourt.xyz asks101// both questions together and acts on the pair — it publishes the guild AND102// records the setter as who signed for it — and two qeval calls can straddle a103// change. A moderator re-aiming the binding between them hands one decision's104// guild to another decision's signer, which is then written into the site's105// database as fact. A pair that cannot be read atomically should not be read as a106// pair.107//108// BOTH EMPTY OR NEITHER. A court that never chose answers ("", ""), which is the109// commonest answer and is not an error.110//111// THE SETTER IS WHO ASKED, NOT WHO MODERATES NOW. A court that replaces its112// moderators does not change this — the choice was made by whoever made it, and113// rewriting that would destroy the only record of who did. Whether they STILL114// moderate is a separate question, and kourtv2 is the one to ask it.115//116// STRINGS AND NOT AN address, because the caller is a machine reading qeval's117// printed output: an address prints as `("g1…" .uverse.address)` and a string as118// `("g1…" string)`, and the second is the one a parser can hold to.119func Chosen(courtSlug string) (guildID, by string) {120	c, ok := guildOf.Get(courtSlug).(choice)121	if !ok {122		return "", ""123	}124	return c.guildID, c.by.String()125}126127// Courts lists every court that has chosen a server, in slug order — so the128// service can reconcile the whole set without knowing what to ask for.129func Courts() []string {130	out := []string{}131	guildOf.Iterate("", "", func(k string, _ any) bool {132		out = append(out, k)133		return false134	})135	return out136}137138func Render(path string) string {139	var b strings.Builder140	b.WriteString("# Court Discord servers\n\n")141	b.WriteString("Which Discord server each court's moderators chose. ")142	b.WriteString("Set by a moderator of the court, read by kourt.xyz.\n\n")143	n := 0144	guildOf.Iterate("", "", func(k string, v any) bool {145		c, _ := v.(choice)146		b.WriteString("- **" + k + "** — `" + c.guildID + "` (set by " + c.by.String() + ")\n")147		n++148		return false149	})150	if n == 0 {151		b.WriteString("_No court has chosen one yet._\n")152	}153	return b.String()154}155

Functions

  • Chosen(courtSlug string) (guildID string, by string)

  • Courts() []string

  • GuildOf(courtSlug string) string

  • Render(path string) string

  • Set(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, courtSlug string, guildID string)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.