Smart Contract
SolarpupsNFT
A.a8d493db1bb4df56.SolarpupsNFT
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4
5/**
6 * This contract defines the structure and behaviour of Solarpups NFT assets.
7 * By using the SolarpupsNFT contract, assets can be registered in the AssetRegistry
8 * so that NFTs, belonging to that asset can be minted. Assets and NFT tokens can
9 * also be locked by this contract.
10 */
11pub contract SolarpupsNFT: NonFungibleToken {
12
13 pub let SolarpupsNFTPublicPath: PublicPath
14 pub let SolarpupsNFTPrivatePath: PrivatePath
15 pub let SolarpupsNFTStoragePath: StoragePath
16 pub let AssetRegistryStoragePath: StoragePath
17 pub let MinterFactoryStoragePath: StoragePath
18
19 pub event ContractInitialized()
20 pub event Withdraw(id: UInt64, from: Address?)
21 pub event Deposit(id: UInt64, to: Address?)
22 pub event MintAsset(id: UInt64, assetId: String)
23 pub event BurnAsset(id: UInt64, assetId: String)
24 pub event CollectionDeleted(from: Address?)
25
26 pub var totalSupply: UInt64
27 access(self) let assets: {String: Asset}
28
29 // Common interface for the NFT data.
30 pub resource interface TokenDataAware {
31 pub let data: TokenData
32 }
33
34 /**
35 * This resource represents a specific Solarpups NFT which can be
36 * minted and transferred. Each NFT belongs to an asset id and has
37 * an edition information. In addition to that each NFT can have other
38 * NFTs which makes it composable.
39 */
40 pub resource NFT: NonFungibleToken.INFT, TokenDataAware, MetadataViews.Resolver {
41 pub let id: UInt64
42 pub let data: TokenData
43 access(self) let items: @{String:{TokenDataAware, NonFungibleToken.INFT}}
44
45 init(id: UInt64, data: TokenData, items: @{String:{TokenDataAware, NonFungibleToken.INFT}}) {
46 self.id = id
47 self.data = data
48 self.items <- items
49 }
50
51 destroy() {
52 emit BurnAsset(id: self.id, assetId: self.data.assetId)
53 destroy self.items
54 }
55
56 pub fun getViews(): [Type] {
57 return [
58 Type<MetadataViews.Display>(),
59 Type<MetadataViews.Royalties>(),
60 Type<MetadataViews.Editions>(),
61 Type<MetadataViews.ExternalURL>(),
62 Type<MetadataViews.NFTCollectionData>(),
63 Type<MetadataViews.NFTCollectionDisplay>(),
64 Type<MetadataViews.Serial>()
65 ]
66 }
67 pub fun resolveView(_ view: Type): AnyStruct? {
68 let asset = SolarpupsNFT.getAsset(assetId: self.data.assetId)
69 let url = "https://cdn.krikeyapp.com/nft_web/nft_images/".concat(self.data.assetId).concat(".png")
70 switch view {
71 case Type<MetadataViews.Display>():
72 return MetadataViews.Display(
73 name: "Solarpups NFT",
74 description: "The world's most adorable and sensitive pup.",
75 thumbnail: MetadataViews.HTTPFile(
76 url: url,
77 )
78 )
79 case Type<MetadataViews.Editions>():
80 // There is no max number of NFTs that can be minted from this contract
81 // so the max edition field value is set to nil
82 let edition=UInt64(self.data.edition)
83 let editionInfo = MetadataViews.Edition(name: "Solarpups NFT Edition", number: edition, max: nil)
84 let editionList: [MetadataViews.Edition] = [editionInfo]
85 return MetadataViews.Editions(
86 editionList
87 )
88 case Type<MetadataViews.Serial>():
89 return MetadataViews.Serial(
90 self.id
91 )
92 case Type<MetadataViews.Royalties>():
93 let RECEIVER_PATH = /public/flowTokenReceiver
94 var royaltyReceiver = getAccount(0xcf99ac7bc594366c).getCapability<&{FungibleToken.Receiver}>(RECEIVER_PATH)
95 let royalty = MetadataViews.Royalty(receiver: royaltyReceiver, cut: asset!.royalty, description: "Solarpups Krikey Creator Royalty")
96 return MetadataViews.Royalties(
97 [royalty]
98 )
99 case Type<MetadataViews.ExternalURL>():
100 return MetadataViews.ExternalURL(url)
101 case Type<MetadataViews.NFTCollectionData>():
102 return MetadataViews.NFTCollectionData(
103 storagePath: SolarpupsNFT.SolarpupsNFTStoragePath,
104 publicPath: SolarpupsNFT.SolarpupsNFTPublicPath,
105 providerPath: SolarpupsNFT.SolarpupsNFTPrivatePath,
106 publicCollection: Type<&SolarpupsNFT.Collection{SolarpupsNFT.CollectionPublic}>(),
107 publicLinkedType: Type<&SolarpupsNFT.Collection{SolarpupsNFT.CollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
108 providerLinkedType: Type<&SolarpupsNFT.Collection{SolarpupsNFT.CollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
109 createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
110 return <-SolarpupsNFT.createEmptyCollection()
111 })
112 )
113 case Type<MetadataViews.NFTCollectionDisplay>():
114 let media = MetadataViews.Media(
115 file: MetadataViews.HTTPFile(
116 url: "https://cdn.solarpups.com/web/assets/img/solar-pups/logo.png"
117 ),
118 mediaType: "image/png"
119 )
120 return MetadataViews.NFTCollectionDisplay(
121 name: "The Krikey Solarpups Collection",
122 description: "The world's most adorable and sensitive pups.",
123 externalURL: MetadataViews.ExternalURL("https://www.solarpups.com/marketplace"),
124 squareImage: media,
125 bannerImage: media,
126 socials: {
127 "discord": MetadataViews.ExternalURL("https://discord.com/invite/krikey"),
128 "facebook": MetadataViews.ExternalURL("https://www.facebook.com/krikeyappAR/"),
129 "twitter": MetadataViews.ExternalURL("https://twitter.com/SolarPupsNFTs"),
130 "instagram": MetadataViews.ExternalURL("https://www.instagram.com/krikeyapp/?hl=en"),
131 "youtube": MetadataViews.ExternalURL("https://www.youtube.com/channel/UCdTV4cmkQwWgaZ89ITMO-bg")
132 }
133 )
134 }
135 return nil
136 }
137 }
138
139 /**
140 * The data of a NFT token. The asset id references to the asset in the
141 * asset registry which holds all the information the NFT is about.
142 */
143 pub struct TokenData {
144 pub let assetId: String
145 pub let edition: UInt16
146
147 init(assetId: String, edition: UInt16) {
148 self.assetId = assetId
149 self.edition = edition
150 }
151 }
152
153 /**
154 * This resource is used to register an asset in order to mint NFT tokens of it.
155 * The asset registry manages the supply of the asset and is also able to lock it.
156 */
157 pub resource AssetRegistry {
158
159 pub fun store(asset: Asset) {
160 pre { SolarpupsNFT.assets[asset.assetId] == nil: "asset id already registered" }
161
162 SolarpupsNFT.assets[asset.assetId] = asset
163 }
164
165 access(contract) fun setMaxSupply(assetId: String) {
166 pre { SolarpupsNFT.assets[assetId] != nil: "asset not found" }
167 SolarpupsNFT.assets[assetId]!.setMaxSupply()
168 }
169
170 }
171
172 /**
173 * This structure defines all the information an asset has. The content
174 * attribute is a IPFS link to a data structure which contains all
175 * the data the NFT asset is about.
176 *
177 */
178 pub struct Asset {
179 pub let assetId: String
180 pub let creators: {Address:UFix64}
181 pub let content: String
182 pub let royalty: UFix64
183 pub let supply: Supply
184
185 access(contract) fun setMaxSupply() {
186 self.supply.setMax(supply: 1)
187 }
188
189 access(contract) fun setCurSupply(supply: UInt16) {
190 self.supply.setCur(supply: supply)
191 }
192
193 init(creators: {Address:UFix64}, assetId: String, content: String) {
194 pre {
195 creators.length > 0: "no address found"
196 }
197
198 var sum:UFix64 = 0.0
199 for value in creators.values {
200 sum = sum + value
201 }
202 assert(sum == 1.0, message: "invalid creator shares")
203
204 self.creators = creators
205 self.assetId = assetId
206 self.content = content
207 self.royalty = 0.05
208 self.supply = Supply(max: 1)
209 }
210 }
211
212 /**
213 * This structure defines all information about the asset supply.
214 */
215 pub struct Supply {
216 pub var max: UInt16
217 pub var cur: UInt16
218
219 access(contract) fun setMax(supply: UInt16) {
220 pre {
221 supply <= self.max: "supply must be lower or equal than current max supply"
222 supply >= self.cur: "supply must be greater or equal than current supply"
223 }
224 self.max = supply
225 }
226
227 access(contract) fun setCur(supply: UInt16) {
228 pre {
229 supply <= self.max: "max supply limit reached"
230 supply > self.cur: "supply must be greater than current supply"
231 }
232 self.cur = supply
233 }
234
235 init(max: UInt16) {
236 self.max = max
237 self.cur = 0
238 }
239 }
240
241 /**
242 * This resource is used by an account to collect Solarpups NFTs.
243 */
244 pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
245 pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
246 pub var ownedAssets: {String: {UInt16:UInt64}}
247
248 init () {
249 self.ownedNFTs <- {}
250 self.ownedAssets = {}
251 }
252
253 pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
254 let token <- (self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")) as! @SolarpupsNFT.NFT
255 self.ownedAssets[token.data.assetId]?.remove(key: token.data.edition)
256 if (self.ownedAssets[token.data.assetId]?.length == 0) {
257 self.ownedAssets.remove(key: token.data.assetId)
258 }
259
260 if (self.owner?.address != nil) {
261 emit Withdraw(id: token.id, from: self.owner?.address!)
262 }
263 return <-token
264 }
265
266 pub fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection {
267 var batchCollection <- create Collection()
268 for id in ids {
269 batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
270 }
271 return <-batchCollection
272 }
273
274 pub fun deposit(token: @NonFungibleToken.NFT) {
275 let token <- token as! @SolarpupsNFT.NFT
276 let id: UInt64 = token.id
277
278 if (self.ownedAssets[token.data.assetId] == nil) {
279 self.ownedAssets[token.data.assetId] = {}
280 }
281 self.ownedAssets[token.data.assetId]!.insert(key: token.data.edition, token.id)
282
283 let oldToken <- self.ownedNFTs[id] <- token
284 if (self.owner?.address != nil) {
285 emit Deposit(id: id, to: self.owner?.address!)
286 }
287 destroy oldToken
288 }
289
290 pub fun batchDeposit(tokens: @NonFungibleToken.Collection) {
291 for key in tokens.getIDs() {
292 self.deposit(token: <-tokens.withdraw(withdrawID: key))
293 }
294 destroy tokens
295 }
296
297 pub fun getIDs(): [UInt64] {
298 return self.ownedNFTs.keys
299 }
300
301 pub fun getAssetIDs(): [String] {
302 return self.ownedAssets.keys
303 }
304
305 pub fun getTokenIDs(assetId: String): [UInt64] {
306 return (self.ownedAssets[assetId] ?? {}).values
307 }
308
309 pub fun getEditions(assetId: String): {UInt16:UInt64} {
310 return self.ownedAssets[assetId] ?? {}
311 }
312
313 pub fun getOwnedAssets(): {String: {UInt16:UInt64}} {
314 return self.ownedAssets
315 }
316
317 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
318 pre {
319 self.ownedNFTs[id] != nil: "this NFT is nil"
320 }
321 let ref = &self.ownedNFTs[id] as &NonFungibleToken.NFT?
322 return ref! as! &NonFungibleToken.NFT
323 }
324
325 pub fun borrowSolarpupsNFT(id: UInt64): &SolarpupsNFT.NFT {
326 pre {
327 self.ownedNFTs[id] != nil: "this NFT is nil"
328 }
329 let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
330 let solarpupsNFT = nft as! &SolarpupsNFT.NFT
331 return solarpupsNFT as &SolarpupsNFT.NFT
332 }
333 pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
334 let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
335 let solarpupsNFT = nft as! &SolarpupsNFT.NFT
336 return solarpupsNFT as &AnyResource{MetadataViews.Resolver}
337 }
338
339 destroy() {
340 destroy self.ownedNFTs
341 self.ownedAssets = {}
342 if (self.owner?.address != nil) {
343 emit CollectionDeleted(from: self.owner?.address!)
344 }
345 }
346 }
347
348 pub fun createEmptyCollection(): @NonFungibleToken.Collection {
349 return <- create Collection()
350 }
351
352 // This is the interface that users can cast their SolarpupsNFT Collection as
353 // to allow others to deposit SolarpupsNFTs into their Collection. It also allows for reading
354 // the details of SolarpupsNFTs in the Collection.
355 pub resource interface CollectionPublic {
356 pub fun getIDs(): [UInt64]
357 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
358 pub fun getAssetIDs(): [String]
359 pub fun deposit(token: @NonFungibleToken.NFT)
360 pub fun batchDeposit(tokens: @NonFungibleToken.Collection)
361 pub fun getTokenIDs(assetId: String): [UInt64]
362 pub fun getEditions(assetId: String): {UInt16:UInt64}
363 pub fun getOwnedAssets(): {String: {UInt16:UInt64}}
364 pub fun borrowSolarpupsNFT(id: UInt64): &NonFungibleToken.NFT? {
365 post {
366 (result == nil) || (result?.id == id):
367 "Cannot borrow SolarpupsNFT reference: the ID of the returned reference is incorrect"
368 }
369 }
370 pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver}
371 }
372
373 pub resource MinterFactory {
374 pub fun createMinter(): @Minter {
375 return <- create Minter()
376 }
377 }
378
379 // This resource is used to mint Solarpups NFTs.
380 pub resource Minter {
381
382 pub fun mint(assetId: String): @NonFungibleToken.Collection {
383 pre {
384 SolarpupsNFT.assets[assetId] != nil: "asset not found"
385 }
386
387 let collection <- create Collection()
388 let supply = SolarpupsNFT.assets[assetId]!.supply
389
390 supply.setCur(supply: supply.cur + (1 as UInt16))
391
392 let data = TokenData(assetId: assetId, edition: supply.cur)
393 let token <- create NFT(id: SolarpupsNFT.totalSupply, data: data, items: <- {})
394 collection.deposit(token: <- token)
395
396 SolarpupsNFT.totalSupply = SolarpupsNFT.totalSupply + (1 as UInt64)
397 emit MintAsset(id: SolarpupsNFT.totalSupply, assetId: assetId)
398 SolarpupsNFT.assets[assetId]!.setCurSupply(supply: supply.cur)
399 return <- collection
400 }
401 }
402
403 access(account) fun getAsset(assetId: String): &SolarpupsNFT.Asset? {
404 pre { self.assets[assetId] != nil: "asset not found" }
405 return &self.assets[assetId] as &SolarpupsNFT.Asset?
406 }
407
408 pub fun getAssetIds(): [String] {
409 return self.assets.keys
410 }
411
412 init() {
413 self.totalSupply = 0
414 self.assets = {}
415
416 self.SolarpupsNFTPublicPath = /public/SolarpupsNFTsProd01
417 self.SolarpupsNFTPrivatePath = /private/SolarpupsNFTsProd01
418 self.SolarpupsNFTStoragePath = /storage/SolarpupsNFTsProd01
419 self.AssetRegistryStoragePath = /storage/SolarpupsAssetRegistryProd01
420 self.MinterFactoryStoragePath = /storage/SolarpupsMinterFactoryProd01
421
422 self.account.save(<- create AssetRegistry(), to: self.AssetRegistryStoragePath)
423 self.account.save(<- create MinterFactory(), to: self.MinterFactoryStoragePath)
424 self.account.save(<- create Collection(), to: self.SolarpupsNFTStoragePath)
425
426 // create a public capability for the collection
427 self.account.link<&SolarpupsNFT.Collection{NonFungibleToken.CollectionPublic, SolarpupsNFT.CollectionPublic, MetadataViews.ResolverCollection}>(
428 self.SolarpupsNFTPublicPath,
429 target: self.SolarpupsNFTStoragePath
430 )
431
432 emit ContractInitialized()
433 }
434
435}
436