Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

stdnft.gno

5.37 Kb · 266 lines
  1package stdnft
  2
  3import (
  4	"chain"
  5
  6	"gno.land/p/nt/grc721/enumerable/v0"
  7	"gno.land/p/nt/grc721/metadata/v0"
  8	"gno.land/p/nt/grc721/v0"
  9	"gno.land/p/nt/seqid/v0"
 10)
 11
 12// Adena probe: collection realm wrapping genesis GRC721 (string TokenID,
 13// metadata TokenURI, enumerable TokenOfOwnerByIndex). No List/Buy escrow.
 14
 15const (
 16	maxImageLen  = 200
 17	defaultImage = "https://bazaar-gno.vercel.app/gnomies.png"
 18)
 19
 20var (
 21	token    *grc721.Token
 22	ledger   *grc721.PrivateLedger
 23	metaView *metadata.Metadata
 24	metaLed  *metadata.Ledger
 25	enumView *enumerable.Enumerable
 26	tokenSeq seqid.ID
 27	inited   bool
 28	creator  address
 29)
 30
 31func Init(cur realm, name, symbol string) {
 32	if !cur.IsCurrent() {
 33		panic("stdnft: spoofed realm")
 34	}
 35	if !cur.Previous().IsUserCall() {
 36		panic("stdnft: EOA only")
 37	}
 38	if inited {
 39		panic("stdnft: already initialized")
 40	}
 41	tok, led := grc721.NewToken(name, symbol, tokenSeq.Next(), cur)
 42	token = tok
 43	ledger = led
 44	metaView, metaLed = metadata.NewMetadata(led)
 45	enumView, _ = enumerable.NewEnumerable(led)
 46	creator = cur.Previous().Address()
 47	inited = true
 48	chain.Emit("Init", "creator", creator.String(), "name", name, "symbol", symbol)
 49}
 50
 51func Mint(cur realm, tid, name, image string) {
 52	caller := requireUser(cur)
 53	if caller != creator {
 54		panic("stdnft: not creator")
 55	}
 56	if tid == "" {
 57		panic("stdnft: tid")
 58	}
 59	if name == "" || len(name) > grc721.MaxNameLen {
 60		panic("stdnft: name")
 61	}
 62	if image == "" {
 63		image = defaultImage
 64	}
 65	if len(image) > maxImageLen {
 66		panic("stdnft: image")
 67	}
 68	id := grc721.TokenID(tid)
 69	if err := ledger.Mint(caller, id); err != nil {
 70		panic("stdnft: mint")
 71	}
 72	if err := metaLed.SetTokenURI(id, tokenJSON(name, image)); err != nil {
 73		panic("stdnft: tokenURI")
 74	}
 75	if err := metaLed.SetTokenMetadata(id, metadata.Data{
 76		Name:        name,
 77		Description: name,
 78		Image:       image,
 79		Attributes:  []metadata.Trait{{TraitType: "Kind", Value: "stdnft"}},
 80	}); err != nil {
 81		panic("stdnft: metadata")
 82	}
 83	chain.Emit("Mint", "id", tid, "owner", caller.String())
 84}
 85
 86func Name() string {
 87	requireInit()
 88	return token.GetName()
 89}
 90
 91func Symbol() string {
 92	requireInit()
 93	return token.GetSymbol()
 94}
 95
 96func TotalSupply() int64 {
 97	requireInit()
 98	return token.TotalSupply()
 99}
