Smart Contract

TenantService

A.965411a02ca21b87.TenantService

Deployed

13h ago
Mar 07, 2026, 02:13:59 PM UTC

Dependents

0 imports
1
2import NonFungibleToken from 0x1d7e57aa55817448
3
4pub contract TenantService: NonFungibleToken {
5
6    // basic data about the tenant
7    pub let id: String
8    pub let name: String
9    pub let description: String
10    pub var closed: Bool
11
12    // NFT
13    pub var totalSupply: UInt64
14
15    // paths
16    access(all) let ADMIN_OBJECT_PATH: StoragePath
17    access(all) let PRIVATE_NFT_COLLECTION_PATH: StoragePath
18    access(all) let PUBLIC_NFT_COLLECTION_PATH: PublicPath
19    access(all) let PUBLIC_NFT_COLLECTION_PATH_CUSTOM: PublicPath
20
21    // archetypes
22    access(self) let archetypes: {UInt64: Archetype}
23    access(self) let archetypeAdmins: @{UInt64: ArchetypeAdmin}
24    access(self) var archetypeSeq: UInt64
25    access(self) let artifactsByArchetype: {UInt64: {UInt64: Bool}} // archetypeId -> {artifactId: true}
26
27    // artifacts
28    access(self) let artifacts: {UInt64: Artifact}
29    access(self) let artifactAdmins: @{UInt64: ArtifactAdmin}
30    access(self) var artifactSeq: UInt64
31    access(self) var nextNftSerialNumber: {UInt64: UInt64}
32    access(self) let setsByArtifact: {UInt64: {UInt64: Bool}} // artifactId -> {setId: true}
33    access(self) let faucetsByArtifact: {UInt64: {UInt64: Bool}} // artifactId -> {faucetId: true}
34
35    // sets
36    access(self) let sets: {UInt64: Set}
37    access(self) let setAdmins: @{UInt64: SetAdmin}
38    access(self) var setSeq: UInt64
39    access(self) let artifactsBySet: {UInt64: {UInt64: Bool}} // setId -> {artifactId: true}
40    access(self) let faucetsBySet: {UInt64: {UInt64: Bool}} // setId -> {faucetId: true}
41
42    // prints
43    access(self) let prints: {UInt64: Print}
44    access(self) let printAdmins: @{UInt64: PrintAdmin}
45    access(self) var printSeq: UInt64
46
47    // faucets
48    access(self) let faucets: {UInt64: Faucet}
49    access(self) let faucetAdmins: @{UInt64: FaucetAdmin}
50    access(self) var faucetSeq: UInt64
51
52    // tenant events
53    pub event TenantClosed()
54
55    // NFT events
56    pub event ContractInitialized()
57    pub event Withdraw(id: UInt64, from: Address?)
58    pub event Deposit(id: UInt64, to: Address?)
59
60    init(
61        tenantId: String,
62        tenantName: String,
63        tenantDescription: String,
64        ADMIN_OBJECT_PATH: StoragePath,
65        PRIVATE_NFT_COLLECTION_PATH: StoragePath,
66        PUBLIC_NFT_COLLECTION_PATH: PublicPath,
67        PUBLIC_NFT_COLLECTION_PATH_CUSTOM: PublicPath
68    ) {
69        self.id = tenantId
70        self.name = tenantName
71        self.description = tenantDescription
72        self.closed = false
73
74        self.archetypes = {}
75        self.archetypeAdmins <- {}
76        self.archetypeSeq = 1
77        self.artifactsByArchetype = {}
78
79        self.artifacts = {}
80        self.artifactAdmins <- {}
81        self.artifactSeq = 1
82        self.nextNftSerialNumber = {}
83        self.setsByArtifact = {}
84        self.faucetsByArtifact = {}
85
86        self.sets = {}
87        self.setAdmins <- {}
88        self.setSeq = 1
89        self.artifactsBySet = {}
90        self.faucetsBySet = {}
91
92        self.prints = {}
93        self.printAdmins <- {}
94        self.printSeq = 1
95
96        self.faucets = {}
97        self.faucetAdmins <- {}
98        self.faucetSeq = 1
99
100        self.totalSupply = 0
101
102        self.OBJECT_TYPE_MASK = UInt64.max << 55
103        self.SEQUENCE_MASK = (UInt64.max << UInt64(9)) >> UInt64(9)
104
105        self.ADMIN_OBJECT_PATH = ADMIN_OBJECT_PATH
106        self.PRIVATE_NFT_COLLECTION_PATH = PRIVATE_NFT_COLLECTION_PATH
107        self.PUBLIC_NFT_COLLECTION_PATH = PUBLIC_NFT_COLLECTION_PATH
108        self.PUBLIC_NFT_COLLECTION_PATH_CUSTOM = PUBLIC_NFT_COLLECTION_PATH_CUSTOM
109
110        // create a collection for the admin
111        self.account.save<@ShardedCollection>(<- TenantService.createEmptyShardedCollection(numBuckets: 32), to: TenantService.PRIVATE_NFT_COLLECTION_PATH)
112
113        // Create a public capability for the Collection
114        self.account.link<&{NonFungibleToken.CollectionPublic}>(TenantService.PUBLIC_NFT_COLLECTION_PATH, target: TenantService.PRIVATE_NFT_COLLECTION_PATH)
115        self.account.link<&{CollectionPublic}>(TenantService.PUBLIC_NFT_COLLECTION_PATH_CUSTOM, target: TenantService.PRIVATE_NFT_COLLECTION_PATH)
116
117        // put the admin in storage
118        self.account.save<@TenantAdmin>(<- create TenantAdmin(), to: TenantService.ADMIN_OBJECT_PATH)
119
120        emit ContractInitialized()
121    }
122
123    pub enum ObjectType: UInt8 {
124        pub case UNKNOWN
125
126        // An Archetype is a high level organizational unit for a type of NFT. For instance, in the
127        // case that the Tenant is a company dealing with professional sports they might have an Archetype
128        // for each of the sports that they support, ie: Basketball, Baseball, Football, etc.
129        //
130        pub case ARCHETYPE
131
132        // An Artifact is the actual object that is minted as an NFT. It contains all of the meta data data
133        // and a reference to the Archetype that it belongs to.
134        //
135        pub case ARTIFACT
136
137        // NFTs can be minted into a Set. A set could be something along the lines of "Greatest Pitchers",
138        // "Slam Dunk Artists", or "Running Backs" (continuing with the sports theme from above). NFT do
139        // not have to be minted into a set. Also, an NFT could be minted from an Artifact by itself, and
140        // in another instance as part of a set - so that the NFT references the same Artifact, but only
141        // one of them belongs to the Set.
142        //
143        pub case SET
144
145        // A Print reserves a block of serial numbers for minting at a later time. It is associated with
146        // a single Artifact and when the Print is minted it reserves the next serial number through however
147        // many serial numbers are to be reserved. NFTs can then later be minted from the Print and will
148        // be given the reserved serial numbers.
149        //
150        pub case PRINT
151
152        // A Faucet is similar to a Print except that it doesn't reserve a block of serial numbers, it merely
153        // mints NFTs from a given Artifact on demand. A Faucet can have a maxMintCount or be unbound and
154        // mint infinitely (or however many NFTs are allowed to be minted for the Artifact that it is bound to).
155        //
156        pub case FAUCET
157
158        // An NFT holds metadata, a reference to it's Artifact (and therefore Archetype), a reference to
159        // it's Set (if it belongs to one), a reference to it's Print (if it was minted by one), a reference
160        // to it's Faucet (if it was minted by one) and has a unique serial number.
161        pub case NFT
162    }
163
164    pub let OBJECT_TYPE_MASK: UInt64
165    pub let SEQUENCE_MASK: UInt64
166
167    // Generates an ID for the given object type and sequence. We generate IDs this way
168    // so that they are unique across the various types of objects supported by this
169    // contract.
170    //
171    pub fun generateId(_ objectType: ObjectType, _ sequence: UInt64): UInt64 {
172        if (sequence > 36028797018963967) {
173            panic("sequence may only have 55 bits and must be less than 36028797018963967")
174        }
175        var ret: UInt64 = UInt64(objectType.rawValue)
176        ret = ret << UInt64(55)
177        ret = ret | ((sequence << UInt64(9)) >> UInt64(9))
178        return ret
179    }
180
181    // Extracts the ObjectType from an id
182    //
183    pub fun getObjectType(_ id: UInt64): ObjectType {
184        return ObjectType(rawValue: UInt8(id >> UInt64(55)))!
185    }
186
187    // Extracts the sequence from an id
188    //
189    pub fun getSequence(_ id: UInt64): UInt64 {
190        return id & TenantService.SEQUENCE_MASK
191    }
192
193    // Indicates whether or not the given id is for a given ObjectType.
194    //
195    pub fun isObjectType(_ id: UInt64, _ objectType: ObjectType): Bool {
196        return (TenantService.getObjectType(id) == objectType)
197    }
198
199    // Returns the tenant id that was supplied when the contract was created
200    //
201    pub fun getTenantId(): String {
202        return self.id
203    }
204
205    // Returns the version of this contract
206    //
207    pub fun getVersion(): UInt32 {
208        return 2
209    }
210
211    // TenantAdmin is used for administering the Tenant
212    //
213    pub resource TenantAdmin {
214
215        // Closes the Tenant, rendering any write access impossible
216        //
217        pub fun close() {
218            if !TenantService.closed {
219                TenantService.closed = true
220                emit TenantClosed()
221            }
222        }
223
224        // Creates a new Archetype returning it's id.
225        //
226        pub fun createArchetype(
227            name: String,
228            description: String,
229            metadata: {String: TenantService.MetadataField}
230        ): UInt64 {
231            pre {
232                TenantService.closed != true: "The Tenant is closed"
233            }
234            var archetype = Archetype(name: name, description: description, metadata: metadata)
235            TenantService.archetypes[archetype.id] = archetype
236            TenantService.archetypeAdmins[archetype.id] <-! create ArchetypeAdmin(archetype.id)
237            return archetype.id
238        }
239
240        // Grants admin access to the given Archetype
241        //
242        pub fun borrowArchetypeAdmin(_ id: UInt64): &ArchetypeAdmin? {
243            pre {
244                TenantService.closed != true: "The Tenant is closed"
245                TenantService.archetypeAdmins[id] != nil: "Archetype not found"
246                TenantService.isObjectType(id, ObjectType.ARCHETYPE): "ObjectType is not an Archetype"
247            }
248            return &TenantService.archetypeAdmins[id] as &ArchetypeAdmin?
249        }
250
251        // Creates a new Artifact returning it's id.
252        //
253        pub fun createArtifact(
254            archetypeId: UInt64,
255            name: String,
256            description: String,
257            maxMintCount: UInt64,
258            metadata: {String: TenantService.MetadataField}
259        ): UInt64 {
260            pre {
261                TenantService.closed != true: "The Tenant is closed"
262                TenantService.archetypes[archetypeId] != nil: "The Archetype wasn't found"
263                self.borrowArchetypeAdmin(archetypeId)?.closed != true: "The Archetype is closed"
264            }
265            var artifact = Artifact(archetypeId: archetypeId, name: name, description: description, maxMintCount: maxMintCount, metadata: metadata)
266            TenantService.artifacts[artifact.id] = artifact
267            TenantService.artifactAdmins[artifact.id] <-! create ArtifactAdmin(id: artifact.id)
268            TenantService.nextNftSerialNumber[artifact.id] = 1
269            return artifact.id
270        }
271
272        // Grants admin access to the given Artifact
273        //
274        pub fun borrowArtifactAdmin(_ id: UInt64): &ArtifactAdmin? {
275            pre {
276                TenantService.closed != true: "The Tenant is closed"
277                TenantService.artifactAdmins[id] != nil: "Artifact not found"
278                TenantService.isObjectType(id, ObjectType.ARTIFACT): "ObjectType is not an Artifact"
279            }
280            return &TenantService.artifactAdmins[id] as &ArtifactAdmin?
281        }
282
283        // Creates a new Set returning it's id.
284        //
285        pub fun createSet(name: String, description: String, metadata: {String: TenantService.MetadataField}): UInt64 {
286            pre {
287                TenantService.closed != true: "The Tenant is closed"
288            }
289            var set = Set(name: name, description: description, metadata: metadata)
290            TenantService.sets[set.id] = set
291            TenantService.setAdmins[set.id] <-! create SetAdmin(set.id)
292            return set.id
293        }
294
295        // Grants admin access to the given Set
296        //
297        pub fun borrowSetAdmin(_ id: UInt64): &SetAdmin? {
298            pre {
299                TenantService.closed != true: "The Tenant is closed"
300                TenantService.setAdmins[id] != nil: "Set not found"
301                TenantService.isObjectType(id, ObjectType.SET): "ObjectType is not a Set"
302            }
303            return &TenantService.setAdmins[id] as &SetAdmin?
304        }
305
306        // Creates a new Print returning it's id.
307        //
308        pub fun createPrint(
309           artifactId: UInt64,
310           setId: UInt64?,
311           name: String,
312           description: String,
313           maxMintCount: UInt64,
314           metadata: {String: TenantService.MetadataField}
315       ): UInt64 {
316            pre {
317                TenantService.closed != true: "The Tenant is closed"
318                self.borrowArtifactAdmin(artifactId)?.closed != true: "The Artifact is closed"
319                setId == nil || self.borrowSetAdmin(setId!)?.closed != true: "The Set is closed"
320            }
321            var print = Print(artifactId: artifactId, setId: setId, name: name, description: description, maxMintCount: maxMintCount, metadata: metadata)
322            TenantService.prints[print.id] = print
323            TenantService.printAdmins[print.id] <-! create PrintAdmin(print.id, print.serialNumberStart)
324            return print.id
325        }
326
327        // Grants admin access to the given Print
328        //
329        pub fun borrowPrintAdmin(_ id: UInt64): &PrintAdmin? {
330            pre {
331                TenantService.closed != true: "The Tenant is closed"
332                TenantService.printAdmins[id] != nil: "Print not found"
333                TenantService.isObjectType(id, ObjectType.PRINT): "ObjectType is not a print"
334            }
335            return &TenantService.printAdmins[id] as &PrintAdmin?
336        }
337
338        // Creates a new Faucet returning it's id.
339        //
340        pub fun createFaucet(
341           artifactId: UInt64,
342           setId: UInt64?,
343           name: String,
344           description: String,
345           maxMintCount: UInt64,
346           metadata: {String: TenantService.MetadataField}
347       ): UInt64 {
348            pre {
349                TenantService.closed != true: "The Tenant is closed"
350                self.borrowArtifactAdmin(artifactId)?.closed != true: "The Artifact is closed"
351                setId == nil || self.borrowSetAdmin(setId!)?.closed != true: "The Set is closed"
352            }
353            var faucet = Faucet(artifactId: artifactId, setId: setId, name: name, description: description, maxMintCount: maxMintCount, metadata: metadata)
354            TenantService.faucets[faucet.id] = faucet
355            TenantService.faucetAdmins[faucet.id] <-! create FaucetAdmin(id: faucet.id)
356            return faucet.id
357        }
358
359        // Grants admin access to the given Faucet
360        //
361        pub fun borrowFaucetAdmin(_ id: UInt64): &FaucetAdmin? {
362            pre {
363                TenantService.closed != true: "The Tenant is closed"
364                TenantService.faucetAdmins[id] != nil: "Faucet not found"
365                TenantService.isObjectType(id, ObjectType.FAUCET): "ObjectType is not a faucet"
366            }
367            return &TenantService.faucetAdmins[id] as &FaucetAdmin?
368        }
369
370        // Mints an NFT
371        //
372        pub fun mintNFT(artifactId: UInt64, printId: UInt64?, faucetId: UInt64?, setId: UInt64?, metadata: {String: TenantService.MetadataField}): @NFT {
373            pre {
374                TenantService.artifacts[artifactId] != nil: "Cannot mint the NFT: The Artifact wasn't found"
375                self.borrowArtifactAdmin(artifactId)?.closed != true: "The Artifact is closed"
376
377                printId == nil || TenantService.isObjectType(printId!, ObjectType.PRINT): "Id supplied for printId is not an ObjectType of print"
378                faucetId == nil || TenantService.isObjectType(faucetId!, ObjectType.FAUCET): "Id supplied for faucetId is not an ObjectType of faucet"
379                setId == nil || TenantService.isObjectType(setId!, ObjectType.SET): "Id supplied for setId is not an ObjectType of set"
380
381                printId == nil || TenantService.prints[printId!] != nil: "Cannot mint the NFT: The Print wasn't found"
382                faucetId == nil || TenantService.faucets[faucetId!] != nil: "Cannot mint the NFT: The Faucet wasn't found"
383                setId == nil || TenantService.sets[setId!] != nil: "Cannot mint the NFT: The Set wasn't found"
384
385                printId == nil || self.borrowPrintAdmin(printId!)?.closed != true: "The Print is closed"
386                faucetId == nil || self.borrowFaucetAdmin(faucetId!)?.closed != true: "The Faucet is closed"
387                setId == nil || self.borrowSetAdmin(setId!)?.closed != true: "The Set is closed"
388
389                faucetId == nil || TenantService.faucets[faucetId!]!.artifactId == artifactId: "The artifactId doesn't match the Faucet's artifactId"
390                printId == nil || TenantService.prints[printId!]!.artifactId == artifactId: "The artifactId doesn't match the Print's artifactId"
391
392                faucetId == nil || TenantService.faucets[faucetId!]!.setId == setId: "The setId doesn't match the Faucet's setId"
393                printId == nil || TenantService.prints[printId!]!.setId == setId: "The setId doesn't match the Print's setId"
394
395                !(faucetId != nil && printId != nil): "Can only mint from one of a faucet or print"
396            }
397
398            let artifact: Artifact = TenantService.artifacts[artifactId]!
399            let artifactAdmin = self.borrowArtifactAdmin(artifactId)!
400            artifactAdmin.logMint(1)
401            if printId != nil {
402                artifactAdmin.logPrint(1)
403            }
404
405            let archetype: Archetype = TenantService.archetypes[artifact.archetypeId]!
406            let archetypeAdmin = self.borrowArchetypeAdmin(artifact.archetypeId)!
407            if archetypeAdmin != nil {
408                archetypeAdmin.logMint(1)
409                if printId != nil {
410                    archetypeAdmin.logPrint(1)
411                }
412            }
413
414            if faucetId != nil {
415                let faucetAdmin = self.borrowFaucetAdmin(faucetId!)!
416                faucetAdmin.logMint(1)
417            }
418
419            if setId != nil {
420                let setAdmin = self.borrowSetAdmin(setId!)!
421                setAdmin.logMint(1)
422                if printId != nil {
423                    setAdmin.logPrint(1)
424                }
425            }
426
427            if printId != nil {
428                let printAdmin = self.borrowPrintAdmin(printId!)!
429                printAdmin.logMint(1)
430            }
431
432            let newNFT: @NFT <- create NFT(
433                archetypeId: artifact.archetypeId,
434                artifactId: artifact.id,
435                printId: printId,
436                faucetId: faucetId,
437                setId: setId,
438                metadata: metadata)
439
440            return <- newNFT
441        }
442
443        // Mints many NFTs
444        //
445        pub fun batchMintNFTs(
446            count: UInt64,
447            artifactId: UInt64,
448            printId: UInt64?,
449            faucetId: UInt64?,
450            setId: UInt64?,
451            metadata: {String: TenantService.MetadataField}
452        ): @Collection {
453            let newCollection <- create Collection()
454            var i: UInt64 = 0
455            while i < count {
456                newCollection.deposit(token: <-self.mintNFT(
457                    artifactId: artifactId,
458                    printId: printId,
459                    faucetId: faucetId,
460                    setId: setId,
461                    metadata: metadata
462                ))
463                i = i + (1 as UInt64)
464            }
465            return <- newCollection
466        }
467
468        // Creates a new TenantAdmin that allows for another account
469        // to administer the Tenant
470        //
471        pub fun createNewTenantAdmin(): @TenantAdmin {
472            return <- create TenantAdmin()
473        }
474    }
475
476    // =====================================
477    // Archetype
478    // =====================================
479
480    pub event ArchetypeCreated(_ id: UInt64)
481    pub event ArchetypeDestroyed(_ id: UInt64)
482    pub event ArchetypeClosed(_ id: UInt64)
483
484    pub fun getArchetype(_ id: UInt64): Archetype? {
485        pre {
486            TenantService.isObjectType(id, ObjectType.ARCHETYPE): "Id supplied is not for an archetype"
487        }
488        return TenantService.archetypes[id]
489    }
490
491    pub fun getArchetypeView(_ id: UInt64): ArchetypeView? {
492        pre {
493            TenantService.isObjectType(id, ObjectType.ARCHETYPE): "Id supplied is not for an archetype"
494        }
495        if TenantService.archetypes[id] == nil {
496            return nil
497        }
498        let archetype = TenantService.archetypes[id]!
499        let archetypeAdmin = (&TenantService.archetypeAdmins[id] as &ArchetypeAdmin?)!
500        return ArchetypeView(
501            id: archetype.id,
502            name: archetype.name,
503            description: archetype.description,
504            metadata: archetype.metadata,
505            mintCount: archetypeAdmin.mintCount,
506            printCount: archetypeAdmin.printCount,
507            closed: archetypeAdmin.closed
508        )
509    }
510
511    pub fun getArchetypeViews(_ archetypes: [UInt64]): [ArchetypeView] {
512        let ret: [ArchetypeView] = []
513        for archetype in archetypes {
514            let element = self.getArchetypeView(archetype)
515            if element != nil {
516                ret.append(element!)
517            }
518        }
519        return ret
520    }
521
522    pub fun getAllArchetypes(): [Archetype] {
523        return TenantService.archetypes.values
524    }
525
526    // The immutable data for an Archetype
527    //
528    pub struct Archetype {
529        pub let id: UInt64
530        pub let name: String
531        pub let description: String
532        pub let metadata: {String: TenantService.MetadataField}
533
534        init(name: String, description: String, metadata: {String: TenantService.MetadataField}) {
535            self.id = TenantService.generateId(ObjectType.ARCHETYPE, TenantService.archetypeSeq)
536            self.name = name
537            self.description = description
538            self.metadata = metadata
539            TenantService.archetypeSeq = TenantService.archetypeSeq + 1 as UInt64
540            emit ArchetypeCreated(self.id)
541        }
542    }
543
544    // The mutable data for an Archetype
545    //
546    pub resource ArchetypeAdmin {
547
548        pub let id: UInt64
549        pub var mintCount: UInt64
550        pub var printCount: UInt64
551        pub var closed: Bool
552
553        init(_ id: UInt64) {
554            self.id = id
555            self.mintCount = 0
556            self.printCount = 0
557            self.closed = false
558        }
559
560        pub fun close() {
561            if !self.closed {
562                self.closed = true
563                emit ArchetypeClosed(self.id)
564            }
565        }
566
567        pub fun logMint(_ count: UInt64) {
568            pre {
569                TenantService.closed != true: "The Tenant is closed"
570                self.closed != true: "The Archetype is closed"
571            }
572            self.mintCount = self.mintCount + count
573        }
574
575        pub fun logPrint(_ count: UInt64) {
576            pre {
577                TenantService.closed != true: "The Tenant is closed"
578                self.closed != true: "The Archetype is closed"
579            }
580            self.printCount = self.printCount + count
581        }
582
583        destroy() {
584            emit ArchetypeDestroyed(self.id)
585        }
586    }
587
588    // An immutable view for an Archetype and all of it's data
589    //
590    pub struct ArchetypeView {
591        pub let id: UInt64
592        pub let name: String
593        pub let description: String
594        pub let metadata: {String: TenantService.MetadataField}
595        pub let mintCount: UInt64
596        pub let printCount: UInt64
597        pub let closed: Bool
598
599        init(
600            id: UInt64,
601            name: String,
602            description: String,
603            metadata: {String: TenantService.MetadataField},
604            mintCount: UInt64,
605            printCount: UInt64,
606            closed: Bool
607        ) {
608            self.id = id
609            self.name = name
610            self.description = description
611            self.metadata = metadata
612            self.mintCount = mintCount
613            self.printCount = printCount
614            self.closed = closed
615        }
616    }
617
618    // =====================================
619    // Artifact
620    // =====================================
621
622    pub event ArtifactCreated(_ id: UInt64)
623    pub event ArtifactMaxMintCountChanged(_ id: UInt64, _ oldMaxMintCount: UInt64, _ newMaxMintCount: UInt64)
624    pub event ArtifactDestroyed(_ id: UInt64)
625    pub event ArtifactClosed(_ id: UInt64)
626
627    pub fun getArtifact(_ id: UInt64): Artifact? {
628        pre {
629            TenantService.isObjectType(id, ObjectType.ARTIFACT): "Id supplied is not for an artifact"
630        }
631        return TenantService.artifacts[id]
632    }
633
634    pub fun getArtifactView(_ id: UInt64): ArtifactView? {
635        pre {
636            TenantService.isObjectType(id, ObjectType.ARTIFACT): "Id supplied is not for an artifact"
637        }
638        if TenantService.artifacts[id] == nil {
639            return nil
640        }
641        let artifact = TenantService.artifacts[id]!
642        let artifactAdmin = (&TenantService.artifactAdmins[id] as &ArtifactAdmin?)!
643        return ArtifactView(
644            id: artifact.id,
645            archetypeId: artifact.archetypeId,
646            name: artifact.name,
647            description: artifact.description,
648            metadata: artifact.metadata,
649            maxMintCount: artifact.maxMintCount,
650            mintCount: artifactAdmin.mintCount,
651            printCount: artifactAdmin.printCount,
652            closed: artifactAdmin.closed
653        )
654    }
655
656    pub fun getArtifactViews(_ artifacts: [UInt64]): [ArtifactView] {
657        let ret: [ArtifactView] = []
658        for artifact in artifacts {
659            let element = self.getArtifactView(artifact)
660            if element != nil {
661                ret.append(element!)
662            }
663        }
664        return ret
665    }
666
667    pub fun getAllArtifacts(): [Artifact] {
668        return TenantService.artifacts.values
669    }
670
671    pub fun getArtifactsBySet(_ setId: UInt64): [UInt64] {
672        let map = TenantService.artifactsBySet[setId]
673        if map != nil {
674            return map!.keys
675        }
676        return []
677    }
678
679    pub fun getFaucetsBySet(_ setId: UInt64): [UInt64] {
680        let map = TenantService.faucetsBySet[setId]
681        if map != nil {
682            return map!.keys
683        }
684        return []
685    }
686
687    pub fun getSetsByArtifact(_ artifactId: UInt64): [UInt64] {
688        let map = TenantService.setsByArtifact[artifactId]
689        if map != nil {
690            return map!.keys
691        }
692        return []
693    }
694
695    pub fun getFaucetsByArtifact(_ artifactId: UInt64): [UInt64] {
696        let map = TenantService.faucetsByArtifact[artifactId]
697        if map != nil {
698            return map!.keys
699        }
700        return []
701    }
702
703    pub fun getArtifactsByArchetype(_ archetypeId: UInt64): [UInt64] {
704        let map = TenantService.artifactsByArchetype[archetypeId]
705        if map != nil {
706            return map!.keys
707        }
708        return []
709    }
710
711    // The immutable data for an Artifact
712    //
713    pub struct Artifact {
714        pub let id: UInt64
715        pub let archetypeId: UInt64
716        pub let name: String
717        pub let description: String
718        pub let maxMintCount: UInt64
719        pub let metadata: {String: TenantService.MetadataField}
720
721        init(archetypeId: UInt64, name: String, description: String, maxMintCount: UInt64, metadata: {String: TenantService.MetadataField}) {
722            self.id = TenantService.generateId(ObjectType.ARTIFACT, TenantService.artifactSeq)
723            self.archetypeId = archetypeId
724            self.name = name
725            self.description = description
726            self.maxMintCount = maxMintCount
727            self.metadata = metadata
728            TenantService.artifactSeq = TenantService.artifactSeq + 1 as UInt64
729            emit ArtifactCreated(self.id)
730        }
731    }
732
733    // The mutable data for an Artifact
734    //
735    pub resource ArtifactAdmin {
736
737        pub let id: UInt64
738        pub var mintCount: UInt64
739        pub var printCount: UInt64
740        pub var closed: Bool
741
742        init(id: UInt64) {
743            self.id = id
744            self.mintCount = 0
745            self.printCount = 0
746            self.closed = false
747        }
748
749        pub fun close() {
750            if !self.closed {
751                self.closed = true
752                emit ArtifactClosed(self.id)
753            }
754        }
755
756        pub fun logMint(_ count: UInt64) {
757            pre {
758                TenantService.closed != true: "The Tenant is closed"
759                self.closed != true: "The Artifact is closed"
760                ((TenantService.artifacts[self.id]!.maxMintCount == (0 as UInt64))
761                    || (TenantService.artifacts[self.id]!.maxMintCount >= (self.mintCount + count))): "The Artifact would exceed it's maxMintCount"
762            }
763            self.mintCount = self.mintCount + count
764        }
765
766        pub fun logPrint(_ count: UInt64) {
767            pre {
768                TenantService.closed != true: "The Tenant is closed"
769                self.closed != true: "The Artifact is closed"
770            }
771            self.printCount = self.printCount + count
772        }
773
774        destroy() {
775            emit ArtifactDestroyed(self.id)
776        }
777    }
778
779    // An immutable view for an Artifact and all of it's data
780    //
781    pub struct ArtifactView {
782        pub let id: UInt64
783        pub let archetypeId: UInt64
784        pub let name: String
785        pub let description: String
786        pub let metadata: {String: TenantService.MetadataField}
787        pub let maxMintCount: UInt64
788        pub let mintCount: UInt64
789        pub let printCount: UInt64
790        pub let closed: Bool
791
792        init(
793            id: UInt64,
794            archetypeId: UInt64,
795            name: String,
796            description: String,
797            metadata: {String: TenantService.MetadataField},
798            maxMintCount: UInt64,
799            mintCount: UInt64,
800            printCount: UInt64,
801            closed: Bool
802        ) {
803            self.id = id
804            self.archetypeId = archetypeId
805            self.name = name
806            self.description = description
807            self.metadata = metadata
808            self.maxMintCount = maxMintCount
809            self.mintCount = mintCount
810            self.printCount = printCount
811            self.closed = closed
812        }
813    }
814
815    // =====================================
816    // Set
817    // =====================================
818
819    pub event SetCreated(_ id: UInt64)
820    pub event SetDestroyed(_ id: UInt64)
821    pub event SetClosed(_ id: UInt64)
822
823    pub fun getSet(_ id: UInt64): Set? {
824        pre {
825            TenantService.isObjectType(id, ObjectType.SET): "Id supplied is not for an set"
826        }
827        return TenantService.sets[id]
828    }
829
830    pub fun getSetView(_ id: UInt64): SetView? {
831        pre {
832            TenantService.isObjectType(id, ObjectType.SET): "Id supplied is not for an set"
833        }
834        if TenantService.sets[id] == nil {
835            return nil
836        }
837        let set = TenantService.sets[id]!
838        let setAdmin = (&TenantService.setAdmins[id] as &SetAdmin?)!
839        return SetView(
840            id: set.id,
841            name: set.name,
842            description: set.description,
843            metadata: set.metadata,
844            mintCount: setAdmin.mintCount,
845            printCount: setAdmin.printCount,
846            closed: setAdmin.closed
847        )
848    }
849
850    pub fun getSetViews(_ sets: [UInt64]): [SetView] {
851        let ret: [SetView] = []
852        for set in sets {
853            let element = self.getSetView(set)
854            if element != nil {
855                ret.append(element!)
856            }
857        }
858        return ret
859    }
860
861    pub fun getAllSets(): [Set] {
862        return TenantService.sets.values
863    }
864
865    // The immutable data for an Set
866    //
867    pub struct Set {
868        pub let id: UInt64
869        pub let name: String
870        pub let description: String
871        pub let metadata: {String: TenantService.MetadataField}
872
873        init(
874            name: String,
875            description: String,
876            metadata: {String: TenantService.MetadataField}
877        ) {
878            self.id = TenantService.generateId(ObjectType.SET, TenantService.setSeq)
879            self.name = name
880            self.description = description
881            self.metadata = metadata
882            TenantService.setSeq = TenantService.setSeq + 1 as UInt64
883            TenantService.faucetsBySet[self.id] = {}
884            emit SetCreated(self.id)
885        }
886    }
887
888    // The mutable data for an Set
889    //
890    pub resource SetAdmin {
891        pub let id: UInt64
892        pub var mintCount: UInt64
893        pub var printCount: UInt64
894        pub var closed: Bool
895
896        init(_ id: UInt64) {
897            self.id = id
898            self.mintCount = 0
899            self.printCount = 0
900            self.closed = false
901        }
902
903        pub fun close() {
904            if !self.closed {
905                self.closed = true
906                emit SetClosed(self.id)
907            }
908        }
909
910        pub fun logMint(_ count: UInt64) {
911            pre {
912                TenantService.closed != true: "The Tenant is closed"
913                self.closed != true: "The Set is closed"
914            }
915            self.mintCount = self.mintCount + count
916        }
917
918        pub fun logPrint(_ count: UInt64) {
919            pre {
920                TenantService.closed != true: "The Tenant is closed"
921                self.closed != true: "The Set is closed"
922            }
923            self.printCount = self.printCount + count
924        }
925
926        destroy() {
927            emit SetDestroyed(self.id)
928        }
929    }
930
931    // An immutable view for an Set and all of it's data
932    //
933    pub struct SetView {
934        pub let id: UInt64
935        pub let name: String
936        pub let description: String
937        pub let metadata: {String: TenantService.MetadataField}
938        pub let mintCount: UInt64
939        pub let printCount: UInt64
940        pub let closed: Bool
941
942        init(
943            id: UInt64,
944            name: String,
945            description: String,
946            metadata: {String: TenantService.MetadataField},
947            mintCount: UInt64,
948            printCount: UInt64,
949            closed: Bool
950        ) {
951            self.id = id
952            self.name = name
953            self.description = description
954            self.metadata = metadata
955            self.mintCount = mintCount
956            self.printCount = printCount
957            self.closed = closed
958        }
959    }
960
961    // =====================================
962    // Print
963    // =====================================
964
965    pub event PrintCreated(_ id: UInt64)
966    pub event PrintDestroyed(_ id: UInt64)
967    pub event PrintClosed(_ id: UInt64)
968
969    pub fun getPrint(_ id: UInt64): Print? {
970        pre {
971            TenantService.isObjectType(id, ObjectType.PRINT): "Id supplied is not for a print"
972        }
973        return TenantService.prints[id]
974    }
975
976    pub fun getPrintView(_ id: UInt64): PrintView? {
977        pre {
978            TenantService.isObjectType(id, ObjectType.PRINT): "Id supplied is not for a print"
979        }
980        if TenantService.prints[id] == nil {
981            return nil
982        }
983        let print = TenantService.prints[id]!
984        let printAdmin = (&TenantService.printAdmins[id] as &PrintAdmin?)!
985        return PrintView(
986            id: print.id,
987            artifactId: print.artifactId,
988            setId: print.setId,
989            name: print.name,
990            description: print.description,
991            maxMintCount: print.maxMintCount,
992            metadata: print.metadata,
993            serialNumberStart: print.serialNumberStart,
994            nextNftSerialNumber: printAdmin.nextNftSerialNumber,
995            mintCount: printAdmin.mintCount,
996            closed: printAdmin.closed
997        )
998    }
999
1000    pub fun getPrintViews(_ prints: [UInt64]): [PrintView] {
1001        let ret: [PrintView] = []
1002        for print in prints {
1003            let element = self.getPrintView(print)
1004            if element != nil {
1005                ret.append(element!)
1006            }
1007        }
1008        return ret
1009    }
1010
1011    pub fun getAllPrints(): [Print] {
1012        return TenantService.prints.values
1013    }
1014
1015    // The immutable data for an Print
1016    //
1017    pub struct Print {
1018        pub let id: UInt64
1019        pub let artifactId: UInt64
1020        pub let setId: UInt64?
1021        pub let name: String
1022        pub let description: String
1023        pub let maxMintCount: UInt64
1024        pub let metadata: {String: TenantService.MetadataField}
1025        pub let serialNumberStart: UInt64
1026
1027        init(
1028            artifactId: UInt64,
1029            setId: UInt64?,
1030            name: String,
1031            description: String,
1032            maxMintCount: UInt64,
1033            metadata: {String: TenantService.MetadataField}
1034        ) {
1035            pre {
1036                maxMintCount > 0 : "maxMintCount must be greater than 0"
1037            }
1038            self.id = TenantService.generateId(ObjectType.PRINT, TenantService.printSeq)
1039            self.artifactId = artifactId
1040            self.setId = setId
1041            self.name = name
1042            self.description = description
1043            self.maxMintCount = maxMintCount
1044            self.metadata = metadata
1045            self.serialNumberStart = TenantService.nextNftSerialNumber[artifactId]!
1046            TenantService.nextNftSerialNumber[artifactId] = self.serialNumberStart + maxMintCount
1047            TenantService.printSeq = TenantService.printSeq + 1 as UInt64
1048            emit PrintCreated(self.id)
1049        }
1050    }
1051
1052    // The mutable data for an Print
1053    //
1054    pub resource PrintAdmin {
1055
1056        pub let id: UInt64
1057        pub var nextNftSerialNumber: UInt64
1058        pub var mintCount: UInt64
1059        pub var closed: Bool
1060
1061        init(_ id: UInt64, _ serialNumberStart: UInt64) {
1062            self.id = id
1063            self.mintCount = 0
1064            self.closed = false
1065            self.nextNftSerialNumber = serialNumberStart
1066        }
1067
1068        pub fun close() {
1069            if !self.closed {
1070                self.closed = true
1071                emit PrintClosed(self.id)
1072            }
1073        }
1074
1075        pub fun getAndIncrementSerialNumber(): UInt64 {
1076            let ret: UInt64 = self.nextNftSerialNumber
1077            self.nextNftSerialNumber = ret + (1 as UInt64)
1078            return ret
1079        }
1080
1081        pub fun logMint(_ count: UInt64) {
1082            pre {
1083                TenantService.closed != true: "The Tenant is closed"
1084                self.closed != true: "The Print is closed"
1085                TenantService.prints[self.id]!.maxMintCount >= (self.mintCount + count): "The Print would exceed it's maxMintCount"
1086            }
1087            self.mintCount = self.mintCount + count
1088        }
1089
1090        destroy() {
1091            emit PrintDestroyed(self.id)
1092        }
1093    }
1094
1095    // An immutable view for an Print and all of it's data
1096    //
1097    pub struct PrintView {
1098        pub let id: UInt64
1099        pub let artifactId: UInt64
1100        pub let setId: UInt64?
1101        pub let name: String
1102        pub let description: String
1103        pub let maxMintCount: UInt64
1104        pub let metadata: {String: TenantService.MetadataField}
1105        pub let serialNumberStart: UInt64
1106        pub let nextNftSerialNumber: UInt64
1107        pub let mintCount: UInt64
1108        pub let closed: Bool
1109
1110        init(
1111            id: UInt64,
1112            artifactId: UInt64,
1113            setId: UInt64?,
1114            name: String,
1115            description: String,
1116            maxMintCount: UInt64,
1117            metadata: {String: TenantService.MetadataField},
1118            serialNumberStart: UInt64,
1119            nextNftSerialNumber: UInt64,
1120            mintCount: UInt64,
1121            closed: Bool
1122        ) {
1123            self.id = id
1124            self.artifactId = artifactId
1125            self.setId = setId
1126            self.name = name
1127            self.description = description
1128            self.maxMintCount = maxMintCount
1129            self.metadata = metadata
1130            self.serialNumberStart = serialNumberStart
1131            self.nextNftSerialNumber = nextNftSerialNumber
1132            self.mintCount = mintCount
1133            self.closed = closed
1134        }
1135    }
1136
1137    // =====================================
1138    // Faucet
1139    // =====================================
1140
1141    pub event FaucetCreated(_ id: UInt64)
1142    pub event FaucetMaxMintCountChanged(_ id: UInt64, _ oldMaxMintCount: UInt64, _ newMaxMintCount: UInt64)
1143    pub event FaucetDestroyed(_ id: UInt64)
1144    pub event FaucetClosed(_ id: UInt64)
1145
1146    pub fun getFaucet(_ id: UInt64): Faucet? {
1147        pre {
1148            TenantService.isObjectType(id, ObjectType.FAUCET): "Id supplied is not for a faucet"
1149        }
1150        return TenantService.faucets[id]
1151    }
1152
1153    pub fun getFaucetView(_ id: UInt64): FaucetView? {
1154        pre {
1155            TenantService.isObjectType(id, ObjectType.FAUCET): "Id supplied is not for a faucet"
1156        }
1157        if TenantService.faucets[id] == nil {
1158            return nil
1159        }
1160        let faucet = TenantService.faucets[id]!
1161        let faucetAdmin = (&TenantService.faucetAdmins[id] as &FaucetAdmin?)!
1162        return FaucetView(
1163            id: faucet.id,
1164            artifactId: faucet.artifactId,
1165            setId: faucet.setId,
1166            name: faucet.name,
1167            description: faucet.description,
1168            maxMintCount: faucet.maxMintCount,
1169            metadata: faucet.metadata,
1170            mintCount: faucetAdmin.mintCount,
1171            closed: faucetAdmin.closed
1172        )
1173    }
1174
1175    pub fun getFaucetViews(_ faucets: [UInt64]): [FaucetView] {
1176        let ret: [FaucetView] = []
1177        for faucet in faucets {
1178            let element = self.getFaucetView(faucet)
1179            if element != nil {
1180                ret.append(element!)
1181            }
1182        }
1183        return ret
1184    }
1185
1186    pub fun getAllFaucets(): [Faucet] {
1187        return TenantService.faucets.values
1188    }
1189
1190    // The immutable data for an Faucet
1191    //
1192    pub struct Faucet {
1193        pub let id: UInt64
1194        pub let artifactId: UInt64
1195        pub let setId: UInt64?
1196        pub let name: String
1197        pub let description: String
1198        pub let maxMintCount: UInt64
1199        pub let metadata: {String: TenantService.MetadataField}
1200
1201        init(
1202            artifactId: UInt64,
1203            setId: UInt64?,
1204            name: String,
1205            description: String,
1206            maxMintCount: UInt64,
1207            metadata: {String: TenantService.MetadataField}
1208        ) {
1209            self.id = TenantService.generateId(ObjectType.FAUCET, TenantService.faucetSeq)
1210            self.artifactId = artifactId
1211            self.setId = setId
1212            self.name = name
1213            self.description = description
1214            self.maxMintCount = maxMintCount
1215            self.metadata = metadata
1216            TenantService.faucetSeq = TenantService.faucetSeq + 1 as UInt64
1217
1218            if self.setId != nil {
1219                let faucetsBySet = TenantService.faucetsBySet[self.setId!]!
1220                faucetsBySet[self.id] = true
1221            }
1222
1223            emit FaucetCreated(self.id)
1224        }
1225    }
1226
1227    // The mutable data for an Faucet
1228    //
1229    pub resource FaucetAdmin {
1230
1231        pub let id: UInt64
1232        pub var mintCount: UInt64
1233        pub var closed: Bool
1234
1235        init(id: UInt64) {
1236            self.id = id
1237            self.mintCount = 0
1238            self.closed = false
1239        }
1240
1241        pub fun close() {
1242            if !self.closed {
1243                self.closed = true
1244                emit FaucetClosed(self.id)
1245            }
1246        }
1247
1248        pub fun logMint(_ count: UInt64) {
1249            pre {
1250                TenantService.closed != true: "The Tenant is closed"
1251                self.closed != true: "The Faucet is closed"
1252                ((TenantService.faucets[self.id]!.maxMintCount == (0 as UInt64))
1253                    || (TenantService.faucets[self.id]!.maxMintCount >= (self.mintCount + count))): "The Faucet would exceed it's maxMintCount"
1254            }
1255            self.mintCount = self.mintCount + count
1256        }
1257
1258        destroy() {
1259            emit FaucetDestroyed(self.id)
1260        }
1261    }
1262
1263    // An immutable view for an Faucet and all of it's data
1264    //
1265    pub struct FaucetView {
1266        pub let id: UInt64
1267        pub let artifactId: UInt64
1268        pub let setId: UInt64?
1269        pub let name: String
1270        pub let description: String
1271        pub let maxMintCount: UInt64
1272        pub let metadata: {String: TenantService.MetadataField}
1273        pub let mintCount: UInt64
1274        pub let closed: Bool
1275
1276        init(
1277            id: UInt64,
1278            artifactId: UInt64,
1279            setId: UInt64?,
1280            name: String,
1281            description: String,
1282            maxMintCount: UInt64,
1283            metadata: {String: TenantService.MetadataField},
1284            mintCount: UInt64,
1285            closed: Bool
1286        ) {
1287            self.id = id
1288            self.artifactId = artifactId
1289            self.setId = setId
1290            self.name = name
1291            self.description = description
1292            self.maxMintCount = maxMintCount
1293            self.metadata = metadata
1294            self.mintCount = mintCount
1295            self.closed = closed
1296        }
1297    }
1298
1299    // =====================================
1300    // NFT
1301    // =====================================
1302
1303    pub event NFTCreated(_ id: UInt64)
1304    pub event NFTDestroyed(_ id: UInt64)
1305
1306    pub fun getNFTView(_ nft: &NFT): NFTView {
1307        let archetype = self.getArchetypeView(nft.archetypeId)!
1308        let artifact = self.getArtifactView(nft.artifactId)!
1309
1310        var set: SetView? = nil
1311        if nft.setId != nil {
1312            set = self.getSetView(nft.setId!)!
1313        }
1314
1315        var print: PrintView? = nil
1316        if nft.printId != nil {
1317            print = self.getPrintView(nft.printId!)!
1318        }
1319
1320        var faucet: FaucetView? = nil
1321        if nft.faucetId != nil {
1322            faucet = self.getFaucetView(nft.faucetId!)!
1323        }
1324
1325        return NFTView(
1326            id: nft.id,
1327            archetype: archetype,
1328            artifact: artifact,
1329            print: print,
1330            faucet: faucet,
1331            set: set,
1332            serialNumber: nft.serialNumber,
1333            metadata: nft.getMetadata()
1334        )
1335    }
1336
1337    pub fun getNFTViews(_ nfts: [&NFT]): [NFTView] {
1338        let ret: [NFTView] = []
1339        for nft in nfts {
1340            ret.append(self.getNFTView(nft))
1341        }
1342        return ret
1343    }
1344
1345    // The immutable data for an NFT, this is the actual NFT
1346    //
1347    pub resource NFT: NonFungibleToken.INFT {
1348        pub let id: UInt64
1349        pub let archetypeId: UInt64
1350        pub let artifactId: UInt64
1351        pub let printId: UInt64?
1352        pub let faucetId: UInt64?
1353        pub let setId: UInt64?
1354        pub let serialNumber: UInt64
1355        access(self) let metadata: {String: TenantService.MetadataField}
1356
1357        init(
1358            archetypeId: UInt64,
1359            artifactId: UInt64,
1360            printId: UInt64?,
1361            faucetId: UInt64?,
1362            setId: UInt64?,
1363            metadata: {String: TenantService.MetadataField}
1364        ) {
1365            self.id = TenantService.generateId(ObjectType.NFT, TenantService.totalSupply)
1366            self.archetypeId = archetypeId
1367            self.artifactId = artifactId
1368            self.printId = printId
1369            self.faucetId = faucetId
1370            self.setId = setId
1371            self.metadata = metadata
1372
1373            if self.printId != nil {
1374                let printAdmin = (&TenantService.printAdmins[self.printId!!] as &PrintAdmin?)!
1375                self.serialNumber = printAdmin.getAndIncrementSerialNumber()
1376
1377            } else {
1378                self.serialNumber = TenantService.nextNftSerialNumber[self.artifactId]!
1379                TenantService.nextNftSerialNumber[self.artifactId] = self.serialNumber + (1 as UInt64)
1380
1381            }
1382
1383            TenantService.totalSupply = TenantService.totalSupply + (1 as UInt64)
1384
1385            if self.setId != nil {
1386                if TenantService.setsByArtifact[self.artifactId] == nil {
1387                    TenantService.setsByArtifact[self.artifactId] = {}
1388                }
1389                let setsByArtifact = TenantService.setsByArtifact[self.artifactId]!
1390                setsByArtifact[self.setId!] = true
1391
1392                if TenantService.artifactsBySet[self.setId!] == nil {
1393                    TenantService.artifactsBySet[self.setId!] = {}
1394                }
1395                let artifactsBySet = TenantService.artifactsBySet[self.setId!]!
1396                artifactsBySet[self.artifactId] = true
1397            }
1398
1399            if self.faucetId != nil {
1400                if TenantService.faucetsByArtifact[self.artifactId] == nil {
1401                    TenantService.faucetsByArtifact[self.artifactId] = {}
1402                }
1403                let faucetsByArtifact = TenantService.faucetsByArtifact[self.artifactId]!
1404                faucetsByArtifact[self.faucetId!] = true
1405            }
1406
1407            if TenantService.artifactsByArchetype[self.archetypeId] == nil {
1408                TenantService.artifactsByArchetype[self.archetypeId] = {}
1409            }
1410            let artifactsByArchetype = TenantService.artifactsByArchetype[self.archetypeId]!
1411            artifactsByArchetype[self.artifactId] = true
1412
1413            emit NFTCreated(self.id)
1414        }
1415
1416        pub fun getMetadata(): {String: TenantService.MetadataField} {
1417            return self.metadata
1418        }
1419
1420        destroy() {
1421            emit NFTDestroyed(self.id)
1422        }
1423    }
1424
1425    // An immutable view for an NFT and all of it's data
1426    //
1427    pub struct NFTView {
1428        pub let id: UInt64
1429        pub let archetype: ArchetypeView
1430        pub let artifact: ArtifactView
1431        pub let print: PrintView?
1432        pub let faucet: FaucetView?
1433        pub let set: SetView?
1434        pub let serialNumber: UInt64
1435        pub let metadata: {String: TenantService.MetadataField}
1436
1437        init(
1438            id: UInt64,
1439            archetype: ArchetypeView,
1440            artifact: ArtifactView,
1441            print: PrintView?,
1442            faucet: FaucetView?,
1443            set: SetView?,
1444            serialNumber: UInt64,
1445            metadata: {String: TenantService.MetadataField}
1446        ) {
1447            self.id = id
1448            self.archetype = archetype
1449            self.artifact = artifact
1450            self.print = print
1451            self.faucet = faucet
1452            self.set = set
1453            self.serialNumber = serialNumber
1454            self.metadata = metadata
1455        }
1456    }
1457
1458    // The public version of the collection that accounts can use
1459    // to deposit NFTs into other accounts
1460    //
1461    pub resource interface CollectionPublic {
1462        pub fun deposit(token: @NonFungibleToken.NFT)
1463        pub fun batchDeposit(tokens: @NonFungibleToken.Collection)
1464        pub fun getIDs(): [UInt64]
1465        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
1466        pub fun borrowNFTData(id: UInt64): &TenantService.NFT?
1467        pub fun borrowNFTDatas(ids: [UInt64]): [&TenantService.NFT]
1468    }
1469
1470    // The collection where NFTs are stored
1471    //
1472    pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic {
1473        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
1474
1475        init() {
1476            self.ownedNFTs <- {}
1477        }
1478
1479        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
1480            let token <- self.ownedNFTs.remove(key: withdrawID)
1481                ?? panic("Cannot withdraw: NFT does not exist in the collection")
1482            emit Withdraw(id: token.id, from: self.owner?.address)
1483            return <-token
1484        }
1485
1486        pub fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection {
1487            var batchCollection <- create Collection()
1488            for id in ids {
1489                batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
1490            }
1491            return <-batchCollection
1492        }
1493
1494        pub fun deposit(token: @NonFungibleToken.NFT) {
1495            let token <- token as! @TenantService.NFT
1496            let id = token.id
1497            let oldToken <- self.ownedNFTs[id] <- token
1498            if self.owner?.address != nil {
1499                emit Deposit(id: id, to: self.owner?.address)
1500            }
1501            destroy oldToken
1502        }
1503
1504        pub fun batchDeposit(tokens: @NonFungibleToken.Collection) {
1505            let keys = tokens.getIDs()
1506            for key in keys {
1507                self.deposit(token: <-tokens.withdraw(withdrawID: key))
1508            }
1509            destroy tokens
1510        }
1511
1512        pub fun getIDs(): [UInt64] {
1513            return self.ownedNFTs.keys
1514        }
1515
1516        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
1517            pre {
1518                TenantService.isObjectType(id, ObjectType.NFT): "Id supplied is not for an nft"
1519            }
1520            return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
1521        }
1522
1523        pub fun borrowNFTData(id: UInt64): &TenantService.NFT? {
1524            pre {
1525                TenantService.isObjectType(id, ObjectType.NFT): "Id supplied is not for an nft"
1526            }
1527            if self.ownedNFTs[id] != nil {
1528                let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
1529                return ref as! &TenantService.NFT
1530            } else {
1531                return nil
1532            }
1533        }
1534
1535        pub fun borrowNFTDatas(ids: [UInt64]): [&TenantService.NFT] {
1536            let nfts: [&TenantService.NFT] = []
1537            for id in ids {
1538                let nft = self.borrowNFTData(id: id)
1539                if nft != nil {
1540                    nfts.append(nft!)
1541                }
1542            }
1543            return nfts
1544        }
1545
1546        pub fun getNFTView(id: UInt64): NFTView? {
1547            pre {
1548                TenantService.isObjectType(id, ObjectType.NFT): "Id supplied is not for an nft"
1549            }
1550            let nft = self.borrowNFTData(id: id)
1551            if nft == nil {
1552                return nil
1553            }
1554            return TenantService.getNFTView(nft!)
1555        }
1556
1557        destroy() {
1558            destroy self.ownedNFTs
1559        }
1560    }
1561
1562    pub fun createEmptyCollection(): @NonFungibleToken.Collection {
1563        return <-create TenantService.Collection()
1564    }
1565
1566    // ShardedCollection stores a dictionary of TenantService Collections
1567    // An NFT is stored in the field that corresponds to its id % numBuckets
1568    //
1569    pub resource ShardedCollection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic {
1570
1571        pub var collections: @{UInt64: Collection}
1572        pub let numBuckets: UInt64
1573
1574        init(numBuckets: UInt64) {
1575            self.collections <- {}
1576            self.numBuckets = numBuckets
1577            var i: UInt64 = 0
1578            while i < numBuckets {
1579                self.collections[i] <-! TenantService.createEmptyCollection() as! @Collection
1580                i = i + UInt64(1)
1581            }
1582        }
1583
1584        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
1585            post {
1586                result.id == withdrawID: "The ID of the withdrawn NFT is incorrect"
1587            }
1588            let bucket = withdrawID % self.numBuckets
1589            let token <- self.collections[bucket]?.withdraw(withdrawID: withdrawID)!
1590            return <-token
1591        }
1592
1593        pub fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection {
1594            var batchCollection <- TenantService.createEmptyCollection()
1595            for id in ids {
1596                batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
1597            }
1598            return <-batchCollection
1599        }
1600
1601        pub fun deposit(token: @NonFungibleToken.NFT) {
1602            let bucket = token.id % self.numBuckets
1603            let collection <- self.collections.remove(key: bucket)!
1604            collection.deposit(token: <-token)
1605            self.collections[bucket] <-! collection
1606        }
1607
1608        pub fun batchDeposit(tokens: @NonFungibleToken.Collection) {
1609            let keys = tokens.getIDs()
1610            for key in keys {
1611                self.deposit(token: <-tokens.withdraw(withdrawID: key))
1612            }
1613            destroy tokens
1614        }
1615
1616        pub fun getIDs(): [UInt64] {
1617            var ids: [UInt64] = []
1618            for key in self.collections.keys {
1619                for id in self.collections[key]?.getIDs() ?? [] {
1620                    ids.append(id)
1621                }
1622            }
1623            return ids
1624        }
1625
1626        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
1627            let bucket = id % self.numBuckets
1628            return self.collections[bucket]?.borrowNFT(id: id)!
1629        }
1630
1631        pub fun borrowNFTData(id: UInt64): &TenantService.NFT? {
1632            let bucket = id % self.numBuckets
1633            return self.collections[bucket]?.borrowNFTData(id: id) ?? nil
1634        }
1635
1636        pub fun borrowNFTDatas(ids: [UInt64]): [&TenantService.NFT] {
1637            let nfts: [&TenantService.NFT] = []
1638            for id in ids {
1639                let nft = self.borrowNFTData(id: id)
1640                if nft != nil {
1641                    nfts.append(nft!)
1642                }
1643            }
1644            return nfts
1645        }
1646
1647        pub fun getNFTView(id: UInt64): NFTView? {
1648            let bucket = id % self.numBuckets
1649            return self.collections[bucket]?.getNFTView(id: id) ?? nil
1650        }
1651
1652        destroy() {
1653            destroy self.collections
1654        }
1655    }
1656
1657    pub fun createEmptyShardedCollection(numBuckets: UInt64): @ShardedCollection {
1658        return <-create ShardedCollection(numBuckets: numBuckets)
1659    }
1660
1661    // =====================================
1662    // Metadata
1663    // =====================================
1664
1665    // The type of a meta data field
1666    //
1667    pub enum MetadataFieldType: UInt8 {
1668        pub case STRING
1669        pub case MIME
1670        pub case NUMBER
1671        pub case BOOLEAN
1672        pub case DATE
1673        pub case DATE_TIME
1674        pub case URL
1675        pub case URL_WITH_HASH
1676        pub case GEO_POINT
1677    }
1678
1679    // a meta data field of variable type
1680    //
1681    pub struct MetadataField {
1682        pub let type: MetadataFieldType
1683        pub let value: AnyStruct
1684
1685        init(_ type: MetadataFieldType, _ value: AnyStruct) {
1686            self.type = type
1687            self.value = value
1688        }
1689
1690        pub fun getMimeValue(): Mime? {
1691            if self.type != MetadataFieldType.MIME {
1692                return nil
1693            }
1694            return self.value as? Mime
1695        }
1696
1697        pub fun getStringValue(): String? {
1698            if self.type != MetadataFieldType.STRING {
1699                return nil
1700            }
1701            return self.value as? String
1702        }
1703
1704        pub fun getNumberValue(): String? {
1705            if self.type != MetadataFieldType.NUMBER {
1706                return nil
1707            }
1708            return self.value as? String
1709        }
1710
1711        pub fun getBooleanValue(): Bool? {
1712            if self.type != MetadataFieldType.BOOLEAN {
1713                return nil
1714            }
1715            return self.value as? Bool
1716        }
1717
1718        pub fun getURLValue(): String? {
1719            if self.type != MetadataFieldType.URL {
1720                return nil
1721            }
1722            return self.value as? String
1723        }
1724
1725        pub fun getDateValue(): String? {
1726            if self.type != MetadataFieldType.DATE {
1727                return nil
1728            }
1729            return self.value as? String
1730        }
1731
1732        pub fun getDateTimeValue(): String? {
1733            if self.type != MetadataFieldType.DATE_TIME {
1734                return nil
1735            }
1736            return self.value as? String
1737        }
1738
1739        pub fun getURLWithHashValue(): URLWithHash? {
1740            if self.type != MetadataFieldType.URL_WITH_HASH {
1741                return nil
1742            }
1743            return self.value as? URLWithHash
1744        }
1745
1746        pub fun getGeoPointValue(): GeoPoint? {
1747            if self.type != MetadataFieldType.GEO_POINT {
1748                return nil
1749            }
1750            return self.value as? GeoPoint
1751        }
1752    }
1753
1754    // A url with a hash of the contents found at the url
1755    //
1756    pub struct URLWithHash {
1757        pub let url: String
1758        pub let hash: String?
1759        pub let hashAlgo: String?
1760        init(_ url: String, _ hash: String, _ hashAlgo: String?) {
1761            self.url = url
1762            self.hash = hash
1763            self.hashAlgo = hashAlgo
1764        }
1765    }
1766
1767    // A geo point without any specific projection
1768    //
1769    pub struct GeoPoint {
1770        pub let lat: UFix64
1771        pub let lng: UFix64
1772        init(_ lat: UFix64, _ lng: UFix64) {
1773            self.lat = lat
1774            self.lng = lng
1775        }
1776    }
1777
1778    // A piece of Mime content
1779    //
1780    pub struct Mime {
1781        pub let type: String
1782        pub let bytes: [UInt8]
1783        init(_ type: String, _ bytes: [UInt8]) {
1784            self.type = type
1785            self.bytes = bytes
1786        }
1787    }
1788}
1789