PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

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/g12yg9nh4ncma44emgm8msxe8aavzywt0p95tanv/home

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
home
Namespace
g12yg9nh4ncma44emgm8msxe8aavzywt0p95tanv
Files
5 (gnomod.toml)
Exported functions
15
Module
gno.land/r/g12yg9nh4ncma44emgm8msxe8aavzywt0p95tanv/home
gno
0.9

Files (5)

  • gnomod.tomltoml
  • content.gnogno
  • home.gnogno
  • widgets.gnogno
  • writes.gnogno
writes.gnogno
1package home23import (4	"chain"5	"chain/banker"6	"chain/runtime"7	"chain/runtime/unsafe"8	"strconv"9	"strings"10	"time"11	"unicode"12	"unicode/utf8"13)1415// Every write function calls runtime.AssertOriginCall() first, in its own16// body: from inside a helper it refuses every call.1718const maxSignBytes = 5601920func invalid(field string) string { return errInvalidInput + field }2122// checkLine enforces valid UTF-8, a rune count in [min, max], and no control23// characters (so no newline either): the text may end up inside an SVG,24// where they would break the XML.25func checkLine(field, s string, min, max int) {26	if !utf8.ValidString(s) {27		panic(invalid(field))28	}29	n := 030	for _, r := range s {31		if r < 0x20 || r == 0x7f || r == 0xfffe || r == 0xffff {32			panic(invalid(field))33		}34		n++35	}36	if n < min || n > max {37		panic(invalid(field))38	}39}4041// assertMasterKey refuses a transaction signed by a session key. Delegated42// keys may edit content, but not move ownership or hide an entry for good.43func assertMasterKey() {44	if _, _, _, isSession := runtime.GetSessionInfo(); isSession {45		panic(errMasterKey)46	}47}4849// checkBadgeURL wants a printable-ASCII https:// URL (non-ASCII must be50// percent-encoded) with no quotes, backslashes, backticks, parentheses,51// square brackets, < or >, so the markdown link can't end early or become52// javascript:.53func checkBadgeURL(u string) {54	if !strings.HasPrefix(u, "https://") {55		panic(invalid("url"))56	}57	for i := 0; i < len(u); i++ {58		c := u[i]59		if c <= 0x20 || c >= 0x7f || strings.IndexByte("\"'\\`()[]<>", c) >= 0 {60			panic(invalid("url"))61		}62	}63}6465// parseBadges reads one badge per line, "label | value | url". Blank lines66// between two badges start a new row; other blank lines are skipped.67func parseBadges(spec string) []badge {68	var out []badge69	row := false70	for _, line := range strings.Split(spec, "\n") {71		line = strings.TrimSpace(line)72		if line == "" {73			row = len(out) > 074			continue75		}76		parts := strings.Split(line, "|")77		if len(parts) != 3 || len(out) == maxBadges {78			panic(invalid("spec"))79		}80		b := badge{81			label: strings.TrimSpace(parts[0]),82			value: strings.TrimSpace(parts[1]),83			url:   strings.TrimSpace(parts[2]),84			row:   row,85		}86		row = false87		checkLine("label", b.label, 1, 24)88		checkLine("value", b.value, 1, 24)89		checkBadgeURL(b.url)90		out = append(out, b)91	}92	return out93}9495// hasVisible reports whether s has a character that draws something: not a96// space, a format character (zero-width, bidi), a combining mark, or one of97// the blank-looking fillers (Hangul fillers, braille blank).98func hasVisible(s string) bool {99	for _, r := range s {100		switch {101		case unicode.IsSpace(r), unicode.In(r, unicode.Cf, unicode.Mn, unicode.Me):102		case r == 0x115f, r == 0x1160, r == 0x3164, r == 0xffa0, r == 0x2800:103		default:104			return true105		}106	}107	return false108}109110// validName wants 1 to max characters from a-z, 0-9 and "-": safe in a111// link, a tab label, and a path gnoweb passes to Render.112func validName(s string, max int) bool {113	if len(s) < 1 || len(s) > max {114		return false115	}116	for _, c := range s {117		if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {118			return false119		}120	}121	return true122}123124// checkPage enforces the tab name, the page size, and the tab count.125func checkPage(tab, md string) {126	if !validName(tab, maxTabName) {127		panic(invalid("tab"))128	}129	if len(md) > maxPageBytes || !utf8.ValidString(md) {130		panic(invalid("md"))131	}132	if _, exists := pages[tab]; !exists && len(tabs) >= maxTabs {133		panic(invalid("tab"))134	}135}136137// checkPost enforces the slug, a one-line title, and the body size. Title138// and body must each show something.139func checkPost(slug, title, body string) {140	if !validName(slug, maxSlugBytes) {141		panic(invalid("slug"))142	}143	checkLine("title", title, 1, maxTitleRunes)144	if !hasVisible(title) {145		panic(invalid("title"))146	}147	if len(body) > maxPageBytes || !utf8.ValidString(body) || !hasVisible(body) {148		panic(invalid("body"))149	}150}151152// seqKey zero-pads n so that keys sort in number order.153func seqKey(n int) string {154	s := strconv.Itoa(n)155	return strings.Repeat("0", seqWidth-len(s)) + s156}157158func touch() { updated = runtime.ChainHeight() }159160// ----- owner only -----161162// SetNow sets the one-line status shown on Home; empty clears it.163func SetNow(cur realm, text string) {164	runtime.AssertOriginCall()165	assertOwner(cur)166	checkLine("text", text, 0, maxLineRunes)167	now = nowLine{text: text, height: runtime.ChainHeight()}168	touch()169	chain.Emit("NowSet", "text", text)170}171172// SetProfile sets the name and tagline drawn on the banner.173func SetProfile(cur realm, name, tagline string) {174	runtime.AssertOriginCall()175	assertOwner(cur)176	checkLine("name", name, 1, 24)177	checkLine("tagline", tagline, 0, 60)178	profile = profileData{name: name, tagline: tagline}179	touch()180	chain.Emit("ProfileSet", "name", name, "tagline", tagline)181}182183// SetBadges replaces every badge. One badge per line: "label | value | url";184// a blank line starts a new row.185func SetBadges(cur realm, spec string) {186	runtime.AssertOriginCall()187	assertOwner(cur)188	badges = parseBadges(spec)189	touch()190	chain.Emit("BadgesSet", "count", strconv.Itoa(len(badges)))191}192193// SetPage creates or replaces any tab's markdown, Blog and Guestbook194// included. A new tab goes just before Guestbook, which stays last.195func SetPage(cur realm, tab, md string) {196	runtime.AssertOriginCall()197	assertOwner(cur)198	checkPage(tab, md)199	if _, exists := pages[tab]; !exists {200		tabs = append(tabs[:len(tabs)-1], tab, guestbookTab) // guestbook stays last201	}202	pages[tab] = md203	touch()204	chain.Emit("PageSet", "tab", tab)205}206207// RemovePage removes any tab except home, blog and guestbook.208func RemovePage(cur realm, tab string) {209	runtime.AssertOriginCall()210	assertOwner(cur)211	if tab == homeTab || tab == blogTab || tab == guestbookTab {212		panic(errTabsRequired)213	}214	if _, ok := pages[tab]; !ok {215		panic(errNoTab)216	}217	delete(pages, tab)218	kept := make([]string, 0, len(tabs)-1)219	for _, t := range tabs {220		if t != tab {221			kept = append(kept, t)222		}223	}224	tabs = kept225	touch()226	chain.Emit("PageRemoved", "tab", tab)227}228229// SetPost publishes a post, or edits the post at slug. An edit keeps the230// post's place in the list and its publish date.231func SetPost(cur realm, slug, title, body string) {232	runtime.AssertOriginCall()233	assertOwner(cur)234	checkPost(slug, title, body)235	today := time.Now().UTC().Format("2006-01-02")236	touch()237	if p, ok := posts.Get(slug).(*post); ok {238		p.title, p.body, p.edited = title, body, today239		chain.Emit("PostEdited", "slug", slug)240		return241	}242	postSeq++243	seq := seqKey(postSeq)244	posts.Set(slug, &post{title: title, body: body, published: today, seq: seq})245	postOrder.Set(seq, slug)246	chain.Emit("PostPublished", "slug", slug)247}248249// RemovePost deletes a post for good; its slug can be used again.250func RemovePost(cur realm, slug string) {251	runtime.AssertOriginCall()252	assertOwner(cur)253	v, ok := posts.Remove(slug)254	if !ok {255		panic(errNoPost)256	}257	postOrder.Remove(v.(*post).seq)258	touch()259	chain.Emit("PostRemoved", "slug", slug)260}261262// Hide hides an author's guestbook entry for good. The entry stays stored,263// so the author can't sign again, and nobody's deposit is refunded. Hiding264// a hidden entry does nothing.265func Hide(cur realm, author address) {266	runtime.AssertOriginCall()267	assertOwner(cur)268	assertMasterKey()269	e, ok := entries.Get(author.String()).(*entry)270	if !ok {271		panic(errNoEntry)272	}273	if e.hidden {274		return275	}276	e.hidden = true277	touch()278	chain.Emit("EntryHidden", "author", author.String())279}280281// TransferOwnership records newOwner as pending; nothing changes until that282// address calls AcceptOwnership. Naming the current owner cancels.283func TransferOwnership(cur realm, newOwner address) {284	runtime.AssertOriginCall()285	assertOwner(cur)286	assertMasterKey()287	if !newOwner.IsValid() {288		panic(invalid("newOwner"))289	}290	touch()291	if newOwner == owner {292		pendingOwner = ""293		chain.Emit("OwnershipTransferCanceled", "owner", owner.String())294		return295	}296	pendingOwner = newOwner297	chain.Emit("OwnershipTransferStarted", "from", owner.String(), "to", newOwner.String())298}299300// AcceptOwnership completes a transfer. Only the pending owner may call it,301// and a realm address can't pass the account guard, so it never can.302func AcceptOwnership(cur realm) {303	runtime.AssertOriginCall()304	caller := callerOf(cur)305	if pendingOwner == "" || caller != pendingOwner {306		panic(errPendingOnly)307	}308	prev := owner309	owner = caller310	pendingOwner = ""311	touch()312	chain.Emit("OwnershipTransferred", "from", prev.String(), "to", caller.String())313}314315// ----- visitors -----316317// Wave adds one to the wave count. It's a plain counter: anyone can wave318// again, and a wave stores nothing but the count.319func Wave(cur realm) {320	runtime.AssertOriginCall()321	from := callerOf(cur).String()322	waves++323	chain.Emit("Waved", "from", from)324}325326// Sign leaves one guestbook entry per address. The message is stored trimmed327// and sanitized when rendered, and must contain a visible character.328func Sign(cur realm, msg string) {329	runtime.AssertOriginCall()330	caller := callerOf(cur)331	if len(msg) > maxSignBytes {332		panic(invalid("msg"))333	}334	msg = strings.TrimSpace(msg)335	checkLine("msg", msg, 1, maxLineRunes)336	if !hasVisible(msg) {337		panic(invalid("msg")) // the entry would show up blank338	}339	key := caller.String()340	if entries.Has(key) {341		panic(errSigned)342	}343	entries.Set(key, &entry{msg: msg, height: runtime.ChainHeight()})344	order.Set(seqKey(order.Size()+1), caller)345	chain.Emit("Signed", "from", key)346}347348// Tip forwards exactly the ugnot sent with this call to the owner. The349// account guard is inlined so the audit harness sees it next to OriginSend.350// It forwards what was sent, never the realm's balance: the OriginSend351// banker refuses to spend more than was sent.352func Tip(cur realm) {353	runtime.AssertOriginCall()354	if !cur.IsCurrent() || !cur.Previous().IsUserCall() {355		panic(errUnauthorized)356	}357	sent := unsafe.OriginSend()358	if len(sent) != 1 || sent[0].Denom != "ugnot" || sent[0].Amount <= 0 {359		panic(errUgnotOnly)360	}361	banker.NewBanker(banker.BankerTypeOriginSend, cur).SendCoins(cur.Address(), owner, sent)362	tips.count++363	tips.total += sent[0].Amount364	chain.Emit("Tipped", "from", cur.Previous().Address().String(), "amount", strconv.FormatInt(sent[0].Amount, 10))365}366

Functions

  • AcceptOwnership(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})

  • Hide(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}, author string)

  • Owner() string

  • RemovePage(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}, tab string)

  • RemovePost(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}, slug string)

  • Render(path string) string

  • SetBadges(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}, spec string)

  • SetNow(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}, text string)

  • SetPage(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}, tab string, md string)

  • SetPost(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}, slug string, title string, body string)

  • SetProfile(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}, name string, tagline string)

  • Sign(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}, msg string)

  • Tip(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})

  • TransferOwnership(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}, newOwner string)

  • Wave(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})

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.