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.

profile.gno

5.07 Kb · 234 lines
  1// Package profile is the Shoal sailor card. One address has one username.
  2// This realm holds no ugnot. It does not mint FISH and it does not mint a fish NFT.
  3// Deployed on onyx-1 at gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/shoal/profile.
  4// The older profile package at the founder root stays as it was.
  5package profile
  6
  7import (
  8	"chain"
  9	"chain/runtime/unsafe"
 10)
 11
 12type card struct {
 13	username string
 14	country  string
 15	crest    string
 16}
 17
 18var (
 19	byAddr       map[address]*card
 20	byName       map[string]address
 21	admin        address
 22	adminSet     bool
 23	extraCountry map[string]bool
 24	extraCrest   map[string]bool
 25)
 26
 27// SetProfile stores this caller's sailor card. The caller does not need a fish NFT.
 28// A later fact is a new function. This one keeps username, country, and crest.
 29func SetProfile(cur realm, username, country, crest string) {
 30	caller := requireUser(cur)
 31	takeExact(0)
 32	name := cleanUser(username)
 33	if name == "" {
 34		panic("profile: name")
 35	}
 36	if !knownCountry(country) {
 37		panic("profile: country")
 38	}
 39	if !knownCrest(crest) {
 40		panic("profile: crest")
 41	}
 42	ensure()
 43	if owner, ok := byName[name]; ok && owner != caller {
 44		panic("profile: taken")
 45	}
 46	prev := byAddr[caller]
 47	if prev != nil && prev.username == name && prev.country == country && prev.crest == crest {
 48		return
 49	}
 50	if prev != nil && prev.username != name {
 51		delete(byName, prev.username)
 52	}
 53	byAddr[caller] = &card{username: name, country: country, crest: crest}
 54	byName[name] = caller
 55	chain.Emit("SetProfile", "who", caller.String(), "username", name, "country", country, "crest", crest)
 56}
 57
 58// Version names this local package shape. The live profile realm is unchanged.
 59func Version() string { return "profile-1" }
 60
 61// Admin is empty until ClaimAdmin. SetProfile does not claim it.
 62func Admin() address { return admin }
 63
 64// ClaimAdmin records the first caller. A later caller cannot replace them here.
 65func ClaimAdmin(cur realm) {
 66	caller := requireUser(cur)
 67	takeExact(0)
 68	if adminSet {
 69		panic("profile: admin")
 70	}
 71	admin = caller
 72	adminSet = true
 73	chain.Emit("ClaimAdmin", "who", caller.String())
 74}
 75
 76// AddCountry lets the admin accept one more country name. Built-in names stay.
 77func AddCountry(cur realm, name string) {
 78	caller := requireUser(cur)
 79	takeExact(0)
 80	if !adminSet || caller != admin {
 81		panic("profile: admin")
 82	}
 83	if !placeName(name) || knownCountry(name) {
 84		panic("profile: country")
 85	}
 86	if extraCountry == nil {
 87		extraCountry = make(map[string]bool)
 88	}
 89	extraCountry[name] = true
 90	chain.Emit("AddCountry", "name", name)
 91}
 92
 93// AddCrest lets the admin accept one more crest name. Built-in names stay.
 94func AddCrest(cur realm, name string) {
 95	caller := requireUser(cur)
 96	takeExact(0)
 97	if !adminSet || caller != admin {
 98		panic("profile: admin")
 99	}
100	if !placeName(name) || knownCrest(name) {
101		panic("profile: crest")
102	}
103	if extraCrest == nil {
104		extraCrest = make(map[string]bool)
105	}
106	extraCrest[name] = true
107	chain.Emit("AddCrest", "name", name)
108}
109
110func Has(cur realm, who address) bool {
111	takeExact(0)
112	return cardOf(who) != nil
113}
114
115func UsernameOf(cur realm, who address) string {
116	takeExact(0)
117	body := cardOf(who)
118	if body == nil {
119		return ""
120	}
121	return body.username
122}
123
124func CountryOf(cur realm, who address) string {
125	takeExact(0)
126	body := cardOf(who)
127	if body == nil {
128		return ""
129	}
130	return body.country
131}
132
133func CrestOf(cur realm, who address) string {
134	takeExact(0)
135	body := cardOf(who)
136	if body == nil {
137		return ""
138	}
139	return body.crest
140}
141
142func AddressOf(cur realm, username string) address {
143	takeExact(0)
144	name := cleanUser(username)
145	if name == "" || byName == nil {
146		return address("")
147	}
148	return byName[name]
149}
150
151func requireUser(cur realm) address {
152	if !cur.Previous().IsUserCall() {
153		panic("profile: eoa")
154	}
155	return cur.Previous().Address()
156}
157
158func takeExact(n int64) {
159	sent := unsafe.OriginSend()
160	if n == 0 {
161		if len(sent) != 0 {
162			panic("profile: payment")
163		}
164		return
165	}
166	panic("profile: payment")
167}
168
169func ensure() {
170	if byAddr == nil {
171		byAddr = make(map[address]*card)
172	}
173	if byName == nil {
174		byName = make(map[string]address)
175	}
176}
177
178func cardOf(who address) *card {
179	if byAddr == nil || !who.IsValid() {
180		return nil
181	}
182	return byAddr[who]
183}
184
185func cleanUser(name string) string {
186	for len(name) > 0 && name[0] == ' ' {
187		name = name[1:]
188	}
189	for len(name) > 0 && name[len(name)-1] == ' ' {
190		name = name[:len(name)-1]
191	}
192	if len(name) < 3 || len(name) > 16 {
193		return ""
194	}
195	out := ""
196	for _, c := range name {
197		if c >= 'A' && c <= 'Z' {
198			c = c - 'A' + 'a'
199		}
200		if (c < 'a' || c > 'z') && (c < '0' || c > '9') {
201			return ""
202		}
203		out += string(c)
204	}
205	return out
206}
207
208func knownCountry(s string) bool {
209	switch s {
210	case "Sea", "Vietnam", "Korea", "Japan", "Brazil", "France", "Nigeria", "Canada", "Australia":
211		return true
212	}
213	return extraCountry != nil && extraCountry[s]
214}
215
216func knownCrest(s string) bool {
217	switch s {
218	case "wave", "reef", "moon", "sun", "fang", "shell":
219		return true
220	}
221	return extraCrest != nil && extraCrest[s]
222}
223
224func placeName(s string) bool {
225	if len(s) < 2 || len(s) > 24 {
226		return false
227	}
228	for _, c := range s {
229		if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') {
230			return false
231		}
232	}
233	return true
234}