Smart Contract
HotspotOperatorNFT
A.efc9bea2fda54f34.HotspotOperatorNFT
1// HotspotOperatorNFT.cdc
2//
3// This contract implements a non-fungible token (NFT) for representing Hotspot Operator NFTs.
4// It allows users to register hotspots and receive coverage rewards.
5
6import NonFungibleToken from 0x1d7e57aa55817448
7import ViewResolver from 0x1d7e57aa55817448
8import MetadataViews from 0x1d7e57aa55817448
9import FIVEGCOIN from 0xefc9bea2fda54f34
10import RandomPicker from 0x85ba07db77467695
11import CrossVMMetadataViews from 0x1d7e57aa55817448
12import EVM from 0xe467b9dd11fa00df
13access(all) contract HotspotOperatorNFT: NonFungibleToken, ViewResolver {
14
15 // Events
16 access(all) event ContractInitialized()
17 access(all) event Withdraw(id: UInt64, from: Address?)
18 access(all) event Deposit(id: UInt64, to: Address?)
19 access(all) event Minted(id: UInt64, recipient: Address?)
20
21 // Named paths
22 access(all) let CollectionStoragePath: StoragePath
23 access(all) let CollectionPublicPath: PublicPath
24
25 // Total supply of HotspotOperatorNFTs in existence
26 access(all) var totalSupply: UInt64
27 access(all) var minters: [Address]
28 access(all) var thumbnails: [String]
29
30 // Represents the metadata for a HotspotOperator NFT
31 access(all) struct HotspotOperatorData {
32 access(all) let name: String
33 access(all) let description: String
34 access(all) let thumbnail: String
35 access(all) let serial: UInt64
36 access(all) let createdAt: UFix64
37
38 init(name: String, description: String, thumbnail: String, serial: UInt64) {
39 self.name = name
40 self.description = description
41 self.thumbnail = thumbnail
42 self.serial = serial
43 self.createdAt = getCurrentBlock().timestamp
44 }
45 }
46
47 //------------------------------------------------------------
48 // NFT Resource
49 //------------------------------------------------------------
50 // The NFT resource that represents ownership rights to a hotspot
51 access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
52 access(all) let id: UInt64
53 access(all) var metadata: HotspotOperatorData
54 access(all) var rewardsVault: @FIVEGCOIN.Vault
55
56 init(thumbnail: String) {
57
58 let metadata: HotspotOperatorData = HotspotOperatorData(
59 name: "Waddle 5G Operator",
60 description: "A 🎶 smooth operator 🎶 that represents ownership rights to a hotspot",
61 thumbnail: thumbnail,
62 serial: HotspotOperatorNFT.totalSupply
63 )
64
65 self.id = self.uuid
66 self.metadata = metadata
67 self.rewardsVault <- FIVEGCOIN.createEmptyVault(vaultType: Type<@FIVEGCOIN.Vault>())
68 }
69
70 access(all) view fun getViews(): [Type] {
71 return [
72 Type<MetadataViews.Display>(),
73 Type<MetadataViews.NFTCollectionDisplay>(),
74 Type<MetadataViews.NFTCollectionData>(),
75 Type<CrossVMMetadataViews.EVMPointer>(),
76 // Type<MetadataViews.EVMBridgedMetadata>(),
77 Type<MetadataViews.ExternalURL>(),
78 Type<MetadataViews.Traits>(),
79 Type<MetadataViews.Serial>()
80 ]
81 }
82
83 access(all) fun resolveView(_ view: Type): AnyStruct? {
84 switch view {
85 case Type<MetadataViews.Display>():
86 return MetadataViews.Display(
87 name: self.metadata.name.concat(" #").concat(self.metadata.serial.toString()),
88 description: self.metadata.description,
89 thumbnail: MetadataViews.HTTPFile(url: self.metadata.thumbnail)
90 )
91 case Type<MetadataViews.NFTCollectionDisplay>():
92 return HotspotOperatorNFT.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionDisplay>())
93 case Type<MetadataViews.NFTCollectionData>():
94 return HotspotOperatorNFT.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>())
95 case Type<CrossVMMetadataViews.EVMPointer>():
96 return HotspotOperatorNFT.resolveContractView(resourceType: nil, viewType: Type<CrossVMMetadataViews.EVMPointer>())
97 // case Type<MetadataViews.EVMBridgedMetadata>():
98 // return MetadataViews.EVMBridgedMetadata(
99 // name: "Waddle 5G Operators",
100 // symbol: "WADDLE",
101 // uri: MetadataViews.URI(
102 // baseURI: "https://metadata-api.production.studio-platform.dapperlabs.com/v1/topshot/moment/", // TODO
103 // value: self.id.toString()
104 // )
105 // )
106 case Type<MetadataViews.ExternalURL>():
107 return MetadataViews.ExternalURL("https://waddle-git-main-basicbeasts.vercel.app/")
108 case Type<MetadataViews.Serial>():
109 return MetadataViews.Serial(
110 self.id
111 )
112 }
113 return nil
114 }
115
116 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
117 return <-HotspotOperatorNFT.createEmptyCollection(nftType: Type<@NFT>())
118 }
119 }
120
121 // Interface that users can cast their Collection as to allow others to deposit HotspotOperatorNFTs
122 access(all) resource interface HotspotOperatorNFTCollectionPublic {
123 access(all) fun deposit(token: @{NonFungibleToken.NFT})
124 access(all) fun getIDs(): [UInt64]
125 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
126 access(all) fun borrowHotspotOperator(id: UInt64): &NFT? {
127 post {
128 (result == nil) || (result?.id == id):
129 "Cannot borrow HotspotOperator reference: The ID of the returned reference is incorrect"
130 }
131 }
132 }
133
134 //------------------------------------------------------------
135 // Collection resource that holds multiple HotspotOperator NFTs
136 //------------------------------------------------------------
137
138 access(all) resource Collection: HotspotOperatorNFTCollectionPublic, NonFungibleToken.Collection, ViewResolver.ResolverCollection {
139
140 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
141
142 init() {
143 self.ownedNFTs <- {}
144 }
145
146 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
147 let supportedTypes: {Type: Bool} = {}
148 supportedTypes[Type<@HotspotOperatorNFT.NFT>()] = true
149 return supportedTypes
150 }
151
152 access(all) view fun isSupportedNFTType(type: Type): Bool {
153 if type == Type<@HotspotOperatorNFT.NFT>() {
154 return true
155 }
156 return false
157 }
158
159 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
160 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
161
162 emit Withdraw(id: token.id, from: self.owner?.address)
163
164 return <-token
165 }
166
167 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
168 let token <- token as! @HotspotOperatorNFT.NFT
169 let id: UInt64 = token.id
170
171 let oldToken <- self.ownedNFTs[id] <- token
172
173 emit Deposit(id: id, to: self.owner?.address)
174
175 destroy oldToken
176 }
177
178 access(all) view fun getIDs(): [UInt64] {
179 return self.ownedNFTs.keys
180 }
181
182 access(all) view fun getLength(): Int {
183 return self.ownedNFTs.keys.length
184 }
185
186 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
187 return &self.ownedNFTs[id]
188 }
189
190 access(all) fun borrowHotspotOperator(id: UInt64): &NFT? {
191 return self.borrowNFT(id) as! &NFT?
192 }
193
194 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
195 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
196 return nft as &{ViewResolver.Resolver}
197 }
198 return nil
199 }
200
201 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
202 return <-HotspotOperatorNFT.createEmptyCollection(nftType: Type<@NFT>())
203 }
204 }
205
206 //------------------------------------------------------------
207 // Contract Secure Onchain Random NFT Minting
208 //------------------------------------------------------------
209
210 access(all) fun mintNFTCommit(): @RandomPicker.Receipt {
211 var values: [UInt64] = []
212 var i: UInt64 = 0
213 while i < UInt64(HotspotOperatorNFT.thumbnails.length) {
214 values.append(i)
215 i = i + 1
216 }
217 return <-RandomPicker.commit(values: values)
218 }
219
220 access(all) fun mintNFTReveal(recipient: &{HotspotOperatorNFTCollectionPublic}, receipt: @RandomPicker.Receipt) {
221
222 let randomIndex: UInt64 = RandomPicker.reveal(receipt: <-receipt)
223
224 let newNFT: @HotspotOperatorNFT.NFT <- create NFT(thumbnail: HotspotOperatorNFT.thumbnails[randomIndex])
225
226 recipient.deposit(token: <-newNFT)
227
228 HotspotOperatorNFT.totalSupply = HotspotOperatorNFT.totalSupply + 1
229
230 emit Minted(id: HotspotOperatorNFT.totalSupply - 1, recipient: recipient.owner?.address)
231 }
232
233 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
234 return <- create Collection()
235 }
236
237 //------------------------------------------------------------
238 // Contract MetadataViews
239 //------------------------------------------------------------
240
241 access(all) view fun getContractViews(resourceType: Type?): [Type] {
242 return [
243 Type<MetadataViews.NFTCollectionDisplay>(),
244 Type<MetadataViews.NFTCollectionData>(),
245 Type<CrossVMMetadataViews.EVMPointer>()
246 ]
247 }
248
249 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
250 switch viewType {
251 case Type<MetadataViews.NFTCollectionDisplay>():
252 let externalURL = MetadataViews.ExternalURL("https://waddle-git-main-basicbeasts.vercel.app/")
253 let squareImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_logo.png"), mediaType: "image/png")
254 let bannerImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_banner_1.png"), mediaType: "image/png")
255 let socialMap:{ String: MetadataViews.ExternalURL} ={ "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain"), "discord": MetadataViews.ExternalURL("https://discord.com/invite/xgFtWhwSaR")}
256 return MetadataViews.NFTCollectionDisplay(
257 name: "Waddle 5G Operators",
258 description: "An experimental NFT collection for Waddle 5G Operators",
259 externalURL: externalURL,
260 squareImage: squareImage,
261 bannerImage: bannerImage,
262 socials: socialMap
263 )
264 case Type<MetadataViews.NFTCollectionData>():
265 return MetadataViews.NFTCollectionData(
266 storagePath: /storage/HotspotOperatorNFTCollection_1,
267 publicPath: /public/HotspotOperatorNFTCollection_1,
268 publicCollection: Type<&HotspotOperatorNFT.Collection>(),
269 publicLinkedType: Type<&HotspotOperatorNFT.Collection>(),
270 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
271 return <- HotspotOperatorNFT.createEmptyCollection(nftType: Type<@NFT>())
272 })
273 )
274 case Type<CrossVMMetadataViews.EVMPointer>():
275 return CrossVMMetadataViews.EVMPointer(
276 cadenceType: Type<@HotspotOperatorNFT.NFT>(),
277 cadenceContractAddress: self.account.address,
278 evmContractAddress: EVM.addressFromString("0xf9780aa473942a68b4c4387fd256c179ccd132ae"),
279 nativeVM: CrossVMMetadataViews.VM.Cadence
280 )
281 }
282 return nil
283 }
284
285 //------------------------------------------------------------
286 // Contract Initialization
287 //------------------------------------------------------------
288
289 init() {
290 self.totalSupply = 0
291 self.minters = []
292 self.thumbnails = [
293 "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_operator_1.png",
294 "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_operator_2.png",
295 "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_operator_3.png",
296 "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_operator_4.png",
297 "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_operator_5.png",
298 "https://raw.githubusercontent.com/bz-hashtag-0780/waddle/refs/heads/main/images/waddle_operator_6.png"
299 ]
300
301 self.CollectionStoragePath = /storage/HotspotOperatorNFTCollection_1
302 self.CollectionPublicPath = /public/HotspotOperatorNFTCollection_1
303
304 emit ContractInitialized()
305 }
306}