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