Smart Contract

MetaPandaVoucher

A.f2af175e411dfff8.MetaPandaVoucher

Deployed

2h ago
Feb 28, 2026, 11:20:45 PM UTC

Dependents

0 imports
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}