Smart Contract

Ordinal

A.9212a87501a8a6a2.Ordinal

Deployed

2d ago
Feb 25, 2026, 07:57:21 AM UTC

Dependents

54 imports
1/*
2    Ordinal.cdc
3
4    Author: Brian Min brian@flowverse.co
5*/
6
7import NonFungibleToken from 0x1d7e57aa55817448
8import MetadataViews from 0x1d7e57aa55817448
9import FungibleToken from 0xf233dcee88fe0abe
10import OrdinalVendor from 0x9212a87501a8a6a2
11import ViewResolver from 0x1d7e57aa55817448
12
13access(all) contract Ordinal: NonFungibleToken {
14    // Events
15    access(all) event OrdinalMinted(id: UInt64, nftUUID: UInt64, creator: Address, type: String)
16    access(all) event OrdinalUpdated(id: UInt64, size: Int)
17
18    // Paths
19    access(all) let CollectionStoragePath: StoragePath
20    access(all) let CollectionPublicPath: PublicPath
21    access(all) let AdminStoragePath: StoragePath
22
23    // Incremented each time a new Ordinal is minted
24    access(all) var totalSupply: UInt64
25
26    // Custom metadata view for Ordinal Inscription Data
27    access(all) struct InscriptionMetadataView {
28        access(all) let type: String?
29        access(all) let inscriptionData: String?
30
31        view init(type: String?, inscriptionData: String?) {
32            self.type = type
33            self.inscriptionData = inscriptionData
34        }
35    }
36
37    access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
38        // The inscription number
39        access(all) let id: UInt64
40
41        // The original creator
42        access(all) let creator: Address
43
44        // The type of ordinal (text, domain or image)
45        access(all) let type: String
46
47        // The inscription data
48        access(self) var data: String
49
50        init(creator: Address, type: String, data: String) {
51            self.id = Ordinal.totalSupply
52            self.creator = creator
53            self.type = type
54            self.data = data
55
56            emit OrdinalMinted(id: self.id, nftUUID: self.uuid, creator: self.creator, type: self.type)
57        }
58        
59        access(contract) fun updateData(data: String) {
60            assert(self.data.length > 300000, message: "This ordinal inscription cannot be updated as it is less than 300KB")
61            assert(self.type == "image", message: "Inscription data can only be updated for image ordinals")
62            assert(data.length > 0 && data.length <= 300000, message: "Inscription data must be non-empty and less than 300KB")
63            self.data = data
64
65            emit OrdinalUpdated(id: self.id, size: data.length)
66        }
67
68        access(all) view fun getViews(): [Type] {
69            let supportedViews = [
70                Type<MetadataViews.Display>(),
71                Type<MetadataViews.Royalties>(),
72                Type<MetadataViews.Edition>(),
73                Type<MetadataViews.Traits>(),
74                Type<MetadataViews.ExternalURL>(),
75                Type<MetadataViews.Rarity>(),
76                Type<InscriptionMetadataView>()
77            ]
78            return supportedViews
79        }
80
81        access(all) view fun resolveView(_ view: Type): AnyStruct? {
82            var data = self.data
83            let isOrdinalRestricted = OrdinalVendor.checkOrdinalRestricted(id: self.id)
84            if isOrdinalRestricted {
85                data = "Content restricted due to policy violation"
86            }
87            switch view {
88                case Type<MetadataViews.Display>():
89                    return MetadataViews.Display(
90                        name: "Ordinal".concat(" #").concat(self.id.toString()),
91                        description: "Ordinals on the Flow blockchain",
92                        thumbnail: MetadataViews.HTTPFile(
93                          url: "https://flowverse.myfilebase.com/ipfs/QmQ45TvzGVTmoMCfGqxgbiMmR4rdmSHAhz661bPyUfFrAT"
94                        )
95                    )
96                case Type<MetadataViews.Royalties>():
97                    let royalties : [MetadataViews.Royalty] = [
98                        MetadataViews.Royalty(
99                            receiver: getAccount(0x604b63bcbef5974f).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!,
100                            cut: 0.01,
101                            description: "Platform Royalty Fee")
102                    ]
103                    return MetadataViews.Royalties(royalties)
104                case Type<MetadataViews.Traits>():
105                    let traits: [MetadataViews.Trait] = [
106                        MetadataViews.Trait(
107                            name: "Inscription Number",
108                            value: self.id,
109                            displayType: "Number",
110                            rarity: nil
111                        ),
112                        MetadataViews.Trait(
113                            name: "Type",
114                            value: self.type,
115                            displayType: nil,
116                            rarity: nil
117                        ),
118                        MetadataViews.Trait(
119                            name: "Size",
120                            value: self.data.length,
121                            displayType: "Number",
122                            rarity: nil
123                        ),
124                        MetadataViews.Trait(
125                            name: "Creator",
126                            value: self.creator.toString(),
127                            displayType: nil,
128                            rarity: nil
129                        ),
130                        MetadataViews.Trait(
131                            name: "UUID",
132                            value: self.uuid.toString(),
133                            displayType: nil,
134                            rarity: nil
135                        )
136                    ]
137                    return MetadataViews.Traits(traits)
138                case Type<InscriptionMetadataView>():
139                    return InscriptionMetadataView(
140                        type: self.type,
141                        inscriptionData: data
142                    )
143                case Type<MetadataViews.Rarity>():
144                    var description = ""
145                    if self.id <= 100 {
146                        description = "Sub 100"
147                    } else if self.id <= 1000 {
148                        description = "Sub 1K"
149                    } else if self.id <= 5000 {
150                        description = "Sub 5K"
151                    } else if self.id <= 10000 {
152                        description = "Sub 10K"
153                    } else if self.id <= 25000 {
154                        description = "Sub 25K"
155                    } else if self.id <= 50000 {
156                        description = "Sub 50K"
157                    } else {
158                        description = "Sub 100K"
159                    }
160                    return MetadataViews.Rarity(
161                        score: nil,
162                        max: nil,
163                        description: description
164                    )
165                case Type<MetadataViews.ExternalURL>():
166                    return MetadataViews.ExternalURL("https://nft.flowverse.co/ordinals/".concat(self.id.toString()))
167            }
168            return nil
169        }
170
171        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
172            return <- Ordinal.createEmptyCollection(nftType: Type<@Ordinal.NFT>())
173        }
174    }
175
176    access(self) fun mint(creator: Address, type: String, data: String): @NFT {
177        pre {
178            type == "image" || type == "text" || type == "domain" : "Invalid type (must be either image, text or domain)"
179            data.length > 0 : "Invalid data (must be non-empty)"
180        }
181
182        if type == "domain" {
183            assert(OrdinalVendor.checkDomainAvailability(domain: data), message: "domain already exists")
184        }
185
186        // Increment the inscription number
187        Ordinal.totalSupply = Ordinal.totalSupply + UInt64(1)
188
189        // Mint the new NFT
190        let nft: @NFT <- create NFT(creator: creator, type: type, data: data)
191        return <-nft
192    }
193
194    access(all) resource Minter: OrdinalVendor.IMinter {
195        init() {}
196        access(all) fun mint(creator: Address, type: String, data: String): @NFT {
197            return <-Ordinal.mint(creator: creator, type: type, data: data)
198        }
199    }
200
201    access(all) resource Admin {
202        access(all) fun createMinter(): @Minter {
203            return <-create Minter()
204        }
205
206        access(all) fun createNewAdmin(): @Admin {
207            return <-create Admin()
208        }
209    }
210
211    //TODO: remove this as it's not used
212    access(all) resource interface CollectionPrivate {
213        access(all) fun borrowUpdateOrdinalNFT(id: UInt64): &Ordinal.NFT?  
214        access(all) fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?
215    }
216
217    access(all) resource interface CollectionUpdate {
218        access(all) fun updateData(id: UInt64, data: String)
219    }
220    
221    // Public interface for the FlowverseShirt Collection that allows users access to certain functionalities
222    access(all) resource interface CollectionPublic {
223        access(all) fun deposit(token: @{NonFungibleToken.NFT})
224        access(all) view fun getIDs(): [UInt64]
225        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
226        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?
227    }
228
229    // Collection of FlowverseShirt NFTs owned by an account
230    access(all) resource Collection: CollectionPublic, CollectionUpdate, NonFungibleToken.Collection {
231        // Dictionary of entity instances conforming tokens
232        // NFT is a resource type with a UInt64 ID field
233        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
234
235        init () {
236            self.ownedNFTs <- {}
237        }
238
239        /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
240        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
241            let supportedTypes: {Type: Bool} = {}
242            supportedTypes[Type<@Ordinal.NFT>()] = true
243            return supportedTypes
244        }
245
246        /// Returns whether or not the given type is accepted by the collection
247        /// A collection that can accept any type should just return true by default
248        access(all) view fun isSupportedNFTType(type: Type): Bool {
249           if type == Type<@Ordinal.NFT>() {
250            return true
251           } else {
252            return false
253           }
254        }
255
256        /// Gets the amount of NFTs stored in the collection
257        access(all) view fun getLength(): Int {
258            return self.ownedNFTs.keys.length
259        }
260
261        /// withdraw removes an NFT from the collection and moves it to the caller
262        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
263            let token <- self.ownedNFTs.remove(key: withdrawID)
264                ?? panic("Could not withdraw an NFT with the provided ID from the collection")
265
266            return <-token
267        }
268
269        /// deposit takes a NFT and adds it to the collections dictionary
270        /// and adds the ID to the id array
271        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
272            let token <- token as! @Ordinal.NFT
273            let id = token.id
274
275            // add the new token to the dictionary which removes the old one
276            let oldToken <- self.ownedNFTs[token.id] <- token
277
278            destroy oldToken
279        }
280
281        access(all) view fun getIDs(): [UInt64] {
282            return self.ownedNFTs.keys
283        }
284
285        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
286            return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
287        }
288
289        /// Borrow the view resolver for the specified NFT ID
290        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
291            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
292                return nft as &{ViewResolver.Resolver}
293            }
294            return nil
295        }
296
297        access(all) fun updateData(id: UInt64, data: String) {
298            if let nft = self.borrowNFT(id) {
299                let ordinalNFT = nft as! &Ordinal.NFT
300                ordinalNFT.updateData(data: data)
301            }
302        }
303
304        /// createEmptyCollection creates an empty Collection of the same type
305        /// and returns it to the caller
306        /// @return A an empty collection of the same type
307        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
308            return <-Ordinal.createEmptyCollection(nftType: Type<@Ordinal.NFT>())
309        }
310    }
311
312    // -----------------------------------------------------------------------
313    // Ordinal contract-level function definitions
314    // -----------------------------------------------------------------------
315
316    /// createEmptyCollection creates an empty Collection for the specified NFT type
317    /// and returns it to the caller so that they can own NFTs
318    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
319        return <- create Collection()
320    }
321
322    
323    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
324    ///
325    /// @return An array of Types defining the implemented views. This value will be used by
326    ///         developers to know which parameter to pass to the resolveView() method.
327    ///
328    access(all) view fun getContractViews(resourceType: Type?): [Type] {
329        return [
330            Type<MetadataViews.NFTCollectionData>(),
331            Type<MetadataViews.NFTCollectionDisplay>()
332        ]
333    }
334
335    /// Function that resolves a metadata view for this contract.
336    ///
337    /// @param view: The Type of the desired view.
338    /// @return A structure representing the requested view.
339    ///
340    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
341        switch viewType {
342            case Type<MetadataViews.NFTCollectionData>():
343                    return MetadataViews.NFTCollectionData(
344                        storagePath: self.CollectionStoragePath,
345                        publicPath: self.CollectionPublicPath,
346                        publicCollection: Type<&Ordinal.Collection>(),
347                        publicLinkedType: Type<&Ordinal.Collection>(),
348                        createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {return <- Ordinal.createEmptyCollection(nftType: Type<@Ordinal.NFT>())}),
349                )
350            case Type<MetadataViews.NFTCollectionDisplay>():
351                return MetadataViews.NFTCollectionDisplay(
352                    name: "Ordinals",
353                    description: "Ordinals on the Flow blockchain",
354                    externalURL: MetadataViews.ExternalURL("https://twitter.com/flowverse_"),
355                    squareImage: MetadataViews.Media(
356                        file: MetadataViews.HTTPFile(
357                            url: "https://flowverse.myfilebase.com/ipfs/QmQ45TvzGVTmoMCfGqxgbiMmR4rdmSHAhz661bPyUfFrAT"
358                        ),
359                        mediaType: "image/png"
360                    ),
361                    bannerImage: MetadataViews.Media(
362                        file: MetadataViews.HTTPFile(
363                            url: "https://flowverse.myfilebase.com/ipfs/QmaTj276rAUFoFiik84xCx1PYZnqZHcGp78vG6xqHLfoXo"
364                        ),
365                        mediaType: "image/png"
366                    ),
367                    socials: {
368                        "twitter": MetadataViews.ExternalURL("https://twitter.com/flowverse_"),
369                        "discord": MetadataViews.ExternalURL("https://discord.gg/flowverse"),
370                        "instagram": MetadataViews.ExternalURL("https://www.instagram.com/flowverseofficial")
371                    }
372                )
373        }
374        return nil
375    }
376
377    // -----------------------------------------------------------------------
378    // Ordinal initialization function
379    // -----------------------------------------------------------------------
380    //
381    init() {
382        self.CollectionStoragePath = /storage/OrdinalCollection
383        self.CollectionPublicPath = /public/OrdinalCollection
384        self.AdminStoragePath = /storage/OrdinalAdmin
385
386        // Initialize contract fields
387        self.totalSupply = 0
388
389        // Create and save a new Collection in storage
390        let collection <- create Collection()
391        self.account.storage.save(<-collection, to: self.CollectionStoragePath)
392
393        // Issue a public capability for the Collection
394        let collectionCap = self.account.capabilities.storage.issue<&Ordinal.Collection>(self.CollectionStoragePath)
395        self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
396
397        // Create and store Admin resource
398        self.account.storage.save<@Admin>(<- create Admin(), to: self.AdminStoragePath)
399    }
400}