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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

collection.gno

4.54 Kb · 226 lines
  1package shop
  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	admins   avl.Tree
 32	listings avl.Tree
 33	sales    [saleCap]sale
 34	saleN    int
 35)
 36
 37type listing struct {
 38	Seller address
 39	Price  int64
 40}
 41
 42type sale struct {
 43	Tid    string
 44	Seller address
 45	Buyer  address
 46	Price  int64
 47	Name   string
 48}
 49
 50// Init creates the collection once. The caller is the creator and can mint.
 51func Init(cur realm, name, symbol string) {
 52	caller := requireUser(cur)
 53	if ready {
 54		panic("market: already init")
 55	}
 56	token, ledger = grc721.NewToken(name, symbol, tokenSeq.Next(), cur)
 57	metaView, metaLed = metadata.NewMetadata(ledger)
 58	enumView, _ = enumerable.NewEnumerable(ledger)
 59	creator = caller
 60	ready = true
 61	chain.Emit("Init", "creator", caller.String(), "name", name, "symbol", symbol)
 62}
 63
 64// Grant lets who mint. Only the creator can call it.
 65func Grant(cur realm, who address) {
 66	caller := requireUser(cur)
 67	requireReady()
 68	if caller != creator || !who.IsValid() || who == creator {
 69		panic("market: admin")
 70	}
 71	admins.Set(who.String(), true)
 72	chain.Emit("Grant", "admin", who.String())
 73}
 74
 75// Revoke removes a minter. Only the creator can call it.
 76func Revoke(cur realm, who address) {
 77	caller := requireUser(cur)
 78	requireReady()
 79	if caller != creator {
 80		panic("market: admin")
 81	}
 82	if _, removed := admins.Remove(who.String()); !removed {
 83		panic("market: admin")
 84	}
 85	chain.Emit("Revoke", "admin", who.String())
 86}
 87
 88func canMint(caller address) bool {
 89	if caller == creator {
 90		return true
 91	}
 92	return admins.Get(caller.String()) != nil
 93}
 94
 95// Mint issues one token to the caller. image is an https URL.
 96func Mint(cur realm, tid, name, image string) {
 97	caller := requireUser(cur)
 98	requireReady()
 99	if !canMint(caller) {
100		panic("market: not minter")
101	}
102	id := parseTid(tid)
103	if name == "" || len(name) > grc721.MaxNameLen || !ascii(name) {
104		panic("market: name")
105	}
106	if !httpsURL(image) {
107		panic("market: image")
108	}
109	if err := ledger.Mint(caller, id); err != nil {
110		panic("market: mint")
111	}
112	if err := metaLed.SetTokenURI(id, image); err != nil {
113		panic("market: tokenURI")
114	}
115	if err := metaLed.SetTokenMetadata(id, metadata.Data{
116		Name:        name,
117		Description: name,
118		Image:       image,
119	}); err != nil {
120		panic("market: metadata")
121	}
122	chain.Emit("Mint", "tid", tid, "owner", caller.String(), "name", name)
123}
124
125// Transfer moves a token the caller owns.
126func Transfer(cur realm, to address, tid string) {
127	caller := requireUser(cur)
128	requireReady()
129	if !to.IsValid() || to == caller {
130		panic("market: to")
131	}
132	id := parseTid(tid)
133	if err := ledger.TransferFrom(caller, caller, to, id); err != nil {
134		panic("market: transfer")
135	}
136}
137
138func requireReady() {
139	if !ready {
140		panic("market: call Init")
141	}
142}
143
144func requireUser(cur realm) address {
145	prev := cur.Previous()
146	if !prev.IsUserCall() {
147		panic("market: eoa")
148	}
149	return prev.Address()
150}
151
152func parseTid(tid string) grc721.TokenID {
153	if tid == "" || len(tid) > maxTidLen || !ascii(tid) {
154		panic("market: tid")
155	}
156	for i := 0; i < len(tid); i++ {
157		c := tid[i]
158		ok := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'
159		if !ok {
160			panic("market: tid")
161		}
162	}
163	return grc721.TokenID(tid)
164}
165
166func httpsURL(s string) bool {
167	const prefix = "https://"
168	if len(s) < len(prefix)+1 || len(s) > maxImageLen || !ascii(s) {
169		return false
170	}
171	return s[:len(prefix)] == prefix
172}
173
174func ascii(s string) bool {
175	for i := 0; i < len(s); i++ {
176		if s[i] < 0x20 || s[i] > 0x7e {
177			return false
178		}
179	}
180	return true
181}
182
183func ownerOf(tid grc721.TokenID) address {
184	owner, err := token.OwnerOf(tid)
185	if err != nil {
186		panic("market: owner")
187	}
188	return owner
189}
190
191func metaNameImage(tid grc721.TokenID) (string, string) {
192	data, err := metaView.TokenMetadata(tid)
193	if err != nil {
194		return "", ""
195	}
196	return data.Name, data.Image
197}
198
199func page(offset, limit int) (int, int) {
200	if offset < 0 {
201		offset = 0
202	}
203	if limit < 1 {
204		limit = 1
205	}
206	if limit > pageMax {
207		limit = pageMax
208	}
209	return offset, limit
210}
211
212func quote(s string) string {
213	out := "\""
214	for i := 0; i < len(s); i++ {
215		c := s[i]
216		if c == '\\' || c == '"' {
217			out += "\\"
218		}
219		out += s[i : i+1]
220	}
221	return out + "\""
222}
223
224func i64(n int64) string {
225	return strconv.FormatInt(n, 10)
226}