Smart Contract
Royalties
A.9212a87501a8a6a2.Royalties
1 /*
2 Royalties.cdc
3
4 The contract manages royalty fee distributions for Flowverse NFT platform
5
6 Author: Brian Min brian@flowverse.co
7*/
8
9import NFTStorefrontV2 from 0x4eb8a10cb9f87357
10import FungibleToken from 0xf233dcee88fe0abe
11import MetadataViews from 0x1d7e57aa55817448
12import NonFungibleToken from 0x1d7e57aa55817448
13
14access(all) contract Royalties {
15 access(all) let AdminStoragePath: StoragePath
16
17 access(all) struct Override {
18 access(all) let rateOverride: UFix64?
19 access(all) let descriptionOverride: String?
20 access(all) let receiverOverride: Capability<&{FungibleToken.Receiver}>?
21 access(all) let rateMatcher: UFix64?
22 access(all) let descriptionMatcher: String?
23 access(all) let receiverIdentifierMatcher: String?
24 init(
25 rateOverride: UFix64?,
26 descriptionOverride: String?,
27 receiverOverride: Capability<&{FungibleToken.Receiver}>?,
28 rateMatcher: UFix64?,
29 descriptionMatcher: String?,
30 receiverIdentifierMatcher: String?
31 ) {
32 pre {
33 rateOverride == nil || (rateOverride! >= 0.0 && rateOverride! <= 1.0): "Rate should be in valid range i.e [0,1]"
34 rateMatcher == nil || (rateMatcher! >= 0.0 && rateMatcher! <= 1.0): "Rate should be in valid range i.e [0,1]"
35 }
36 self.rateOverride = rateOverride
37 self.descriptionOverride = descriptionOverride
38 self.receiverOverride = receiverOverride
39 self.rateMatcher = rateMatcher
40 self.descriptionMatcher = descriptionMatcher
41 self.receiverIdentifierMatcher = receiverIdentifierMatcher
42 }
43 }
44
45 access(contract) var overrides: {String: [Override]}
46
47 access(all) resource RoyaltiesAdmin {
48 access(all) fun addRoyaltyOverride(
49 identifier: String,
50 rateOverride: UFix64?,
51 descriptionOverride: String?,
52 receiverOverride: Capability<&{FungibleToken.Receiver}>?,
53 rateMatcher: UFix64?,
54 descriptionMatcher: String?,
55 receiverIdentifierMatcher: String?
56 ) {
57 if Royalties.overrides[identifier] == nil {
58 Royalties.overrides[identifier] = []
59 }
60
61 Royalties.overrides[identifier]!.append(
62 Override(
63 rateOverride: rateOverride,
64 descriptionOverride: descriptionOverride,
65 receiverOverride: receiverOverride,
66 rateMatcher: rateMatcher,
67 descriptionMatcher: descriptionMatcher,
68 receiverIdentifierMatcher: receiverIdentifierMatcher
69 )
70 )
71 }
72
73 access(all) fun removeRoyaltyOverride(identifier: String, index: Int) {
74 if Royalties.overrides[identifier] == nil {
75 panic("Royalty override not found")
76 }
77
78 Royalties.overrides[identifier]!.remove(at: index)
79 if Royalties.overrides[identifier]!.length == 0 {
80 Royalties.overrides.remove(key: identifier)
81 }
82 }
83 }
84
85 access(all) view fun getOverridesMapping(): {String: [Override]} {
86 return Royalties.overrides
87 }
88
89 access(all) view fun getOverrides(_ identifier: String): [Override] {
90 return Royalties.overrides[identifier] ?? []
91 }
92
93 access(all) view fun findMatchingOverride(overrides: [Override], royalty: MetadataViews.Royalty, ftReceiverPathIdentifier: String): Override? {
94 for o in overrides {
95 if o.rateMatcher != nil && o.rateMatcher! != royalty.cut {
96 continue
97 }
98 if o.descriptionMatcher != nil && o.descriptionMatcher!.toLower() != royalty.description.toLower() {
99 continue
100 }
101 if o.receiverIdentifierMatcher != nil && o.receiverIdentifierMatcher! != ftReceiverPathIdentifier {
102 continue
103 }
104 return o
105 }
106 return nil
107 }
108
109 access(all) fun getNFTRoyalties(nft: &{NonFungibleToken.NFT}, ftReceiverPath: PublicPath, receiverAddressOverrides: [Address]): [MetadataViews.Royalty] {
110 let royalties: [MetadataViews.Royalty] = []
111 if nft.getViews().contains(Type<MetadataViews.Royalties>()) {
112 let royaltyOverrides = self.getOverrides(nft.getType().identifier)
113 let royaltiesRef = nft.resolveView(Type<MetadataViews.Royalties>()) ?? panic("Unable to retrieve the royalties")
114 let metadataRoyalties = (royaltiesRef as! MetadataViews.Royalties).getRoyalties()
115 for i, royalty in metadataRoyalties {
116 let o = self.findMatchingOverride(overrides: royaltyOverrides, royalty: royalty, ftReceiverPathIdentifier: ftReceiverPath.toString())
117 var rate: UFix64 = royalty.cut
118 var description: String = royalty.description
119 var receiver: Capability<&{FungibleToken.Receiver}>? = nil
120 if o != nil {
121 if o!.rateOverride != nil {
122 rate = o!.rateOverride!
123 }
124 if o!.descriptionOverride != nil {
125 description = o!.descriptionOverride!
126 }
127 if o!.receiverOverride != nil {
128 receiver = o!.receiverOverride!
129 }
130 }
131 // Skip if rate is 0
132 if rate == 0.0 {
133 continue
134 }
135 if receiverAddressOverrides.length > i {
136 receiver = getAccount(receiverAddressOverrides[i]).capabilities.get<&{FungibleToken.Receiver}>(ftReceiverPath)
137 }
138 if receiver == nil {
139 receiver = getAccount(royalty.receiver.address).capabilities.get<&{FungibleToken.Receiver}>(ftReceiverPath)
140 }
141 if(receiver!.check()) {
142 royalties.append(
143 MetadataViews.Royalty(
144 receiver: receiver!,
145 cut: rate,
146 description: description
147 )
148 )
149 }
150 }
151 }
152 return royalties
153 }
154
155 access(all) fun getRoyaltySaleCutsForStorefrontV2(
156 nft: &{NonFungibleToken.NFT},
157 salePrice: UFix64,
158 ftReceiverPath: PublicPath,
159 receiverAddressOverrides: [Address]
160 ): [NFTStorefrontV2.SaleCut] {
161 let saleCuts: [NFTStorefrontV2.SaleCut] = []
162 let royalties = self.getNFTRoyalties(nft: nft, ftReceiverPath: ftReceiverPath, receiverAddressOverrides: receiverAddressOverrides)
163 for royalty in royalties {
164 saleCuts.append(NFTStorefrontV2.SaleCut(receiver: royalty.receiver, amount: royalty.cut * salePrice))
165 }
166 return saleCuts
167 }
168
169 init() {
170 self.overrides = {}
171 self.AdminStoragePath = /storage/FlowverseRoyaltiesAdmin
172 let admin <- create RoyaltiesAdmin()
173 self.account.storage.save<@RoyaltiesAdmin>(<-admin, to: self.AdminStoragePath)
174 }
175}
176