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

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}