← Back to Realms

Realm

gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol

Overview

Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol
Exported Functions
17
State Entries
27
Source Files
2
Total Package Entries
54

Exported Functions

17 exported functions

State

27 state entries

Source Code

FILES
gcol.gno
go
1package gcol
2
3import (
4	"chain"
5	"errors"
6	"strconv"
7	"strings"
8
9	"gno.land/p/nt/avl/v0"
10	"gno.land/p/nt/grc721/metadata/v0"
11	"gno.land/p/nt/grc721/v0"
12	"gno.land/p/nt/seqid/v0"
13)
14
15// Holder perk for Bazaar: col.Buy subtracts HolderDiscount(trader) from
16// factory.ProtocolBps() (highest FeeDiscount in that wallet). Every Genesis NFT
17// also has ShareWeight = FeeDiscount for the 11% protocol-fee pool (claim later).
18
19const (
20	maxImageLen    = 200
21	maxDiscountBps = int64(150)
22	maxSupply      = 1111
23	supplyCommon   = 666
24	supplyUncommon = 222
25	supplyRare     = 111
26	supplyEpic     = 66
27	supplyLegend   = 33
28	supplyMythic   = 13
29	defaultImage   = "https://bazaar-gno.vercel.app/gnomies.png"
30	shareBps       = int64(1100) // 11% of protocol take on Buy
31)
32
33type item struct {
34	id          int
35	owner       address
36	name        string
37	image       string
38	rarity      string
39	discountBps int64
40}
41
42var (
43	token    *grc721.Token
44	ledger   *grc721.PrivateLedger
45	metaView *metadata.Metadata
46	metaLed  *metadata.Ledger
47	tokenSeq seqid.ID
48	items    avl.Tree
49	inited         bool
50	creator        address
51	nextID         int
52	mintedCommon   int
53	mintedUncommon int
54	mintedRare     int
55	mintedEpic     int
56	mintedLegend   int
57	mintedMythic   int
58)
59
60func Init(cur realm, name, symbol string) {
61	if !cur.IsCurrent() {
62		panic("genesis: spoofed realm")
63	}
64	if !cur.Previous().IsUserCall() {
65		panic("genesis: EOA only")
66	}
67	if inited {
68		panic("genesis: already initialized")
69	}
70	tok, led := grc721.NewToken(name, symbol, tokenSeq.Next(), cur)
71	token = tok
72	ledger = led
73	metaView, metaLed = metadata.NewMetadata(led)
74	creator = cur.Previous().Address()
75	inited = true
76	chain.Emit("Init", "creator", creator.String())
77	
78}
79
80func Mint(cur realm, name, image, rarity string, discountBps int64) int {
81	caller := requireUser(cur)
82	if caller != creator {
83		panic("genesis: not creator")
84	}
85	if name == "" || len(name) > grc721.MaxNameLen {
86		panic("genesis: name")
87	}
88	if image == "" {
89		image = defaultImage
90	}
91	if len(image) > maxImageLen {
92		panic("genesis: image")
93	}
94	d := discountBps
95	if d < 0 {
96		d = rarityDiscount(rarity)
97	}
98	if d > maxDiscountBps {
99		panic("genesis: discount")
100	}
101	if nextID >= maxSupply {
102		panic("genesis: sold out")
103	}
104	takeRarity(rarity)
105	nextID++
106	id := nextID
107	tid := grc721.TokenID(strconv.Itoa(id))
108	if err := ledger.Mint(caller, tid); err != nil {
109		panic("genesis: mint")
110	}
111	if err := metaLed.SetTokenURI(tid, tokenJSON(name, image, rarity, d)); err != nil {
112		panic("genesis: tokenURI")
113	}
114	if err := metaLed.SetTokenMetadata(tid, metadata.Data{
115		Name:        name,
116		Description: name,
117		Image:       image,
118		Attributes: []metadata.Trait{
119			{TraitType: "Rarity", Value: rarity},
120			{TraitType: "FeeDiscount", Value: strconv.FormatInt(d, 10)},
121			{TraitType: "ShareWeight", Value: strconv.FormatInt(d, 10)},
122		},
123	}); err != nil {
124		panic("genesis: metadata")
125	}
126	items.Set(strconv.Itoa(id), &item{id: id, owner: caller, name: name, image: image, rarity: rarity, discountBps: d})
127	
128	chain.Emit("Mint", "id", strconv.Itoa(id), "discount", strconv.FormatInt(d, 10))
129	return id
130}
131
132// HolderDiscount is the best FeeDiscount among Genesis NFTs currently in `owner`'s wallet (not listed/escrowed).
133func HolderDiscount(owner string) int64 {
134	if !inited {
135		return 0
136	}
137	who := address(owner)
138	if !who.IsValid() {
139		return 0
140	}
141	var best int64
142	items.Iterate("", "", func(_ string, value any) bool {
143		it := value.(*item)
144		if it.owner == who && it.discountBps > best {
145			best = it.discountBps
146		}
147		return false
148	})
149	return best
150}
151
152// ShareWeight sums FeeDiscount of every Genesis NFT in `owner`'s wallet (not listed/escrowed).
153func ShareWeight(owner string) int64 {
154	if !inited {
155		return 0
156	}
157	who := address(owner)
158	if !who.IsValid() {
159		return 0
160	}
161	var sum int64
162	items.Iterate("", "", func(_ string, value any) bool {
163		it := value.(*item)
164		if it.owner == who {
165			sum += it.discountBps
166		}
167		return false
168	})
169	return sum
170}
171
172func DiscountOf(tid string) int64 {
173	return mustItem(parseTid(tid)).discountBps
174}
175
176func ShareBps() int64 {
177	return shareBps
178}
179
180func MaxSupply() int {
181	return maxSupply
182}
183
184func TotalSupply() int {
185	requireInit()
186	return nextID
187}
188
189func SupplyOf(rarity string) int {
190	switch canonRarity(rarity) {
191	case "mythic":
192		return supplyMythic
193	case "legendary":
194		return supplyLegend
195	case "epic":
196		return supplyEpic
197	case "rare":
198		return supplyRare
199	case "uncommon":
200		return supplyUncommon
201	default:
202		return supplyCommon
203	}
204}
205
206func MintedOf(rarity string) int {
207	requireInit()
208	switch canonRarity(rarity) {
209	case "mythic":
210		return mintedMythic
211	case "legendary":
212		return mintedLegend
213	case "epic":
214		return mintedEpic
215	case "rare":
216		return mintedRare
217	case "uncommon":
218		return mintedUncommon
219	default:
220		return mintedCommon
221	}
222}
223
224func Name() string {
225	requireInit()
226	return token.GetName()
227}
228
229func Symbol() string {
230	requireInit()
231	return token.GetSymbol()
232}
233
234func BalanceOf(owner string) int64 {
235	requireInit()
236	who := address(owner)
237	if !who.IsValid() {
238		return 0
239	}
240	n, err := token.BalanceOf(who)
241	if err != nil {
242		return 0
243	}
244	return n
245}
246
247func OwnerOf(tid string) (address, error) {
248	requireInit()
249	id, err := strconv.Atoi(tid)
250	if err != nil || id < 1 {
251		return address(""), errors.New("genesis: tid")
252	}
253	v := items.Get(strconv.Itoa(id))
254	if v == nil {
255		return address(""), errors.New("genesis: unknown item")
256	}
257	return v.(*item).owner, nil
258}
259
260func TokenURI(tid string) string {
261	u, err := metaView.TokenURI(grc721.TokenID(tid))
262	if err != nil || u == "" {
263		it := mustItem(parseTid(tid))
264		return tokenJSON(it.name, it.image, it.rarity, it.discountBps)
265	}
266	return u
267}
268
269func TokensOf(owner string) string {
270	requireInit()
271	who := address(owner)
272	if !who.IsValid() {
273		return ""
274	}
275	out := ""
276	items.Iterate("", "", func(_ string, value any) bool {
277		it := value.(*item)
278		if it.owner != who {
279			return false
280		}
281		if out != "" {
282			out += ","
283		}
284		out += strconv.Itoa(it.id)
285		return false
286	})
287	return out
288}
289
290func TransferFrom(cur realm, from, to, tid string) {
291	caller := requireUser(cur)
292	id := parseTid(tid)
293	src := address(from)
294	dst := address(to)
295	if !src.IsValid() || !dst.IsValid() {
296		panic("genesis: addr")
297	}
298	if err := ledger.TransferFrom(caller, src, dst, grc721.TokenID(tid)); err != nil {
299		panic("genesis: transfer")
300	}
301	it := mustItem(id)
302	it.owner = dst
303	items.Set(strconv.Itoa(id), it)
304	
305}
306
307func canonRarity(rarity string) string {
308	switch strings.ToLower(strings.TrimSpace(rarity)) {
309	case "mythic":
310		return "mythic"
311	case "legendary":
312		return "legendary"
313	case "epic":
314		return "epic"
315	case "rare":
316		return "rare"
317	case "uncommon":
318		return "uncommon"
319	default:
320		return "common"
321	}
322}
323
324func takeRarity(rarity string) {
325	switch canonRarity(rarity) {
326	case "mythic":
327		if mintedMythic >= supplyMythic {
328			panic("genesis: rarity full")
329		}
330		mintedMythic++
331	case "legendary":
332		if mintedLegend >= supplyLegend {
333			panic("genesis: rarity full")
334		}
335		mintedLegend++
336	case "epic":
337		if mintedEpic >= supplyEpic {
338			panic("genesis: rarity full")
339		}
340		mintedEpic++
341	case "rare":
342		if mintedRare >= supplyRare {
343			panic("genesis: rarity full")
344		}
345		mintedRare++
346	case "uncommon":
347		if mintedUncommon >= supplyUncommon {
348			panic("genesis: rarity full")
349		}
350		mintedUncommon++
351	default:
352		if mintedCommon >= supplyCommon {
353			panic("genesis: rarity full")
354		}
355		mintedCommon++
356	}
357}
358
359func rarityDiscount(rarity string) int64 {
360	switch canonRarity(rarity) {
361	case "mythic":
362		return 150
363	case "legendary":
364		return 125
365	case "epic":
366		return 100
367	case "rare":
368		return 75
369	case "uncommon":
370		return 50
371	default:
372		return 25
373	}
374}
375
376func requireInit() {
377	if !inited {
378		panic("genesis: call Init first")
379	}
380}
381
382func requireUser(cur realm) address {
383	requireInit()
384	if !cur.IsCurrent() {
385		panic("genesis: spoofed realm")
386	}
387	if !cur.Previous().IsUserCall() {
388		panic("genesis: EOA only")
389	}
390	return cur.Previous().Address()
391}
392
393func parseTid(tid string) int {
394	id, err := strconv.Atoi(tid)
395	if err != nil || id < 1 {
396		panic("genesis: tid")
397	}
398	return id
399}
400
401func mustItem(id int) *item {
402	v := items.Get(strconv.Itoa(id))
403	if v == nil {
404		panic("genesis: unknown item")
405	}
406	return v.(*item)
407}
408
409func tokenJSON(name, image, rarity string, discount int64) string {
410	body := `{"name":` + jsonStr(name) + `,"image":` + jsonStr(image) + `,"attributes":[{"trait_type":"Rarity","value":` + jsonStr(rarity) + `},{"trait_type":"FeeDiscount","value":` + jsonStr(strconv.FormatInt(discount, 10)) + `},{"trait_type":"ShareWeight","value":` + jsonStr(strconv.FormatInt(discount, 10)) + `}]}`
411	return "data:application/json;charset=utf-8," + pctURI(body)
412}
413
414func jsonStr(s string) string {
415	out := `"`
416	for _, r := range s {
417		switch r {
418		case '"':
419			out += `\"`
420		case '\\':
421			out += `\\`
422		default:
423			out += string(r)
424		}
425	}
426	return out + `"`
427}
428
429func pctURI(s string) string {
430	out := ""
431	for i := 0; i < len(s); i++ {
432		c := s[i]
433		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' {
434			out += string(c)
435			continue
436		}
437		hex := "0123456789ABCDEF"
438		out += "%"
439		out += string([]byte{hex[c>>4], hex[c&15]})
440	}
441	return out
442}
443

Raw Package Data

Raw JSON data