registry.gno
6.01 Kb · 222 lines
1package nftregistry
2
3import (
4 "chain/runtime"
5
6 "gno.land/p/nt/avl"
7 "gno.land/p/demo/tokens/grc721"
8)
9
10// CollectionInfo - Extended metadata stored in the registry
11type CollectionInfo struct {
12 Address address // Realm address of the NFT collection
13 Name string // Collection name (from IGRC721CollectionMetadata)
14 Symbol string // Collection symbol (from IGRC721CollectionMetadata)
15 Creator address // Address that registered the collection
16 RegisteredAt int64 // Block height at registration
17 Verified bool // Admin verification status
18 Category string // Optional: "art", "gaming", "pfp", "utility", etc.
19 Description string // Human-readable description
20 ExternalURL string // Collection website/homepage
21 NFTGetter grc721.NFTGetter // Function to get the NFT instance
22 SupportsMetadata bool // Does collection implement IGRC721MetadataOnchain?
23}
24
25var (
26 // Main registry: address → CollectionInfo
27 collections avl.Tree // key: string(address), value: *CollectionInfo
28
29 // Admin addresses that can verify collections
30 admins avl.Tree // key: string(address), value: bool
31
32 // Category index for faster filtering
33 categoriesIndex avl.Tree // key: category, value: []address
34
35 // Owner of the registry
36 owner address
37
38 // Registration fee (in ugnot)
39 registrationFee int64 = 1000000 // 1 GNOT default
40
41 // Stats
42 totalCollections int
43 verifiedCount int
44)
45
46func init() {
47 owner = runtime.PreviousRealm().Address()
48 admins.Set(owner.String(), true)
49}
50
51// RegisterCollection - Register a new NFT collection
52// The collection realm must call this function itself
53func RegisterCollection(
54 category string,
55 description string,
56 externalURL string,
57 getter grc721.NFTGetter,
58) {
59 caller := runtime.PreviousRealm().Address()
60
61 // Check if collection already registered
62 if collections.Has(caller.String()) {
63 panic("Collection already registered")
64 }
65
66 if getter == nil {
67 panic("NFT getter function is required")
68 }
69
70 // Get the NFT instance to extract name and symbol
71 nftInstance := getter()
72
73 // Try to get collection metadata
74 var name, symbol string
75 var supportsMetadata bool
76
77 // Check if implements IGRC721CollectionMetadata
78 if metadataCollection, ok := nftInstance.(grc721.IGRC721CollectionMetadata); ok {
79 name = metadataCollection.Name()
80 symbol = metadataCollection.Symbol()
81 } else {
82 panic("Collection must implement IGRC721CollectionMetadata (Name() and Symbol())")
83 }
84
85 // Check if implements IGRC721MetadataOnchain for rich metadata
86 if _, ok := nftInstance.(grc721.IGRC721MetadataOnchain); ok {
87 supportsMetadata = true
88 }
89
90 // Validate inputs
91 if name == "" || symbol == "" {
92 panic("Name and symbol cannot be empty")
93 }
94
95 // Note: Uncomment for production with payment
96 // sent := banker.OriginSend()
97 // amount := sent.AmountOf("ugnot")
98 // if amount < registrationFee {
99 // panic(ufmt.Sprintf("Insufficient registration fee. Required: %d ugnot", registrationFee))
100 // }
101
102 // Create collection info
103 info := &CollectionInfo{
104 Address: caller,
105 Name: name,
106 Symbol: symbol,
107 Creator: caller,
108 RegisteredAt: runtime.ChainHeight(),
109 Verified: false,
110 Category: category,
111 Description: description,
112 ExternalURL: externalURL,
113 NFTGetter: getter,
114 SupportsMetadata: supportsMetadata,
115 }
116
117 collections.Set(caller.String(), info)
118 totalCollections++
119}
120
121// IsRegistered - Check if a collection is registered
122func IsRegistered(collectionAddr address) bool {
123 return collections.Has(collectionAddr.String())
124}
125
126// GetCollection - Get collection info by address
127func GetCollection(collectionAddr address) *CollectionInfo {
128 val, exists := collections.Get(collectionAddr.String())
129 if !exists {
130 return nil
131 }
132 return val.(*CollectionInfo)
133}
134
135// GetNFTGetter - Get the NFT getter function for a collection
136// This is the main function marketplaces will use
137func GetNFTGetter(collectionAddr address) (grc721.NFTGetter, bool) {
138 info := GetCollection(collectionAddr)
139 if info == nil {
140 return nil, false
141 }
142 return info.NFTGetter, true
143}
144
145// GetTokenMetadata - Get metadata for a specific token (if collection supports it)
146func GetTokenMetadata(collectionAddr address, tokenId grc721.TokenID) (grc721.Metadata, error) {
147 info := GetCollection(collectionAddr)
148 if info == nil {
149 panic("Collection not registered")
150 }
151
152 if !info.SupportsMetadata {
153 panic("Collection does not support onchain metadata")
154 }
155
156 nftInstance := info.NFTGetter()
157 metadataCollection := nftInstance.(grc721.IGRC721MetadataOnchain)
158
159 return metadataCollection.TokenMetadata(tokenId)
160}
161
162// VerifyCollection - Admin function to verify a collection
163func VerifyCollection(collectionAddr address) {
164 caller := runtime.PreviousRealm().Address()
165
166 if !isAdmin(caller) {
167 panic("Only admins can verify collections")
168 }
169
170 info := GetCollection(collectionAddr)
171 if info == nil {
172 panic("Collection not found")
173 }
174
175 if !info.Verified {
176 info.Verified = true
177 verifiedCount++
178 collections.Set(collectionAddr.String(), info)
179 }
180}
181
182// UnverifyCollection - Admin function to remove verification
183func UnverifyCollection(collectionAddr address) {
184 caller := runtime.PreviousRealm().Address()
185
186 if !isAdmin(caller) {
187 panic("Only admins can unverify collections")
188 }
189
190 info := GetCollection(collectionAddr)
191 if info == nil {
192 panic("Collection not found")
193 }
194
195 if info.Verified {
196 info.Verified = false
197 verifiedCount--
198 collections.Set(collectionAddr.String(), info)
199 }
200}
201
202// UpdateCollectionInfo - Collection creator can update mutable metadata
203func UpdateCollectionInfo(category, description, externalURL string) {
204 caller := runtime.PreviousRealm().Address()
205
206 info := GetCollection(caller)
207 if info == nil {
208 panic("Collection not registered")
209 }
210
211 info.Category = category
212 info.Description = description
213 info.ExternalURL = externalURL
214 collections.Set(caller.String(), info)
215}
216
217// Helper functions
218
219func isAdmin(addr address) bool {
220 _, exists := admins.Get(addr.String())
221 return exists
222}