Smart Contract

Royalties

A.9212a87501a8a6a2.Royalties

Deployed

1d ago
Feb 27, 2026, 08:40:02 AM UTC

Dependents

0 imports
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