100
101func BalanceOf(owner string) int64 {
102	requireInit()
103	who := address(owner)
104	if !who.IsValid() {
105		return 0
106	}
107	n, err := token.BalanceOf(who)
108	if err != nil {
109		return 0
110	}
111	return n
112}
113
114func OwnerOf(tid string) string {
115	requireInit()
116	owner, err := token.OwnerOf(grc721.TokenID(tid))
117	if err != nil {
118		panic("stdnft: owner")
119	}
120	return owner.String()
121}
122
123func TokenURI(tid string) string {
124	requireInit()
125	u, err := metaView.TokenURI(grc721.TokenID(tid))
126	if err != nil {
127		panic("stdnft: uri")
128	}
129	return u
130}
131
132func TokenOfOwnerByIndex(owner string, index int64) string {
133	requireInit()
134	who := address(owner)
135	if !who.IsValid() {
136		panic("stdnft: addr")
137	}
138	id, err := enumView.TokenOfOwnerByIndex(who, index)
139	if err != nil {
140		panic("stdnft: index")
141	}
142	return id.String()
143}
144
145func TokenByIndex(index int64) string {
146	requireInit()
147	id, err := enumView.TokenByIndex(index)
148	if err != nil {
149		panic("stdnft: index")
150	}
151	return id.String()
152}
153
154func TokensOf(owner string) string {
155	requireInit()
156	who := address(owner)
157	if !who.IsValid() {
158		return ""
159	}
160	n, err := token.BalanceOf(who)
161	if err != nil || n == 0 {
162		return ""
163	}
164	out := ""
165	var i int64
166	for i = 0; i < n; i++ {
167		id, err := enumView.TokenOfOwnerByIndex(who, i)
168		if err != nil {
169			panic("stdnft: tokens")
170		}
171		if out != "" {
172			out += ","
173		}
174		out += id.String()
175	}
176	return out
177}
178
179func Approve(cur realm, to, tid string) {
180	caller := requireUser(cur)
181	who := address(to)
182	if to != "" && !who.IsValid() {
183		panic("stdnft: addr")
184	}
185	if err := ledger.Approve(caller, who, grc721.TokenID(tid)); err != nil {
186		panic("stdnft: approve")
187	}
188}
189
190func TransferFrom(cur realm, from, to, tid string) {
191	caller := requireUser(cur)
192	src := address(from)
193	dst := address(to)
194	if !src.IsValid() || !dst.IsValid() {
195		panic("stdnft: addr")
196	}
197	if err := ledger.TransferFrom(caller, src, dst, grc721.TokenID(tid)); err != nil {
198		panic("stdnft: transfer")
199	}
200}
201
202func Creator() string {
203	requireInit()
204	return creator.String()
205}
206
207func Render(_ string) string {
208	if !inited {
209		return "# StdNFT\n\ncall Init first\n"
210	}
211	return token.RenderHome() + "\ncreator: " + creator.String() + "\n"
212}
213
214func requireInit() {
215	if !inited {
216		panic("stdnft: call Init first")
217	}
218}
219
220func requireUser(cur realm) address {
221	requireInit()
222	if !cur.IsCurrent() {
223		panic("stdnft: spoofed realm")
224	}
225	if !cur.Previous().IsUserCall() {
226		panic("stdnft: EOA only")
227	}
228	return cur.Previous().Address()
229}
230
231func tokenJSON(name, image string) string {
232	body := `{"name":` + jsonStr(name) + `,"description":` + jsonStr(name) + `,"image":` + jsonStr(image) + `,"attributes":[{"trait_type":"Kind","value":"stdnft"}]}`
233	return "data:application/json;charset=utf-8," + pctURI(body)
234}
235
236func jsonStr(s string) string {
237	out := `"`
238	for _, r := range s {
239		switch r {
240		case '"':
241			out += `\"`
242		case '\\':
243			out += `\\`
244		case '\n':
245			out += `\n`
246		default:
247			out += string(r)
248		}
249	}
250	return out + `"`
251}
252
253func pctURI(s string) string {
254	out := ""
255	for i := 0; i < len(s); i++ {
256		c := s[i]
257		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' {
258			out += string(c)
259			continue
260		}
261		hex := "0123456789ABCDEF"
262		out += "%"
263		out += string([]byte{hex[c>>4], hex[c&15]})
264	}
265	return out
266}