Smart Contract
NonFungibleToken
A.3eebe1cb4a1126b2.NonFungibleToken
1/**
2
3## The Flow Non-Fungible Token standard
4
5## `NonFungibleToken` contract
6
7The interface that all Non-Fungible Token contracts should conform to.
8If a user wants to deploy a new NFT contract, their contract should implement
9The types defined here
10
11/// Contributors (please add to this list if you contribute!):
12/// - Joshua Hannan - https://github.com/joshuahannan
13/// - Bastian Müller - https://twitter.com/turbolent
14/// - Dete Shirley - https://twitter.com/dete73
15/// - Bjarte Karlsen - https://twitter.com/0xBjartek
16/// - Austin Kline - https://twitter.com/austin_flowty
17/// - Giovanni Sanchez - https://twitter.com/gio_incognito
18/// - Deniz Edincik - https://twitter.com/bluesign
19///
20/// Repo reference: https://github.com/onflow/flow-nft
21
22## `NFT` resource interface
23
24The core resource type that represents an NFT in the smart contract.
25
26## `Collection` Resource interface
27
28The resource that stores a user's NFT collection.
29It includes a few functions to allow the owner to easily
30move tokens in and out of the collection.
31
32## `Provider` and `Receiver` resource interfaces
33
34These interfaces declare functions with some pre and post conditions
35that require the Collection to follow certain naming and behavior standards.
36
37They are separate because it gives developers the ability to define functions
38that can use any type that implements these interfaces
39
40By using resources and interfaces, users of NFT smart contracts can send
41and receive tokens peer-to-peer, without having to interact with a central ledger
42smart contract.
43
44To send an NFT to another user, a user would simply withdraw the NFT
45from their Collection, then call the deposit function on another user's
46Collection to complete the transfer.
47
48*/
49
50import ViewResolver from 0x1d7e57aa55817448
51
52/// The main NFT contract interface. Other NFT contracts will import
53/// and implement this interface as well the interfaces defined in this interface
54///
55access(all) contract interface NonFungibleToken: ViewResolver {
56
57 /// An entitlement for allowing the withdrawal of tokens from a Vault
58 access(all) entitlement Withdraw
59
60 /// An entitlement for allowing updates and update events for an NFT
61 access(all) entitlement Update
62
63 /// Event that contracts should emit when the metadata of an NFT is updated
64 /// It can only be emitted by calling the `emitNFTUpdated` function
65 /// with an `Update` entitled reference to the NFT that was updated
66 /// The entitlement prevents spammers from calling this from other users' collections
67 /// because only code within a collection or that has special entitled access
68 /// to the collections methods will be able to get the entitled reference
69 ///
70 /// The event makes it so that third-party indexers can monitor the events
71 /// and query the updated metadata from the owners' collections.
72 ///
73 access(all) event Updated(type: String, id: UInt64, uuid: UInt64, owner: Address?)
74 access(all) view fun emitNFTUpdated(_ nftRef: auth(Update) &{NonFungibleToken.NFT})
75 {
76 emit Updated(type: nftRef.getType().identifier, id: nftRef.id, uuid: nftRef.uuid, owner: nftRef.owner?.address)
77 }
78
79
80 /// Event that is emitted when a token is withdrawn,
81 /// indicating the type, id, uuid, the owner of the collection that it was withdrawn from,
82 /// and the UUID of the resource it was withdrawn from, usually a collection.
83 ///
84 /// If the collection is not in an account's storage, `from` will be `nil`.
85 ///
86 access(all) event Withdrawn(type: String, id: UInt64, uuid: UInt64, from: Address?, providerUUID: UInt64)
87
88 /// Event that emitted when a token is deposited to a collection.
89 /// Indicates the type, id, uuid, the owner of the collection that it was deposited to,
90 /// and the UUID of the collection it was deposited to
91 ///
92 /// If the collection is not in an account's storage, `from`, will be `nil`.
93 ///
94 access(all) event Deposited(type: String, id: UInt64, uuid: UInt64, to: Address?, collectionUUID: UInt64)
95
96 /// Interface that the NFTs must conform to
97 ///
98 access(all) resource interface NFT: ViewResolver.Resolver {
99
100 /// unique ID for the NFT
101 access(all) let id: UInt64
102
103 /// Event that is emitted automatically every time a resource is destroyed
104 /// The type information is included in the metadata event so it is not needed as an argument
105 access(all) event ResourceDestroyed(id: UInt64 = self.id, uuid: UInt64 = self.uuid)
106
107 /// createEmptyCollection creates an empty Collection that is able to store the NFT
108 /// and returns it to the caller so that they can own NFTs
109 ///
110 /// @return A an empty collection that can store this NFT
111 ///
112 access(all) fun createEmptyCollection(): @{Collection} {
113 post {
114 result.getLength() == 0:
115 "NonFungibleToken.NFT.createEmptyCollection: Cannot create an empty collection! "
116 .concat("The created NonFungibleToken Collection has a non-zero length. ")
117 .concat(" A newly created collection must be empty!")
118 result.isSupportedNFTType(type: self.getType()):
119 "NonFungibleToken.NFT.createEmptyCollection: Cannot create an empty collection! "
120 .concat("The created NonFungibleToken Collection does not support NFTs of type <")
121 .concat(self.getType().identifier)
122 .concat(">. The collection must support NFTs of type <")
123 .concat(self.getType().identifier).concat(">.")
124 }
125 }
126
127 /// Gets all the NFTs that this NFT directly owns
128 ///
129 /// @return A dictionary of all subNFTS keyed by type
130 ///
131 access(all) view fun getAvailableSubNFTS(): {Type: [UInt64]} {
132 return {}
133 }
134
135 /// Get a reference to an NFT that this NFT owns
136 /// Both arguments are optional to allow the NFT to choose
137 /// how it returns sub NFTs depending on what arguments are provided
138 /// For example, if `type` has a value, but `id` doesn't, the NFT
139 /// can choose which NFT of that type to return if there is a "default"
140 /// If both are `nil`, then NFTs that only store a single NFT can just return
141 /// that. This helps callers who aren't sure what they are looking for
142 ///
143 /// @param type: The Type of the desired NFT
144 /// @param id: The id of the NFT to borrow
145 ///
146 /// @return A structure representing the requested view.
147 access(all) fun getSubNFT(type: Type, id: UInt64) : &{NonFungibleToken.NFT}? {
148 return nil
149 }
150 }
151
152 /// Interface to mediate withdrawals from a resource, usually a Collection
153 ///
154 access(all) resource interface Provider {
155
156 // We emit withdraw events from the provider interface because conficting withdraw
157 // events aren't as confusing to event listeners as conflicting deposit events
158
159 /// withdraw removes an NFT from the collection and moves it to the caller
160 /// It does not specify whether the ID is UUID or not
161 ///
162 /// @param withdrawID: The id of the NFT to withdraw from the collection
163 /// @return @{NFT}: The NFT that was withdrawn
164 ///
165 access(Withdraw) fun withdraw(withdrawID: UInt64): @{NFT} {
166 post {
167 result.id == withdrawID:
168 "NonFungibleToken.Provider.withdraw: Cannot withdraw NFT! "
169 .concat("The ID of the withdrawn NFT (")
170 .concat(result.id.toString())
171 .concat(") must be the same as the requested ID (")
172 .concat(withdrawID.toString())
173 .concat(").")
174 emit Withdrawn(type: result.getType().identifier, id: result.id, uuid: result.uuid, from: self.owner?.address, providerUUID: self.uuid)
175 }
176 }
177 }
178
179 /// Interface to mediate deposits to the Collection
180 ///
181 access(all) resource interface Receiver {
182
183 /// deposit takes an NFT as an argument and adds it to the Collection
184 /// @param token: The NFT to deposit
185 access(all) fun deposit(token: @{NFT})
186
187 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
188 /// @return A dictionary of types mapped to booleans indicating if this
189 /// reciever supports it
190 access(all) view fun getSupportedNFTTypes(): {Type: Bool}
191
192 /// Returns whether or not the given type is accepted by the collection
193 /// A collection that can accept any type should just return true by default
194 /// @param type: An NFT type
195 /// @return A boolean indicating if this receiver can recieve the desired NFT type
196 access(all) view fun isSupportedNFTType(type: Type): Bool
197 }
198
199 /// Kept for backwards-compatibility reasons
200 access(all) resource interface CollectionPublic {
201 access(all) fun deposit(token: @{NFT})
202 access(all) view fun getLength(): Int
203 access(all) view fun getIDs(): [UInt64]
204 access(all) fun forEachID(_ f: fun (UInt64): Bool): Void
205 access(all) view fun borrowNFT(_ id: UInt64): &{NFT}?
206 }
207
208 /// Requirement for the concrete resource type in the implementing contract
209 /// to implement this interface. Since this interface inherits from
210 /// all the other necessary interfaces, resources that implement it do not
211 /// also need to include the other interfaces in their conformance lists
212 ///
213 access(all) resource interface Collection: Provider, Receiver, CollectionPublic, ViewResolver.ResolverCollection {
214
215 /// Field that contains all the NFTs that the collection owns
216 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
217
218 /// deposit takes a NFT as an argument and stores it in the collection
219 /// @param token: The NFT to deposit into the collection
220 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
221 pre {
222 // We emit the deposit event in the `Collection` interface
223 // because the `Collection` interface is almost always the final destination
224 // of tokens and deposit emissions from custom receivers could be confusing
225 // and hard to reconcile to event listeners
226 emit Deposited(type: token.getType().identifier, id: token.id, uuid: token.uuid, to: self.owner?.address, collectionUUID: self.uuid)
227 }
228 }
229
230 /// Gets the amount of NFTs stored in the collection
231 /// @return An integer indicating the size of the collection
232 access(all) view fun getLength(): Int {
233 return self.ownedNFTs.length
234 }
235
236 /// Allows a given function to iterate through the list
237 /// of owned NFT IDs in a collection without first
238 /// having to load the entire list into memory
239 access(all) fun forEachID(_ f: fun (UInt64): Bool): Void {
240 self.ownedNFTs.forEachKey(f)
241 }
242
243 /// Borrows a reference to an NFT stored in the collection
244 /// If the NFT with the specified ID is not in the collection,
245 /// the function should return `nil` and not panic.
246 ///
247 /// @param id: The desired nft id in the collection to return a referece for.
248 /// @return An optional reference to the NFT
249 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
250 post {
251 (result == nil) || (result?.id == id):
252 "NonFungibleToken.Collection.borrowNFT: Cannot borrow NFT reference! "
253 .concat("The ID of the returned reference (")
254 .concat(result!.id.toString())
255 .concat(") does not match the ID that was specified (")
256 .concat(id.toString())
257 .concat(")")
258 }
259 }
260
261 /// createEmptyCollection creates an empty Collection of the same type
262 /// and returns it to the caller
263 /// @return A an empty collection of the same type
264 access(all) fun createEmptyCollection(): @{Collection} {
265 post {
266 result.getType() == self.getType():
267 "NonFungibleToken.Collection.createEmptyCollection: Cannot create empty collection! "
268 .concat("The created collection type <")
269 .concat(result.getType().identifier)
270 .concat("> does not have the same type as the collection that was used to create it <")
271 .concat(self.getType().identifier)
272 .concat(">.")
273 result.getLength() == 0:
274 "NonFungibleToken.Collection.createEmptyCollection: Cannot create empty collection! "
275 .concat("The created collection has a non-zero length.")
276 .concat(" A newly created collection must be empty!")
277 }
278 }
279 }
280
281 /// createEmptyCollection creates an empty Collection for the specified NFT type
282 /// and returns it to the caller so that they can own NFTs
283 /// @param nftType: The desired nft type to return a collection for.
284 /// @return An array of NFT Types that the implementing contract defines.
285 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
286 post {
287 result.getIDs().length == 0:
288 "NonFungibleToken.createEmptyCollection: Cannot create empty collection! "
289 .concat("The created collection has a non-zero length. ")
290 .concat("A newly created collection must be empty!")
291 }
292 }
293}
294