extensions.gno
3.61 Kb · 131 lines
1package guardians
2
3import (
4 "gno.land/p/nt/grc721/enumerable/v0"
5 "gno.land/p/nt/grc721/metadata/v0"
6 "gno.land/p/nt/grc721/royalty/v0"
7 "gno.land/p/nt/grc721/v0"
8)
9
10const (
11 defaultRoyaltyBps = int64(400) // 4% creator royalty on marketplace sales.
12 royaltyMaxBps = int64(1000) // Permanent collection cap: 10%.
13)
14
15// Replace this value through SetVeiledURI before the first public mint.
16// The explicit placeholder prevents an unfinished CID from looking production-ready.
17var veiledURI = "ipfs://REPLACE_WITH_VEILED_METADATA_CID/metadata.json"
18
19var (
20 metadataView *metadata.Metadata
21 metadataLedger *metadata.Ledger
22 enumerableView *enumerable.Enumerable
23 enumerableLedger *enumerable.Ledger
24 royaltyView *royalty.Royalty
25 royaltyLedger *royalty.Ledger
26)
27
28// attachExtensions must run before the first mint so the enumerable index and
29// metadata hooks observe every token from genesis.
30func attachExtensions(coreLedger *grc721.PrivateLedger) {
31 metadataView, metadataLedger = metadata.NewMetadata(coreLedger)
32 enumerableView, enumerableLedger = enumerable.NewEnumerable(coreLedger)
33 royaltyView, royaltyLedger = royalty.NewRoyalty(coreLedger, royaltyMaxBps)
34
35 if err := royaltyLedger.SetDefaultRoyalty(address("g1lp547ww848djw0ammpqswegg7tc6xczqqtx07n"), defaultRoyaltyBps); err != nil {
36 panic(err)
37 }
38}
39
40// Metadata / veil facade.
41
42func VeiledURI() string { return veiledURI }
43
44func TokenURI(tokenID string) string {
45 uri, err := metadataView.TokenURI(grc721.TokenID(tokenID))
46 if err != nil {
47 panic(err)
48 }
49 return uri
50}
51
52// SetVeiledURI allows the final placeholder CID to be configured once before
53// minting begins. Existing token URIs are never silently rewritten.
54func SetVeiledURI(cur realm, uri string) {
55 assertAdmin(cur.Previous().Address())
56 supply := token.TotalSupply()
57 if !canConfigureVeiledURI(uri, supply) {
58 if supply != 0 {
59 panic("guardians: veiled uri is locked after the first mint")
60 }
61 panic("guardians: veiled uri cannot be empty")
62 }
63 veiledURI = uri
64}
65
66func validVeiledURI(uri string) bool { return uri != "" }
67
68func canConfigureVeiledURI(uri string, supply int64) bool {
69 return supply == 0 && validVeiledURI(uri)
70}
71
72func setInitialTokenURI(tokenID grc721.TokenID) {
73 if err := metadataLedger.SetTokenURI(tokenID, veiledURI); err != nil {
74 panic(err)
75 }
76}
77
78// Enumerable facade.
79
80func EnumerableSupply() int64 { return enumerableView.TotalSupply() }
81
82func TokenByIndex(index int64) string {
83 tokenID, err := enumerableView.TokenByIndex(index)
84 if err != nil {
85 panic(err)
86 }
87 return tokenID.String()
88}
89
90func TokenOfOwnerByIndex(owner address, index int64) string {
91 tokenID, err := enumerableView.TokenOfOwnerByIndex(owner, index)
92 if err != nil {
93 panic(err)
94 }
95 return tokenID.String()
96}
97
98// Royalty facade. This is EIP-2981-style royalty information. Actual payment
99// enforcement belongs in the project marketplace settlement realm.
100
101func RoyaltyMaxBps() int64 { return royaltyView.MaxBps() }
102
103func DefaultRoyalty() (address, int64) {
104 info, ok := royaltyView.DefaultRoyalty()
105 if !ok {
106 return address(""), 0
107 }
108 return info.Receiver, info.Bps
109}
110
111func RoyaltyInfo(tokenID string, salePrice int64) (address, int64) {
112 receiver, amount, err := royaltyView.RoyaltyInfo(grc721.TokenID(tokenID), salePrice)
113 if err != nil {
114 panic(err)
115 }
116 return receiver, amount
117}
118
119func SetDefaultRoyalty(cur realm, receiver address, bps int64) {
120 assertAdmin(cur.Previous().Address())
121 if !validRoyaltyBps(bps) {
122 panic("guardians: royalty bps out of range")
123 }
124 if err := royaltyLedger.SetDefaultRoyalty(receiver, bps); err != nil {
125 panic(err)
126 }
127}
128
129func validRoyaltyBps(bps int64) bool {
130 return bps >= 0 && bps <= royaltyMaxBps
131}