diff --git a/packages/r/pierre115/nftregistry/admin.gno b/packages/r/pierre115/nftregistry/admin.gno new file mode 100644 index 0000000..e87f109 --- /dev/null +++ b/packages/r/pierre115/nftregistry/admin.gno @@ -0,0 +1,40 @@ +package nftregistry + +import ( + "chain/runtime" +) + +// Admin functions + +// AddAdmin - Add a new admin address +func AddAdmin(newAdmin address) { + caller := runtime.PreviousRealm().Address() + if caller != owner { + panic("Only owner can add admins") + } + admins.Set(newAdmin.String(), true) +} + +// RemoveAdmin - Remove an admin address +func RemoveAdmin(admin address) { + caller := runtime.PreviousRealm().Address() + if caller != owner { + panic("Only owner can remove admins") + } + if admin == owner { + panic("Cannot remove owner as admin") + } + admins.Remove(admin.String()) +} + +// SetRegistrationFee - Update the registration fee +func SetRegistrationFee(fee int64) { + caller := runtime.PreviousRealm().Address() + if caller != owner { + panic("Only owner can set registration fee") + } + if fee < 0 { + panic("Fee cannot be negative") + } + registrationFee = fee +} diff --git a/packages/r/pierre115/nftregistry/gnomod.toml b/packages/r/pierre115/nftregistry/gnomod.toml new file mode 100644 index 0000000..70eaa6a --- /dev/null +++ b/packages/r/pierre115/nftregistry/gnomod.toml @@ -0,0 +1,2 @@ +module = "gno.land/r/pierre115/nftregistry" +gno = "0.9" diff --git a/packages/r/pierre115/nftregistry/query.gno b/packages/r/pierre115/nftregistry/query.gno new file mode 100644 index 0000000..1977754 --- /dev/null +++ b/packages/r/pierre115/nftregistry/query.gno @@ -0,0 +1,46 @@ +package nftregistry + +// Query functions for frontend or marketplaces + +// GetAllCollections - Returns all registered collections +func GetAllCollections() []CollectionInfo { + result := make([]CollectionInfo, 0, GetTotalCollections()) + + collections.Iterate("", "", func(key string, value interface{}) bool { + info := value.(*CollectionInfo) + result = append(result, *info) + return false + }) + + return result +} + +// GetVerifiedCollections - Returns only verified collections +func GetVerifiedCollections() []CollectionInfo { + result := make([]CollectionInfo, 0, GetVerifiedCount()) + + collections.Iterate("", "", func(key string, value interface{}) bool { + info := value.(*CollectionInfo) + if info.Verified { + result = append(result, *info) + } + return false + }) + + return result +} + +// GetCollectionsByCategory - Returns collections in a specific category +func GetCollectionsByCategory(category string) []CollectionInfo { + result := make([]CollectionInfo, 0) + + collections.Iterate("", "", func(key string, value interface{}) bool { + info := value.(*CollectionInfo) + if info.Category == category { + result = append(result, *info) + } + return false + }) + + return result +} diff --git a/packages/r/pierre115/nftregistry/registry.gno b/packages/r/pierre115/nftregistry/registry.gno new file mode 100644 index 0000000..0c6a864 --- /dev/null +++ b/packages/r/pierre115/nftregistry/registry.gno @@ -0,0 +1,201 @@ +package nftregistry + +import ( + "chain/runtime" + + "gno.land/p/demo/tokens/grc721" + "gno.land/p/nt/avl" +) + +// CollectionInfo - Extended metadata stored in the registry +type CollectionInfo struct { + Address address + Name string + Symbol string + Creator address + RegisteredAt int64 + Verified bool + Category string + Description string + ExternalURL string + NFTGetter grc721.NFTGetter + SupportsMetadata bool +} + +var ( + collections avl.Tree + admins avl.Tree + categoriesIndex avl.Tree + owner address + registrationFee int64 = 1000000 +) + +func init() { + owner = runtime.PreviousRealm().Address() + admins.Set(owner.String(), true) +} + +// RegisterCollection - Register a new NFT collection +func RegisterCollection( + realmAddr address, + name string, + symbol string, + category string, + description string, + externalURL string, + supportsMetadata bool, + getter grc721.NFTGetter, +) { + creator := runtime.OriginCaller() + + // Check if collection already registered + if collections.Has(realmAddr.String()) { + panic("Collection already registered") + } + + if getter == nil { + panic("NFT getter function is required") + } + + if name == "" || symbol == "" { + panic("Name and symbol cannot be empty") + } + + info := &CollectionInfo{ + Address: realmAddr, + Creator: creator, + Name: name, + Symbol: symbol, + RegisteredAt: runtime.ChainHeight(), + Verified: false, + Category: category, + Description: description, + ExternalURL: externalURL, + NFTGetter: getter, + SupportsMetadata: supportsMetadata, + } + + collections.Set(realmAddr.String(), info) +} + +// IsRegistered - Check if a collection is registered +func IsRegistered(collectionAddr address) bool { + return collections.Has(collectionAddr.String()) +} + +// GetTotalCollections - Total number of registered collections +func GetTotalCollections() int { + count := 0 + collections.Iterate("", "", func(key string, value interface{}) bool { + count++ + return false + }) + return count +} + +// GetVerifiedCount - Count of verified collections +func GetVerifiedCount() int { + count := 0 + collections.Iterate("", "", func(key string, value interface{}) bool { + info := value.(*CollectionInfo) + if info.Verified { + count++ + } + return false + }) + return count +} + +// GetCollection - Retrieve collection info by address +func GetCollection(collectionAddr address) *CollectionInfo { + val, exists := collections.Get(collectionAddr.String()) + if !exists { + return nil + } + return val.(*CollectionInfo) +} + +// GetNFTGetter - Retrieve the NFTGetter function for a collection +func GetNFTGetter(collectionAddr address) (grc721.NFTGetter, bool) { + info := GetCollection(collectionAddr) + if info == nil { + return nil, false + } + return info.NFTGetter, true +} + +// GetTokenMetadata - Retrieve onchain metadata for a token +func GetTokenMetadata(collectionAddr address, tokenId grc721.TokenID) (grc721.Metadata, error) { + info := GetCollection(collectionAddr) + if info == nil { + panic("Collection not registered") + } + + if !info.SupportsMetadata { + panic("Collection does not support onchain metadata") + } + + nftInstance := info.NFTGetter() + metadataCollection := nftInstance.(grc721.IGRC721MetadataOnchain) + + return metadataCollection.TokenMetadata(tokenId) +} + +// VerifyCollection - Mark a collection as verified +func VerifyCollection(collectionAddr address) { + caller := runtime.PreviousRealm().Address() + + if !isAdmin(caller) { + panic("Only admins can verify collections") + } + + info := GetCollection(collectionAddr) + if info == nil { + panic("Collection not found") + } + + if !info.Verified { + info.Verified = true + collections.Set(collectionAddr.String(), info) + } +} + +// UnverifyCollection - Remove verified status from a collection +func UnverifyCollection(collectionAddr address) { + caller := runtime.PreviousRealm().Address() + + if !isAdmin(caller) { + panic("Only admins can unverify collections") + } + + info := GetCollection(collectionAddr) + if info == nil { + panic("Collection not found") + } + + if info.Verified { + info.Verified = false + collections.Set(collectionAddr.String(), info) + } +} + +// UpdateCollectionInfo - Update collection metadata +func UpdateCollectionInfo(category, description, externalURL string) { + caller := runtime.PreviousRealm().Address() + + info := GetCollection(caller) + if info == nil { + panic("Collection not registered") + } + + info.Category = category + info.Description = description + info.ExternalURL = externalURL + collections.Set(caller.String(), info) +} + +// isAdmin - Check if an address is an admin +func isAdmin(addr address) bool { + _, exists := admins.Get(addr.String()) + return exists +} diff --git a/packages/r/pierre115/nftregistry/render.gno b/packages/r/pierre115/nftregistry/render.gno new file mode 100644 index 0000000..1bd467c --- /dev/null +++ b/packages/r/pierre115/nftregistry/render.gno @@ -0,0 +1,120 @@ +package nftregistry + +import ( + "gno.land/p/nt/ufmt" +) + +// Render functions + +func Render(path string) string { + if path == "" { + return renderHome() + } + + if path == "verified" { + return renderVerified() + } + + // Render specific category + return renderCategory(path) +} + +func renderHome() string { + output := "# NFT Registry\n\n" + output += "**The unified NFT collection directory for Gnoland in GRC721**\n\n" + output += "---\n\n" + output += ufmt.Sprintf("**Total Collections**: %d\n", GetTotalCollections()) + output += ufmt.Sprintf("**Verified Collections**: %d\n", GetVerifiedCount()) + output += ufmt.Sprintf("**Registration Fee**: %d ugnot\n\n", registrationFee) + + output += "## All Collections\n\n" + + if GetTotalCollections() == 0 { + output += "_No collections registered yet_\n" + return output + } + + collections.Iterate("", "", func(key string, value interface{}) bool { + info := value.(*CollectionInfo) + output += renderCollectionCard(info) + return false + }) + + return output +} + +// Render verified collections +func renderVerified() string { + output := "# Verified Collections\n\n" + output += ufmt.Sprintf("Showing %d verified collections\n\n", GetVerifiedCount()) + + if GetVerifiedCount() == 0 { + output += "_No verified collections yet_\n" + return output + } + + collections.Iterate("", "", func(key string, value interface{}) bool { + info := value.(*CollectionInfo) + if info.Verified { + output += renderCollectionCard(info) + } + return false + }) + + return output +} + +// Render collections by category +func renderCategory(category string) string { + output := ufmt.Sprintf("# Category: %s\n\n", category) + + count := 0 + collections.Iterate("", "", func(key string, value interface{}) bool { + info := value.(*CollectionInfo) + if info.Category == category { + output += renderCollectionCard(info) + count++ + } + return false + }) + + if count == 0 { + output += "_No collections in this category_\n" + } + + return output +} + +// Render a single collection card +func renderCollectionCard(info *CollectionInfo) string { + verified := "" + if info.Verified { + verified = " ✓" + } + + metadata := "" + if info.SupportsMetadata { + metadata = " 🎭" + } + + output := ufmt.Sprintf("### %s (%s)%s%s\n", info.Name, info.Symbol, verified, metadata) + output += ufmt.Sprintf("- **Address**: `%s`\n", info.Address) + output += ufmt.Sprintf("- **Creator**: `%s`\n", info.Creator) + + if info.Category != "" { + output += ufmt.Sprintf("- **Category**: %s\n", info.Category) + } + + if info.Description != "" { + output += ufmt.Sprintf("- **Description**: %s\n", info.Description) + } + + if info.ExternalURL != "" { + output += ufmt.Sprintf("- **Website**: %s\n", info.ExternalURL) + } + + output += ufmt.Sprintf("- **Registered**: Block #%d\n", info.RegisteredAt) + output += "\n" + + return output +}