Smart Contract
CryptoPiggo
A.d3df824bf81910a4.CryptoPiggo
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 ViewResolver from 0x1d7e57aa55817448
18import FungibleToken from 0xf233dcee88fe0abe
19
20access(all) contract CryptoPiggo: NonFungibleToken {
21 access(all) var totalSupply: UInt64
22 access(all) let maxSupply: UInt64
23
24 access(all) event Minted(id: UInt64, initMeta: {String: String})
25
26 access(all) let CollectionStoragePath: StoragePath
27 access(all) let CollectionPublicPath: PublicPath
28 access(all) let MinterStoragePath: StoragePath
29
30 // idToAddress
31 // Maps each item ID to its current owner
32 //
33 access(self) let idToAddress: [Address]
34
35 access(all) resource NFT: NonFungibleToken.NFT {
36 access(all) let id: UInt64
37 access(self) let metadata: {String: String}
38
39 access(all) fun getMetadata(): {String: String} {
40 return self.metadata
41 }
42
43 init(initID: UInt64, initMeta: {String: String}) {
44 self.id = initID
45 self.metadata = initMeta
46 }
47
48 access(all) view fun getViews(): [Type] {
49 return [
50 Type<MetadataViews.Display>(),
51 Type<MetadataViews.Royalties>(),
52 Type<MetadataViews.Editions>(),
53 Type<MetadataViews.ExternalURL>(),
54 Type<MetadataViews.NFTCollectionData>(),
55 Type<MetadataViews.NFTCollectionDisplay>(),
56 Type<MetadataViews.Serial>(),
57 Type<MetadataViews.Traits>()
58 ]
59 }
60
61 access(all) fun resolveView(_ view: Type): AnyStruct? {
62 switch view {
63 case Type<MetadataViews.Display>():
64 return MetadataViews.Display(
65 name: "Crypto Piggo NFT",
66 description: "Crypto Piggo NFT #".concat(self.id.toString()),
67 thumbnail: MetadataViews.HTTPFile(
68 url: "https://s3.us-west-2.amazonaws.com/crypto-piggo.nft/piggo-".concat(self.id.toString()).concat(".png")
69 )
70 )
71
72 case Type<MetadataViews.Editions>():
73 // There is no max number of NFTs that can be minted from this contract
74 // so the max edition field value is set to nil
75 return MetadataViews.Editions([
76 MetadataViews.Edition(
77 name: "Crypto Piggo NFT Edition",
78 number: self.id,
79 max: nil
80 )
81 ])
82
83 case Type<MetadataViews.Serial>():
84 return MetadataViews.Serial(self.id)
85
86 case Type<MetadataViews.Royalties>():
87 let royalties: [MetadataViews.Royalty] = []
88 let receiver = getAccount(0x4cfbe4c6abc0e12a)
89 .capabilities.get<&{FungibleToken.Receiver}>(
90 MetadataViews.getRoyaltyReceiverPublicPath()
91 )
92 if receiver.check() {
93 royalties.append(
94 MetadataViews.Royalty(
95 receiver: receiver,
96 cut: 0.07,
97 description: ""
98 )
99 )
100 }
101
102 return MetadataViews.Royalties(royalties)
103
104 case Type<MetadataViews.ExternalURL>():
105 return MetadataViews.ExternalURL("https://s3.us-west-2.amazonaws.com/crypto-piggo.nft/piggo-".concat(self.id.toString()).concat(".png"))
106
107 case Type<MetadataViews.NFTCollectionData>():
108 return CryptoPiggo.resolveContractView(resourceType: Type<@NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
109
110 case Type<MetadataViews.NFTCollectionDisplay>():
111 return CryptoPiggo.resolveContractView(resourceType: Type<@NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
112
113 case Type<MetadataViews.Traits>():
114 return MetadataViews.dictToTraits(dict: self.metadata, excludedNames: [])
115 }
116 return nil
117 }
118
119 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
120 return <- CryptoPiggo.createEmptyCollection(nftType: Type<@NFT>())
121 }
122 }
123
124 access(all) resource interface CryptoPiggoCollectionPublic {}
125
126 access(all) resource Collection: CryptoPiggoCollectionPublic, NonFungibleToken.Collection {
127 // dictionary of NFT conforming tokens
128 // NFT is a resource type with an `UInt64` ID field
129 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
130
131 init () {
132 self.ownedNFTs <- {}
133 }
134
135 // withdraw removes an NFT from the collection and moves it to the caller
136 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
137 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
138
139 return <-token
140 }
141
142 // deposit takes a NFT and adds it to the collections dictionary
143 // and adds the ID to the id array
144 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
145 let token <- token as! @CryptoPiggo.NFT
146
147 let id: UInt64 = token.id
148
149 // add the new token to the dictionary which removes the old one
150 let oldToken <- self.ownedNFTs[id] <- token
151
152 // update owner
153 CryptoPiggo.idToAddress[id] = self.owner!.address
154
155 destroy oldToken
156 }
157
158 // transfer takes an NFT ID and a reference to a recipient's collection
159 // and transfers the NFT corresponding to that ID to the recipient
160 access(all) fun transfer(id: UInt64, recipient: &{NonFungibleToken.CollectionPublic}) {
161 post {
162 self.ownedNFTs[id] == nil: "The specified NFT was not transferred"
163 recipient.borrowNFT(id) != nil: "Recipient did not receive the intended NFT"
164 }
165
166 let nft <- self.withdraw(withdrawID: id)
167
168 recipient.deposit(token: <- nft)
169 }
170
171 // burn destroys an NFT
172 access(all) fun burn(id: UInt64) {
173 post {
174 self.ownedNFTs[id] == nil: "The specified NFT was not burned"
175 }
176
177 destroy <- self.withdraw(withdrawID: id)
178 }
179
180 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
181 let supportedTypes: {Type: Bool} = {}
182 supportedTypes[Type<@NFT>()] = true
183 return supportedTypes
184 }
185
186 access(all) view fun isSupportedNFTType(type: Type): Bool {
187 return type == Type<@NFT>()
188 }
189
190 // getIDs returns an array of the IDs that are in the collection
191 access(all) view fun getIDs(): [UInt64] {
192 return self.ownedNFTs.keys
193 }
194
195 // borrowNFT gets a reference to an NFT in the collection
196 // so that the caller can read its metadata and call its methods
197 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
198 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
199 }
200
201 // borrowItem gets a reference to an NFT in the collection as a CryptoPiggo,
202 // exposing all of its fields. This is safe as there are no functions that
203 // can be called on the CryptoPiggo.
204 access(all) fun borrowItem(id: UInt64): &CryptoPiggo.NFT? {
205 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
206 return nft as! &NFT
207 }
208 return nil
209 }
210
211 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
212 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
213 return nft as &{ViewResolver.Resolver}
214 }
215 return nil
216 }
217
218 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
219 return <- CryptoPiggo.createEmptyCollection(nftType: Type<@NFT>())
220 }
221 }
222
223 // createEmptyCollection
224 // public function that anyone can call to create a new empty collection
225 //
226 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
227 return <- create Collection()
228 }
229
230 // getMetadataCID
231 // public function that anyone can call to get the IPFS CID of the JSON file with
232 // all the NFT metadata of this collection. The file will not contain any of the
233 // NFTs burned by the admin account.
234 //
235 access(all) fun getMetadataCID(): MetadataViews.IPFSFile {
236 return MetadataViews.IPFSFile(
237 cid: "QmTd2TspsYLNLsg7HrGcmHhCAJcSkQbLKGWJKPwsLGQuvq",
238 path: nil
239 )
240 }
241
242 // getMetadataURL
243 // public function that anyone can call to get the IPFS URL of the JSON file with
244 // all the NFT metadata of this collection. The file will not contain any of the
245 // NFTs burned by the admin account.
246 //
247 access(all) fun getMetadataURL(): String {
248 return "https://ipfs.tenzingai.com/ipfs/QmTd2TspsYLNLsg7HrGcmHhCAJcSkQbLKGWJKPwsLGQuvq"
249 }
250
251 // NFTMinter
252 // Resource that an admin or something similar would own to be
253 // able to mint new NFTs
254 //
255 access(all) resource NFTMinter {
256 // mintNFT
257 // Mints a new NFT with a new ID
258 // and deposit it in the recipients collection using their collection reference
259 //
260 access(all) fun mintNFT(recipient: Address, initMetadata: {String: String}) {
261 let nftID = CryptoPiggo.totalSupply
262 if nftID < CryptoPiggo.maxSupply {
263 let receiver = getAccount(recipient)
264 .capabilities.borrow<&{NonFungibleToken.CollectionPublic}>(CryptoPiggo.CollectionPublicPath)
265 ?? panic("Could not get receiver reference to the NFT Collection")
266 emit Minted(id: nftID, initMeta: initMetadata)
267 CryptoPiggo.idToAddress.append(recipient)
268 CryptoPiggo.totalSupply = nftID + 1
269 receiver.deposit(token: <-create CryptoPiggo.NFT(initID: nftID, initMeta: initMetadata))
270 } else {
271 panic("No more piggos can be minted")
272 }
273 }
274 }
275
276 // getOwner
277 // Gets the current owner of the given item
278 //
279 access(all) fun getOwner(itemID: UInt64): Address? {
280 if itemID >= 0 && itemID < self.maxSupply {
281 if (itemID < CryptoPiggo.totalSupply) {
282 return CryptoPiggo.idToAddress[itemID]
283 } else {
284 return nil
285 }
286 }
287 return nil
288 }
289
290 access(all) view fun getContractViews(resourceType: Type?): [Type] {
291 return [
292 Type<MetadataViews.NFTCollectionData>(),
293 Type<MetadataViews.NFTCollectionDisplay>()
294 ]
295 }
296
297 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
298 switch viewType {
299 case Type<MetadataViews.NFTCollectionData>():
300 let collectionData = MetadataViews.NFTCollectionData(
301 storagePath: self.CollectionStoragePath,
302 publicPath: self.CollectionPublicPath,
303 publicCollection: Type<&Collection>(),
304 publicLinkedType: Type<&Collection>(),
305 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
306 return <- CryptoPiggo.createEmptyCollection(nftType: Type<@NFT>())
307 })
308 )
309 return collectionData
310 case Type<MetadataViews.NFTCollectionDisplay>():
311 let media = MetadataViews.Media(
312 file: MetadataViews.HTTPFile(url: "https://ipfs.tenzingai.com/ipfs/QmUk3s7BoVSS56V2U4rxd1syVp8USUEygu7NmARppH183U"),
313 mediaType: "image/png"
314 )
315 return MetadataViews.NFTCollectionDisplay(
316 name: "The Crypto Piggo NFT Collection",
317 description: "",
318 externalURL: MetadataViews.ExternalURL("https://www.rareworx.com/"),
319 squareImage: media,
320 bannerImage: media,
321 socials: {}
322 )
323 }
324 return nil
325 }
326
327 // initializer
328 //
329 // test update
330 init() {
331 // Set our named paths
332 self.CollectionStoragePath = /storage/CryptoPiggoCollection
333 self.CollectionPublicPath = /public/CryptoPiggoCollection
334 self.MinterStoragePath = /storage/CryptoPiggoMinter
335
336 // Initialize the total supply
337 self.totalSupply = 0
338
339 // Initialize the max supply
340 self.maxSupply = 10000
341
342 // Initalize mapping from ID to address
343 self.idToAddress = []
344
345 // Create a Minter resource and save it to storage
346 let minter <- create NFTMinter()
347 self.account.storage.save(<-minter, to: self.MinterStoragePath)
348 }
349}
350