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