@flowtyio/flow-contracts 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contracts/emerald-city/FLOAT.cdc +1161 -0
- package/contracts/find/FindViews.cdc +392 -0
- package/flow.json +19 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1161 @@
|
|
|
1
|
+
// MADE BY: Emerald City, Jacob Tucker
|
|
2
|
+
|
|
3
|
+
// This contract is for FLOAT, a proof of participation platform
|
|
4
|
+
// on Flow. It is similar to POAP, but a lot, lot cooler. ;)
|
|
5
|
+
|
|
6
|
+
// The main idea is that FLOATs are simply NFTs. They are minted from
|
|
7
|
+
// a FLOATEvent, which is basically an event that a host starts. For example,
|
|
8
|
+
// if I have a Twitter space and want to create an event for it, I can create
|
|
9
|
+
// a new FLOATEvent in my FLOATEvents collection and mint FLOATs to people
|
|
10
|
+
// from this Twitter space event representing that they were there.
|
|
11
|
+
|
|
12
|
+
// The complicated part is the FLOATVerifiers contract. That contract
|
|
13
|
+
// defines a list of "verifiers" that can be tagged onto a FLOATEvent to make
|
|
14
|
+
// the claiming more secure. For example, a host can decide to put a time
|
|
15
|
+
// constraint on when users can claim a FLOAT. They would do that by passing in
|
|
16
|
+
// a Timelock struct (defined in FLOATVerifiers.cdc) with a time period for which
|
|
17
|
+
// users can claim.
|
|
18
|
+
|
|
19
|
+
// For a whole list of verifiers, see FLOATVerifiers.cdc
|
|
20
|
+
|
|
21
|
+
import "NonFungibleToken"
|
|
22
|
+
import "MetadataViews"
|
|
23
|
+
import "FungibleToken"
|
|
24
|
+
import "FlowToken"
|
|
25
|
+
import "FindViews"
|
|
26
|
+
import "ViewResolver"
|
|
27
|
+
|
|
28
|
+
pub contract FLOAT: NonFungibleToken, ViewResolver {
|
|
29
|
+
|
|
30
|
+
/***********************************************/
|
|
31
|
+
/******************** PATHS ********************/
|
|
32
|
+
/***********************************************/
|
|
33
|
+
|
|
34
|
+
pub let FLOATCollectionStoragePath: StoragePath
|
|
35
|
+
pub let FLOATCollectionPublicPath: PublicPath
|
|
36
|
+
pub let FLOATEventsStoragePath: StoragePath
|
|
37
|
+
pub let FLOATEventsPublicPath: PublicPath
|
|
38
|
+
pub let FLOATEventsPrivatePath: PrivatePath
|
|
39
|
+
|
|
40
|
+
/************************************************/
|
|
41
|
+
/******************** EVENTS ********************/
|
|
42
|
+
/************************************************/
|
|
43
|
+
|
|
44
|
+
pub event ContractInitialized()
|
|
45
|
+
pub event FLOATMinted(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, recipient: Address, serial: UInt64)
|
|
46
|
+
pub event FLOATClaimed(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, eventName: String, recipient: Address, serial: UInt64)
|
|
47
|
+
pub event FLOATDestroyed(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, serial: UInt64)
|
|
48
|
+
pub event FLOATTransferred(id: UInt64, eventHost: Address, eventId: UInt64, newOwner: Address?, serial: UInt64)
|
|
49
|
+
pub event FLOATPurchased(id: UInt64, eventHost: Address, eventId: UInt64, recipient: Address, serial: UInt64)
|
|
50
|
+
pub event FLOATEventCreated(eventId: UInt64, description: String, host: Address, image: String, name: String, url: String)
|
|
51
|
+
pub event FLOATEventDestroyed(eventId: UInt64, host: Address, name: String)
|
|
52
|
+
|
|
53
|
+
pub event Deposit(id: UInt64, to: Address?)
|
|
54
|
+
pub event Withdraw(id: UInt64, from: Address?)
|
|
55
|
+
|
|
56
|
+
/***********************************************/
|
|
57
|
+
/******************** STATE ********************/
|
|
58
|
+
/***********************************************/
|
|
59
|
+
|
|
60
|
+
// The total amount of FLOATs that have ever been
|
|
61
|
+
// created (does not go down when a FLOAT is destroyed)
|
|
62
|
+
pub var totalSupply: UInt64
|
|
63
|
+
// The total amount of FLOATEvents that have ever been
|
|
64
|
+
// created (does not go down when a FLOATEvent is destroyed)
|
|
65
|
+
pub var totalFLOATEvents: UInt64
|
|
66
|
+
|
|
67
|
+
/***********************************************/
|
|
68
|
+
/**************** FUNCTIONALITY ****************/
|
|
69
|
+
/***********************************************/
|
|
70
|
+
|
|
71
|
+
// A helpful wrapper to contain an address,
|
|
72
|
+
// the id of a FLOAT, and its serial
|
|
73
|
+
pub struct TokenIdentifier {
|
|
74
|
+
pub let id: UInt64
|
|
75
|
+
pub let address: Address
|
|
76
|
+
pub let serial: UInt64
|
|
77
|
+
|
|
78
|
+
init(_id: UInt64, _address: Address, _serial: UInt64) {
|
|
79
|
+
self.id = _id
|
|
80
|
+
self.address = _address
|
|
81
|
+
self.serial = _serial
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub struct TokenInfo {
|
|
86
|
+
pub let path: PublicPath
|
|
87
|
+
pub let price: UFix64
|
|
88
|
+
|
|
89
|
+
init(_path: PublicPath, _price: UFix64) {
|
|
90
|
+
self.path = _path
|
|
91
|
+
self.price = _price
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Represents a FLOAT
|
|
96
|
+
pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
|
|
97
|
+
// The `uuid` of this resource
|
|
98
|
+
pub let id: UInt64
|
|
99
|
+
|
|
100
|
+
// Some of these are also duplicated on the event,
|
|
101
|
+
// but it's necessary to put them here as well
|
|
102
|
+
// in case the FLOATEvent host deletes the event
|
|
103
|
+
pub let dateReceived: UFix64
|
|
104
|
+
pub let eventDescription: String
|
|
105
|
+
pub let eventHost: Address
|
|
106
|
+
pub let eventId: UInt64
|
|
107
|
+
pub let eventImage: String
|
|
108
|
+
pub let eventName: String
|
|
109
|
+
pub let originalRecipient: Address
|
|
110
|
+
pub let serial: UInt64
|
|
111
|
+
|
|
112
|
+
// A capability that points to the FLOATEvents this FLOAT is from.
|
|
113
|
+
// There is a chance the event host unlinks their event from
|
|
114
|
+
// the public, in which case it's impossible to know details
|
|
115
|
+
// about the event. Which is fine, since we store the
|
|
116
|
+
// crucial data to know about the FLOAT in the FLOAT itself.
|
|
117
|
+
pub let eventsCap: Capability<&FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection}>
|
|
118
|
+
|
|
119
|
+
// Helper function to get the metadata of the event
|
|
120
|
+
// this FLOAT is from.
|
|
121
|
+
pub fun getEventRef(): &FLOATEvent{FLOATEventPublic}? {
|
|
122
|
+
if let events: &FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection} = self.eventsCap.borrow() {
|
|
123
|
+
return events.borrowPublicEventRef(eventId: self.eventId)
|
|
124
|
+
}
|
|
125
|
+
return nil
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
pub fun getExtraMetadata(): {String: AnyStruct} {
|
|
129
|
+
if let event: &FLOATEvent{FLOATEventPublic} = self.getEventRef() {
|
|
130
|
+
return event.getExtraFloatMetadata(serial: self.serial)
|
|
131
|
+
}
|
|
132
|
+
return {}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pub fun getSpecificExtraMetadata(key: String): AnyStruct? {
|
|
136
|
+
return self.getExtraMetadata()[key]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pub fun getImage(): String {
|
|
140
|
+
if let extraEventMetadata: {String: AnyStruct} = self.getEventRef()?.getExtraMetadata() {
|
|
141
|
+
if FLOAT.extraMetadataToStrOpt(extraEventMetadata, "visibilityMode") == "picture" {
|
|
142
|
+
return self.eventImage
|
|
143
|
+
}
|
|
144
|
+
if let certificateType: String = FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateType") {
|
|
145
|
+
if certificateType == "medal" {
|
|
146
|
+
// Extra metadata about medal colors
|
|
147
|
+
if let medalType: String = FLOAT.extraMetadataToStrOpt(self.getExtraMetadata(), "medalType") {
|
|
148
|
+
return FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateImage.".concat(medalType)) ?? self.eventImage
|
|
149
|
+
}
|
|
150
|
+
// if there is no medal type for the FLOAT
|
|
151
|
+
return FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateImage.participation") ?? self.eventImage
|
|
152
|
+
}
|
|
153
|
+
return FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateImage") ?? self.eventImage
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return self.eventImage
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// This is for the MetdataStandard
|
|
160
|
+
pub fun getViews(): [Type] {
|
|
161
|
+
let supportedViews = [
|
|
162
|
+
Type<MetadataViews.Display>(),
|
|
163
|
+
Type<MetadataViews.Royalties>(),
|
|
164
|
+
Type<MetadataViews.ExternalURL>(),
|
|
165
|
+
Type<MetadataViews.NFTCollectionData>(),
|
|
166
|
+
Type<MetadataViews.NFTCollectionDisplay>(),
|
|
167
|
+
Type<MetadataViews.Traits>(),
|
|
168
|
+
Type<MetadataViews.Serial>(),
|
|
169
|
+
Type<TokenIdentifier>()
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
if self.getEventRef()?.transferrable == false {
|
|
173
|
+
supportedViews.append(Type<FindViews.SoulBound>())
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return supportedViews
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// This is for the MetdataStandard
|
|
180
|
+
pub fun resolveView(_ view: Type): AnyStruct? {
|
|
181
|
+
switch view {
|
|
182
|
+
case Type<MetadataViews.Display>():
|
|
183
|
+
return MetadataViews.Display(
|
|
184
|
+
name: self.eventName,
|
|
185
|
+
description: self.eventDescription,
|
|
186
|
+
thumbnail: MetadataViews.HTTPFile(url: "https://nftstorage.link/ipfs/".concat(self.getImage()))
|
|
187
|
+
)
|
|
188
|
+
case Type<MetadataViews.Royalties>():
|
|
189
|
+
return MetadataViews.Royalties([
|
|
190
|
+
MetadataViews.Royalty(
|
|
191
|
+
recepient: getAccount(0x5643fd47a29770e7).getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver),
|
|
192
|
+
cut: 0.05, // 5% royalty on secondary sales
|
|
193
|
+
description: "Emerald City DAO receives a 5% royalty from secondary sales because this NFT was created using FLOAT (https://floats.city/), a proof of attendance platform created by Emerald City DAO."
|
|
194
|
+
)
|
|
195
|
+
])
|
|
196
|
+
case Type<MetadataViews.ExternalURL>():
|
|
197
|
+
return MetadataViews.ExternalURL("https://floats.city/".concat(self.owner!.address.toString()).concat("/float/").concat(self.id.toString()))
|
|
198
|
+
case Type<MetadataViews.NFTCollectionData>():
|
|
199
|
+
return FLOAT.resolveView(view)
|
|
200
|
+
case Type<MetadataViews.NFTCollectionDisplay>():
|
|
201
|
+
return FLOAT.resolveView(view)
|
|
202
|
+
case Type<MetadataViews.Serial>():
|
|
203
|
+
return MetadataViews.Serial(
|
|
204
|
+
self.serial
|
|
205
|
+
)
|
|
206
|
+
case Type<TokenIdentifier>():
|
|
207
|
+
return TokenIdentifier(
|
|
208
|
+
_id: self.id,
|
|
209
|
+
_address: self.owner!.address,
|
|
210
|
+
_serial: self.serial
|
|
211
|
+
)
|
|
212
|
+
case Type<FindViews.SoulBound>():
|
|
213
|
+
if self.getEventRef()?.transferrable == false {
|
|
214
|
+
return FindViews.SoulBound(
|
|
215
|
+
"This FLOAT is soulbound because the event host toggled off transferring."
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
return nil
|
|
219
|
+
case Type<MetadataViews.Traits>():
|
|
220
|
+
let traitsView: MetadataViews.Traits = MetadataViews.dictToTraits(dict: self.getExtraMetadata(), excludedNames: nil)
|
|
221
|
+
|
|
222
|
+
if let eventRef: &FLOATEvent{FLOATEventPublic} = self.getEventRef() {
|
|
223
|
+
let eventExtraMetadata: {String: AnyStruct} = eventRef.getExtraMetadata()
|
|
224
|
+
|
|
225
|
+
let certificateType: MetadataViews.Trait = MetadataViews.Trait(name: "certificateType", value: eventExtraMetadata["certificateType"], displayType: nil, rarity: nil)
|
|
226
|
+
traitsView.addTrait(certificateType)
|
|
227
|
+
|
|
228
|
+
let eventType: MetadataViews.Trait = MetadataViews.Trait(name: "eventType", value: eventExtraMetadata["eventType"], displayType: nil, rarity: nil)
|
|
229
|
+
traitsView.addTrait(eventType)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return traitsView
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return nil
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
init(_eventDescription: String, _eventHost: Address, _eventId: UInt64, _eventImage: String, _eventName: String, _originalRecipient: Address, _serial: UInt64) {
|
|
239
|
+
self.id = self.uuid
|
|
240
|
+
self.dateReceived = getCurrentBlock().timestamp
|
|
241
|
+
self.eventDescription = _eventDescription
|
|
242
|
+
self.eventHost = _eventHost
|
|
243
|
+
self.eventId = _eventId
|
|
244
|
+
self.eventImage = _eventImage
|
|
245
|
+
self.eventName = _eventName
|
|
246
|
+
self.originalRecipient = _originalRecipient
|
|
247
|
+
self.serial = _serial
|
|
248
|
+
|
|
249
|
+
// Stores a capability to the FLOATEvents of its creator
|
|
250
|
+
self.eventsCap = getAccount(_eventHost)
|
|
251
|
+
.getCapability<&FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection}>(FLOAT.FLOATEventsPublicPath)
|
|
252
|
+
|
|
253
|
+
emit FLOATMinted(
|
|
254
|
+
id: self.id,
|
|
255
|
+
eventHost: _eventHost,
|
|
256
|
+
eventId: _eventId,
|
|
257
|
+
eventImage: _eventImage,
|
|
258
|
+
recipient: _originalRecipient,
|
|
259
|
+
serial: _serial
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
FLOAT.totalSupply = FLOAT.totalSupply + 1
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
destroy() {
|
|
266
|
+
emit FLOATDestroyed(
|
|
267
|
+
id: self.id,
|
|
268
|
+
eventHost: self.eventHost,
|
|
269
|
+
eventId: self.eventId,
|
|
270
|
+
eventImage: self.eventImage,
|
|
271
|
+
serial: self.serial
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// A public interface for people to call into our Collection
|
|
277
|
+
pub resource interface CollectionPublic {
|
|
278
|
+
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
|
|
279
|
+
pub fun borrowFLOAT(id: UInt64): &NFT?
|
|
280
|
+
pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}
|
|
281
|
+
pub fun deposit(token: @NonFungibleToken.NFT)
|
|
282
|
+
pub fun getIDs(): [UInt64]
|
|
283
|
+
pub fun getAllIDs(): [UInt64]
|
|
284
|
+
pub fun ownedIdsFromEvent(eventId: UInt64): [UInt64]
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// A Collection that holds all of the users FLOATs.
|
|
288
|
+
// Withdrawing is not allowed. You can only transfer.
|
|
289
|
+
pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection, CollectionPublic {
|
|
290
|
+
// Maps a FLOAT id to the FLOAT itself
|
|
291
|
+
pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
|
|
292
|
+
// Maps an eventId to the ids of FLOATs that
|
|
293
|
+
// this user owns from that event. It's possible
|
|
294
|
+
// for it to be out of sync until June 2022 spork,
|
|
295
|
+
// but it is used merely as a helper, so that's okay.
|
|
296
|
+
access(self) var events: {UInt64: {UInt64: Bool}}
|
|
297
|
+
|
|
298
|
+
// Deposits a FLOAT to the collection
|
|
299
|
+
pub fun deposit(token: @NonFungibleToken.NFT) {
|
|
300
|
+
let nft <- token as! @NFT
|
|
301
|
+
let id = nft.id
|
|
302
|
+
let eventId = nft.eventId
|
|
303
|
+
|
|
304
|
+
// Update self.events[eventId] to have
|
|
305
|
+
// this FLOAT's id in it
|
|
306
|
+
if self.events[eventId] == nil {
|
|
307
|
+
self.events[eventId] = {id: true}
|
|
308
|
+
} else {
|
|
309
|
+
self.events[eventId]!.insert(key: id, true)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
emit Deposit(id: id, to: self.owner!.address)
|
|
313
|
+
emit FLOATTransferred(id: id, eventHost: nft.eventHost, eventId: nft.eventId, newOwner: self.owner!.address, serial: nft.serial)
|
|
314
|
+
self.ownedNFTs[id] <-! nft
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
|
|
318
|
+
let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("You do not own this FLOAT in your collection")
|
|
319
|
+
let nft <- token as! @NFT
|
|
320
|
+
|
|
321
|
+
// Update self.events[eventId] to not
|
|
322
|
+
// have this FLOAT's id in it
|
|
323
|
+
self.events[nft.eventId]!.remove(key: withdrawID)
|
|
324
|
+
|
|
325
|
+
// This checks if the FLOATEvent host wanted this
|
|
326
|
+
// FLOAT to be transferrable. Secondary marketplaces will use this
|
|
327
|
+
// withdraw function, so if the FLOAT is not transferrable,
|
|
328
|
+
// you can't sell it there.
|
|
329
|
+
if let floatEvent: &FLOATEvent{FLOATEventPublic} = nft.getEventRef() {
|
|
330
|
+
assert(
|
|
331
|
+
floatEvent.transferrable,
|
|
332
|
+
message: "This FLOAT is not transferrable."
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
emit Withdraw(id: withdrawID, from: self.owner!.address)
|
|
337
|
+
emit FLOATTransferred(id: withdrawID, eventHost: nft.eventHost, eventId: nft.eventId, newOwner: nil, serial: nft.serial)
|
|
338
|
+
return <- nft
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
pub fun delete(id: UInt64) {
|
|
342
|
+
let token <- self.ownedNFTs.remove(key: id) ?? panic("You do not own this FLOAT in your collection")
|
|
343
|
+
let nft <- token as! @NFT
|
|
344
|
+
|
|
345
|
+
// Update self.events[eventId] to not
|
|
346
|
+
// have this FLOAT's id in it
|
|
347
|
+
self.events[nft.eventId]!.remove(key: id)
|
|
348
|
+
|
|
349
|
+
destroy nft
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Only returns the FLOATs for which we can still
|
|
353
|
+
// access data about their event.
|
|
354
|
+
pub fun getIDs(): [UInt64] {
|
|
355
|
+
let ids: [UInt64] = []
|
|
356
|
+
for key in self.ownedNFTs.keys {
|
|
357
|
+
let nftRef = self.borrowFLOAT(id: key)!
|
|
358
|
+
if nftRef.eventsCap.check() {
|
|
359
|
+
ids.append(key)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return ids
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Returns all the FLOATs ids
|
|
366
|
+
pub fun getAllIDs(): [UInt64] {
|
|
367
|
+
return self.ownedNFTs.keys
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Returns an array of ids that belong to
|
|
371
|
+
// the passed in eventId
|
|
372
|
+
//
|
|
373
|
+
// It's possible for FLOAT ids to be present that
|
|
374
|
+
// shouldn't be if people tried to withdraw directly
|
|
375
|
+
// from `ownedNFTs` (not possible after June 2022 spork),
|
|
376
|
+
// but this makes sure the returned
|
|
377
|
+
// ids are all actually owned by this account.
|
|
378
|
+
pub fun ownedIdsFromEvent(eventId: UInt64): [UInt64] {
|
|
379
|
+
let answer: [UInt64] = []
|
|
380
|
+
if let idsInEvent = self.events[eventId]?.keys {
|
|
381
|
+
for id in idsInEvent {
|
|
382
|
+
if self.ownedNFTs[id] != nil {
|
|
383
|
+
answer.append(id)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return answer
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
|
|
391
|
+
return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
pub fun borrowFLOAT(id: UInt64): &NFT? {
|
|
395
|
+
if self.ownedNFTs[id] != nil {
|
|
396
|
+
let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
|
|
397
|
+
return ref as! &NFT
|
|
398
|
+
}
|
|
399
|
+
return nil
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
|
|
403
|
+
let tokenRef = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
|
|
404
|
+
let nftRef = tokenRef as! &NFT
|
|
405
|
+
return nftRef as &{MetadataViews.Resolver}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
init() {
|
|
409
|
+
self.ownedNFTs <- {}
|
|
410
|
+
self.events = {}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
destroy() {
|
|
414
|
+
destroy self.ownedNFTs
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// An interface that every "verifier" must implement.
|
|
419
|
+
// A verifier is one of the options on the FLOAT Event page,
|
|
420
|
+
// for example, a "time limit," or a "limited" number of
|
|
421
|
+
// FLOATs that can be claimed.
|
|
422
|
+
// All the current verifiers can be seen inside FLOATVerifiers.cdc
|
|
423
|
+
pub struct interface IVerifier {
|
|
424
|
+
// A function every verifier must implement.
|
|
425
|
+
// Will have `assert`s in it to make sure
|
|
426
|
+
// the user fits some criteria.
|
|
427
|
+
access(account) fun verify(_ params: {String: AnyStruct})
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// A public interface to read the FLOATEvent
|
|
431
|
+
pub resource interface FLOATEventPublic {
|
|
432
|
+
pub var claimable: Bool
|
|
433
|
+
pub let dateCreated: UFix64
|
|
434
|
+
pub let description: String
|
|
435
|
+
pub let eventId: UInt64
|
|
436
|
+
pub let host: Address
|
|
437
|
+
pub let image: String
|
|
438
|
+
pub let name: String
|
|
439
|
+
pub var totalSupply: UInt64
|
|
440
|
+
pub var transferrable: Bool
|
|
441
|
+
pub let url: String
|
|
442
|
+
|
|
443
|
+
pub fun claim(recipient: &Collection, params: {String: AnyStruct})
|
|
444
|
+
pub fun purchase(recipient: &Collection, params: {String: AnyStruct}, payment: @FungibleToken.Vault)
|
|
445
|
+
|
|
446
|
+
pub fun getExtraMetadata(): {String: AnyStruct}
|
|
447
|
+
pub fun getSpecificExtraMetadata(key: String): AnyStruct?
|
|
448
|
+
pub fun getVerifiers(): {String: [{IVerifier}]}
|
|
449
|
+
pub fun getPrices(): {String: TokenInfo}?
|
|
450
|
+
pub fun getExtraFloatMetadata(serial: UInt64): {String: AnyStruct}
|
|
451
|
+
pub fun getSpecificExtraFloatMetadata(serial: UInt64, key: String): AnyStruct?
|
|
452
|
+
pub fun getClaims(): {UInt64: TokenIdentifier}
|
|
453
|
+
pub fun getSerialsUserClaimed(address: Address): [UInt64]
|
|
454
|
+
pub fun userHasClaimed(address: Address): Bool
|
|
455
|
+
pub fun userCanMint(address: Address): Bool
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
//
|
|
459
|
+
// FLOATEvent
|
|
460
|
+
//
|
|
461
|
+
pub resource FLOATEvent: FLOATEventPublic, MetadataViews.Resolver {
|
|
462
|
+
// Whether or not users can claim from our event (can be toggled
|
|
463
|
+
// at any time)
|
|
464
|
+
pub var claimable: Bool
|
|
465
|
+
pub let dateCreated: UFix64
|
|
466
|
+
pub let description: String
|
|
467
|
+
// This is equal to this resource's uuid
|
|
468
|
+
pub let eventId: UInt64
|
|
469
|
+
// Who created this FLOAT Event
|
|
470
|
+
pub let host: Address
|
|
471
|
+
// The image of the FLOAT Event
|
|
472
|
+
pub let image: String
|
|
473
|
+
// The name of the FLOAT Event
|
|
474
|
+
pub let name: String
|
|
475
|
+
// The total number of FLOATs that have been
|
|
476
|
+
// minted from this event
|
|
477
|
+
pub var totalSupply: UInt64
|
|
478
|
+
// Whether or not the FLOATs that users own
|
|
479
|
+
// from this event can be transferred on the
|
|
480
|
+
// FLOAT platform itself (transferring allowed
|
|
481
|
+
// elsewhere)
|
|
482
|
+
pub var transferrable: Bool
|
|
483
|
+
// A url of where the event took place
|
|
484
|
+
pub let url: String
|
|
485
|
+
// A list of verifiers this FLOAT Event contains.
|
|
486
|
+
// Will be used every time someone "claims" a FLOAT
|
|
487
|
+
// to see if they pass the requirements
|
|
488
|
+
access(self) let verifiers: {String: [{IVerifier}]}
|
|
489
|
+
// Used to store extra metadata about the event but
|
|
490
|
+
// also individual FLOATs, because Jacob forgot to
|
|
491
|
+
// put a dictionary in the NFT resource :/ Idiot
|
|
492
|
+
access(self) var extraMetadata: {String: AnyStruct}
|
|
493
|
+
|
|
494
|
+
// DEPRECATED, DO NOT USE
|
|
495
|
+
access(self) var claimed: {Address: TokenIdentifier}
|
|
496
|
+
// DEPRECATED, DO NOT USE (used for storing claim info now)
|
|
497
|
+
access(self) var currentHolders: {UInt64: TokenIdentifier}
|
|
498
|
+
// DEPRECATED, DO NOT USE
|
|
499
|
+
access(self) var groups: {String: Bool}
|
|
500
|
+
|
|
501
|
+
// Type: Admin Toggle
|
|
502
|
+
pub fun toggleClaimable(): Bool {
|
|
503
|
+
self.claimable = !self.claimable
|
|
504
|
+
return self.claimable
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Type: Admin Toggle
|
|
508
|
+
pub fun toggleTransferrable(): Bool {
|
|
509
|
+
self.transferrable = !self.transferrable
|
|
510
|
+
return self.transferrable
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Type: Admin Toggle
|
|
514
|
+
pub fun toggleVisibilityMode() {
|
|
515
|
+
if let currentVisibilityMode: String = FLOAT.extraMetadataToStrOpt(self.getExtraMetadata(), "visibilityMode") {
|
|
516
|
+
if currentVisibilityMode == "certificate" {
|
|
517
|
+
self.extraMetadata["visibilityMode"] = "picture"
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
self.extraMetadata["visibilityMode"] = "certificate"
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Type: Contract Setter
|
|
525
|
+
access(self) fun setUserClaim(serial: UInt64, address: Address, floatId: UInt64) {
|
|
526
|
+
self.currentHolders[serial] = TokenIdentifier(_id: floatId, _address: address, _serial: serial)
|
|
527
|
+
|
|
528
|
+
if self.extraMetadata["userClaims"] == nil {
|
|
529
|
+
let userClaims: {Address: [UInt64]} = {}
|
|
530
|
+
self.extraMetadata["userClaims"] = userClaims
|
|
531
|
+
}
|
|
532
|
+
let e = (&self.extraMetadata["userClaims"] as auth &AnyStruct?)!
|
|
533
|
+
let claims = e as! &{Address: [UInt64]}
|
|
534
|
+
|
|
535
|
+
if let specificUserClaims: [UInt64] = claims[address] {
|
|
536
|
+
claims[address]!.append(serial)
|
|
537
|
+
} else {
|
|
538
|
+
claims[address] = [serial]
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Type: Contract Setter
|
|
543
|
+
access(self) fun setExtraFloatMetadata(serial: UInt64, metadata: {String: AnyStruct}) {
|
|
544
|
+
if self.extraMetadata["extraFloatMetadatas"] == nil {
|
|
545
|
+
let extraFloatMetadatas: {UInt64: AnyStruct} = {}
|
|
546
|
+
self.extraMetadata["extraFloatMetadatas"] = extraFloatMetadatas
|
|
547
|
+
}
|
|
548
|
+
let e = (&self.extraMetadata["extraFloatMetadatas"] as auth &AnyStruct?)!
|
|
549
|
+
let extraFloatMetadatas = e as! &{UInt64: AnyStruct}
|
|
550
|
+
extraFloatMetadatas[serial] = metadata
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Type: Contract Setter
|
|
554
|
+
access(self) fun setSpecificExtraFloatMetadata(serial: UInt64, key: String, value: AnyStruct) {
|
|
555
|
+
if self.extraMetadata["extraFloatMetadatas"] == nil {
|
|
556
|
+
let extraFloatMetadatas: {UInt64: AnyStruct} = {}
|
|
557
|
+
self.extraMetadata["extraFloatMetadatas"] = extraFloatMetadatas
|
|
558
|
+
}
|
|
559
|
+
let e = (&self.extraMetadata["extraFloatMetadatas"] as auth &AnyStruct?)!
|
|
560
|
+
let extraFloatMetadatas = e as! &{UInt64: AnyStruct}
|
|
561
|
+
|
|
562
|
+
if extraFloatMetadatas[serial] == nil {
|
|
563
|
+
let extraFloatMetadata: {String: AnyStruct} = {}
|
|
564
|
+
extraFloatMetadatas[serial] = extraFloatMetadata
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
let f = (&extraFloatMetadatas[serial] as auth &AnyStruct?)!
|
|
568
|
+
let extraFloatMetadata = e as! &{String: AnyStruct}
|
|
569
|
+
extraFloatMetadata[key] = value
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Type: Getter
|
|
573
|
+
// Description: Get extra metadata on a specific FLOAT from this event
|
|
574
|
+
pub fun getExtraFloatMetadata(serial: UInt64): {String: AnyStruct} {
|
|
575
|
+
if self.extraMetadata["extraFloatMetadatas"] != nil {
|
|
576
|
+
if let e: {UInt64: AnyStruct} = self.extraMetadata["extraFloatMetadatas"]! as? {UInt64: AnyStruct} {
|
|
577
|
+
if e[serial] != nil {
|
|
578
|
+
if let f: {String: AnyStruct} = e[serial]! as? {String: AnyStruct} {
|
|
579
|
+
return f
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return {}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Type: Getter
|
|
588
|
+
// Description: Get specific extra metadata on a specific FLOAT from this event
|
|
589
|
+
pub fun getSpecificExtraFloatMetadata(serial: UInt64, key: String): AnyStruct? {
|
|
590
|
+
return self.getExtraFloatMetadata(serial: serial)[key]
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Type: Getter
|
|
594
|
+
// Description: Returns claim info of all the serials
|
|
595
|
+
pub fun getClaims(): {UInt64: TokenIdentifier} {
|
|
596
|
+
return self.currentHolders
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Type: Getter
|
|
600
|
+
// Description: Will return an array of all the serials a user claimed.
|
|
601
|
+
// Most of the time this will be a maximum length of 1 because most
|
|
602
|
+
// events only allow 1 claim per user.
|
|
603
|
+
pub fun getSerialsUserClaimed(address: Address): [UInt64] {
|
|
604
|
+
var serials: [UInt64] = []
|
|
605
|
+
if let userClaims: {Address: [UInt64]} = self.getSpecificExtraMetadata(key: "userClaims") as! {Address: [UInt64]}? {
|
|
606
|
+
serials = userClaims[address] ?? []
|
|
607
|
+
}
|
|
608
|
+
// take into account claims during FLOATv1
|
|
609
|
+
if let oldClaim: TokenIdentifier = self.claimed[address] {
|
|
610
|
+
serials.append(oldClaim.serial)
|
|
611
|
+
}
|
|
612
|
+
return serials
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Type: Getter
|
|
616
|
+
// Description: Returns true if the user has either claimed
|
|
617
|
+
// or been minted at least one float from this event
|
|
618
|
+
pub fun userHasClaimed(address: Address): Bool {
|
|
619
|
+
return self.getSerialsUserClaimed(address: address).length >= 1
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Type: Getter
|
|
623
|
+
// Description: Get extra metadata on this event
|
|
624
|
+
pub fun getExtraMetadata(): {String: AnyStruct} {
|
|
625
|
+
return self.extraMetadata
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Type: Getter
|
|
629
|
+
// Description: Get specific extra metadata on this event
|
|
630
|
+
pub fun getSpecificExtraMetadata(key: String): AnyStruct? {
|
|
631
|
+
return self.extraMetadata[key]
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Type: Getter
|
|
635
|
+
// Description: Checks if a user can mint a new FLOAT from this event
|
|
636
|
+
pub fun userCanMint(address: Address): Bool {
|
|
637
|
+
if let allows: Bool = self.getSpecificExtraMetadata(key: "allowMultipleClaim") as! Bool? {
|
|
638
|
+
if allows || self.getSerialsUserClaimed(address: address).length == 0 {
|
|
639
|
+
return true
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return false
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Type: Getter
|
|
646
|
+
// Description: Gets all the verifiers that will be used
|
|
647
|
+
// for claiming
|
|
648
|
+
pub fun getVerifiers(): {String: [{IVerifier}]} {
|
|
649
|
+
return self.verifiers
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Type: Getter
|
|
653
|
+
// Description: Returns a dictionary whose key is a token identifier
|
|
654
|
+
// and value is the path to that token and price of the FLOAT in that
|
|
655
|
+
// currency
|
|
656
|
+
pub fun getPrices(): {String: TokenInfo}? {
|
|
657
|
+
return self.extraMetadata["prices"] as! {String: TokenInfo}?
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Type: Getter
|
|
661
|
+
// Description: For MetadataViews
|
|
662
|
+
pub fun getViews(): [Type] {
|
|
663
|
+
return [
|
|
664
|
+
Type<MetadataViews.Display>()
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Type: Getter
|
|
669
|
+
// Description: For MetadataViews
|
|
670
|
+
pub fun resolveView(_ view: Type): AnyStruct? {
|
|
671
|
+
switch view {
|
|
672
|
+
case Type<MetadataViews.Display>():
|
|
673
|
+
return MetadataViews.Display(
|
|
674
|
+
name: self.name,
|
|
675
|
+
description: self.description,
|
|
676
|
+
thumbnail: MetadataViews.IPFSFile(cid: self.image, path: nil)
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return nil
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Type: Admin / Helper Function for `verifyAndMint`
|
|
684
|
+
// Description: Used to give a person a FLOAT from this event.
|
|
685
|
+
// If the event owner directly mints to a user, it does not
|
|
686
|
+
// run the verifiers on the user. It bypasses all of them.
|
|
687
|
+
// Return the id of the FLOAT it minted.
|
|
688
|
+
pub fun mint(recipient: &Collection{NonFungibleToken.CollectionPublic}, optExtraFloatMetadata: {String: AnyStruct}?): UInt64 {
|
|
689
|
+
pre {
|
|
690
|
+
self.userCanMint(address: recipient.owner!.address): "Only 1 FLOAT allowed per user, and this user already claimed their FLOAT!"
|
|
691
|
+
}
|
|
692
|
+
let recipientAddr: Address = recipient.owner!.address
|
|
693
|
+
let serial = self.totalSupply
|
|
694
|
+
|
|
695
|
+
let token <- create NFT(
|
|
696
|
+
_eventDescription: self.description,
|
|
697
|
+
_eventHost: self.host,
|
|
698
|
+
_eventId: self.eventId,
|
|
699
|
+
_eventImage: self.image,
|
|
700
|
+
_eventName: self.name,
|
|
701
|
+
_originalRecipient: recipientAddr,
|
|
702
|
+
_serial: serial
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
if let extraFloatMetadata: {String: AnyStruct} = optExtraFloatMetadata {
|
|
706
|
+
// ensure
|
|
707
|
+
assert(
|
|
708
|
+
FLOAT.validateExtraFloatMetadata(data: extraFloatMetadata),
|
|
709
|
+
message: "Extra FLOAT metadata is not proper. Check the `FLOAT.validateExtraFloatMetadata` function."
|
|
710
|
+
)
|
|
711
|
+
self.setExtraFloatMetadata(serial: serial, metadata: extraFloatMetadata)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
let id: UInt64 = token.id
|
|
715
|
+
|
|
716
|
+
self.setUserClaim(serial: serial, address: recipientAddr, floatId: id)
|
|
717
|
+
|
|
718
|
+
self.totalSupply = self.totalSupply + 1
|
|
719
|
+
recipient.deposit(token: <- token)
|
|
720
|
+
return id
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Type: Helper Function for `claim` and `purchase`
|
|
724
|
+
// Description: Will get run by the public, so verifies
|
|
725
|
+
// the user can mint
|
|
726
|
+
access(self) fun verifyAndMint(recipient: &Collection, params: {String: AnyStruct}): UInt64 {
|
|
727
|
+
params["event"] = &self as &FLOATEvent{FLOATEventPublic}
|
|
728
|
+
params["claimee"] = recipient.owner!.address
|
|
729
|
+
|
|
730
|
+
// Runs a loop over all the verifiers that this FLOAT Events
|
|
731
|
+
// implements. For example, "Limited", "Timelock", "Secret", etc.
|
|
732
|
+
// All the verifiers are in the FLOATVerifiers.cdc contract
|
|
733
|
+
for identifier in self.verifiers.keys {
|
|
734
|
+
let typedModules = (&self.verifiers[identifier] as &[{IVerifier}]?)!
|
|
735
|
+
var i = 0
|
|
736
|
+
while i < typedModules.length {
|
|
737
|
+
let verifier = &typedModules[i] as &{IVerifier}
|
|
738
|
+
verifier.verify(params)
|
|
739
|
+
i = i + 1
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
var optExtraFloatMetadata: {String: AnyStruct}? = nil
|
|
744
|
+
// if this is a medal type float and user is publicly claiming, assign to participation
|
|
745
|
+
if FLOAT.extraMetadataToStrOpt(self.getExtraMetadata(), "certificateType") == "medal" {
|
|
746
|
+
optExtraFloatMetadata = {"medalType": "participation"}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// You're good to go.
|
|
750
|
+
let id: UInt64 = self.mint(recipient: recipient, optExtraFloatMetadata: optExtraFloatMetadata)
|
|
751
|
+
|
|
752
|
+
emit FLOATClaimed(
|
|
753
|
+
id: id,
|
|
754
|
+
eventHost: self.host,
|
|
755
|
+
eventId: self.eventId,
|
|
756
|
+
eventImage: self.image,
|
|
757
|
+
eventName: self.name,
|
|
758
|
+
recipient: recipient.owner!.address,
|
|
759
|
+
serial: self.totalSupply - 1
|
|
760
|
+
)
|
|
761
|
+
return id
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// For the public to claim FLOATs. Must be claimable to do so.
|
|
765
|
+
// You can pass in `params` that will be forwarded to the
|
|
766
|
+
// customized `verify` function of the verifier.
|
|
767
|
+
//
|
|
768
|
+
// For example, the FLOAT platform allows event hosts
|
|
769
|
+
// to specify a secret phrase. That secret phrase will
|
|
770
|
+
// be passed in the `params`.
|
|
771
|
+
pub fun claim(recipient: &Collection, params: {String: AnyStruct}) {
|
|
772
|
+
pre {
|
|
773
|
+
self.getPrices() == nil:
|
|
774
|
+
"You need to purchase this FLOAT."
|
|
775
|
+
self.claimable:
|
|
776
|
+
"This FLOAT event is not claimable, and thus not currently active."
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
self.verifyAndMint(recipient: recipient, params: params)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
pub fun purchase(recipient: &Collection, params: {String: AnyStruct}, payment: @FungibleToken.Vault) {
|
|
783
|
+
pre {
|
|
784
|
+
self.getPrices() != nil:
|
|
785
|
+
"Don't call this function. The FLOAT is free. Call the claim function instead."
|
|
786
|
+
self.getPrices()![payment.getType().identifier] != nil:
|
|
787
|
+
"This FLOAT does not support purchasing in the passed in token."
|
|
788
|
+
payment.balance == self.getPrices()![payment.getType().identifier]!.price:
|
|
789
|
+
"You did not pass in the correct amount of tokens."
|
|
790
|
+
self.claimable:
|
|
791
|
+
"This FLOAT event is not claimable, and thus not currently active."
|
|
792
|
+
}
|
|
793
|
+
let royalty: UFix64 = 0.05
|
|
794
|
+
let emeraldCityTreasury: Address = 0x5643fd47a29770e7
|
|
795
|
+
let paymentType: String = payment.getType().identifier
|
|
796
|
+
let tokenInfo: TokenInfo = self.getPrices()![paymentType]!
|
|
797
|
+
|
|
798
|
+
let EventHostVault = getAccount(self.host).getCapability(tokenInfo.path)
|
|
799
|
+
.borrow<&{FungibleToken.Receiver}>()
|
|
800
|
+
?? panic("Could not borrow the &{FungibleToken.Receiver} from the event host.")
|
|
801
|
+
|
|
802
|
+
assert(
|
|
803
|
+
EventHostVault.getType().identifier == paymentType,
|
|
804
|
+
message: "The event host's path is not associated with the intended token."
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
let EmeraldCityVault = getAccount(emeraldCityTreasury).getCapability(tokenInfo.path)
|
|
808
|
+
.borrow<&{FungibleToken.Receiver}>()
|
|
809
|
+
?? panic("Could not borrow the &{FungibleToken.Receiver} from Emerald City's Vault.")
|
|
810
|
+
|
|
811
|
+
assert(
|
|
812
|
+
EmeraldCityVault.getType().identifier == paymentType,
|
|
813
|
+
message: "Emerald City's path is not associated with the intended token."
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
let emeraldCityCut <- payment.withdraw(amount: payment.balance * royalty)
|
|
817
|
+
|
|
818
|
+
EmeraldCityVault.deposit(from: <- emeraldCityCut)
|
|
819
|
+
EventHostVault.deposit(from: <- payment)
|
|
820
|
+
|
|
821
|
+
let id = self.verifyAndMint(recipient: recipient, params: params)
|
|
822
|
+
|
|
823
|
+
emit FLOATPurchased(id: id, eventHost: self.host, eventId: self.eventId, recipient: recipient.owner!.address, serial: self.totalSupply - 1)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
init (
|
|
827
|
+
_claimable: Bool,
|
|
828
|
+
_description: String,
|
|
829
|
+
_extraMetadata: {String: AnyStruct},
|
|
830
|
+
_host: Address,
|
|
831
|
+
_image: String,
|
|
832
|
+
_name: String,
|
|
833
|
+
_transferrable: Bool,
|
|
834
|
+
_url: String,
|
|
835
|
+
_verifiers: {String: [{IVerifier}]}
|
|
836
|
+
) {
|
|
837
|
+
self.claimable = _claimable
|
|
838
|
+
self.claimed = {}
|
|
839
|
+
self.currentHolders = {}
|
|
840
|
+
self.dateCreated = getCurrentBlock().timestamp
|
|
841
|
+
self.description = _description
|
|
842
|
+
self.eventId = self.uuid
|
|
843
|
+
self.extraMetadata = _extraMetadata
|
|
844
|
+
self.host = _host
|
|
845
|
+
self.image = _image
|
|
846
|
+
self.name = _name
|
|
847
|
+
self.transferrable = _transferrable
|
|
848
|
+
self.totalSupply = 0
|
|
849
|
+
self.url = _url
|
|
850
|
+
self.verifiers = _verifiers
|
|
851
|
+
self.groups = {}
|
|
852
|
+
|
|
853
|
+
FLOAT.totalFLOATEvents = FLOAT.totalFLOATEvents + 1
|
|
854
|
+
emit FLOATEventCreated(eventId: self.eventId, description: self.description, host: self.host, image: self.image, name: self.name, url: self.url)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
destroy() {
|
|
858
|
+
emit FLOATEventDestroyed(eventId: self.eventId, host: self.host, name: self.name)
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// DEPRECATED
|
|
863
|
+
pub resource Group {
|
|
864
|
+
pub let id: UInt64
|
|
865
|
+
pub let name: String
|
|
866
|
+
pub let image: String
|
|
867
|
+
pub let description: String
|
|
868
|
+
access(self) var events: {UInt64: Bool}
|
|
869
|
+
init() {
|
|
870
|
+
self.id = 0
|
|
871
|
+
self.name = ""
|
|
872
|
+
self.image = ""
|
|
873
|
+
self.description = ""
|
|
874
|
+
self.events = {}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
//
|
|
879
|
+
// FLOATEvents
|
|
880
|
+
//
|
|
881
|
+
pub resource interface FLOATEventsPublic {
|
|
882
|
+
// Public Getters
|
|
883
|
+
pub fun borrowPublicEventRef(eventId: UInt64): &FLOATEvent{FLOATEventPublic}?
|
|
884
|
+
pub fun getAllEvents(): {UInt64: String}
|
|
885
|
+
pub fun getIDs(): [UInt64]
|
|
886
|
+
pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// A "Collection" of FLOAT Events
|
|
890
|
+
pub resource FLOATEvents: FLOATEventsPublic, MetadataViews.ResolverCollection {
|
|
891
|
+
// All the FLOAT Events this collection stores
|
|
892
|
+
access(self) var events: @{UInt64: FLOATEvent}
|
|
893
|
+
// DEPRECATED
|
|
894
|
+
access(self) var groups: @{String: Group}
|
|
895
|
+
|
|
896
|
+
// Creates a new FLOAT Event
|
|
897
|
+
//
|
|
898
|
+
// Read below for a description on all the values and expectations here
|
|
899
|
+
//
|
|
900
|
+
// claimable: Do you want this FLOAT to be publicly claimable by users?
|
|
901
|
+
// transferrable: Should this FLOAT be transferrable or soulbound?
|
|
902
|
+
// url: A generic url to your FLOAT Event
|
|
903
|
+
// verifiers: An array of verifiers from FLOATVerifiers contract
|
|
904
|
+
// allowMultipleClaim: Should users be able to claim/receive multiple
|
|
905
|
+
// of this FLOAT?
|
|
906
|
+
// certificateType: Determines how the FLOAT is displayed on the FLOAT platform. Must be one of the
|
|
907
|
+
// following or it will fail: "ticket", "medal", "certificate"
|
|
908
|
+
// visibilityMode: Determines how the FLOAT is displayed on the FLOAT platform. Must be one of the
|
|
909
|
+
// following: "picture", "certificate"
|
|
910
|
+
// extraMetadata: Any extra metadata for your event. Here are some restrictions on the keys of this dictionary:
|
|
911
|
+
// userClaims: You cannot provide a userClaims key
|
|
912
|
+
// extraFloatMetadatas: You cannot provide a extraFloatMetadatas key
|
|
913
|
+
// certificateImage: Must either be nil or a String type
|
|
914
|
+
// backImage: The IPFS CID of what will display on the back of your FLOAT. Must either be nil or a String type
|
|
915
|
+
// eventType: Must either be nil or a String type
|
|
916
|
+
pub fun createEvent(
|
|
917
|
+
claimable: Bool,
|
|
918
|
+
description: String,
|
|
919
|
+
image: String,
|
|
920
|
+
name: String,
|
|
921
|
+
transferrable: Bool,
|
|
922
|
+
url: String,
|
|
923
|
+
verifiers: [{IVerifier}],
|
|
924
|
+
allowMultipleClaim: Bool,
|
|
925
|
+
certificateType: String,
|
|
926
|
+
visibilityMode: String,
|
|
927
|
+
extraMetadata: {String: AnyStruct}
|
|
928
|
+
): UInt64 {
|
|
929
|
+
pre {
|
|
930
|
+
certificateType == "ticket" || certificateType == "medal" || certificateType == "certificate": "You must either choose 'ticket', 'medal', or 'certificate' for certificateType. This is how your FLOAT will be displayed."
|
|
931
|
+
visibilityMode == "certificate" || visibilityMode == "picture": "You must either choose 'certificate' or 'picture' for visibilityMode. This is how your FLOAT will be displayed."
|
|
932
|
+
extraMetadata["userClaims"] == nil: "Cannot use userClaims key in extraMetadata."
|
|
933
|
+
extraMetadata["extraFloatMetadatas"] == nil: "Cannot use extraFloatMetadatas key in extraMetadata."
|
|
934
|
+
extraMetadata["certificateImage"] == nil || extraMetadata["certificateImage"]!.getType() == Type<String>(): "certificateImage must be a String or nil type."
|
|
935
|
+
extraMetadata["backImage"] == nil || extraMetadata["backImage"]!.getType() == Type<String>(): "backImage must be a String or nil type."
|
|
936
|
+
extraMetadata["eventType"] == nil || extraMetadata["eventType"]!.getType() == Type<String>(): "eventType must be a String or nil type."
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
let typedVerifiers: {String: [{IVerifier}]} = {}
|
|
940
|
+
for verifier in verifiers {
|
|
941
|
+
let identifier = verifier.getType().identifier
|
|
942
|
+
if typedVerifiers[identifier] == nil {
|
|
943
|
+
typedVerifiers[identifier] = [verifier]
|
|
944
|
+
} else {
|
|
945
|
+
typedVerifiers[identifier]!.append(verifier)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
extraMetadata["allowMultipleClaim"] = allowMultipleClaim
|
|
950
|
+
extraMetadata["certificateType"] = certificateType
|
|
951
|
+
extraMetadata["visibilityMode"] = visibilityMode
|
|
952
|
+
|
|
953
|
+
let FLOATEvent <- create FLOATEvent(
|
|
954
|
+
_claimable: claimable,
|
|
955
|
+
_description: description,
|
|
956
|
+
_extraMetadata: extraMetadata,
|
|
957
|
+
_host: self.owner!.address,
|
|
958
|
+
_image: image,
|
|
959
|
+
_name: name,
|
|
960
|
+
_transferrable: transferrable,
|
|
961
|
+
_url: url,
|
|
962
|
+
_verifiers: typedVerifiers
|
|
963
|
+
)
|
|
964
|
+
let eventId = FLOATEvent.eventId
|
|
965
|
+
self.events[eventId] <-! FLOATEvent
|
|
966
|
+
|
|
967
|
+
return eventId
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Deletes an event.
|
|
971
|
+
pub fun deleteEvent(eventId: UInt64) {
|
|
972
|
+
let eventRef = self.borrowEventRef(eventId: eventId) ?? panic("This FLOAT does not exist.")
|
|
973
|
+
destroy self.events.remove(key: eventId)
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
pub fun borrowEventRef(eventId: UInt64): &FLOATEvent? {
|
|
977
|
+
return &self.events[eventId] as &FLOATEvent?
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Get a public reference to the FLOATEvent
|
|
981
|
+
// so you can call some helpful getters
|
|
982
|
+
pub fun borrowPublicEventRef(eventId: UInt64): &FLOATEvent{FLOATEventPublic}? {
|
|
983
|
+
return &self.events[eventId] as &FLOATEvent{FLOATEventPublic}?
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
pub fun getIDs(): [UInt64] {
|
|
987
|
+
return self.events.keys
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Maps the eventId to the name of that
|
|
991
|
+
// event. Just a kind helper.
|
|
992
|
+
pub fun getAllEvents(): {UInt64: String} {
|
|
993
|
+
let answer: {UInt64: String} = {}
|
|
994
|
+
for id in self.events.keys {
|
|
995
|
+
let ref = (&self.events[id] as &FLOATEvent?)!
|
|
996
|
+
answer[id] = ref.name
|
|
997
|
+
}
|
|
998
|
+
return answer
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
|
|
1002
|
+
return (&self.events[id] as &{MetadataViews.Resolver}?)!
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
init() {
|
|
1006
|
+
self.events <- {}
|
|
1007
|
+
self.groups <- {}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
destroy() {
|
|
1011
|
+
destroy self.events
|
|
1012
|
+
destroy self.groups
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
pub fun createEmptyCollection(): @Collection {
|
|
1017
|
+
return <- create Collection()
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
pub fun createEmptyFLOATEventCollection(): @FLOATEvents {
|
|
1021
|
+
return <- create FLOATEvents()
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// A function to validate expected FLOAT metadata that must be in a
|
|
1025
|
+
// certain format as to not cause aborts during expected casting
|
|
1026
|
+
pub fun validateExtraFloatMetadata(data: {String: AnyStruct}): Bool {
|
|
1027
|
+
if data.containsKey("medalType") {
|
|
1028
|
+
let medalType: String? = FLOAT.extraMetadataToStrOpt(data, "medalType")
|
|
1029
|
+
if medalType == nil || (medalType != "gold" && medalType != "silver" && medalType != "bronze" && medalType != "participation") {
|
|
1030
|
+
return false
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return true
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Helper to cast dictionary value to String? type
|
|
1037
|
+
//
|
|
1038
|
+
// Note about all the casting going on:
|
|
1039
|
+
// You might be saying, "Why are you double force unwrapping
|
|
1040
|
+
// medalType Jacob?" "Why would an unwrapped type still needed to be unwrapped?"
|
|
1041
|
+
// The reason is because in Cadence dictionaries, you can encounter double optionals
|
|
1042
|
+
// where the actual type that lies in the value of a dictionary is itself
|
|
1043
|
+
// nil. In other words, it's possible to have `{ "jacob": nil }` in a dictionary.
|
|
1044
|
+
// So we force unwrap due to the dictionary, then unwrap the value within.
|
|
1045
|
+
// It will never abort because we have checked for nil above, which checks
|
|
1046
|
+
// for both types of nil.
|
|
1047
|
+
pub fun extraMetadataToStrOpt(_ dict: {String: AnyStruct}, _ key: String): String? {
|
|
1048
|
+
// `dict[key] == nil` means:
|
|
1049
|
+
// 1. the key doesn't exist
|
|
1050
|
+
// 2. the value for the key is nil
|
|
1051
|
+
if dict[key] == nil || dict[key]!!.getType() != Type<String>() {
|
|
1052
|
+
return nil
|
|
1053
|
+
}
|
|
1054
|
+
return dict[key]!! as! String
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/// Function that returns all the Metadata Views implemented by a Non Fungible Token
|
|
1058
|
+
///
|
|
1059
|
+
/// @return An array of Types defining the implemented views. This value will be used by
|
|
1060
|
+
/// developers to know which parameter to pass to the resolveView() method.
|
|
1061
|
+
///
|
|
1062
|
+
pub fun getViews(): [Type] {
|
|
1063
|
+
return [
|
|
1064
|
+
Type<MetadataViews.NFTCollectionData>(),
|
|
1065
|
+
Type<MetadataViews.NFTCollectionDisplay>()
|
|
1066
|
+
]
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/// Function that resolves a metadata view for this contract.
|
|
1070
|
+
///
|
|
1071
|
+
/// @param view: The Type of the desired view.
|
|
1072
|
+
/// @return A structure representing the requested view.
|
|
1073
|
+
///
|
|
1074
|
+
pub fun resolveView(_ view: Type): AnyStruct? {
|
|
1075
|
+
switch view {
|
|
1076
|
+
case Type<MetadataViews.NFTCollectionData>():
|
|
1077
|
+
return MetadataViews.NFTCollectionData(
|
|
1078
|
+
storagePath: FLOAT.FLOATCollectionStoragePath,
|
|
1079
|
+
publicPath: FLOAT.FLOATCollectionPublicPath,
|
|
1080
|
+
providerPath: /private/FLOATCollectionPrivatePath,
|
|
1081
|
+
publicCollection: Type<&Collection{CollectionPublic}>(),
|
|
1082
|
+
publicLinkedType: Type<&Collection{CollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
|
|
1083
|
+
providerLinkedType: Type<&Collection{CollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Provider, MetadataViews.ResolverCollection}>(),
|
|
1084
|
+
createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
|
|
1085
|
+
return <- FLOAT.createEmptyCollection()
|
|
1086
|
+
})
|
|
1087
|
+
)
|
|
1088
|
+
case Type<MetadataViews.NFTCollectionDisplay>():
|
|
1089
|
+
let squareMedia: MetadataViews.Media = MetadataViews.Media(
|
|
1090
|
+
file: MetadataViews.HTTPFile(
|
|
1091
|
+
url: "https://i.imgur.com/v0Njnnk.png"
|
|
1092
|
+
),
|
|
1093
|
+
mediaType: "image"
|
|
1094
|
+
)
|
|
1095
|
+
let bannerMedia: MetadataViews.Media = MetadataViews.Media(
|
|
1096
|
+
file: MetadataViews.HTTPFile(
|
|
1097
|
+
url: "https://i.imgur.com/ETeVZZU.jpg"
|
|
1098
|
+
),
|
|
1099
|
+
mediaType: "image"
|
|
1100
|
+
)
|
|
1101
|
+
return MetadataViews.NFTCollectionDisplay(
|
|
1102
|
+
name: "FLOAT",
|
|
1103
|
+
description: "FLOAT is a proof of attendance platform on the Flow blockchain.",
|
|
1104
|
+
externalURL: MetadataViews.ExternalURL("https://floats.city"),
|
|
1105
|
+
squareImage: squareMedia,
|
|
1106
|
+
bannerImage: bannerMedia,
|
|
1107
|
+
socials: {
|
|
1108
|
+
"twitter": MetadataViews.ExternalURL("https://twitter.com/emerald_dao"),
|
|
1109
|
+
"discord": MetadataViews.ExternalURL("https://discord.gg/emeraldcity")
|
|
1110
|
+
}
|
|
1111
|
+
)
|
|
1112
|
+
}
|
|
1113
|
+
return nil
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
init() {
|
|
1117
|
+
self.totalSupply = 0
|
|
1118
|
+
self.totalFLOATEvents = 0
|
|
1119
|
+
emit ContractInitialized()
|
|
1120
|
+
|
|
1121
|
+
self.FLOATCollectionStoragePath = /storage/FLOATCollectionStoragePath
|
|
1122
|
+
self.FLOATCollectionPublicPath = /public/FLOATCollectionPublicPath
|
|
1123
|
+
self.FLOATEventsStoragePath = /storage/FLOATEventsStoragePath
|
|
1124
|
+
self.FLOATEventsPrivatePath = /private/FLOATEventsPrivatePath
|
|
1125
|
+
self.FLOATEventsPublicPath = /public/FLOATEventsPublicPath
|
|
1126
|
+
|
|
1127
|
+
// delete later
|
|
1128
|
+
|
|
1129
|
+
if self.account.borrow<&FLOAT.Collection>(from: FLOAT.FLOATCollectionStoragePath) == nil {
|
|
1130
|
+
self.account.save(<- FLOAT.createEmptyCollection(), to: FLOAT.FLOATCollectionStoragePath)
|
|
1131
|
+
self.account.link<&FLOAT.Collection{NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection, FLOAT.CollectionPublic}>
|
|
1132
|
+
(FLOAT.FLOATCollectionPublicPath, target: FLOAT.FLOATCollectionStoragePath)
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if self.account.borrow<&FLOAT.FLOATEvents>(from: FLOAT.FLOATEventsStoragePath) == nil {
|
|
1136
|
+
self.account.save(<- FLOAT.createEmptyFLOATEventCollection(), to: FLOAT.FLOATEventsStoragePath)
|
|
1137
|
+
self.account.link<&FLOAT.FLOATEvents{FLOAT.FLOATEventsPublic, MetadataViews.ResolverCollection}>
|
|
1138
|
+
(FLOAT.FLOATEventsPublicPath, target: FLOAT.FLOATEventsStoragePath)
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
let FLOATEvents = self.account.borrow<&FLOAT.FLOATEvents>(from: FLOAT.FLOATEventsStoragePath)
|
|
1142
|
+
?? panic("Could not borrow the FLOATEvents from the signer.")
|
|
1143
|
+
|
|
1144
|
+
var verifiers: [{FLOAT.IVerifier}] = []
|
|
1145
|
+
|
|
1146
|
+
let extraMetadata: {String: AnyStruct} = {}
|
|
1147
|
+
|
|
1148
|
+
extraMetadata["backImage"] = "bafkreihwra72f2sby4h2bswej4zzrmparb6jy55ygjrymxjk572tjziatu"
|
|
1149
|
+
extraMetadata["eventType"] = "course"
|
|
1150
|
+
extraMetadata["certificateImage"] = "bafkreidcwg6jkcsugms2jtv6suwk2cao2ij6y57mopz4p4anpnvwswv2ku"
|
|
1151
|
+
|
|
1152
|
+
FLOATEvents.createEvent(claimable: true, description: "Test description for the upcoming Flow Hackathon. This is soooo fun! Woohoo!", image: "bafybeifpsnwb2vkz4p6nxhgsbwgyslmlfd7jyicx5ukbj3tp7qsz7myzrq", name: "Flow Hackathon", transferrable: true, url: "", verifiers: verifiers, allowMultipleClaim: false, certificateType: "medal", visibilityMode: "certificate", extraMetadata: extraMetadata)
|
|
1153
|
+
|
|
1154
|
+
extraMetadata["backImage"] = "bafkreihwra72f2sby4h2bswej4zzrmparb6jy55ygjrymxjk572tjziatu"
|
|
1155
|
+
extraMetadata["eventType"] = "discordMeeting"
|
|
1156
|
+
extraMetadata["certificateImage"] = "bafkreidcwg6jkcsugms2jtv6suwk2cao2ij6y57mopz4p4anpnvwswv2ku"
|
|
1157
|
+
|
|
1158
|
+
FLOATEvents.createEvent(claimable: true, description: "Test description for a Discord meeting. This is soooo fun! Woohoo!", image: "bafybeifpsnwb2vkz4p6nxhgsbwgyslmlfd7jyicx5ukbj3tp7qsz7myzrq", name: "Discord Meeting", transferrable: true, url: "", verifiers: verifiers, allowMultipleClaim: false, certificateType: "ticket", visibilityMode: "picture", extraMetadata: extraMetadata)
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|