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