token.gno
4.23 Kb · 166 lines
1package royalty
2
3import (
4 "chain"
5 "math"
6 "strconv"
7
8 "gno.land/p/nt/grc721/v0"
9)
10
11const Kind = "royalty"
12
13// NewRoyalty attaches a royalty extension; maxBps caps every rate and must be 0..10000.
14func NewRoyalty(coreLedger *grc721.PrivateLedger, maxBps int64) (*Royalty, *Ledger) {
15 if coreLedger == nil {
16 panic("royalty: nil core ledger")
17 }
18
19 if maxBps < 0 || maxBps > FeeDenominator {
20 panic(ErrMaxBpsRange)
21 }
22
23 st := &storage{maxBps: maxBps}
24 core := coreLedger.ReadToken()
25 led := &Ledger{core: core, st: st}
26
27 coreLedger.RegisterExtension(led)
28
29 return &Royalty{core: core, st: st}, led
30}
31
32func (r *Royalty) ExtensionKind() string { return Kind }
33
34func (r *Royalty) TokenID() string { return r.core.ID() }
35
36func (r *Royalty) MaxBps() int64 { return r.st.maxBps }
37
38// RoyaltyInfo implements EIP-2981; per-token override takes precedence over the
39// default, and no royalty returns (zeroAddress, 0) — the "no royalty" signal.
40func (r *Royalty) RoyaltyInfo(tid grc721.TokenID, salePrice int64) (address, int64, error) {
41 if salePrice < 0 {
42 return zeroAddress, 0, ErrInvalidSalePrice
43 }
44
45 info, ok := r.resolve(tid)
46 if !ok {
47 return zeroAddress, 0, nil
48 }
49
50 if info.Bps != 0 && salePrice > math.MaxInt64/info.Bps {
51 return zeroAddress, 0, ErrInvalidSalePrice
52 }
53
54 amount := salePrice * info.Bps / FeeDenominator
55
56 return info.Receiver, amount, nil
57}
58
59func (r *Royalty) DefaultRoyalty() (RoyaltyInfo, bool) {
60 if !r.st.hasDefault {
61 return RoyaltyInfo{}, false
62 }
63
64 return r.st.def, true
65}
66
67func (r *Royalty) TokenRoyalty(tid grc721.TokenID) (RoyaltyInfo, bool) {
68 v := r.st.perToken.Get(tid.String())
69 if v == nil {
70 return RoyaltyInfo{}, false
71 }
72
73 return v.(RoyaltyInfo), true
74}
75
76// resolve returns the effective royalty: per-token override first, then default.
77func (r *Royalty) resolve(tid grc721.TokenID) (RoyaltyInfo, bool) {
78 if v := r.st.perToken.Get(tid.String()); v != nil {
79 return v.(RoyaltyInfo), true
80 }
81
82 if r.st.hasDefault {
83 return r.st.def, true
84 }
85
86 return RoyaltyInfo{}, false
87}
88
89// SetDefaultRoyalty sets the collection default for tokens without an override; issuer-only.
90func (led *Ledger) SetDefaultRoyalty(receiver address, bps int64) error {
91 if err := led.validate(receiver, bps); err != nil {
92 return err
93 }
94
95 led.st.def = RoyaltyInfo{Receiver: receiver, Bps: bps}
96 led.st.hasDefault = true
97
98 led.emitRoyaltyUpdate("default", "", receiver, bps)
99 return nil
100}
101
102func (led *Ledger) DeleteDefaultRoyalty() {
103 led.st.hasDefault = false
104 led.st.def = RoyaltyInfo{}
105
106 led.emitRoyaltyUpdate("default", "", zeroAddress, 0)
107}
108
109// SetTokenRoyalty sets a per-token override for tid; issuer-only.
110func (led *Ledger) SetTokenRoyalty(tid grc721.TokenID, receiver address, bps int64) error {
111 if _, err := led.core.OwnerOf(tid); err != nil {
112 return ErrTokenNotMinted
113 }
114
115 if err := led.validate(receiver, bps); err != nil {
116 return err
117 }
118
119 led.st.perToken.Set(tid.String(), RoyaltyInfo{Receiver: receiver, Bps: bps})
120
121 led.emitRoyaltyUpdate("token", tid.String(), receiver, bps)
122 return nil
123}
124
125// DeleteTokenRoyalty clears the per-token override, falling back to the default.
126func (led *Ledger) DeleteTokenRoyalty(tid grc721.TokenID) {
127 led.st.perToken.Remove(tid.String())
128
129 led.emitRoyaltyUpdate("token", tid.String(), zeroAddress, 0)
130}
131
132// emitRoyaltyUpdate signals a royalty change (EIP-2981 defines no events); a
133// cleared royalty is reported as an empty receiver and bps 0.
134func (led *Ledger) emitRoyaltyUpdate(scope, tokenID string, receiver address, bps int64) {
135 chain.Emit(
136 RoyaltyUpdateEvent,
137 "token", led.core.ID(),
138 "scope", scope,
139 "tokenId", tokenID,
140 "receiver", receiver.String(),
141 "bps", strconv.FormatInt(bps, 10),
142 )
143}
144
145func (led *Ledger) validate(receiver address, bps int64) error {
146 if !receiver.IsValid() {
147 return ErrInvalidReceiver
148 }
149
150 if bps < 0 || bps > led.st.maxBps {
151 return ErrInvalidBps
152 }
153
154 return nil
155}
156
157func (led *Ledger) ExtensionKind() string { return Kind }
158
159func (led *Ledger) OnMint(to address, tid grc721.TokenID) {}
160func (led *Ledger) OnTransfer(from, to address, tid grc721.TokenID) {}
161
162// OnBurn clears the per-token override (cannot resurface on re-mint) but keeps
163// the default, which is collection-level policy, not per-token state.
164func (led *Ledger) OnBurn(tid grc721.TokenID) {
165 led.st.perToken.Remove(tid.String())
166}