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}