Smart Contract

FanTopPermissionV2a

A.86185fba578bc773.FanTopPermissionV2a

Deployed

16h ago
Feb 28, 2026, 02:31:19 AM UTC

Dependents

0 imports
1import FanTopToken from 0x86185fba578bc773
2import FanTopMarket from 0x86185fba578bc773
3import FanTopSerial from 0x86185fba578bc773
4import Signature from 0x86185fba578bc773
5import NonFungibleToken from 0x1d7e57aa55817448
6
7access(all) contract FanTopPermissionV2a {
8    access(all) event PermissionAdded(target: Address, role: String)
9    access(all) event PermissionRemoved(target: Address, role: String)
10
11    access(all) let ownerStoragePath: StoragePath
12    access(all) let receiverStoragePath: StoragePath
13    access(all) let receiverPublicPath: PublicPath
14
15    access(all) entitlement RevokeRole
16    access(all) entitlement BorrowRole
17
18    access(all) resource interface Role {
19        access(all) let role: String
20    }
21
22    access(all) resource Owner: Role {
23        access(all) let role: String
24
25        access(all) fun addAdmin(receiver: &{FanTopPermissionV2a.Receiver}) {
26            FanTopPermissionV2a.addPermission(receiver.owner!.address, role: "admin")
27            receiver.receive(<- create Admin())
28        }
29
30        access(all) fun addPermission(_ address: Address, role: String) {
31            FanTopPermissionV2a.addPermission(address, role: role)
32        }
33
34        access(all) fun removePermission(_ address: Address, role: String) {
35            FanTopPermissionV2a.removePermission(address, role: role)
36        }
37
38        access(self) init() {
39            self.role = "owner"
40        }
41    }
42
43    access(all) resource Admin: Role {
44        access(all) let role: String
45
46        access(all) fun addOperator(receiver: &{FanTopPermissionV2a.Receiver}) {
47            FanTopPermissionV2a.addPermission(receiver.owner!.address, role: "operator")
48            receiver.receive(<- create Operator())
49        }
50
51        access(all) fun removeOperator(_ address: Address) {
52            FanTopPermissionV2a.removePermission(address, role: "operator")
53        }
54
55        access(all) fun addMinter(receiver: &{FanTopPermissionV2a.Receiver}) {
56            FanTopPermissionV2a.addPermission(receiver.owner!.address, role: "minter")
57            receiver.receive(<- create Minter())
58        }
59
60        access(all) fun removeMinter(_ address: Address) {
61            FanTopPermissionV2a.removePermission(address, role: "minter")
62        }
63
64        access(all) fun addAgent(receiver: &{FanTopPermissionV2a.Receiver}) {
65            FanTopPermissionV2a.addPermission(receiver.owner!.address, role: "agent")
66            receiver.receive(<- create Agent())
67        }
68
69        access(all) fun removeAgent(_ address: Address) {
70            FanTopPermissionV2a.removePermission(address, role: "agent")
71        }
72
73        access(all) fun extendMarketCapacity(_ capacity: Int) {
74            FanTopMarket.extendCapacity(by: self.owner!.address, capacity: capacity)
75        }
76
77        access(self) init() {
78            self.role = "admin"
79        }
80    }
81
82    access(all) resource Operator: Role {
83        access(all) let role: String
84
85        access(all) fun createItem(itemId: String, version: UInt32, limit: UInt32, metadata: { String: String }, active: Bool) {
86            FanTopToken.createItem(itemId: itemId, version: version, limit: limit, metadata: metadata, active: active)
87        }
88
89        access(all) fun updateMetadata(itemId: String, version: UInt32, metadata: { String: String }) {
90            FanTopToken.updateMetadata(itemId: itemId, version: version, metadata: metadata)
91        }
92
93        access(all) fun updateLimit(itemId: String, limit: UInt32) {
94            FanTopToken.updateLimit(itemId: itemId, limit: limit)
95        }
96
97        access(all) fun updateActive(itemId: String, active: Bool) {
98            FanTopToken.updateActive(itemId: itemId, active: active)
99        }
100
101        access(all) fun truncateSerialBox(itemId: String, limit: Int) {
102            let boxRef = FanTopSerial.getBoxRef(itemId: itemId) ?? panic("Boxes that do not exist cannot be truncated")
103            boxRef.truncate(limit: limit)
104        }
105
106        access(self) init() {
107            self.role = "operator"
108        }
109    }
110
111    access(all) resource Minter: Role {
112        access(all) let role: String
113
114        access(all) fun mintToken(refId: String, itemId: String, itemVersion: UInt32, metadata: { String: String }): @FanTopToken.NFT {
115            return <- FanTopToken.mintToken(refId: refId, itemId: itemId, itemVersion: itemVersion, metadata: metadata, minter: self.owner!.address)
116        }
117
118        access(all) fun mintTokenWithSerialNumber(refId: String, itemId: String, itemVersion: UInt32, metadata: { String: String }, serialNumber: UInt32): @FanTopToken.NFT {
119            return <- FanTopToken.mintTokenWithSerialNumber(refId: refId, itemId: itemId, itemVersion: itemVersion, metadata: metadata, serialNumber: serialNumber, minter: self.owner!.address)
120        }
121
122        access(all) fun truncateSerialBox(itemId: String, limit: Int) {
123            let boxRef = FanTopSerial.getBoxRef(itemId: itemId) ?? panic("Boxes that do not exist cannot be truncated")
124            boxRef.truncate(limit: limit)
125        }
126
127        access(self) init() {
128            self.role = "minter"
129        }
130    }
131
132    access(all) resource Agent: Role {
133        access(all) let role: String
134
135        access(all) fun update(orderId: String, version: UInt32, metadata: { String: String }) {
136            FanTopMarket.update(agent: self.owner!.address, orderId: orderId, version: version, metadata: metadata)
137        }
138
139        access(all) fun fulfill(orderId: String, version: UInt32, recipient: &{FanTopToken.CollectionPublic}) {
140            FanTopMarket.fulfill(agent: self.owner!.address, orderId: orderId, version: version, recipient: recipient)
141        }
142
143        access(all) fun cancel(orderId: String) {
144            FanTopMarket.cancel(agent: self.owner!.address, orderId: orderId)
145        }
146
147        access(self) init() {
148            self.role = "agent"
149        }
150    }
151
152    access(all) struct User {
153        access(all) fun sell(
154            agent: Address,
155            capability: Capability<auth(NonFungibleToken.Withdraw) &FanTopToken.Collection>,
156            orderId: String,
157            refId: String,
158            nftId: UInt64,
159            version: UInt32,
160            metadata: [String],
161            signature: [UInt8],
162            keyIndex: Int
163        ) {
164            pre {
165                keyIndex >= 0
166                FanTopPermissionV2a.hasPermission(agent, role: "agent")
167                metadata.length % 2 == 0: "Unpaired metadata cannot be used"
168            }
169
170            let account = getAccount(agent)
171            var signedData = agent.toBytes()
172                .concat(capability.address.toBytes())
173                .concat(orderId.utf8)
174                .concat(refId.utf8)
175                .concat(nftId.toBigEndianBytes())
176                .concat(version.toBigEndianBytes())
177
178            let flatMetadata: { String: String } = {}
179            var i = 0
180            while i < metadata.length {
181                let key = metadata[i]
182                let value = metadata[i+1]
183
184                signedData = signedData.concat(key.utf8).concat(value.utf8)
185                flatMetadata[key] = value
186
187                i = i + 2
188            }
189
190            signedData = signedData.concat(keyIndex.toString().utf8)
191
192            assert(
193                Signature.verify(
194                    signature: signature,
195                    signedData: signedData,
196                    account: account,
197                    keyIndex: keyIndex
198                ),
199                message: "Unverified orders cannot be fulfilled"
200            )
201
202            FanTopMarket.sell(
203                agent: agent,
204                capability: capability,
205                orderId: orderId,
206                refId: refId,
207                nftId: nftId,
208                version: version,
209                metadata: flatMetadata
210            )
211        }
212
213        access(all) fun cancel(
214            account: auth(Keys) &Account,
215            orderId: String
216        ) {
217            pre {
218                FanTopMarket.containsOrder(orderId): "Order is not exists"
219                FanTopMarket.isOwnerAddress(orderId: orderId, address: account.address): "Cancel account is not match order account"
220            }
221
222            FanTopMarket.cancel(agent: nil, orderId: orderId)
223        }
224    }
225
226    access(all) resource interface Receiver {
227        access(all) fun receive(_ role: @{FanTopPermissionV2a.Role})
228        access(all) fun check(address: Address): Bool
229    }
230
231    access(all) resource Holder: Receiver {
232        access(self) let address: Address
233        access(self) let resources: @{ String: {FanTopPermissionV2a.Role} }
234
235        access(all) view fun check(address: Address): Bool {
236            return address == self.owner?.address && address == self.address
237        }
238
239        access(all) fun receive(_ role: @{FanTopPermissionV2a.Role}) {
240            assert(!self.resources.containsKey(role.role), message: "Resources for roles that already exist cannot be received")
241            self.resources[role.role] <-! role
242        }
243
244        access(self) fun borrow(by: auth(BorrowValue) &Account, role: String): &{FanTopPermissionV2a.Role} {
245            pre {
246                self.check(address: by.address): "Only borrowing by the owner is allowed"
247                FanTopPermissionV2a.hasPermission(by.address, role: role): "Roles not on the list are not allowed"
248            }
249            return (&self.resources[role] as &{FanTopPermissionV2a.Role}?) ?? panic("Could not borrow role")
250        }
251
252        access(BorrowRole) fun borrowAdmin(by: auth(BorrowValue) &Account): &Admin {
253            return self.borrow(by: by, role: "admin") as! &Admin
254        }
255
256        access(BorrowRole) fun borrowOperator(by: auth(BorrowValue) &Account): &Operator {
257            return self.borrow(by: by, role: "operator") as! &Operator
258        }
259
260        access(BorrowRole) fun borrowMinter(by: auth(BorrowValue) &Account): &Minter {
261            return self.borrow(by: by, role: "minter") as! &Minter
262        }
263
264        access(BorrowRole) fun borrowAgent(by: auth(BorrowValue) &Account): &Agent {
265            return self.borrow(by: by, role: "agent") as! &Agent
266        }
267
268        access(RevokeRole) fun revoke(_ role: String) {
269            pre {
270                FanTopPermissionV2a.isRole(role): "Unknown role cannot be changed"
271            }
272            if FanTopPermissionV2a.hasPermission(self.address, role: role) {
273                FanTopPermissionV2a.removePermission(self.address, role: role)
274            }
275            destroy self.resources.remove(key: role)
276        }
277
278        access(RevokeRole) fun revokeAll() {
279            for role in self.resources.keys {
280                if FanTopPermissionV2a.hasPermission(self.address, role: role) {
281                    FanTopPermissionV2a.removePermission(self.address, role: role)
282                }
283                destroy self.resources.remove(key: role)
284            }
285        }
286
287        access(contract) init(_ address: Address) {
288            self.address = address
289            self.resources <- {}
290        }
291    }
292
293    access(all) fun createHolder(account: auth(SaveValue) &Account): @Holder {
294        return <- create Holder(account.address)
295    }
296
297    access(self) let permissions: { Address: { String: Bool } }
298
299    access(self) fun addPermission(_ address: Address, role: String) {
300        pre {
301            FanTopPermissionV2a.isRole(role): "Unknown role cannot be changed"
302            role != "owner": "Owner cannot be changed"
303            !self.hasPermission(address, role: role): "Permission that already exists cannot be added"
304        }
305
306        let permission = self.permissions[address] ?? {} as { String: Bool}
307        permission[role] = true
308        self.permissions[address] = permission
309
310        emit PermissionAdded(target: address, role: role)
311    }
312
313    access(self) fun removePermission(_ address: Address, role: String) {
314        pre {
315            FanTopPermissionV2a.isRole(role): "Unknown role cannot be changed"
316            role != "owner": "Owner cannot be changed"
317            self.hasPermission(address, role: role): "Permissions that do not exist cannot be deleted"
318        }
319
320        let permission: {String: Bool} = self.permissions[address]!
321        permission[role] = false
322        self.permissions[address] = permission
323
324        emit PermissionRemoved(target: address, role: role)
325    }
326
327    access(all) fun getAllPermissions(): { Address: { String: Bool } } {
328        return self.permissions
329    }
330
331    access(all) view fun hasPermission(_ address: Address, role: String): Bool {
332        if let permission = self.permissions[address] {
333            return permission[role] ?? false
334        }
335
336        return false
337    }
338
339    access(all) view fun isRole(_ role: String): Bool {
340        switch role {
341        case "owner":
342            return true
343        case "admin":
344            return true
345        case "operator":
346            return true
347        case "minter":
348            return true
349        case "agent":
350            return true
351        default:
352            return false
353        }
354    }
355
356    init() {
357        self.ownerStoragePath = /storage/FanTopOwnerV2a
358        self.receiverStoragePath = /storage/FanTopPermissionV2a
359        self.receiverPublicPath = /public/FanTopPermissionV2a
360
361        self.permissions = {
362            self.account.address: { "owner": true }
363        }
364
365        self.account.storage.save<@Owner>(<- create Owner(), to: self.ownerStoragePath)
366    }
367}
368