Smart Contract
FanTopPermissionV2a
A.86185fba578bc773.FanTopPermissionV2a
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