collection.gno
3.79 Kb · 194 lines
1package market
2
3import (
4 "strconv"
5
6 "chain"
7
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/grc721/enumerable/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
15const (
16 maxImageLen = 200
17 maxTidLen = 64
18 saleCap = 100
19 pageMax = 50
20)
21
22var (
23 token *grc721.Token
24 ledger *grc721.PrivateLedger
25 metaView *metadata.Metadata
26 metaLed *metadata.Ledger
27 enumView *enumerable.Enumerable
28 tokenSeq seqid.ID
29 creator address
30 ready bool
31 listings avl.Tree
32 sales [saleCap]sale
33 saleN int
34)
35
36type listing struct {
37 Seller address
38 Price int64
39}
40
41type sale struct {
42 Tid string
43 Seller address
44 Buyer address
45 Price int64
46 Name string
47}
48
49// Init creates the collection once. The caller becomes the only minter.
50func Init(cur realm, name, symbol string) {
51 caller := requireUser(cur)
52 if ready {
53 panic("market: already init")
54 }
55 token, ledger = grc721.NewToken(name, symbol, tokenSeq.Next(), cur)
56 metaView, metaLed = metadata.NewMetadata(ledger)
57 enumView, _ = enumerable.NewEnumerable(ledger)
58 creator = caller
59 ready = true
60 chain.Emit("Init", "creator", caller.String(), "name", name, "symbol", symbol)
61}
62
63// Mint issues one token to the creator. image is an https URL.
64func Mint(cur realm, tid, name, image string) {
65 caller := requireUser(cur)
66 requireReady()
67 if caller != creator {
68 panic("market: not creator")
69 }
70 id := parseTid(tid)
71 if name == "" || len(name) > grc721.MaxNameLen || !ascii(name) {
72 panic("market: name")
73 }
74 if !httpsURL(image) {
75 panic("market: image")
76 }
77 if err := ledger.Mint(caller, id); err != nil {
78 panic("market: mint")
79 }
80 if err := metaLed.SetTokenURI(id, image); err != nil {
81 panic("market: tokenURI")
82 }
83 if err := metaLed.SetTokenMetadata(id, metadata.Data{
84 Name: name,
85 Description: name,
86 Image: image,
87 }); err != nil {
88 panic("market: metadata")
89 }
90 chain.Emit("Mint", "tid", tid, "owner", caller.String(), "name", name)
91}
92
93// Transfer moves a token the caller owns.
94func Transfer(cur realm, to address, tid string) {
95 caller := requireUser(cur)
96 requireReady()
97 if !to.IsValid() || to == caller {
98 panic("market: to")
99 }
100 id := parseTid(tid)
101 if err := ledger.TransferFrom(caller, caller, to, id); err != nil {
102 panic("market: transfer")
103 }
104}
105
106func requireReady() {
107 if !ready {
108 panic("market: call Init")
109 }
110}
111
112func requireUser(cur realm) address {
113 prev := cur.Previous()
114 if !prev.IsUserCall() {
115 panic("market: eoa")
116 }
117 return prev.Address()
118}
119
120func parseTid(tid string) grc721.TokenID {
121 if tid == "" || len(tid) > maxTidLen || !ascii(tid) {
122 panic("market: tid")
123 }
124 for i := 0; i < len(tid); i++ {
125 c := tid[i]
126 ok := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'
127 if !ok {
128 panic("market: tid")
129 }
130 }
131 return grc721.TokenID(tid)
132}
133
134func httpsURL(s string) bool {
135 const prefix = "https://"
136 if len(s) < len(prefix)+1 || len(s) > maxImageLen || !ascii(s) {
137 return false
138 }
139 return s[:len(prefix)] == prefix
140}
141
142func ascii(s string) bool {
143 for i := 0; i < len(s); i++ {
144 if s[i] < 0x20 || s[i] > 0x7e {
145 return false
146 }
147 }
148 return true
149}
150
151func ownerOf(tid grc721.TokenID) address {
152 owner, err := token.OwnerOf(tid)
153 if err != nil {
154 panic("market: owner")
155 }
156 return owner
157}
158
159func metaNameImage(tid grc721.TokenID) (string, string) {
160 data, err := metaView.TokenMetadata(tid)
161 if err != nil {
162 return "", ""
163 }
164 return data.Name, data.Image
165}
166
167func page(offset, limit int) (int, int) {
168 if offset < 0 {
169 offset = 0
170 }
171 if limit < 1 {
172 limit = 1
173 }
174 if limit > pageMax {
175 limit = pageMax
176 }
177 return offset, limit
178}
179
180func quote(s string) string {
181 out := "\""
182 for i := 0; i < len(s); i++ {
183 c := s[i]
184 if c == '\\' || c == '"' {
185 out += "\\"
186 }
187 out += s[i : i+1]
188 }
189 return out + "\""
190}
191
192func i64(n int64) string {
193 return strconv.FormatInt(n, 10)
194}