Smart Contract
MetaPandaVoucher
A.f2af175e411dfff8.MetaPandaVoucher
1/**
2 This program is free software: you can redistribute it and/or modify
3 it under the terms of the GNU General Public License as published by
4 the Free Software Foundation, either version 3 of the License, or
5 (at your option) any later version.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program. If not, see <https://www.gnu.org/licenses/>.
14**/
15import NonFungibleToken from 0x1d7e57aa55817448
16import MetadataViews from 0x1d7e57aa55817448
17import AnchainUtils from 0x7ba45bdcac17806a
18
19// MetaPandaVoucher
20// NFT items for MetaPandaVoucher!
21//
22pub contract MetaPandaVoucher: NonFungibleToken {
23
24 // Standard Events
25 //
26 pub event ContractInitialized()
27 pub event Withdraw(id: UInt64, from: Address?)
28 pub event Deposit(id: UInt64, to: Address?)
29 pub event Minted(id: UInt64, metadata: Metadata)
30
31 // Redeemed
32 // Fires when a user redeems a voucher, prepping
33 // it for consumption to receive a reward
34 //
35 pub event Redeemed(id: UInt64, redeemer: Address)
36
37 // Consumed
38 // Fires when an Admin consumes a voucher, deleting
39 // it forever
40 //
41 pub event Consumed(id: UInt64)
42
43 // redeemers
44 // Tracks all accounts that have redeemed a voucher
45 //
46 access(contract) let redeemers: { UInt64 : Address }
47
48 // Named Paths
49 //
50 pub let RedeemedCollectionStoragePath: StoragePath
51 pub let RedeemedCollectionPublicPath: PublicPath
52 pub let CollectionStoragePath: StoragePath
53 pub let CollectionPublicPath: PublicPath
54 pub let MinterStoragePath: StoragePath
55
56 // totalSupply
57 // The total number of MetaPandaVoucher that have been minted
58 //
59 pub var totalSupply: UInt64
60
61 // MetaPandaVoucher Metadata
62 //
63 pub struct Metadata {
64 // Metadata is kept as flexible as possible so we can introduce
65 // any type of sale conditions we want and enforce these off-chain.
66 // It would be great to eventually move this validation on-chain.
67 pub let details: {String:String}
68 init(
69 details: {String:String}
70 ) {
71 self.details = details
72 }
73 }
74
75 // MetaPandaVoucherView
76 //
77 pub struct MetaPandaVoucherView {
78 pub let uuid: UInt64
79 pub let id: UInt64
80 pub let metadata: Metadata
81 pub let file: AnchainUtils.File
82 init(
83 uuid: UInt64,
84 id: UInt64,
85 metadata: Metadata,
86 file: AnchainUtils.File
87 ) {
88 self.uuid = uuid
89 self.id = id
90 self.metadata = metadata
91 self.file = file
92 }
93 }
94
95 // NFT
96 // A MetaPandaVoucher as an NFT
97 //
98 pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
99 // The token's ID
100 pub let id: UInt64
101
102 // The token's metadata
103 pub let metadata: Metadata
104
105 // The token's file
106 pub let file: AnchainUtils.File
107
108 // initializer
109 //
110 init(id: UInt64, metadata: Metadata, file: AnchainUtils.File) {
111 self.id = id
112 self.metadata = metadata
113 self.file = file
114 }
115
116 // getViews
117 // Returns a list of ways to view this NFT.
118 //
119 pub fun getViews(): [Type] {
120 return [
121 Type<MetadataViews.Display>(),
122 Type<MetaPandaVoucherView>(),
123 Type<AnchainUtils.File>()
124 ]
125 }
126
127 // resolveView
128 // Returns a particular view of this NFT.
129 //
130 pub fun resolveView(_ view: Type): AnyStruct? {
131 switch view {
132
133 case Type<MetadataViews.Display>():
134 return MetadataViews.Display(
135 name: "MetaPandaVoucher ".concat(self.id.toString()),
136 description: "",
137 thumbnail: self.file.thumbnail
138 )
139
140 case Type<MetaPandaVoucherView>():
141 return MetaPandaVoucherView(
142 uuid: self.uuid,
143 id: self.id,
144 metadata: self.metadata,
145 file: self.file
146 )
147
148 case Type<AnchainUtils.File>():
149 return self.file
150
151 }
152 return nil
153 }
154
155 }
156
157 // Collection
158 // A collection of MetaPandaVoucher NFTs owned by an account
159 //
160 pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection, AnchainUtils.ResolverCollection {
161 // dictionary of NFT conforming tokens
162 // NFT is a resource type with an 'UInt64' ID field
163 //
164 pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
165
166 // borrowViewResolverSafe
167 //
168 pub fun borrowViewResolverSafe(id: UInt64): &AnyResource{MetadataViews.Resolver}? {
169 if self.ownedNFTs[id] != nil {
170 let nft = &self.ownedNFTs[id] as auth &NonFungibleToken.NFT?
171 if nft != nil {
172 return nft!
173 as! &MetaPandaVoucher.NFT
174 as &AnyResource{MetadataViews.Resolver}
175 }
176 }
177 return nil
178 }
179
180 // borrowViewResolver
181 //
182 pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
183 if self.ownedNFTs[id] != nil {
184 let nft = &self.ownedNFTs[id] as auth &NonFungibleToken.NFT?
185 if nft != nil {
186 return nft!
187 as! &MetaPandaVoucher.NFT
188 as &AnyResource{MetadataViews.Resolver}
189 }
190 }
191 panic("NFT not found in collection.")
192 }
193
194 // withdraw
195 // Removes an NFT from the collection and moves it to the caller
196 //
197 pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
198 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
199
200 emit Withdraw(id: token.id, from: self.owner?.address)
201
202 return <-token
203 }
204
205 // deposit
206 // Takes a NFT and adds it to the collections dictionary
207 // and adds the ID to the id array
208 //
209 pub fun deposit(token: @NonFungibleToken.NFT) {
210 let token <- token as! @MetaPandaVoucher.NFT
211
212 let id: UInt64 = token.id
213
214 // add the new token to the dictionary which removes the old one
215 let oldToken <- self.ownedNFTs[id] <- token
216
217 emit Deposit(id: id, to: self.owner?.address)
218
219 destroy oldToken
220 }
221
222 // getIDs
223 // Returns an array of the IDs that are in the collection
224 //
225 pub fun getIDs(): [UInt64] {
226 return self.ownedNFTs.keys
227 }
228
229 // borrowNFT
230 // Gets a reference to an NFT in the collection
231 // so that the caller can read its metadata and call its methods
232 //
233 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
234 if self.ownedNFTs[id] != nil {
235 let nft = (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
236 return nft
237 }
238 panic("NFT not found in collection.")
239 }
240
241 // destructor
242 destroy() {
243 destroy self.ownedNFTs
244 }
245
246 // initializer
247 //
248 init () {
249 self.ownedNFTs <- {}
250 }
251 }
252
253 // NFTMinter
254 // Resource that an admin or something similar would own to be
255 // able to mint new NFTs
256 //
257 pub resource NFTMinter {
258
259 // mintNFT
260 // Mints a new NFT with a new ID and deposits it in the recipients
261 // collection using their collection reference
262 //
263 pub fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, metadata: Metadata, file: AnchainUtils.File) {
264 emit Minted(id: MetaPandaVoucher.totalSupply, metadata: metadata)
265 recipient.deposit(token: <-create MetaPandaVoucher.NFT(id: MetaPandaVoucher.totalSupply, metadata: metadata, file: file))
266 MetaPandaVoucher.totalSupply = MetaPandaVoucher.totalSupply + (1 as UInt64)
267 }
268
269 // consume
270 // Consumes a voucher from the redeemed collection by destroying it
271 //
272 pub fun consume(_ voucherID: UInt64): Address {
273 // Obtain a reference to the redeemed collection
274 let redeemedCollection = MetaPandaVoucher.account
275 .borrow<&MetaPandaVoucher.Collection>(
276 from: MetaPandaVoucher.RedeemedCollectionStoragePath
277 )!
278
279 // Burn the voucher
280 destroy <- redeemedCollection.withdraw(withdrawID: voucherID)
281
282 // Let event listeners know that this voucher has been consumed
283 emit Consumed(id: voucherID)
284 return MetaPandaVoucher.redeemers.remove(key: voucherID)!
285 }
286
287 }
288
289 // createEmptyCollection
290 // A public function that anyone can call to create a new empty collection
291 //
292 pub fun createEmptyCollection(): @NonFungibleToken.Collection {
293 return <- create Collection()
294 }
295
296
297 // redeem
298 // This public function represents the core feature of this contract: redemptions.
299 // The NFTs, aka vouchers, can be 'redeemed' into the RedeemedCollection. The admin
300 // can then consume these in exchange for merchandise.
301 //
302 pub fun redeem(collection: &MetaPandaVoucher.Collection, voucherID: UInt64) {
303 // Withdraw the voucher
304 let token <- collection.withdraw(withdrawID: voucherID)
305
306 // Get a reference to our redeemer collection
307 let receiver = MetaPandaVoucher.account
308 .getCapability(MetaPandaVoucher.RedeemedCollectionPublicPath)
309 .borrow<&{NonFungibleToken.CollectionPublic}>()!
310
311 // Deposit the voucher for consumption
312 receiver.deposit(token: <- token)
313
314 // Store who redeemed this voucher
315 MetaPandaVoucher.redeemers[voucherID] = collection.owner!.address
316 emit Redeemed(id: voucherID, redeemer: collection.owner!.address)
317 }
318
319 // getRedeemers
320 // Return the redeemers dictionary
321 //
322 pub fun getRedeemers(): { UInt64 : Address } {
323 return self.redeemers
324 }
325
326 // initializer
327 //
328 init() {
329 // Set our named paths
330 self.RedeemedCollectionStoragePath = /storage/MetaPandaVoucherRedeemedCollection
331 self.RedeemedCollectionPublicPath = /public/MetaPandaVoucherRedeemedCollection
332 self.CollectionStoragePath = /storage/MetaPandaVoucherCollection
333 self.CollectionPublicPath = /public/MetaPandaVoucherCollection
334 self.MinterStoragePath = /storage/MetaPandaVoucherMinter
335
336 // Initialize the total supply
337 self.totalSupply = 0
338
339 // Initialize redeemers mapping
340 self.redeemers = {}
341
342 // Create a Minter resource and save it to storage
343 let minter <- create NFTMinter()
344 self.account.save(<-minter, to: self.MinterStoragePath)
345
346 // Create a collection that users can place their redeemed vouchers in
347 self.account.save(<-create Collection(), to: self.RedeemedCollectionStoragePath)
348 self.account.link<&{
349 NonFungibleToken.CollectionPublic,
350 MetadataViews.ResolverCollection,
351 AnchainUtils.ResolverCollection
352 }>(
353 self.RedeemedCollectionPublicPath,
354 target: self.RedeemedCollectionStoragePath
355 )
356
357 // Create a personal collection just in case the contract ever holds vouchers to distribute later
358 self.account.save(<-create Collection(), to: self.CollectionStoragePath)
359 self.account.link<&{
360 NonFungibleToken.CollectionPublic,
361 MetadataViews.ResolverCollection,
362 AnchainUtils.ResolverCollection
363 }>(
364 self.CollectionPublicPath,
365 target: self.CollectionStoragePath
366 )
367
368 emit ContractInitialized()
369 }
370}