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.

ledgerart.gno

4.18 Kb · 124 lines
  1package nsview
  2
  3import (
  4	"chain/runtime"
  5	"chain/runtime/unsafe"
  6	"strconv"
  7	"strings"
  8	"time"
  9
 10	"gno.land/r/nym-mnsrehearsal001/nsdata/v2"
 11	"gno.land/r/nym-mnsrehearsal001/nsnft/v2"
 12	"gno.land/r/nym-mnsrehearsal001/nsskin/v2"
 13)
 14
 15/*
 16THIS REALM DRAWS THE NAMES IN WALLETS.
 17
 18A wallet asks the LEDGER, nsnft, for TokenURI and TokenMetadata, because
 19the collection id names it. nsnft can never change, so it does not draw:
 20it holds a Renderer handed to it by the one view realm it has approved,
 21and that realm is this one. Swapping the art is deploying a new nsview and
 22approving it on nsnft (a timelocked change), nothing more.
 23
 24The renderer's methods run with THIS realm's authority (gno's
 25declaring-realm borrow), so drawing for the ledger can read anything and
 26write nothing of the ledger's. This realm holds no state worth writing
 27anyway; selfPath is set once, at init.
 28*/
 29
 30// selfPath is this realm's path without the chain domain, for links to
 31// its own gnoweb pages. Set at init, because inside a renderer call the
 32// realm context belongs to whoever asked, not to this realm.
 33var selfPath string
 34
 35type ledgerArt struct{}
 36
 37// TokenImage is nsview's TokenURI: the full card, drawn now.
 38func (ledgerArt) TokenImage(tid string) string { return TokenURI(tid) }
 39
 40func (ledgerArt) TokenMetadata(tid string) string { return tokenMetadata(tid) }
 41
 42func init(cur realm) {
 43	selfPath = strings.TrimPrefix(cur.PkgPath(), runtime.ChainDomain())
 44	// Register only when this realm is the approved one. Registering
 45	// unconditionally would make deploying a NEW view generation fail
 46	// until its approval had cleared the timelock; this way it deploys,
 47	// and RegisterWithLedger finishes the job once it is approved.
 48	if nsnft.RendererPath() == cur.PkgPath() {
 49		nsnft.RegisterRenderer(cross(cur), ledgerArt{})
 50	}
 51}
 52
 53// RegisterWithLedger hands the ledger this realm's renderer. Anyone may
 54// call it: it only ever registers THIS realm's own drawing, and the ledger
 55// refuses unless this realm is the one it has approved.
 56func RegisterWithLedger(cur realm) {
 57	// This realm has no banker and no way to return coins.
 58	if cur.Previous().IsUserCall() && len(unsafe.OriginSend()) > 0 {
 59		panic("nsview: this call takes no payment")
 60	}
 61	nsnft.RegisterRenderer(cross(cur), ledgerArt{})
 62}
 63
 64/*
 65tokenMetadata is the camelCase JSON Adena reads: name, description,
 66externalUrl, attributes of {traitType, value}. Everything in it is read
 67from the registry at the moment of asking, like the card, so a renewal or
 68a sale shows up without anybody writing anything.
 69*/
 70func tokenMetadata(tid string) string {
 71	label, dom := splitTokenID(tid)
 72	if label == "" || dom == "" || !nsdata.NameExists(label, dom) {
 73		return `{"name":"` + escapeJSON(tid) + `","description":"A name on the Meme Name Service.","attributes":[]}`
 74	}
 75	_, registered, expires, _, frozen, paid := nsdata.GetNameInfo(label, dom)
 76	status, _ := statusFields(expires, frozen)
 77	tier := "paid"
 78	if !paid {
 79		tier = "free tier"
 80	}
 81	skin := "none"
 82	if worn := nsskin.SkinOnName(label, dom); worn != "" && nsskin.ArtForName(label, dom) != "" {
 83		skin = worn
 84	}
 85	out := `{"name":"` + escapeJSON(label+"*"+dom) + `",` +
 86		`"description":"A name on the Meme Name Service. You own a word now. Renew it, or somebody else will.",`
 87	if selfPath != "" {
 88		out += `"externalUrl":"` + escapeJSON("https://"+runtime.ChainDomain()+selfPath+":"+label+"*"+dom) + `",`
 89	}
 90	out += `"attributes":[` +
 91		attr("Domain", "*"+dom) + `,` +
 92		attr("Length", strconv.Itoa(len(label))) + `,` +
 93		attr("Status", strings.ToLower(status)) + `,` +
 94		attr("Registered", time.Unix(registered, 0).UTC().Format("2006-01-02")) + `,` +
 95		attr("Expires", time.Unix(expires, 0).UTC().Format("2006-01-02")) + `,` +
 96		attr("Tier", tier) + `,` +
 97		attr("Skin", skin) + `]}`
 98	return out
 99}
100
101func attr(k, v string) string {
102	return `{"traitType":"` + escapeJSON(k) + `","value":"` + escapeJSON(v) + `"}`
103}
104
105func escapeJSON(s string) string {
106	var b strings.Builder
107	for i := 0; i < len(s); i++ {
108		c := s[i]
109		switch {
110		case c == '"':
111			b.WriteString(`\"`)
112		case c == '\\':
113			b.WriteString(`\\`)
114		case c < 0x20:
115			const hex = "0123456789abcdef"
116			b.WriteString(`\u00`)
117			b.WriteByte(hex[c>>4])
118			b.WriteByte(hex[c&0xf])
119		default:
120			b.WriteByte(c)
121		}
122	}
123	return b.String()
124}