@flowtyio/flow-contracts 0.1.0-beta.9 → 0.1.0
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/README.md +1 -1
- package/contracts/FungibleTokenSwitchboard.cdc +360 -0
- package/contracts/MetadataViews.cdc +79 -6
- package/contracts/NonFungibleToken.cdc +17 -10
- package/contracts/capability-cache/CapabilityCache.cdc +97 -0
- package/contracts/dapper/TopShot.cdc +323 -259
- package/contracts/dapper/TopShotLocking.cdc +41 -15
- package/contracts/dapper/offers/DapperOffersV2.cdc +46 -43
- package/contracts/dapper/offers/OffersV2.cdc +40 -56
- package/contracts/dapper/offers/Resolver.cdc +20 -13
- package/contracts/emerald-city/FLOAT.cdc +259 -254
- package/contracts/example/ExampleNFT.cdc +2 -2
- package/contracts/find/FindViews.cdc +357 -353
- package/contracts/flow-utils/ScopedFTProviders.cdc +5 -2
- package/contracts/flow-utils/ScopedNFTProviders.cdc +6 -2
- package/contracts/flowty-drops/ContractManager.cdc +73 -0
- package/contracts/flowty-drops/DropFactory.cdc +75 -0
- package/contracts/flowty-drops/DropTypes.cdc +282 -0
- package/contracts/flowty-drops/FlowtyActiveCheckers.cdc +113 -0
- package/contracts/flowty-drops/FlowtyAddressVerifiers.cdc +64 -0
- package/contracts/flowty-drops/FlowtyDrops.cdc +461 -0
- package/contracts/flowty-drops/FlowtyPricers.cdc +48 -0
- package/contracts/flowty-drops/initializers/ContractBorrower.cdc +14 -0
- package/contracts/flowty-drops/initializers/ContractInitializer.cdc +7 -0
- package/contracts/flowty-drops/initializers/OpenEditionInitializer.cdc +57 -0
- package/contracts/flowty-drops/nft/BaseCollection.cdc +97 -0
- package/contracts/flowty-drops/nft/BaseNFT.cdc +107 -0
- package/contracts/flowty-drops/nft/ContractFactory.cdc +13 -0
- package/contracts/flowty-drops/nft/ContractFactoryTemplate.cdc +48 -0
- package/contracts/flowty-drops/nft/NFTMetadata.cdc +140 -0
- package/contracts/flowty-drops/nft/OpenEditionNFT.cdc +42 -0
- package/contracts/flowty-drops/nft/OpenEditionTemplate.cdc +54 -0
- package/contracts/flowty-drops/nft/UniversalCollection.cdc +29 -0
- package/contracts/fungible-token-router/FungibleTokenRouter.cdc +103 -0
- package/contracts/hybrid-custody/CapabilityDelegator.cdc +28 -26
- package/contracts/hybrid-custody/CapabilityFactory.cdc +20 -18
- package/contracts/hybrid-custody/CapabilityFilter.cdc +41 -24
- package/contracts/hybrid-custody/HybridCustody.cdc +303 -242
- package/contracts/hybrid-custody/factories/FTAllFactory.cdc +16 -4
- package/contracts/hybrid-custody/factories/FTBalanceFactory.cdc +16 -4
- package/contracts/hybrid-custody/factories/FTProviderFactory.cdc +17 -5
- package/contracts/hybrid-custody/factories/FTReceiverBalanceFactory.cdc +16 -4
- package/contracts/hybrid-custody/factories/FTReceiverFactory.cdc +16 -4
- package/contracts/hybrid-custody/factories/FTVaultFactory.cdc +46 -0
- package/contracts/hybrid-custody/factories/NFTCollectionFactory.cdc +45 -0
- package/contracts/hybrid-custody/factories/NFTCollectionPublicFactory.cdc +16 -4
- package/contracts/hybrid-custody/factories/NFTProviderAndCollectionFactory.cdc +22 -0
- package/contracts/hybrid-custody/factories/NFTProviderFactory.cdc +16 -4
- package/contracts/lost-and-found/LostAndFound.cdc +21 -17
- package/flow.json +181 -7
- package/package.json +1 -1
- package/contracts/hybrid-custody/factories/NFTProviderAndCollectionPublicFactory.cdc +0 -10
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import "NonFungibleToken"
|
|
2
|
+
import "FungibleToken"
|
|
3
|
+
import "MetadataViews"
|
|
4
|
+
import "AddressUtils"
|
|
5
|
+
import "FungibleTokenMetadataViews"
|
|
6
|
+
import "FungibleTokenRouter"
|
|
7
|
+
|
|
8
|
+
access(all) contract FlowtyDrops {
|
|
9
|
+
access(all) let ContainerStoragePath: StoragePath
|
|
10
|
+
access(all) let ContainerPublicPath: PublicPath
|
|
11
|
+
|
|
12
|
+
access(all) event DropAdded(address: Address, id: UInt64, name: String, description: String, imageUrl: String, start: UInt64?, end: UInt64?, nftType: String)
|
|
13
|
+
access(all) event Minted(address: Address, dropID: UInt64, phaseID: UInt64, nftID: UInt64, nftType: String)
|
|
14
|
+
access(all) event PhaseAdded(dropID: UInt64, dropAddress: Address, id: UInt64, index: Int, activeCheckerType: String, pricerType: String, addressVerifierType: String)
|
|
15
|
+
access(all) event PhaseRemoved(dropID: UInt64, dropAddress: Address, id: UInt64)
|
|
16
|
+
|
|
17
|
+
access(all) entitlement Owner
|
|
18
|
+
access(all) entitlement EditPhase
|
|
19
|
+
|
|
20
|
+
// Interface to expose all the components necessary to participate in a drop
|
|
21
|
+
// and to ask questions about a drop.
|
|
22
|
+
access(all) resource interface DropPublic {
|
|
23
|
+
access(all) fun borrowPhasePublic(index: Int): &{PhasePublic}
|
|
24
|
+
access(all) fun borrowActivePhases(): [&{PhasePublic}]
|
|
25
|
+
access(all) fun borrowAllPhases(): [&{PhasePublic}]
|
|
26
|
+
access(all) fun mint(
|
|
27
|
+
payment: @{FungibleToken.Vault},
|
|
28
|
+
amount: Int,
|
|
29
|
+
phaseIndex: Int,
|
|
30
|
+
expectedType: Type,
|
|
31
|
+
receiverCap: Capability<&{NonFungibleToken.CollectionPublic}>,
|
|
32
|
+
commissionReceiver: Capability<&{FungibleToken.Receiver}>?,
|
|
33
|
+
data: {String: AnyStruct}
|
|
34
|
+
): @{FungibleToken.Vault}
|
|
35
|
+
access(all) fun getDetails(): DropDetails
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A phase represents a stage of a drop. Some drops will only have one
|
|
39
|
+
// phase, while others could have many. For example, a drop with an allow list
|
|
40
|
+
// and a public mint would likely have two phases.
|
|
41
|
+
access(all) resource Phase: PhasePublic {
|
|
42
|
+
access(all) event ResourceDestroyed(uuid: UInt64 = self.uuid)
|
|
43
|
+
|
|
44
|
+
access(all) let details: PhaseDetails
|
|
45
|
+
|
|
46
|
+
access(all) let data: {String: AnyStruct}
|
|
47
|
+
access(all) let resources: @{String: AnyResource}
|
|
48
|
+
|
|
49
|
+
// returns whether this phase of a drop has started.
|
|
50
|
+
access(all) fun isActive(): Bool {
|
|
51
|
+
return self.details.activeChecker.hasStarted() && !self.details.activeChecker.hasEnded()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
access(all) fun getDetails(): PhaseDetails {
|
|
55
|
+
return self.details
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
access(EditPhase) fun borrowActiveCheckerAuth(): auth(Mutate) &{ActiveChecker} {
|
|
59
|
+
return &self.details.activeChecker
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
access(EditPhase) fun borrowPricerAuth(): auth(Mutate) &{Pricer} {
|
|
63
|
+
return &self.details.pricer
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
access(EditPhase) fun borrowAddressVerifierAuth(): auth(Mutate) &{AddressVerifier} {
|
|
67
|
+
return &self.details.addressVerifier
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
init(details: PhaseDetails) {
|
|
71
|
+
self.details = details
|
|
72
|
+
|
|
73
|
+
self.data = {}
|
|
74
|
+
self.resources <- {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
access(all) resource Drop: DropPublic {
|
|
79
|
+
access(all) event ResourceDestroyed(
|
|
80
|
+
uuid: UInt64 = self.uuid,
|
|
81
|
+
minterAddress: Address = self.minterCap.address,
|
|
82
|
+
nftType: String = self.details.nftType,
|
|
83
|
+
totalMinted: Int = self.details.totalMinted
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
// phases represent the stages of a drop. For example, a drop might have an allowlist and a public mint phase.
|
|
87
|
+
access(self) let phases: @[Phase]
|
|
88
|
+
// the details of a drop. This includes things like display information and total number of mints
|
|
89
|
+
access(self) let details: DropDetails
|
|
90
|
+
access(self) let minterCap: Capability<&{Minter}>
|
|
91
|
+
|
|
92
|
+
access(all) let data: {String: AnyStruct}
|
|
93
|
+
access(all) let resources: @{String: AnyResource}
|
|
94
|
+
|
|
95
|
+
access(all) fun mint(
|
|
96
|
+
payment: @{FungibleToken.Vault},
|
|
97
|
+
amount: Int,
|
|
98
|
+
phaseIndex: Int,
|
|
99
|
+
expectedType: Type,
|
|
100
|
+
receiverCap: Capability<&{NonFungibleToken.CollectionPublic}>,
|
|
101
|
+
commissionReceiver: Capability<&{FungibleToken.Receiver}>?,
|
|
102
|
+
data: {String: AnyStruct}
|
|
103
|
+
): @{FungibleToken.Vault} {
|
|
104
|
+
pre {
|
|
105
|
+
expectedType.isSubtype(of: Type<@{NonFungibleToken.NFT}>()): "expected type must be an NFT"
|
|
106
|
+
expectedType.identifier == self.details.nftType: "expected type does not match drop details type"
|
|
107
|
+
self.phases.length > phaseIndex: "phase index is too high"
|
|
108
|
+
receiverCap.check(): "receiver capability is not valid"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// validate the payment vault amount and type
|
|
112
|
+
let phase: &Phase = &self.phases[phaseIndex]
|
|
113
|
+
assert(
|
|
114
|
+
phase.details.addressVerifier.canMint(addr: receiverCap.address, num: amount, totalMinted: self.details.minters[receiverCap.address] ?? 0, data: {}),
|
|
115
|
+
message: "receiver address has exceeded their mint capacity"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
let paymentAmount = phase.details.pricer.getPrice(num: amount, paymentTokenType: payment.getType(), minter: receiverCap.address)
|
|
119
|
+
let withdrawn <- payment.withdraw(amount: paymentAmount) // make sure that we have a fresh vault resource
|
|
120
|
+
|
|
121
|
+
// take commission
|
|
122
|
+
if commissionReceiver != nil && commissionReceiver!.check() {
|
|
123
|
+
let commission <- withdrawn.withdraw(amount: self.details.commissionRate * withdrawn.balance)
|
|
124
|
+
commissionReceiver!.borrow()!.deposit(from: <-commission)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
assert(phase.details.pricer.getPrice(num: amount, paymentTokenType: withdrawn.getType(), minter: receiverCap.address) * (1.0 - self.details.commissionRate) == withdrawn.balance, message: "incorrect payment amount")
|
|
128
|
+
assert(phase.details.pricer.getPaymentTypes().contains(withdrawn.getType()), message: "unsupported payment type")
|
|
129
|
+
|
|
130
|
+
// mint the nfts
|
|
131
|
+
let minter = self.minterCap.borrow() ?? panic("minter capability could not be borrowed")
|
|
132
|
+
let mintedNFTs: @[{NonFungibleToken.NFT}] <- minter.mint(payment: <-withdrawn, amount: amount, phase: phase, data: data)
|
|
133
|
+
assert(phase.details.activeChecker.hasStarted() && !phase.details.activeChecker.hasEnded(), message: "phase is not active")
|
|
134
|
+
assert(mintedNFTs.length == amount, message: "incorrect number of items returned")
|
|
135
|
+
|
|
136
|
+
// distribute to receiver
|
|
137
|
+
let receiver = receiverCap.borrow() ?? panic("could not borrow receiver capability")
|
|
138
|
+
self.details.addMinted(num: mintedNFTs.length, addr: receiverCap.address)
|
|
139
|
+
|
|
140
|
+
while mintedNFTs.length > 0 {
|
|
141
|
+
let nft <- mintedNFTs.removeFirst()
|
|
142
|
+
|
|
143
|
+
let nftType = nft.getType()
|
|
144
|
+
emit Minted(address: receiverCap.address, dropID: self.uuid, phaseID: phase.uuid, nftID: nft.id, nftType: nftType.identifier)
|
|
145
|
+
|
|
146
|
+
// validate that every nft is the right type
|
|
147
|
+
assert(nftType == expectedType, message: "unexpected nft type was minted")
|
|
148
|
+
|
|
149
|
+
receiver.deposit(token: <-nft)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// cleanup
|
|
153
|
+
destroy mintedNFTs
|
|
154
|
+
|
|
155
|
+
// return excess payment
|
|
156
|
+
return <- payment
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
access(Owner) fun borrowPhase(index: Int): auth(EditPhase) &Phase {
|
|
160
|
+
return &self.phases[index]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
access(all) fun borrowPhasePublic(index: Int): &{PhasePublic} {
|
|
165
|
+
return &self.phases[index]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
access(all) fun borrowActivePhases(): [&{PhasePublic}] {
|
|
169
|
+
let arr: [&{PhasePublic}] = []
|
|
170
|
+
var count = 0
|
|
171
|
+
while count < self.phases.length {
|
|
172
|
+
let ref = self.borrowPhasePublic(index: count)
|
|
173
|
+
let activeChecker = ref.getDetails().activeChecker
|
|
174
|
+
if activeChecker.hasStarted() && !activeChecker.hasEnded() {
|
|
175
|
+
arr.append(ref)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
count = count + 1
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return arr
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
access(all) fun borrowAllPhases(): [&{PhasePublic}] {
|
|
185
|
+
let arr: [&{PhasePublic}] = []
|
|
186
|
+
var index = 0
|
|
187
|
+
while index < self.phases.length {
|
|
188
|
+
let ref = self.borrowPhasePublic(index: index)
|
|
189
|
+
arr.append(ref)
|
|
190
|
+
index = index + 1
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return arr
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
access(Owner) fun addPhase(_ phase: @Phase) {
|
|
197
|
+
emit PhaseAdded(
|
|
198
|
+
dropID: self.uuid,
|
|
199
|
+
dropAddress: self.owner!.address,
|
|
200
|
+
id: phase.uuid,
|
|
201
|
+
index: self.phases.length,
|
|
202
|
+
activeCheckerType: phase.details.activeChecker.getType().identifier,
|
|
203
|
+
pricerType: phase.details.pricer.getType().identifier,
|
|
204
|
+
addressVerifierType: phase.details.addressVerifier.getType().identifier
|
|
205
|
+
)
|
|
206
|
+
self.phases.append(<-phase)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
access(Owner) fun removePhase(index: Int): @Phase {
|
|
210
|
+
pre {
|
|
211
|
+
self.phases.length > index: "index is greater than length of phases"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let phase <- self.phases.remove(at: index)
|
|
215
|
+
emit PhaseRemoved(dropID: self.uuid, dropAddress: self.owner!.address, id: phase.uuid)
|
|
216
|
+
|
|
217
|
+
return <- phase
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
access(all) fun getDetails(): DropDetails {
|
|
221
|
+
return self.details
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
init(details: DropDetails, minterCap: Capability<&{Minter}>, phases: @[Phase]) {
|
|
225
|
+
pre {
|
|
226
|
+
minterCap.check(): "minter capability is not valid"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
self.phases <- phases
|
|
230
|
+
self.details = details
|
|
231
|
+
self.minterCap = minterCap
|
|
232
|
+
|
|
233
|
+
self.data = {}
|
|
234
|
+
self.resources <- {}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
access(all) struct DropDetails {
|
|
239
|
+
access(all) let display: MetadataViews.Display
|
|
240
|
+
access(all) let medias: MetadataViews.Medias?
|
|
241
|
+
access(all) var totalMinted: Int
|
|
242
|
+
access(all) var minters: {Address: Int}
|
|
243
|
+
access(all) let commissionRate: UFix64
|
|
244
|
+
access(all) let nftType: String
|
|
245
|
+
|
|
246
|
+
access(all) let data: {String: AnyStruct}
|
|
247
|
+
|
|
248
|
+
access(contract) fun addMinted(num: Int, addr: Address) {
|
|
249
|
+
self.totalMinted = self.totalMinted + num
|
|
250
|
+
if self.minters[addr] == nil {
|
|
251
|
+
self.minters[addr] = 0
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
self.minters[addr] = self.minters[addr]! + num
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
init(display: MetadataViews.Display, medias: MetadataViews.Medias?, commissionRate: UFix64, nftType: String) {
|
|
258
|
+
pre {
|
|
259
|
+
nftType != "": "nftType should be a composite type identifier"
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
self.display = display
|
|
263
|
+
self.medias = medias
|
|
264
|
+
self.totalMinted = 0
|
|
265
|
+
self.commissionRate = commissionRate
|
|
266
|
+
self.minters = {}
|
|
267
|
+
self.nftType = nftType
|
|
268
|
+
self.data = {}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// An ActiveChecker represents a phase being on or off, and holds information
|
|
273
|
+
// about whether a phase has started or not.
|
|
274
|
+
access(all) struct interface ActiveChecker {
|
|
275
|
+
// Signal that a phase has started. If the phase has not ended, it means that this activeChecker's phase
|
|
276
|
+
// is active
|
|
277
|
+
access(all) view fun hasStarted(): Bool
|
|
278
|
+
// Signal that a phase has ended. If an ActiveChecker has ended, minting will not work. That could mean
|
|
279
|
+
// the drop is over, or it could mean another phase has begun.
|
|
280
|
+
access(all) view fun hasEnded(): Bool
|
|
281
|
+
|
|
282
|
+
access(all) view fun getStart(): UInt64?
|
|
283
|
+
access(all) view fun getEnd(): UInt64?
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
access(all) resource interface PhasePublic {
|
|
287
|
+
// What does a phase need to be able to answer/manage?
|
|
288
|
+
// - What are the details of the phase being interactive with?
|
|
289
|
+
// - How many items are left in the current phase?
|
|
290
|
+
// - Can Address x mint on a phase?
|
|
291
|
+
// - What is the cost to mint for the phase I am interested in (for address x)?
|
|
292
|
+
access(all) fun getDetails(): PhaseDetails
|
|
293
|
+
access(all) fun isActive(): Bool
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
access(all) struct PhaseDetails {
|
|
297
|
+
// handles whether a phase is on or not
|
|
298
|
+
access(all) let activeChecker: {ActiveChecker}
|
|
299
|
+
|
|
300
|
+
// display information about a phase
|
|
301
|
+
access(all) let display: MetadataViews.Display?
|
|
302
|
+
|
|
303
|
+
// handles the pricing of a phase
|
|
304
|
+
access(all) let pricer: {Pricer}
|
|
305
|
+
|
|
306
|
+
// verifies whether an address is able to mint
|
|
307
|
+
access(all) let addressVerifier: {AddressVerifier}
|
|
308
|
+
|
|
309
|
+
// placecholder data dictionary to allow new fields to be accessed
|
|
310
|
+
access(all) let data: {String: AnyStruct}
|
|
311
|
+
|
|
312
|
+
init(activeChecker: {ActiveChecker}, display: MetadataViews.Display?, pricer: {Pricer}, addressVerifier: {AddressVerifier}) {
|
|
313
|
+
self.activeChecker = activeChecker
|
|
314
|
+
self.display = display
|
|
315
|
+
self.pricer = pricer
|
|
316
|
+
self.addressVerifier = addressVerifier
|
|
317
|
+
|
|
318
|
+
self.data = {}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
access(all) struct interface AddressVerifier {
|
|
323
|
+
access(all) fun canMint(addr: Address, num: Int, totalMinted: Int, data: {String: AnyStruct}): Bool {
|
|
324
|
+
return true
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
access(all) fun remainingForAddress(addr: Address, totalMinted: Int): Int? {
|
|
328
|
+
return nil
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
access(all) struct interface Pricer {
|
|
333
|
+
access(all) fun getPrice(num: Int, paymentTokenType: Type, minter: Address?): UFix64
|
|
334
|
+
access(all) fun getPaymentTypes(): [Type]
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
access(all) resource interface Minter {
|
|
338
|
+
access(contract) fun mint(payment: @{FungibleToken.Vault}, amount: Int, phase: &FlowtyDrops.Phase, data: {String: AnyStruct}): @[{NonFungibleToken.NFT}] {
|
|
339
|
+
let resourceAddress = AddressUtils.parseAddress(self.getType())!
|
|
340
|
+
let receiver = getAccount(resourceAddress).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenRouter.PublicPath).borrow()
|
|
341
|
+
?? panic("missing receiver at fungible token router path")
|
|
342
|
+
receiver.deposit(from: <-payment)
|
|
343
|
+
|
|
344
|
+
let nfts: @[{NonFungibleToken.NFT}] <- []
|
|
345
|
+
|
|
346
|
+
var count = 0
|
|
347
|
+
while count < amount {
|
|
348
|
+
count = count + 1
|
|
349
|
+
nfts.append(<- self.createNextNFT())
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return <- nfts
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
access(contract) fun createNextNFT(): @{NonFungibleToken.NFT}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
access(all) struct DropResolver {
|
|
359
|
+
access(self) let cap: Capability<&{ContainerPublic}>
|
|
360
|
+
|
|
361
|
+
access(all) fun borrowContainer(): &{ContainerPublic}? {
|
|
362
|
+
return self.cap.borrow()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
init(cap: Capability<&{ContainerPublic}>) {
|
|
366
|
+
pre {
|
|
367
|
+
cap.check(): "container capability is not valid"
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
self.cap = cap
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
access(all) resource interface ContainerPublic {
|
|
375
|
+
access(all) fun borrowDropPublic(id: UInt64): &{DropPublic}?
|
|
376
|
+
access(all) fun getIDs(): [UInt64]
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Contains drops.
|
|
380
|
+
access(all) resource Container: ContainerPublic {
|
|
381
|
+
access(self) let drops: @{UInt64: Drop}
|
|
382
|
+
|
|
383
|
+
access(all) let data: {String: AnyStruct}
|
|
384
|
+
access(all) let resources: @{String: AnyResource}
|
|
385
|
+
|
|
386
|
+
access(Owner) fun addDrop(_ drop: @Drop) {
|
|
387
|
+
let details = drop.getDetails()
|
|
388
|
+
|
|
389
|
+
let phases = drop.borrowAllPhases()
|
|
390
|
+
assert(phases.length > 0, message: "drops must have at least one phase to be added to a container")
|
|
391
|
+
|
|
392
|
+
let firstPhaseDetails = phases[0].getDetails()
|
|
393
|
+
|
|
394
|
+
emit DropAdded(
|
|
395
|
+
address: self.owner!.address,
|
|
396
|
+
id: drop.uuid,
|
|
397
|
+
name: details.display.name,
|
|
398
|
+
description: details.display.description,
|
|
399
|
+
imageUrl: details.display.thumbnail.uri(),
|
|
400
|
+
start: firstPhaseDetails.activeChecker.getStart(),
|
|
401
|
+
end: firstPhaseDetails.activeChecker.getEnd(),
|
|
402
|
+
nftType: details.nftType
|
|
403
|
+
)
|
|
404
|
+
destroy self.drops.insert(key: drop.uuid, <-drop)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
access(Owner) fun removeDrop(id: UInt64): @Drop {
|
|
408
|
+
pre {
|
|
409
|
+
self.drops.containsKey(id): "drop was not found"
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return <- self.drops.remove(key: id)!
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
access(Owner) fun borrowDrop(id: UInt64): auth(Owner) &Drop? {
|
|
416
|
+
return &self.drops[id]
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
access(all) fun borrowDropPublic(id: UInt64): &{DropPublic}? {
|
|
420
|
+
return &self.drops[id]
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
access(all) fun getIDs(): [UInt64] {
|
|
424
|
+
return self.drops.keys
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
init() {
|
|
428
|
+
self.drops <- {}
|
|
429
|
+
|
|
430
|
+
self.data = {}
|
|
431
|
+
self.resources <- {}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
access(all) fun createPhase(details: PhaseDetails): @Phase {
|
|
436
|
+
return <- create Phase(details: details)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
access(all) fun createDrop(details: DropDetails, minterCap: Capability<&{Minter}>, phases: @[Phase]): @Drop {
|
|
440
|
+
return <- create Drop(details: details, minterCap: minterCap, phases: <- phases)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
access(all) fun createContainer(): @Container {
|
|
444
|
+
return <- create Container()
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
access(all) fun getMinterStoragePath(type: Type): StoragePath {
|
|
448
|
+
let segments = type.identifier.split(separator: ".")
|
|
449
|
+
let identifier = "FlowtyDrops_Minter_".concat(segments[1]).concat("_").concat(segments[2])
|
|
450
|
+
return StoragePath(identifier: identifier)!
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
init() {
|
|
454
|
+
let identifier = "FlowtyDrops_".concat(self.account.address.toString())
|
|
455
|
+
let containerIdentifier = identifier.concat("_Container")
|
|
456
|
+
let minterIdentifier = identifier.concat("_Minter")
|
|
457
|
+
|
|
458
|
+
self.ContainerStoragePath = StoragePath(identifier: containerIdentifier)!
|
|
459
|
+
self.ContainerPublicPath = PublicPath(identifier: containerIdentifier)!
|
|
460
|
+
}
|
|
461
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import "FlowtyDrops"
|
|
2
|
+
import "FlowToken"
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
This contract contains implementations of the FlowtyDrops.Pricer interface.
|
|
6
|
+
You can use these, or any custom implementation for the phases of your drop.
|
|
7
|
+
*/
|
|
8
|
+
access(all) contract FlowtyPricers {
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
The FlatPrice Pricer implementation has a set price and token type. Every mint is the same cost regardless of
|
|
12
|
+
the number minter, or what address is minting
|
|
13
|
+
*/
|
|
14
|
+
access(all) struct FlatPrice: FlowtyDrops.Pricer {
|
|
15
|
+
access(all) var price: UFix64
|
|
16
|
+
access(all) let paymentTokenType: String
|
|
17
|
+
|
|
18
|
+
access(all) view fun getPrice(num: Int, paymentTokenType: Type, minter: Address?): UFix64 {
|
|
19
|
+
return self.price * UFix64(num)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
access(all) view fun getPaymentTypes(): [Type] {
|
|
23
|
+
return [CompositeType(self.paymentTokenType)!]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
access(Mutate) fun setPrice(price: UFix64) {
|
|
27
|
+
self.price = price
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
init(price: UFix64, paymentTokenType: Type) {
|
|
31
|
+
self.price = price
|
|
32
|
+
self.paymentTokenType = paymentTokenType.identifier
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
The Free Pricer can be used for a free mint, it has no price and always marks its payment type as @FlowToken.Vault
|
|
38
|
+
*/
|
|
39
|
+
access(all) struct Free: FlowtyDrops.Pricer {
|
|
40
|
+
access(all) fun getPrice(num: Int, paymentTokenType: Type, minter: Address?): UFix64 {
|
|
41
|
+
return 0.0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
access(all) fun getPaymentTypes(): [Type] {
|
|
45
|
+
return [Type<@FlowToken.Vault>()]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "FlowtyDrops"
|
|
2
|
+
import "NFTMetadata"
|
|
3
|
+
import "AddressUtils"
|
|
4
|
+
import "ContractInitializer"
|
|
5
|
+
|
|
6
|
+
access(all) contract ContractBorrower {
|
|
7
|
+
access(all) fun borrowInitializer(typeIdentifier: String): &{ContractInitializer} {
|
|
8
|
+
let type = CompositeType(typeIdentifier) ?? panic("invalid type identifier")
|
|
9
|
+
let addr = AddressUtils.parseAddress(type)!
|
|
10
|
+
|
|
11
|
+
let contractName = type.identifier.split(separator: ".")[2]
|
|
12
|
+
return getAccount(addr).contracts.borrow<&{ContractInitializer}>(name: contractName)!
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import "FlowtyDrops"
|
|
2
|
+
import "NFTMetadata"
|
|
3
|
+
import "AddressUtils"
|
|
4
|
+
|
|
5
|
+
access(all) contract interface ContractInitializer {
|
|
6
|
+
access(all) fun initialize(contractAcct: auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account, params: {String: AnyStruct}): NFTMetadata.InitializedCaps
|
|
7
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import "ContractInitializer"
|
|
2
|
+
import "NFTMetadata"
|
|
3
|
+
import "FlowtyDrops"
|
|
4
|
+
import "NonFungibleToken"
|
|
5
|
+
import "UniversalCollection"
|
|
6
|
+
|
|
7
|
+
access(all) contract OpenEditionInitializer: ContractInitializer {
|
|
8
|
+
access(all) fun initialize(contractAcct: auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account, params: {String: AnyStruct}): NFTMetadata.InitializedCaps {
|
|
9
|
+
pre {
|
|
10
|
+
params["data"] != nil: "missing param data"
|
|
11
|
+
params["data"]!.getType() == Type<NFTMetadata.Metadata>(): "data param must be of type NFTMetadata.Metadata"
|
|
12
|
+
params["collectionInfo"] != nil: "missing param collectionInfo"
|
|
13
|
+
params["collectionInfo"]!.getType() == Type<NFTMetadata.CollectionInfo>(): "collectionInfo param must be of type NFTMetadata.CollectionInfo"
|
|
14
|
+
params["type"] != nil: "missing param type"
|
|
15
|
+
params["type"]!.getType() == Type<Type>(): "type param must be of type Type"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let data = params["data"]! as! NFTMetadata.Metadata
|
|
19
|
+
let collectionInfo = params["collectionInfo"]! as! NFTMetadata.CollectionInfo
|
|
20
|
+
|
|
21
|
+
let nftType = params["type"]! as! Type
|
|
22
|
+
let contractName = nftType.identifier.split(separator: ".")[2]
|
|
23
|
+
|
|
24
|
+
// do we have information to setup a drop as well?
|
|
25
|
+
if params.containsKey("dropDetails") && params.containsKey("phaseDetails") && params.containsKey("minterController") {
|
|
26
|
+
// extract expected keys
|
|
27
|
+
let minterCap = params["minterController"]! as! Capability<&{FlowtyDrops.Minter}>
|
|
28
|
+
let dropDetails = params["dropDetails"]! as! FlowtyDrops.DropDetails
|
|
29
|
+
let phaseDetails = params["phaseDetails"]! as! [FlowtyDrops.PhaseDetails]
|
|
30
|
+
|
|
31
|
+
assert(minterCap.check(), message: "invalid minter capability")
|
|
32
|
+
assert(CompositeType(dropDetails.nftType) != nil, message: "dropDetails.nftType must be a valid CompositeType")
|
|
33
|
+
|
|
34
|
+
let phases: @[FlowtyDrops.Phase] <- []
|
|
35
|
+
for p in phaseDetails {
|
|
36
|
+
phases.append(<- FlowtyDrops.createPhase(details: p))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let drop <- FlowtyDrops.createDrop(details: dropDetails, minterCap: minterCap, phases: <- phases)
|
|
40
|
+
if contractAcct.storage.borrow<&AnyResource>(from: FlowtyDrops.ContainerStoragePath) == nil {
|
|
41
|
+
contractAcct.storage.save(<- FlowtyDrops.createContainer(), to: FlowtyDrops.ContainerStoragePath)
|
|
42
|
+
|
|
43
|
+
contractAcct.capabilities.unpublish(FlowtyDrops.ContainerPublicPath)
|
|
44
|
+
contractAcct.capabilities.publish(
|
|
45
|
+
contractAcct.capabilities.storage.issue<&{FlowtyDrops.ContainerPublic}>(FlowtyDrops.ContainerStoragePath),
|
|
46
|
+
at: FlowtyDrops.ContainerPublicPath
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let container = contractAcct.storage.borrow<auth(FlowtyDrops.Owner) &FlowtyDrops.Container>(from: FlowtyDrops.ContainerStoragePath)
|
|
51
|
+
?? panic("drops container not found")
|
|
52
|
+
container.addDrop(<- drop)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return NFTMetadata.initialize(acct: contractAcct, collectionInfo: collectionInfo, nftType: nftType)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import "NonFungibleToken"
|
|
2
|
+
import "ViewResolver"
|
|
3
|
+
import "MetadataViews"
|
|
4
|
+
import "NFTMetadata"
|
|
5
|
+
import "FlowtyDrops"
|
|
6
|
+
import "AddressUtils"
|
|
7
|
+
import "StringUtils"
|
|
8
|
+
|
|
9
|
+
access(all) contract interface BaseCollection: ViewResolver {
|
|
10
|
+
access(all) var MetadataCap: Capability<&NFTMetadata.Container>
|
|
11
|
+
access(all) var totalSupply: UInt64
|
|
12
|
+
|
|
13
|
+
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}
|
|
14
|
+
|
|
15
|
+
// The base collection is an interface that attmepts to take more boilerplate
|
|
16
|
+
// off of NFT-standard compliant definitions.
|
|
17
|
+
access(all) resource interface Collection: NonFungibleToken.Collection {
|
|
18
|
+
access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
|
|
19
|
+
access(all) var nftType: Type
|
|
20
|
+
|
|
21
|
+
access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
|
|
22
|
+
pre {
|
|
23
|
+
token.getType() == self.nftType: "unexpected nft type being deposited"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
destroy self.ownedNFTs.insert(key: token.uuid, <-token)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
|
|
30
|
+
return &self.ownedNFTs[id]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
|
|
34
|
+
return {
|
|
35
|
+
self.nftType: true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
access(all) view fun isSupportedNFTType(type: Type): Bool {
|
|
40
|
+
return type == self.nftType
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
|
|
44
|
+
return <- self.ownedNFTs.remove(key: withdrawID)!
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
access(all) view fun getContractViews(resourceType: Type?): [Type] {
|
|
49
|
+
return [
|
|
50
|
+
Type<MetadataViews.NFTCollectionData>(),
|
|
51
|
+
Type<MetadataViews.NFTCollectionDisplay>()
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
|
|
56
|
+
if resourceType == nil {
|
|
57
|
+
return nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let rt = resourceType!
|
|
61
|
+
let segments = rt.identifier.split(separator: ".")
|
|
62
|
+
let pathIdentifier = StringUtils.join([segments[2], segments[1]], "_")
|
|
63
|
+
|
|
64
|
+
let addr = AddressUtils.parseAddress(rt)!
|
|
65
|
+
let acct = getAccount(addr)
|
|
66
|
+
|
|
67
|
+
switch viewType {
|
|
68
|
+
case Type<MetadataViews.NFTCollectionData>():
|
|
69
|
+
let segments = rt.identifier.split(separator: ".")
|
|
70
|
+
let pathIdentifier = StringUtils.join([segments[2], segments[1]], "_")
|
|
71
|
+
|
|
72
|
+
return MetadataViews.NFTCollectionData(
|
|
73
|
+
storagePath: StoragePath(identifier: pathIdentifier)!,
|
|
74
|
+
publicPath: PublicPath(identifier: pathIdentifier)!,
|
|
75
|
+
publicCollection: Type<&{NonFungibleToken.Collection}>(),
|
|
76
|
+
publicLinkedType: Type<&{NonFungibleToken.Collection}>(),
|
|
77
|
+
createEmptyCollectionFunction: fun(): @{NonFungibleToken.Collection} {
|
|
78
|
+
let addr = AddressUtils.parseAddress(rt)!
|
|
79
|
+
let c = getAccount(addr).contracts.borrow<&{BaseCollection}>(name: segments[2])!
|
|
80
|
+
return <- c.createEmptyCollection(nftType: rt)
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
case Type<MetadataViews.NFTCollectionDisplay>():
|
|
84
|
+
let c = getAccount(addr).contracts.borrow<&{BaseCollection}>(name: segments[2])!
|
|
85
|
+
let tmp = c.MetadataCap.borrow()
|
|
86
|
+
if tmp == nil {
|
|
87
|
+
return nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return tmp!.collectionInfo.getDisplay()
|
|
91
|
+
case Type<FlowtyDrops.DropResolver>():
|
|
92
|
+
return FlowtyDrops.DropResolver(cap: acct.capabilities.get<&{FlowtyDrops.ContainerPublic}>(FlowtyDrops.ContainerPublicPath))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return nil
|
|
96
|
+
}
|
|
97
|
+
}
|