← Back to Realms

Realm

gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol2

Overview

Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol2
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
gcol2.gno
go
1package gcol2
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	"gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gpush"
14)
15
16// Holder perk for Bazaar: col.Buy subtracts HolderDiscount(trader) from
17// factory.ProtocolBps() (highest FeeDiscount in that wallet). Every Genesis NFT
18// also has ShareWeight = FeeDiscount for the 11% protocol-fee pool (claim later).
19
20const (
21	maxImageLen    = 200
22	maxDiscountBps = int64(150)
23	maxSupply      = 1111
24	supplyCommon   = 666
25	supplyUncommon = 222
26	supplyRare     = 111
27	supplyEpic     = 66
28	supplyLegend   = 33
29	supplyMythic   = 13
30	defaultImage   = "https://bazaar-gno.vercel.app/gnomies.png"
31	shareBps       = int64(1100) // 11% of protocol take on Buy
32)
33
34type item struct {
35	id          int
36	owner       address
37	name        string
38	image       string
39	rarity      string
40	discountBps int64
41}
42
43var (
44	token    *grc721.Token
45	ledger   *grc721.PrivateLedger
46	metaView *metadata.Metadata
47	metaLed  *metadata.Ledger
48	tokenSeq seqid.ID
49	items    avl.Tree
50	inited         bool
51	creator        address
52	nextID         int
53	mintedCommon   int
54	mintedUncommon int
55	mintedRare     int
56	mintedEpic     int
57	mintedLegend   int
58	mintedMythic   int
59)
60
61func Init(cur realm, name, symbol string) {
62	if !cur.IsCurrent() {
63		panic("genesis: spoofed realm")
64	}
65	if !cur.Previous().IsUserCall() {
66		panic("genesis: EOA only")
67	}
68	if inited {
69		panic("genesis: already initialized")
70	}
71	tok, led := grc721.NewToken(name, symbol, tokenSeq.Next(), cur)
72	token = tok
73	ledger = led
74	metaView, metaLed = metadata.NewMetadata(led)
75	creator = cur.Previous().Address()
76	inited = true
77	chain.Emit("Init", "creator", creator.String())
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	gpush.Sync(cross(cur), caller.String(), HolderDiscount(caller.String()))
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	gpush.Sync(cross(cur), from, HolderDiscount(from))
305	gpush.Sync(cross(cur), to, HolderDiscount(to))
306}
307
308func canonRarity(rarity string) string {
309	switch strings.ToLower(strings.TrimSpace(rarity)) {
310	case "mythic":
311		return "mythic"
312	case "legendary":
313		return "legendary"
314	case "epic":
315		return "epic"
316	case "rare":
317		return "rare"
318	case "uncommon":
319		return "uncommon"
320	default:
321		return "common"
322	}
323}
324
325func takeRarity(rarity string) {
326	switch canonRarity(rarity) {
327	case "mythic":
328		if mintedMythic >= supplyMythic {
329			panic("genesis: rarity full")
330		}
331		mintedMythic++
332	case "legendary":
333		if mintedLegend >= supplyLegend {
334			panic("genesis: rarity full")
335		}
336		mintedLegend++
337	case "epic":
338		if mintedEpic >= supplyEpic {
339			panic("genesis: rarity full")
340		}
341		mintedEpic++
342	case "rare":
343		if mintedRare >= supplyRare {
344			panic("genesis: rarity full")
345		}
346		mintedRare++
347	case "uncommon":
348		if mintedUncommon >= supplyUncommon {
349			panic("genesis: rarity full")
350		}
351		mintedUncommon++
352	default:
353		if mintedCommon >= supplyCommon {
354			panic("genesis: rarity full")
355		}
356		mintedCommon++
357	}
358}
359
360func rarityDiscount(rarity string) int64 {
361	switch canonRarity(rarity) {
362	case "mythic":
363		return 150
364	case "legendary":
365		return 125
366	case "epic":
367		return 100
368	case "rare":
369		return 75
370	case "uncommon":
371		return 50
372	default:
373		return 25
374	}
375}
376
377func requireInit() {
378	if !inited {
379		panic("genesis: call Init first")
380	}
381}
382
383func requireUser(cur realm) address {
384	requireInit()
385	if !cur.IsCurrent() {
386		panic("genesis: spoofed realm")
387	}
388	if !cur.Previous().IsUserCall() {
389		panic("genesis: EOA only")
390	}
391	return cur.Previous().Address()
392}
393
394func parseTid(tid string) int {
395	id, err := strconv.Atoi(tid)
396	if err != nil || id < 1 {
397		panic("genesis: tid")
398	}
399	return id
400}
401
402func mustItem(id int) *item {
403	v := items.Get(strconv.Itoa(id))
404	if v == nil {
405		panic("genesis: unknown item")
406	}
407	return v.(*item)
408}
409
410func tokenJSON(name, image, rarity string, discount int64) string {
411	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)) + `}]}`
412	return "data:application/json;charset=utf-8," + pctURI(body)
413}
414
415func jsonStr(s string) string {
416	out := `"`
417	for _, r := range s {
418		switch r {
419		case '"':
420			out += `\"`
421		case '\\':
422			out += `\\`
423		default:
424			out += string(r)
425		}
426	}
427	return out + `"`
428}
429
430func pctURI(s string) string {
431	out := ""
432	for i := 0; i < len(s); i++ {
433		c := s[i]
434		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' {
435			out += string(c)
436			continue
437		}
438		hex := "0123456789ABCDEF"
439		out += "%"
440		out += string([]byte{hex[c>>4], hex[c&15]})
441	}
442	return out
443}
444

Raw Package Data

Raw JSON data