Smart Contract

SomePlaceCollectible

A.667a16294a089ef8.SomePlaceCollectible

Deployed

1h ago
Feb 28, 2026, 06:07:03 PM UTC

Dependents

0 imports
1/*
2    Description: Central Smart Contract for Some Place NFT Collectibles
3    
4    Some Place Collectibles are available as part of "sets", each with
5    a fixed edition count.
6*/
7
8import NonFungibleToken from 0x1d7e57aa55817448
9import SomePlaceCounter from 0x667a16294a089ef8
10import MetadataViews from 0x1d7e57aa55817448
11import FungibleToken from 0xf233dcee88fe0abe
12
13pub contract SomePlaceCollectible : NonFungibleToken {
14
15    // -----------------------------------------------------------------------
16    // NonFungibleToken Standard Events
17    // -----------------------------------------------------------------------
18    pub event ContractInitialized()
19    pub event Withdraw(id: UInt64, from: Address?)
20    pub event Deposit(id: UInt64, to: Address?)
21
22    // -----------------------------------------------------------------------
23    // SomePlace Events
24    // -----------------------------------------------------------------------
25    pub event Mint(id: UInt64)
26    pub event Burn(id: UInt64)
27    pub event SetCreated(setID: UInt64)
28
29    // -----------------------------------------------------------------------
30    // Named Paths
31    // -----------------------------------------------------------------------
32    pub let CollectionStoragePath: StoragePath
33    pub let CollectionPublicPath: PublicPath
34    pub let CollectionPrivatePath: PrivatePath
35
36    // -----------------------------------------------------------------------
37    // NonFungibleToken Standard Fields
38    // -----------------------------------------------------------------------
39    pub var totalSupply: UInt64
40
41    // -----------------------------------------------------------------------
42    // SomePlace Fields
43    // -----------------------------------------------------------------------
44
45    // Maintains state of what sets and editions have been minted to ensure
46    // there are never 2 of the same set + edition combination
47    // Provides a Set ID -> Edition ID -> Data mapping
48    access(contract) let collectibleData: {UInt64: {UInt64: CollectibleMetadata}}
49
50    // Allows easy access to information for a set
51    // Provides access from a set's setID to the information for that set
52    access(contract) let setData: {UInt64: SomePlaceCollectible.SetMetadata}
53
54    // Allows easy access to pointers from an NFT to its metadata keys
55    // Provides CollectibleID -> (SetID + EditionID) mapping
56    access(contract) let allCollectibleIDs: {UInt64: CollectibleEditionData}
57
58    // -----------------------------------------------------------------------
59    // SomePlace Structs
60    // -----------------------------------------------------------------------
61    pub struct CollectibleMetadata {
62        // The NFT Id is optional so a collectible may have associated metadata prior to being minted
63        // This is useful for porting existing unique collections over to Flow (I.E. ETH NFTs)
64        access(self) var nftID: UInt64?
65        access(self) var metadata: {String: String}
66        access(self) var traits: {String : String}
67
68        init() {
69            self.metadata = {}
70            self.traits = {}
71            self.nftID = nil
72        }
73
74        pub fun getNftID(): UInt64? {
75            return self.nftID
76        }
77        // Returns all metadata for this collectible
78        pub fun getMetadata(): { String: String } {
79            return self.metadata
80        }
81        
82        pub fun getTraits(): { String: String } {
83            return self.traits
84        }
85
86        access(account) fun updateNftID(_ nftID: UInt64) {
87            pre {
88                self.nftID == nil : "An NFT already exists for this collectible."
89            }
90            self.nftID = nftID
91        }
92
93        access(account) fun updateMetadata(_ metadata: { String: String }) {
94            self.metadata = metadata
95        }
96        
97        access(account) fun updateTraits(_ traits: { String: String }) {
98            self.traits = traits
99        }
100    }
101
102    pub struct CollectibleEditionData {
103        access(self) let editionNumber: UInt64
104        access(self) let setID: UInt64
105
106        init(editionNumber: UInt64, setID: UInt64) {
107            self.editionNumber = editionNumber
108            self.setID = setID
109        }
110        pub fun getEditionNumber(): UInt64 {
111            return self.editionNumber
112        }
113        pub fun getSetID(): UInt64 {
114            return self.setID
115        }
116    }
117
118    pub struct SetMetadata {
119        access(self) let setID: UInt64
120        access(self) var metadata: {String : String}
121        access(self) var maxNumberOfEditions: UInt64
122        access(self) var editionCount: UInt64
123        access(self) var sequentialMintMin: UInt64 
124        access(self) var publicFUSDSalePrice: UFix64?
125        access(self) var publicFLOWSalePrice: UFix64?
126        access(self) var publicSaleStartTime: UFix64?
127        access(self) var publicSaleEndTime: UFix64?
128
129        init(
130            setID: UInt64,
131            maxNumberOfEditions: UInt64,
132            metadata: {String: String}
133        ){
134            self.setID = setID
135            self.metadata = metadata
136            self.maxNumberOfEditions = maxNumberOfEditions
137            self.editionCount = 0
138            self.sequentialMintMin = 1
139
140            self.publicFUSDSalePrice = nil
141            self.publicFLOWSalePrice = nil
142            self.publicSaleStartTime = nil
143            self.publicSaleEndTime = nil
144        }
145
146        /*
147            Readonly functions
148        */
149        pub fun getSetID(): UInt64 {
150            return self.setID
151        }
152
153        pub fun getMetadata(): {String : String} {
154            return self.metadata
155        }
156
157        pub fun getMaxNumberOfEditions(): UInt64 {
158            return self.maxNumberOfEditions
159        }
160
161        pub fun getEditionCount(): UInt64 {
162            return self.editionCount
163        }
164        
165        pub fun getSequentialMintMin(): UInt64 {
166            return self.sequentialMintMin
167        }
168
169        pub fun getFUSDPublicSalePrice(): UFix64? {
170            return self.publicFUSDSalePrice
171        }
172
173        pub fun getFLOWPublicSalePrice(): UFix64? {
174            return self.publicFLOWSalePrice
175        }
176
177        pub fun getPublicSaleStartTime(): UFix64? {
178            return self.publicSaleStartTime
179        }
180
181        pub fun getPublicSaleEndTime(): UFix64? {
182            return self.publicSaleEndTime
183        }
184
185        pub fun getEditionMetadata(editionNumber: UInt64): { String: String } {
186            pre {
187                editionNumber >= 1  && editionNumber <= self.maxNumberOfEditions : "Invalid edition number provided"
188                SomePlaceCollectible.collectibleData[self.setID]![editionNumber] != nil : "Requested edition has not yet been minted"
189            }
190            return SomePlaceCollectible.collectibleData[self.setID]![editionNumber]!.getMetadata()
191        }
192        
193        // A public sale allowing for direct minting from the contract is considered active if we have a valid public
194        // sale price listing, current time is after start time, and current time is before end time 
195        pub fun isPublicSaleActive(): Bool {
196            let curBlockTime = getCurrentBlock().timestamp
197            return (self.publicFUSDSalePrice != nil || self.publicFLOWSalePrice != nil) &&
198                        (self.publicSaleStartTime != nil && curBlockTime >= self.publicSaleStartTime!) &&
199                        (self.publicSaleEndTime == nil || curBlockTime < self.publicSaleEndTime!)
200        }
201
202        /*
203            Mutating functions
204        */
205        access(contract) fun incrementEditionCount(): UInt64 {
206            post {
207                self.editionCount <= self.maxNumberOfEditions : "Number of editions is larger than max allowed editions"
208            }
209            self.editionCount = self.editionCount + 1
210            return self.editionCount
211        }
212        
213        access(contract) fun setSequentialMintMin(newMintMin: UInt64) {
214            self.sequentialMintMin = newMintMin
215        }
216
217        access(contract) fun updateSetMetadata(_ newMetadata: {String: String}) {
218            self.metadata = newMetadata
219        }
220        
221        access(contract) fun updateFLOWPublicSalePrice(_ publicFLOWSalePrice: UFix64?) {
222            self.publicFLOWSalePrice = publicFLOWSalePrice
223        }
224
225        access(contract) fun updateFUSDPublicSalePrice(_ publicFUSDSalePrice: UFix64?) {
226            self.publicFUSDSalePrice = publicFUSDSalePrice
227        }
228
229        access(contract) fun updatePublicSaleStartTime(_ startTime: UFix64?) {
230            self.publicSaleStartTime = startTime
231        }
232
233        access(contract) fun updatePublicSaleEndTime(_ endTime: UFix64?) {
234            self.publicSaleEndTime = endTime
235        }
236    }
237
238    // -----------------------------------------------------------------------
239    // SomePlace Interfaces
240    // -----------------------------------------------------------------------
241    pub resource interface CollectibleCollectionPublic {
242        pub fun deposit(token: @NonFungibleToken.NFT)
243        pub fun batchDeposit(collectibleCollection: @NonFungibleToken.Collection)
244        pub fun getIDs(): [UInt64]
245        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
246        pub fun borrowCollectible(id: UInt64): &SomePlaceCollectible.NFT? {
247            // If the result isn't nil, the id of the returned reference
248            // should be the same as the argument to the function
249            post {
250                (result == nil) || (result?.id == id):
251                    "Cannot borrow SomePlace reference: The ID of the returned reference is incorrect"
252            }
253        }
254    }
255
256    // -----------------------------------------------------------------------
257    // NonFungibleToken Standard Resources
258    // -----------------------------------------------------------------------
259    pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
260        pub let id: UInt64
261
262        pub let setID: UInt64
263
264        pub let editionNumber: UInt64
265
266        pub fun getViews(): [Type] {
267            return [
268                Type<MetadataViews.Display>(),
269                Type<MetadataViews.Royalties>(),
270                Type<MetadataViews.ExternalURL>(),
271                Type<MetadataViews.NFTCollectionData>(),
272                Type<MetadataViews.NFTCollectionDisplay>(),
273                Type<MetadataViews.Traits>()
274            ]
275        }
276
277        pub fun resolveView(_ view: Type): AnyStruct? {
278            let metadata: {String: String} = SomePlaceCollectible.getMetadataByEditionID(setID: self.setID, editionNumber: self.editionNumber)!.getMetadata()
279            let traits: {String: String} = SomePlaceCollectible.getMetadataByEditionID(setID: self.setID, editionNumber: self.editionNumber)!.getTraits()
280            switch view {
281                case Type<MetadataViews.Display>():
282                    return MetadataViews.Display(
283                        name: (metadata["NFTName"] as? String) ?? "The Potion",
284                        description: "Time to drink up.",
285                        thumbnail: MetadataViews.HTTPFile(
286                            url: (metadata["IPFS_Image"] as? String) ?? "https://gateway.pinata.cloud/ipfs/QmWEDqCnHPgRtPvLPFq5jDnRc4mXHGchomYBmzjXdJJpQq"
287                        )
288                    )
289                case Type<MetadataViews.Royalties>():
290                    return MetadataViews.Royalties(
291                        [
292                            MetadataViews.Royalty(
293                                receiver: getAccount(0x8e2e0ebf3c03aa88).getCapability<&{FungibleToken.Receiver}>(MetadataViews.getRoyaltyReceiverPublicPath()),
294                                cut: 0.10,
295                                description: "This is a royalty cut for some.place."
296                            )
297                        ]
298                    )
299                case Type<MetadataViews.ExternalURL>():
300                    return MetadataViews.ExternalURL("https://some.place/")
301                case Type<MetadataViews.NFTCollectionData>():
302                    return MetadataViews.NFTCollectionData(
303                        storagePath: SomePlaceCollectible.CollectionStoragePath,
304                        publicPath: SomePlaceCollectible.CollectionPublicPath,
305                        providerPath: SomePlaceCollectible.CollectionPrivatePath,
306                        publicCollection: Type<&Collection{CollectibleCollectionPublic}>(),
307                        publicLinkedType: Type<&Collection{CollectibleCollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
308                        providerLinkedType: Type<&Collection{CollectibleCollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Provider, MetadataViews.ResolverCollection}>(),
309                        createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
310                            return <- SomePlaceCollectible.createEmptyCollection()
311                        })
312                    )
313                case Type<MetadataViews.NFTCollectionDisplay>():
314                    let squareImage = MetadataViews.Media(
315                        file: MetadataViews.HTTPFile(
316                            url: "https://pbs.twimg.com/profile_images/1502703455747534850/O7LzbfKw_400x400.jpg"
317                        ),
318                        mediaType: "image"
319                    )
320                    let bannerImage = MetadataViews.Media(
321                        file: MetadataViews.HTTPFile(
322                            url: "https://pbs.twimg.com/profile_banners/1329160722786336768/1647107895/1500x500"
323                        ),
324                        mediaType: "image"
325                    )
326                    return MetadataViews.NFTCollectionDisplay(
327                        name: "The Potion",
328                        description: "Time to drink up.",
329                        externalURL: MetadataViews.ExternalURL("https://some.place/"),
330                        squareImage: squareImage,
331                        bannerImage: bannerImage,
332                        socials: {
333                            "twitter": MetadataViews.ExternalURL("https://twitter.com/some_place")
334                        }
335                    )
336                case Type<MetadataViews.Traits>():
337                    let traitsView = MetadataViews.dictToTraits(dict: traits, excludedNames: nil)
338                    return traitsView
339
340            }
341            return nil
342        }
343
344        init(setID: UInt64, editionNumber: UInt64) {
345            pre {
346                SomePlaceCollectible.setData.containsKey(setID) : "Invalid Set ID"
347                SomePlaceCollectible.collectibleData[setID]![editionNumber] == nil || SomePlaceCollectible.collectibleData[setID]![editionNumber]!.getNftID() == nil : "This edition already exists"
348                editionNumber > 0 && editionNumber <= SomePlaceCollectible.setData[setID]!.getMaxNumberOfEditions() : "Edition number is too high"
349            }
350            // Update unique set
351            self.id = self.uuid
352            self.setID = setID
353            self.editionNumber = editionNumber
354
355            // If this edition number does not have a metadata object, create one
356            if (SomePlaceCollectible.collectibleData[setID]![editionNumber] == nil) {
357                let ref = SomePlaceCollectible.collectibleData[setID]!
358                ref[editionNumber] = CollectibleMetadata()
359                SomePlaceCollectible.collectibleData[setID] = ref
360            }
361            // Update the metadata object to have a reference to this newly created NFT
362            SomePlaceCollectible.collectibleData[setID]![editionNumber]!.updateNftID(self.id)
363
364            // Create mapping of new nft id to its newly created set and edition data
365            SomePlaceCollectible.allCollectibleIDs[self.uuid] = CollectibleEditionData(editionNumber: editionNumber, setID: setID)
366
367            // Increase total supply of entire someplace collection
368            SomePlaceCollectible.totalSupply = SomePlaceCollectible.totalSupply + (1 as UInt64)
369
370            emit Mint(id: self.uuid)
371        }
372
373        destroy() {
374            emit Burn(id: self.uuid)
375        }
376    }
377
378    pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, CollectibleCollectionPublic, MetadataViews.ResolverCollection {
379        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
380
381        init() {
382            self.ownedNFTs <- {}
383        }
384
385        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
386            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Cannot withdraw: NFT does not exist in the collection")
387
388            emit Withdraw(id: token.uuid, from: self.owner?.address)
389
390            return <-token
391        }
392
393        pub fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection {
394            var batchCollection <- create Collection()
395
396            for id in ids {
397                batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
398            }
399
400            return <-batchCollection
401        }
402
403        pub fun deposit(token: @NonFungibleToken.NFT) {
404            let token <- token as! @SomePlaceCollectible.NFT
405
406            let id: UInt64 = token.id
407
408            let oldToken <- self.ownedNFTs[id] <- token
409
410            emit Deposit(id: id, to: self.owner?.address)
411
412            destroy oldToken
413        }
414
415        pub fun batchDeposit(collectibleCollection: @NonFungibleToken.Collection) {
416            let keys = collectibleCollection.getIDs()
417
418            for key in keys {
419                self.deposit(token: <-collectibleCollection.withdraw(withdrawID: key))
420            }
421
422            destroy collectibleCollection
423        }
424
425        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
426            return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
427        }
428
429        pub fun borrowCollectible(id: UInt64) : &SomePlaceCollectible.NFT? {
430            if self.ownedNFTs[id] == nil {
431                return nil
432            } else {
433                let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
434                return ref as! &SomePlaceCollectible.NFT
435            }
436        }
437
438        pub fun getIDs(): [UInt64] {
439            return self.ownedNFTs.keys
440        }
441
442        pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
443			let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
444			let potion = nft as! &NFT
445			return potion as &AnyResource{MetadataViews.Resolver}
446		}
447
448        destroy() {
449            destroy self.ownedNFTs
450        }
451    }
452
453    // -----------------------------------------------------------------------
454    // SomePlace Admin Functionality
455    // -----------------------------------------------------------------------
456    /*
457        Creation of a new NFT set under the SomePlace umbrella of collectibles
458    */
459    access(account) fun addNFTSet(
460        maxNumberOfEditions: UInt64,
461        metadata: {String:String},
462    ): UInt64 {
463        let id = SomePlaceCounter.nextSetID
464
465        let newSet = SomePlaceCollectible.SetMetadata(setID: id, maxNumberOfEditions: maxNumberOfEditions, metadata: metadata)
466
467        self.collectibleData[id] = {}
468        self.setData[id] = newSet
469
470        SomePlaceCounter.incrementSetCounter()
471
472        emit SetCreated(setID: id)
473        
474        return id
475    }
476
477    /*
478        Update existing set and edition data
479    */
480    access(account) fun updateEditionMetadata(setID: UInt64, editionNumber: UInt64, metadata: {String: String}) {
481        if (SomePlaceCollectible.collectibleData[setID]![editionNumber] == nil) {
482            let ref = SomePlaceCollectible.collectibleData[setID]!
483            ref[editionNumber] = CollectibleMetadata()
484            SomePlaceCollectible.collectibleData[setID] = ref
485        }
486        SomePlaceCollectible.collectibleData[setID]![editionNumber]!.updateMetadata(metadata)
487    }
488    
489    access(account) fun updateEditionTraits(setID: UInt64, editionNumber: UInt64, traits: {String: String}) {
490        if (SomePlaceCollectible.collectibleData[setID]![editionNumber] == nil) {
491            let ref = SomePlaceCollectible.collectibleData[setID]!
492            ref[editionNumber] = CollectibleMetadata()
493            SomePlaceCollectible.collectibleData[setID] = ref
494        }
495        SomePlaceCollectible.collectibleData[setID]![editionNumber]!.updateTraits(traits)
496    }
497
498    access(account) fun updateSetMetadata(setID: UInt64, metadata: {String: String}) {
499        SomePlaceCollectible.setData[setID]!.updateSetMetadata(metadata)
500    }
501    
502    access(account) fun updateFLOWPublicSalePrice(setID: UInt64, price: UFix64?) {
503        SomePlaceCollectible.setData[setID]!.updateFLOWPublicSalePrice(price)
504    }
505
506    access(account) fun updateFUSDPublicSalePrice(setID: UInt64, price: UFix64?) {
507        SomePlaceCollectible.setData[setID]!.updateFUSDPublicSalePrice(price)
508    }
509
510    access(account) fun updatePublicSaleStartTime(setID: UInt64, startTime: UFix64?) {
511        SomePlaceCollectible.setData[setID]!.updatePublicSaleStartTime(startTime)
512    }
513
514    access(account) fun updatePublicSaleEndTime(setID: UInt64, endTime: UFix64?) {
515        SomePlaceCollectible.setData[setID]!.updatePublicSaleEndTime(endTime)
516    }
517
518    /*
519        Minting functions to create editions within a set
520    */
521    // This mint is intended for sequential mints (for a normal in-order drop style)
522    access(account) fun mintSequentialEditionNFT(setID: UInt64): @SomePlaceCollectible.NFT {
523        // Find first valid edition number
524        var curEditionNumber = self.setData[setID]!.getSequentialMintMin()
525        while (SomePlaceCollectible.collectibleData[setID]![curEditionNumber] != nil &&
526               SomePlaceCollectible.collectibleData[setID]![curEditionNumber]!.getNftID() != nil) {
527            curEditionNumber = curEditionNumber + 1
528        }
529        self.setData[setID]!.setSequentialMintMin(newMintMin: curEditionNumber)
530        let editionCount = self.setData[setID]!.incrementEditionCount()
531        
532        let newCollectible <-create SomePlaceCollectible.NFT(setID: setID, editionNumber: curEditionNumber)
533        return <- newCollectible
534    }
535
536    // This mint is intended for settling auctions or manually minting editions,
537    // where we mint specific editions to specific recipients when settling
538    // SetID + editionID to mint is normally decided off-chain
539    access(account) fun mintNFT(setID: UInt64, editionNumber: UInt64): @SomePlaceCollectible.NFT {
540        self.setData[setID]!.incrementEditionCount()
541        return <-create SomePlaceCollectible.NFT(setID: setID, editionNumber: editionNumber)
542    }
543
544    // -----------------------------------------------------------------------
545    // NonFungibleToken Standard Functions
546    // -----------------------------------------------------------------------
547    pub fun createEmptyCollection(): @NonFungibleToken.Collection {
548        return <- create Collection()
549    }
550    
551    // -----------------------------------------------------------------------
552    // SomePlace Functions
553    // -----------------------------------------------------------------------
554    
555    // Retrieves all sets (This can be expensive)
556    pub fun getMetadatas(): {UInt64: SomePlaceCollectible.SetMetadata} {
557        return self.setData
558    }
559    
560    // Retrieves all collectibles (This can be expensive)
561    pub fun getCollectibleMetadatas(): {UInt64: {UInt64: CollectibleMetadata}} {
562        return self.collectibleData
563    }
564
565    // Retrieves how many NFT sets exist
566    pub fun getMetadatasCount(): UInt64 {
567        return UInt64(self.setData.length)
568    }
569
570    pub fun getMetadataForSetID(setID: UInt64): SomePlaceCollectible.SetMetadata? {
571        return self.setData[setID]
572    }
573
574    pub fun getSetMetadataForNFT(nft: &SomePlaceCollectible.NFT): SomePlaceCollectible.SetMetadata? {
575        return self.setData[nft.setID]
576    }
577
578    pub fun getSetMetadataForNFTByUUID(uuid: UInt64): SomePlaceCollectible.SetMetadata? {
579        let collectibleEditionData = self.allCollectibleIDs[uuid]!
580        return self.setData[collectibleEditionData.getSetID()]!
581    }
582
583    pub fun getMetadataForNFTByUUID(uuid: UInt64): SomePlaceCollectible.CollectibleMetadata? {
584        let collectibleEditionData = self.allCollectibleIDs[uuid]!
585        return self.collectibleData[collectibleEditionData.getSetID()]![collectibleEditionData.getEditionNumber()]
586    }
587
588    pub fun getMetadataByEditionID(setID: UInt64, editionNumber: UInt64): SomePlaceCollectible.CollectibleMetadata? {
589        return self.collectibleData[setID]![editionNumber]
590    }
591
592    pub fun getCollectibleDataForNftByUUID(uuid: UInt64): SomePlaceCollectible.CollectibleEditionData? {
593        return self.allCollectibleIDs[uuid]!
594    }
595
596    init() {
597        self.CollectionStoragePath = /storage/somePlaceCollectibleCollection
598        self.CollectionPublicPath = /public/somePlaceCollectibleCollection
599        self.CollectionPrivatePath = /private/somePlaceCollectibleCollection
600        
601        self.totalSupply = 0
602        self.collectibleData = {}
603        self.setData = {}
604        self.allCollectibleIDs = {}
605
606        emit ContractInitialized()
607    }
608
609}
610