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.

meta.gno

5.00 Kb · 224 lines
  1// Package meta stores off-pad token presentation data (description, image, socials).
  2// Independent of pad versions — upgrade without redeploying markets.
  3//
  4// Key = padPkg + "|" + launchId
  5// First SetMeta caller becomes owner; only owner may update later.
  6package meta
  7
  8import (
  9	"chain"
 10	"chain/runtime"
 11	"strconv"
 12	"strings"
 13
 14	"gno.land/p/nt/avl/v0"
 15)
 16
 17const (
 18	maxDescLen    = 500
 19	maxURILen     = 200
 20	maxHandleLen  = 64
 21	maxPadPathLen = 200
 22	maxLaunchId   = 32
 23)
 24
 25// TokenMeta is presentation data for one launch (not trading state).
 26type TokenMeta struct {
 27	PadPkg      string
 28	LaunchID    string
 29	Owner       address
 30	Description string
 31	ImageURI    string
 32	Website     string
 33	Twitter     string
 34	Telegram    string
 35	Updated     int64
 36}
 37
 38var (
 39	// byKey: pad|id -> *TokenMeta
 40	byKey  avl.Tree
 41	inited bool
 42)
 43
 44func Init(cur realm) {
 45	if inited {
 46		panic("meta: already initialized")
 47	}
 48	if !cur.Previous().IsUserCall() {
 49		panic("meta: EOA only")
 50	}
 51	inited = true
 52	byKey = avl.Tree{}
 53	chain.Emit("Init", "by", cur.Previous().Address().String())
 54}
 55
 56func ensure() {
 57	if !inited {
 58		byKey = avl.Tree{}
 59		inited = true
 60	}
 61}
 62
 63func makeKey(padPkg, launchID string) string {
 64	return padPkg + "|" + launchID
 65}
 66
 67func sanitizeText(s string, max int) string {
 68	s = strings.TrimSpace(s)
 69	if len(s) > max {
 70		s = s[:max]
 71	}
 72	out := ""
 73	for _, c := range s {
 74		if c >= 0x20 && c != 0x7f {
 75			out += string(c)
 76		}
 77	}
 78	return out
 79}
 80
 81func validateURI(uri string) {
 82	if uri == "" {
 83		return
 84	}
 85	if !(strings.HasPrefix(uri, "https://") || strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "ipfs://")) {
 86		panic("meta: uri must be http(s) or ipfs")
 87	}
 88}
 89
 90func validatePadPkg(padPkg string) {
 91	padPkg = strings.TrimSpace(padPkg)
 92	if padPkg == "" || len(padPkg) > maxPadPathLen {
 93		panic("meta: invalid pad path")
 94	}
 95	if !strings.HasPrefix(padPkg, "gno.land/r/") {
 96		panic("meta: pad must be gno.land/r/…")
 97	}
 98}
 99
100// SetMeta upserts metadata for a launch. First writer owns the entry.
101func SetMeta(cur realm, padPkg, launchID, description, imageURI, website, twitter, telegram string) {
102	if !cur.Previous().IsUserCall() {
103		panic("meta: EOA only")
104	}
105	ensure()
106	validatePadPkg(padPkg)
107	launchID = sanitizeText(launchID, maxLaunchId)
108	if launchID == "" {
109		panic("meta: launch id required")
110	}
111	description = sanitizeText(description, maxDescLen)
112	imageURI = sanitizeText(imageURI, maxURILen)
113	website = sanitizeText(website, maxURILen)
114	twitter = sanitizeText(twitter, maxHandleLen)
115	telegram = sanitizeText(telegram, maxHandleLen)
116	validateURI(imageURI)
117	validateURI(website)
118
119	key := makeKey(padPkg, launchID)
120	caller := cur.Previous().Address()
121	existing := byKey.Get(key)
122	if existing != nil {
123		tm := existing.(*TokenMeta)
124		if tm.Owner != caller {
125			panic("meta: not owner")
126		}
127		tm.Description = description
128		tm.ImageURI = imageURI
129		tm.Website = website
130		tm.Twitter = twitter
131		tm.Telegram = telegram
132		tm.Updated = runtime.ChainHeight()
133		byKey.Set(key, tm)
134	} else {
135		tm := &TokenMeta{
136			PadPkg:      padPkg,
137			LaunchID:    launchID,
138			Owner:       caller,
139			Description: description,
140			ImageURI:    imageURI,
141			Website:     website,
142			Twitter:     twitter,
143			Telegram:    telegram,
144			Updated:     runtime.ChainHeight(),
145		}
146		byKey.Set(key, tm)
147	}
148	chain.Emit("SetMeta", "key", key, "owner", caller.String())
149}
150
151// GetMeta returns:
152//
153//	owner|description|imageURI|website|twitter|telegram|updated
154//
155// Empty string if none.
156func GetMeta(padPkg, launchID string) string {
157	ensure()
158	key := makeKey(strings.TrimSpace(padPkg), strings.TrimSpace(launchID))
159	v := byKey.Get(key)
160	if v == nil {
161		return ""
162	}
163	tm := v.(*TokenMeta)
164	return tm.Owner.String() + "|" +
165		tm.Description + "|" +
166		tm.ImageURI + "|" +
167		tm.Website + "|" +
168		tm.Twitter + "|" +
169		tm.Telegram + "|" +
170		strconv.FormatInt(tm.Updated, 10)
171}
172
173// HasMeta reports whether metadata exists.
174func HasMeta(padPkg, launchID string) bool {
175	ensure()
176	return byKey.Has(makeKey(strings.TrimSpace(padPkg), strings.TrimSpace(launchID)))
177}
178
179// MetaCount returns number of entries.
180func MetaCount() int {
181	ensure()
182	return byKey.Size()
183}
184
185// ClearMeta removes caller's owned entry.
186func ClearMeta(cur realm, padPkg, launchID string) {
187	if !cur.Previous().IsUserCall() {
188		panic("meta: EOA only")
189	}
190	ensure()
191	key := makeKey(strings.TrimSpace(padPkg), strings.TrimSpace(launchID))
192	v := byKey.Get(key)
193	if v == nil {
194		return
195	}
196	tm := v.(*TokenMeta)
197	if tm.Owner != cur.Previous().Address() {
198		panic("meta: not owner")
199	}
200	byKey.Remove(key)
201	chain.Emit("ClearMeta", "key", key)
202}
203
204func sanitizeMD(s string) string {
205	s = strings.ReplaceAll(s, "`", "'")
206	s = strings.ReplaceAll(s, "<", "")
207	s = strings.ReplaceAll(s, ">", "")
208	return s
209}
210
211func Render(path string) string {
212	path = strings.Trim(path, "/")
213	ensure()
214	if path == "" {
215		return "# gnomi meta\n\n- Entries: **" + strconv.Itoa(byKey.Size()) + "**\n\n" +
216			"SetMeta(padPkg, launchID, description, imageURI, website, twitter, telegram)\n"
217	}
218	return "> [!WARNING]\n> Path not found: " + sanitizeMD(path) + "\n"
219}
220
221func resetForTest() {
222	byKey = avl.Tree{}
223	inited = false
224}