@flowtyio/flow-contracts 0.1.3 → 0.1.5
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/flowty-drops/ContractManager.cdc +29 -0
- package/contracts/flowty-drops/DropFactory.cdc +2 -2
- package/contracts/flowty-drops/DropTypes.cdc +55 -16
- package/contracts/flowty-drops/FlowtyDrops.cdc +64 -30
- package/contracts/flowty-drops/nft/BaseNFT.cdc +7 -0
- package/contracts/flowty-drops/nft/NFTMetadata.cdc +1 -0
- package/flow.json +17 -17
- package/package.json +1 -1
|
@@ -6,8 +6,13 @@ access(all) contract ContractManager {
|
|
|
6
6
|
access(all) let StoragePath: StoragePath
|
|
7
7
|
access(all) let PublicPath: PublicPath
|
|
8
8
|
|
|
9
|
+
access(all) let OwnerStoragePath: StoragePath
|
|
10
|
+
access(all) let OwnerPublicPath: PublicPath
|
|
11
|
+
|
|
9
12
|
access(all) entitlement Manage
|
|
10
13
|
|
|
14
|
+
access(all) event ManagerSaved(uuid: UInt64, contractAddress: Address, ownerAddress: Address)
|
|
15
|
+
|
|
11
16
|
access(all) resource Manager {
|
|
12
17
|
access(self) let acct: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
|
|
13
18
|
access(self) let routerCap: Capability<auth(FungibleTokenRouter.Owner) &FungibleTokenRouter.Router>
|
|
@@ -36,6 +41,26 @@ access(all) contract ContractManager {
|
|
|
36
41
|
return getAccount(self.acct.address)
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
// Should be called after saving a ContractManager resource to signal that a new address stores (and therefore "owns")
|
|
45
|
+
// this manager resource's acct capability. Without this, it is not possible to track the original creator of a contract
|
|
46
|
+
// when using the ContractManager
|
|
47
|
+
access(Manage) fun onSave() {
|
|
48
|
+
let acct = self.acct.borrow()!
|
|
49
|
+
|
|
50
|
+
acct.storage.load<Address>(from: ContractManager.OwnerStoragePath)
|
|
51
|
+
acct.storage.save(self.owner!.address, to: ContractManager.OwnerStoragePath)
|
|
52
|
+
|
|
53
|
+
if !acct.capabilities.get<&Address>(ContractManager.OwnerPublicPath).check() {
|
|
54
|
+
acct.capabilities.unpublish(ContractManager.OwnerPublicPath)
|
|
55
|
+
acct.capabilities.publish(
|
|
56
|
+
acct.capabilities.storage.issue<&Address>(ContractManager.OwnerStoragePath),
|
|
57
|
+
at: ContractManager.OwnerPublicPath
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
emit ManagerSaved(uuid: self.uuid, contractAddress: self.acct.address, ownerAddress: self.owner!.address)
|
|
62
|
+
}
|
|
63
|
+
|
|
39
64
|
init(tokens: @FlowToken.Vault, defaultRouterAddress: Address) {
|
|
40
65
|
pre {
|
|
41
66
|
tokens.balance >= 0.001: "minimum balance of 0.001 required for initialization"
|
|
@@ -69,5 +94,9 @@ access(all) contract ContractManager {
|
|
|
69
94
|
let identifier = "ContractManager_".concat(self.account.address.toString())
|
|
70
95
|
self.StoragePath = StoragePath(identifier: identifier)!
|
|
71
96
|
self.PublicPath = PublicPath(identifier: identifier)!
|
|
97
|
+
|
|
98
|
+
let ownerIdentifier = "ContractManager_Owner_".concat(self.account.address.toString())
|
|
99
|
+
self.OwnerStoragePath = StoragePath(identifier: ownerIdentifier)!
|
|
100
|
+
self.OwnerPublicPath = PublicPath(identifier: ownerIdentifier)!
|
|
72
101
|
}
|
|
73
102
|
}
|
|
@@ -35,7 +35,7 @@ access(all) contract DropFactory {
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier")
|
|
38
|
-
let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05, nftType: nftTypeIdentifier)
|
|
38
|
+
let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05, nftType: nftTypeIdentifier, paymentTokenTypes: {paymentTokenType.identifier: true})
|
|
39
39
|
let drop <- FlowtyDrops.createDrop(details: dropDetails, minterCap: minterCap, phases: <- [<-phase])
|
|
40
40
|
|
|
41
41
|
return <- drop
|
|
@@ -67,7 +67,7 @@ access(all) contract DropFactory {
|
|
|
67
67
|
let phase <- FlowtyDrops.createPhase(details: phaseDetails)
|
|
68
68
|
|
|
69
69
|
let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier")
|
|
70
|
-
let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05, nftType: nftTypeIdentifier)
|
|
70
|
+
let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05, nftType: nftTypeIdentifier, paymentTokenTypes: {paymentTokenType.identifier: true})
|
|
71
71
|
let drop <- FlowtyDrops.createDrop(details: dropDetails, minterCap: minterCap, phases: <- [<-phase])
|
|
72
72
|
|
|
73
73
|
return <- drop
|
|
@@ -2,6 +2,7 @@ import "FlowtyDrops"
|
|
|
2
2
|
import "MetadataViews"
|
|
3
3
|
import "ViewResolver"
|
|
4
4
|
import "AddressUtils"
|
|
5
|
+
import "ContractManager"
|
|
5
6
|
|
|
6
7
|
access(all) contract DropTypes {
|
|
7
8
|
access(all) struct Display {
|
|
@@ -34,6 +35,8 @@ access(all) contract DropTypes {
|
|
|
34
35
|
access(all) let minterCount: Int
|
|
35
36
|
access(all) let commissionRate: UFix64
|
|
36
37
|
access(all) let nftType: String
|
|
38
|
+
access(all) let creator: Address?
|
|
39
|
+
access(all) let paymentTokenTypes: {String: Bool}
|
|
37
40
|
|
|
38
41
|
access(all) let address: Address?
|
|
39
42
|
access(all) let mintedByAddress: Int?
|
|
@@ -55,10 +58,13 @@ access(all) contract DropTypes {
|
|
|
55
58
|
nftType: Type,
|
|
56
59
|
address: Address?,
|
|
57
60
|
phases: [PhaseSummary],
|
|
58
|
-
royaltyRate: UFix64
|
|
61
|
+
royaltyRate: UFix64,
|
|
62
|
+
creator: Address?,
|
|
63
|
+
paymentTokenTypes: {String: Bool}
|
|
59
64
|
) {
|
|
60
65
|
self.id = id
|
|
61
66
|
self.display = Display(display)
|
|
67
|
+
self.creator = creator
|
|
62
68
|
|
|
63
69
|
self.medias = []
|
|
64
70
|
for m in medias?.items ?? [] {
|
|
@@ -78,6 +84,7 @@ access(all) contract DropTypes {
|
|
|
78
84
|
self.blockHeight = b.height
|
|
79
85
|
self.blockTimestamp = UInt64(b.timestamp)
|
|
80
86
|
self.royaltyRate = royaltyRate
|
|
87
|
+
self.paymentTokenTypes = paymentTokenTypes
|
|
81
88
|
}
|
|
82
89
|
}
|
|
83
90
|
|
|
@@ -114,7 +121,7 @@ access(all) contract DropTypes {
|
|
|
114
121
|
access(all) let remainingForAddress: Int?
|
|
115
122
|
access(all) let maxPerMint: Int?
|
|
116
123
|
|
|
117
|
-
access(all) let
|
|
124
|
+
access(all) let quotes: {String: Quote}
|
|
118
125
|
|
|
119
126
|
init(
|
|
120
127
|
index: Int,
|
|
@@ -123,7 +130,7 @@ access(all) contract DropTypes {
|
|
|
123
130
|
totalMinted: Int?,
|
|
124
131
|
minter: Address?,
|
|
125
132
|
quantity: Int?,
|
|
126
|
-
|
|
133
|
+
paymentIdentifiers: [String]
|
|
127
134
|
) {
|
|
128
135
|
self.index = index
|
|
129
136
|
self.id = phase.uuid
|
|
@@ -151,24 +158,24 @@ access(all) contract DropTypes {
|
|
|
151
158
|
self.remainingForAddress = nil
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
self.maxPerMint = d.addressVerifier.getMaxPerMint(addr: self.address, totalMinted: totalMinted ?? 0, data: {}
|
|
161
|
+
self.maxPerMint = d.addressVerifier.getMaxPerMint(addr: self.address, totalMinted: totalMinted ?? 0, data: {})
|
|
162
|
+
self.quotes = {}
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
let price = d.pricer.getPrice(num: quantity!, paymentTokenType: CompositeType(paymentIdentifier
|
|
158
|
-
|
|
159
|
-
self.quote = Quote(price: price, quantity: quantity!, paymentIdentifier: paymentIdentifier!, minter: minter)
|
|
160
|
-
} else {
|
|
161
|
-
self.quote = nil
|
|
164
|
+
for paymentIdentifier in paymentIdentifiers {
|
|
165
|
+
let price = d.pricer.getPrice(num: quantity!, paymentTokenType: CompositeType(paymentIdentifier)!, minter: minter)
|
|
166
|
+
self.quotes[paymentIdentifier] = Quote(price: price, quantity: quantity!, paymentIdentifier: paymentIdentifier, minter: minter)
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
170
|
|
|
166
|
-
access(all) fun getDropSummary(nftTypeIdentifier: String, dropID: UInt64, minter: Address?, quantity: Int?,
|
|
171
|
+
access(all) fun getDropSummary(nftTypeIdentifier: String, dropID: UInt64, minter: Address?, quantity: Int?, paymentIdentifiers: [String]): DropSummary? {
|
|
167
172
|
let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier")
|
|
168
173
|
let segments = nftTypeIdentifier.split(separator: ".")
|
|
169
174
|
let contractAddress = AddressUtils.parseAddress(nftType)!
|
|
170
175
|
let contractName = segments[2]
|
|
171
176
|
|
|
177
|
+
let creator = self.getCreatorAddress(contractAddress)
|
|
178
|
+
|
|
172
179
|
let resolver = getAccount(contractAddress).contracts.borrow<&{ViewResolver}>(name: contractName)
|
|
173
180
|
if resolver == nil {
|
|
174
181
|
return nil
|
|
@@ -200,7 +207,7 @@ access(all) contract DropTypes {
|
|
|
200
207
|
totalMinted: minter != nil ? dropDetails.minters[minter!] : nil,
|
|
201
208
|
minter: minter,
|
|
202
209
|
quantity: quantity,
|
|
203
|
-
|
|
210
|
+
paymentIdentifiers: paymentIdentifiers.length > 0 ? paymentIdentifiers : dropDetails.paymentTokenTypes.keys
|
|
204
211
|
)
|
|
205
212
|
phaseSummaries.append(summary)
|
|
206
213
|
}
|
|
@@ -224,17 +231,21 @@ access(all) contract DropTypes {
|
|
|
224
231
|
nftType: CompositeType(dropDetails.nftType)!,
|
|
225
232
|
address: minter,
|
|
226
233
|
phases: phaseSummaries,
|
|
227
|
-
royaltyRate: royaltyRate
|
|
234
|
+
royaltyRate: royaltyRate,
|
|
235
|
+
creator: creator,
|
|
236
|
+
paymentTokenTypes: dropDetails.paymentTokenTypes
|
|
228
237
|
)
|
|
229
238
|
|
|
230
239
|
return dropSummary
|
|
231
240
|
}
|
|
232
241
|
|
|
233
|
-
access(all) fun getAllDropSummaries(nftTypeIdentifier: String, minter: Address?, quantity: Int?,
|
|
242
|
+
access(all) fun getAllDropSummaries(nftTypeIdentifier: String, minter: Address?, quantity: Int?, paymentIdentifiers: [String]): [DropSummary] {
|
|
234
243
|
let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier")
|
|
235
244
|
let segments = nftTypeIdentifier.split(separator: ".")
|
|
236
245
|
let contractAddress = AddressUtils.parseAddress(nftType)!
|
|
237
246
|
let contractName = segments[2]
|
|
247
|
+
|
|
248
|
+
let creator = self.getCreatorAddress(contractAddress)
|
|
238
249
|
|
|
239
250
|
let resolver = getAccount(contractAddress).contracts.borrow<&{ViewResolver}>(name: contractName)
|
|
240
251
|
if resolver == nil {
|
|
@@ -269,7 +280,7 @@ access(all) contract DropTypes {
|
|
|
269
280
|
totalMinted: minter != nil ? dropDetails.minters[minter!] : nil,
|
|
270
281
|
minter: minter,
|
|
271
282
|
quantity: quantity,
|
|
272
|
-
|
|
283
|
+
paymentIdentifiers: paymentIdentifiers.length > 0 ? paymentIdentifiers : dropDetails.paymentTokenTypes.keys
|
|
273
284
|
)
|
|
274
285
|
phaseSummaries.append(summary)
|
|
275
286
|
}
|
|
@@ -297,10 +308,38 @@ access(all) contract DropTypes {
|
|
|
297
308
|
nftType: CompositeType(dropDetails.nftType)!,
|
|
298
309
|
address: minter,
|
|
299
310
|
phases: phaseSummaries,
|
|
300
|
-
royaltyRate: royaltyRate
|
|
311
|
+
royaltyRate: royaltyRate,
|
|
312
|
+
creator: creator,
|
|
313
|
+
paymentTokenTypes: dropDetails.paymentTokenTypes
|
|
301
314
|
))
|
|
302
315
|
}
|
|
303
316
|
|
|
304
317
|
return summaries
|
|
305
318
|
}
|
|
319
|
+
|
|
320
|
+
access(all) fun getCreatorAddress(_ contractAddress: Address): Address? {
|
|
321
|
+
// We look for a two-way relationship between creator and collection. A contract can expose an address at ContractManager.OwnerPublicPath
|
|
322
|
+
// specifying the owning account. If found, we will check that same account for a &ContractManager.Manager resource at ContractManager.PublicPath,
|
|
323
|
+
// which, when borrowed, can return its underlying account address using &Manager.getAccount().
|
|
324
|
+
//
|
|
325
|
+
// If the addresses match, we consider this account to be the creator of a drop
|
|
326
|
+
let tmp = getAccount(contractAddress).capabilities.borrow<&Address>(ContractManager.OwnerPublicPath)
|
|
327
|
+
if tmp == nil {
|
|
328
|
+
return nil
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let creator = *(tmp!)
|
|
332
|
+
let manager = getAccount(creator).capabilities.borrow<&ContractManager.Manager>(ContractManager.PublicPath)
|
|
333
|
+
if manager == nil {
|
|
334
|
+
return nil
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let contractManagerAccount = manager!.getAccount()
|
|
338
|
+
|
|
339
|
+
if contractManagerAccount.address != contractAddress {
|
|
340
|
+
return nil
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return creator
|
|
344
|
+
}
|
|
306
345
|
}
|
|
@@ -5,12 +5,19 @@ import "AddressUtils"
|
|
|
5
5
|
import "FungibleTokenMetadataViews"
|
|
6
6
|
import "FungibleTokenRouter"
|
|
7
7
|
|
|
8
|
+
// FlowtyDrops is a contract to help collections manage their primary sale needs on flow.
|
|
9
|
+
// Multiple drops can be made for a single contract (like how TopShot has had lots of pack drops),
|
|
10
|
+
// and can be split into phases to represent different behaviors over the course of a drop
|
|
8
11
|
access(all) contract FlowtyDrops {
|
|
12
|
+
// The total number of nfts minted by this contract
|
|
13
|
+
access(all) var TotalMinted: UInt64
|
|
14
|
+
|
|
9
15
|
access(all) let ContainerStoragePath: StoragePath
|
|
10
16
|
access(all) let ContainerPublicPath: PublicPath
|
|
11
17
|
|
|
12
18
|
access(all) event DropAdded(address: Address, id: UInt64, name: String, description: String, imageUrl: String, start: UInt64?, end: UInt64?, nftType: String)
|
|
13
|
-
access(all) event
|
|
19
|
+
access(all) event DropRemoved(address: Address, id: UInt64)
|
|
20
|
+
access(all) event Minted(address: Address, dropID: UInt64, phaseID: UInt64, nftID: UInt64, nftType: String, totalMinted: UInt64)
|
|
14
21
|
access(all) event PhaseAdded(dropID: UInt64, dropAddress: Address, id: UInt64, index: Int, activeCheckerType: String, pricerType: String, addressVerifierType: String)
|
|
15
22
|
access(all) event PhaseRemoved(dropID: UInt64, dropAddress: Address, id: UInt64)
|
|
16
23
|
|
|
@@ -20,9 +27,9 @@ access(all) contract FlowtyDrops {
|
|
|
20
27
|
// Interface to expose all the components necessary to participate in a drop
|
|
21
28
|
// and to ask questions about a drop.
|
|
22
29
|
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}]
|
|
30
|
+
access(all) view fun borrowPhasePublic(index: Int): &{PhasePublic}
|
|
31
|
+
access(all) view fun borrowActivePhases(): [&{PhasePublic}]
|
|
32
|
+
access(all) view fun borrowAllPhases(): [&{PhasePublic}]
|
|
26
33
|
access(all) fun mint(
|
|
27
34
|
payment: @{FungibleToken.Vault},
|
|
28
35
|
amount: Int,
|
|
@@ -31,8 +38,18 @@ access(all) contract FlowtyDrops {
|
|
|
31
38
|
receiverCap: Capability<&{NonFungibleToken.CollectionPublic}>,
|
|
32
39
|
commissionReceiver: Capability<&{FungibleToken.Receiver}>?,
|
|
33
40
|
data: {String: AnyStruct}
|
|
34
|
-
): @{FungibleToken.Vault}
|
|
35
|
-
|
|
41
|
+
): @{FungibleToken.Vault} {
|
|
42
|
+
pre {
|
|
43
|
+
self.getDetails().paymentTokenTypes[payment.getType().identifier] == true: "unsupported payment token type"
|
|
44
|
+
receiverCap.check(): "unvalid nft receiver capability"
|
|
45
|
+
commissionReceiver == nil || commissionReceiver!.check(): "commission receiver must be nil or a valid capability"
|
|
46
|
+
self.getType() == Type<@Drop>(): "unsupported type implementing DropPublic"
|
|
47
|
+
expectedType.isSubtype(of: Type<@{NonFungibleToken.NFT}>()): "expected type must be an NFT"
|
|
48
|
+
expectedType.identifier == self.getDetails().nftType: "expected type does not match drop details type"
|
|
49
|
+
receiverCap.check(): "receiver capability is not valid"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
access(all) view fun getDetails(): DropDetails
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
// A phase represents a stage of a drop. Some drops will only have one
|
|
@@ -47,23 +64,23 @@ access(all) contract FlowtyDrops {
|
|
|
47
64
|
access(all) let resources: @{String: AnyResource}
|
|
48
65
|
|
|
49
66
|
// returns whether this phase of a drop has started.
|
|
50
|
-
access(all) fun isActive(): Bool {
|
|
67
|
+
access(all) view fun isActive(): Bool {
|
|
51
68
|
return self.details.activeChecker.hasStarted() && !self.details.activeChecker.hasEnded()
|
|
52
69
|
}
|
|
53
70
|
|
|
54
|
-
access(all) fun getDetails(): PhaseDetails {
|
|
71
|
+
access(all) view fun getDetails(): PhaseDetails {
|
|
55
72
|
return self.details
|
|
56
73
|
}
|
|
57
74
|
|
|
58
|
-
access(EditPhase) fun borrowActiveCheckerAuth(): auth(Mutate) &{ActiveChecker} {
|
|
75
|
+
access(EditPhase) view fun borrowActiveCheckerAuth(): auth(Mutate) &{ActiveChecker} {
|
|
59
76
|
return &self.details.activeChecker
|
|
60
77
|
}
|
|
61
78
|
|
|
62
|
-
access(EditPhase) fun borrowPricerAuth(): auth(Mutate) &{Pricer} {
|
|
79
|
+
access(EditPhase) view fun borrowPricerAuth(): auth(Mutate) &{Pricer} {
|
|
63
80
|
return &self.details.pricer
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
access(EditPhase) fun borrowAddressVerifierAuth(): auth(Mutate) &{AddressVerifier} {
|
|
83
|
+
access(EditPhase) view fun borrowAddressVerifierAuth(): auth(Mutate) &{AddressVerifier} {
|
|
67
84
|
return &self.details.addressVerifier
|
|
68
85
|
}
|
|
69
86
|
|
|
@@ -75,6 +92,8 @@ access(all) contract FlowtyDrops {
|
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
|
|
95
|
+
// The primary resource of this contract. A drop has some top-level details, and some phase-specific details which are encapsulated
|
|
96
|
+
// by each phase.
|
|
78
97
|
access(all) resource Drop: DropPublic {
|
|
79
98
|
access(all) event ResourceDestroyed(
|
|
80
99
|
uuid: UInt64 = self.uuid,
|
|
@@ -87,8 +106,11 @@ access(all) contract FlowtyDrops {
|
|
|
87
106
|
access(self) let phases: @[Phase]
|
|
88
107
|
// the details of a drop. This includes things like display information and total number of mints
|
|
89
108
|
access(self) let details: DropDetails
|
|
109
|
+
// capability to mint nfts with. Regardless of where a drop is hosted, the minter itself is what is responsible for creating nfts
|
|
110
|
+
// and is used by the drop's mint method.
|
|
90
111
|
access(self) let minterCap: Capability<&{Minter}>
|
|
91
112
|
|
|
113
|
+
// general-purpose property bags which are needed to ensure extensibility of this resource
|
|
92
114
|
access(all) let data: {String: AnyStruct}
|
|
93
115
|
access(all) let resources: @{String: AnyResource}
|
|
94
116
|
|
|
@@ -102,10 +124,7 @@ access(all) contract FlowtyDrops {
|
|
|
102
124
|
data: {String: AnyStruct}
|
|
103
125
|
): @{FungibleToken.Vault} {
|
|
104
126
|
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
127
|
self.phases.length > phaseIndex: "phase index is too high"
|
|
108
|
-
receiverCap.check(): "receiver capability is not valid"
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
// validate the payment vault amount and type
|
|
@@ -116,6 +135,7 @@ access(all) contract FlowtyDrops {
|
|
|
116
135
|
)
|
|
117
136
|
|
|
118
137
|
let paymentAmount = phase.details.pricer.getPrice(num: amount, paymentTokenType: payment.getType(), minter: receiverCap.address)
|
|
138
|
+
assert(payment.balance >= paymentAmount, message: "payment balance is lower than payment amount")
|
|
119
139
|
let withdrawn <- payment.withdraw(amount: paymentAmount) // make sure that we have a fresh vault resource
|
|
120
140
|
|
|
121
141
|
// take commission
|
|
@@ -124,13 +144,14 @@ access(all) contract FlowtyDrops {
|
|
|
124
144
|
commissionReceiver!.borrow()!.deposit(from: <-commission)
|
|
125
145
|
}
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
// The balance of the payment sent to the creator is equal to the paymentAmount - fees
|
|
148
|
+
assert(paymentAmount * (1.0 - self.details.commissionRate) == withdrawn.balance, message: "incorrect payment amount")
|
|
128
149
|
assert(phase.details.pricer.getPaymentTypes().contains(withdrawn.getType()), message: "unsupported payment type")
|
|
150
|
+
assert(phase.details.activeChecker.hasStarted() && !phase.details.activeChecker.hasEnded(), message: "phase is not active")
|
|
129
151
|
|
|
130
152
|
// mint the nfts
|
|
131
153
|
let minter = self.minterCap.borrow() ?? panic("minter capability could not be borrowed")
|
|
132
154
|
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
155
|
assert(mintedNFTs.length == amount, message: "incorrect number of items returned")
|
|
135
156
|
|
|
136
157
|
// distribute to receiver
|
|
@@ -141,7 +162,8 @@ access(all) contract FlowtyDrops {
|
|
|
141
162
|
let nft <- mintedNFTs.removeFirst()
|
|
142
163
|
|
|
143
164
|
let nftType = nft.getType()
|
|
144
|
-
|
|
165
|
+
FlowtyDrops.TotalMinted = FlowtyDrops.TotalMinted + 1
|
|
166
|
+
emit Minted(address: receiverCap.address, dropID: self.uuid, phaseID: phase.uuid, nftID: nft.id, nftType: nftType.identifier, totalMinted: FlowtyDrops.TotalMinted)
|
|
145
167
|
|
|
146
168
|
// validate that every nft is the right type
|
|
147
169
|
assert(nftType == expectedType, message: "unexpected nft type was minted")
|
|
@@ -156,23 +178,23 @@ access(all) contract FlowtyDrops {
|
|
|
156
178
|
return <- payment
|
|
157
179
|
}
|
|
158
180
|
|
|
159
|
-
access(Owner) fun borrowPhase(index: Int): auth(EditPhase) &Phase {
|
|
181
|
+
access(Owner) view fun borrowPhase(index: Int): auth(EditPhase) &Phase {
|
|
160
182
|
return &self.phases[index]
|
|
161
183
|
}
|
|
162
184
|
|
|
163
185
|
|
|
164
|
-
access(all) fun borrowPhasePublic(index: Int): &{PhasePublic} {
|
|
186
|
+
access(all) view fun borrowPhasePublic(index: Int): &{PhasePublic} {
|
|
165
187
|
return &self.phases[index]
|
|
166
188
|
}
|
|
167
189
|
|
|
168
|
-
access(all) fun borrowActivePhases(): [&{PhasePublic}] {
|
|
169
|
-
|
|
190
|
+
access(all) view fun borrowActivePhases(): [&{PhasePublic}] {
|
|
191
|
+
var arr: [&{PhasePublic}] = []
|
|
170
192
|
var count = 0
|
|
171
193
|
while count < self.phases.length {
|
|
172
194
|
let ref = self.borrowPhasePublic(index: count)
|
|
173
195
|
let activeChecker = ref.getDetails().activeChecker
|
|
174
196
|
if activeChecker.hasStarted() && !activeChecker.hasEnded() {
|
|
175
|
-
arr.
|
|
197
|
+
arr = arr.concat([ref])
|
|
176
198
|
}
|
|
177
199
|
|
|
178
200
|
count = count + 1
|
|
@@ -181,12 +203,12 @@ access(all) contract FlowtyDrops {
|
|
|
181
203
|
return arr
|
|
182
204
|
}
|
|
183
205
|
|
|
184
|
-
access(all) fun borrowAllPhases(): [&{PhasePublic}] {
|
|
185
|
-
|
|
206
|
+
access(all) view fun borrowAllPhases(): [&{PhasePublic}] {
|
|
207
|
+
var arr: [&{PhasePublic}] = []
|
|
186
208
|
var index = 0
|
|
187
209
|
while index < self.phases.length {
|
|
188
210
|
let ref = self.borrowPhasePublic(index: index)
|
|
189
|
-
arr.
|
|
211
|
+
arr = arr.concat([ref])
|
|
190
212
|
index = index + 1
|
|
191
213
|
}
|
|
192
214
|
|
|
@@ -217,7 +239,7 @@ access(all) contract FlowtyDrops {
|
|
|
217
239
|
return <- phase
|
|
218
240
|
}
|
|
219
241
|
|
|
220
|
-
access(all) fun getDetails(): DropDetails {
|
|
242
|
+
access(all) view fun getDetails(): DropDetails {
|
|
221
243
|
return self.details
|
|
222
244
|
}
|
|
223
245
|
|
|
@@ -242,6 +264,7 @@ access(all) contract FlowtyDrops {
|
|
|
242
264
|
access(all) var minters: {Address: Int}
|
|
243
265
|
access(all) let commissionRate: UFix64
|
|
244
266
|
access(all) let nftType: String
|
|
267
|
+
access(all) let paymentTokenTypes: {String: Bool}
|
|
245
268
|
|
|
246
269
|
access(all) let data: {String: AnyStruct}
|
|
247
270
|
|
|
@@ -254,7 +277,7 @@ access(all) contract FlowtyDrops {
|
|
|
254
277
|
self.minters[addr] = self.minters[addr]! + num
|
|
255
278
|
}
|
|
256
279
|
|
|
257
|
-
init(display: MetadataViews.Display, medias: MetadataViews.Medias?, commissionRate: UFix64, nftType: String) {
|
|
280
|
+
init(display: MetadataViews.Display, medias: MetadataViews.Medias?, commissionRate: UFix64, nftType: String, paymentTokenTypes: {String: Bool}) {
|
|
258
281
|
pre {
|
|
259
282
|
nftType != "": "nftType should be a composite type identifier"
|
|
260
283
|
}
|
|
@@ -265,6 +288,8 @@ access(all) contract FlowtyDrops {
|
|
|
265
288
|
self.commissionRate = commissionRate
|
|
266
289
|
self.minters = {}
|
|
267
290
|
self.nftType = nftType
|
|
291
|
+
self.paymentTokenTypes = paymentTokenTypes
|
|
292
|
+
|
|
268
293
|
self.data = {}
|
|
269
294
|
}
|
|
270
295
|
}
|
|
@@ -289,8 +314,8 @@ access(all) contract FlowtyDrops {
|
|
|
289
314
|
// - How many items are left in the current phase?
|
|
290
315
|
// - Can Address x mint on a phase?
|
|
291
316
|
// - 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
|
|
317
|
+
access(all) view fun getDetails(): PhaseDetails
|
|
318
|
+
access(all) view fun isActive(): Bool
|
|
294
319
|
}
|
|
295
320
|
|
|
296
321
|
access(all) struct PhaseDetails {
|
|
@@ -319,6 +344,7 @@ access(all) contract FlowtyDrops {
|
|
|
319
344
|
}
|
|
320
345
|
}
|
|
321
346
|
|
|
347
|
+
// The AddressVerifier interface is responsible for determining whether an address is permitted to mint or not
|
|
322
348
|
access(all) struct interface AddressVerifier {
|
|
323
349
|
access(all) fun canMint(addr: Address, num: Int, totalMinted: Int, data: {String: AnyStruct}): Bool {
|
|
324
350
|
return true
|
|
@@ -333,12 +359,15 @@ access(all) contract FlowtyDrops {
|
|
|
333
359
|
}
|
|
334
360
|
}
|
|
335
361
|
|
|
362
|
+
// The pricer interface is responsible for the cost of a mint. It can vary by phase
|
|
336
363
|
access(all) struct interface Pricer {
|
|
337
364
|
access(all) fun getPrice(num: Int, paymentTokenType: Type, minter: Address?): UFix64
|
|
338
365
|
access(all) fun getPaymentTypes(): [Type]
|
|
339
366
|
}
|
|
340
367
|
|
|
341
368
|
access(all) resource interface Minter {
|
|
369
|
+
// mint is only able to be called either by this contract (FlowtyDrops) or the implementing contract.
|
|
370
|
+
// In its default implementation, it is assumed that the receiver capability for payment is the FungibleTokenRouter
|
|
342
371
|
access(contract) fun mint(payment: @{FungibleToken.Vault}, amount: Int, phase: &FlowtyDrops.Phase, data: {String: AnyStruct}): @[{NonFungibleToken.NFT}] {
|
|
343
372
|
let resourceAddress = AddressUtils.parseAddress(self.getType())!
|
|
344
373
|
let receiver = getAccount(resourceAddress).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenRouter.PublicPath).borrow()
|
|
@@ -356,9 +385,11 @@ access(all) contract FlowtyDrops {
|
|
|
356
385
|
return <- nfts
|
|
357
386
|
}
|
|
358
387
|
|
|
388
|
+
// required so that the minter interface has a way to create NFTs on its implementing resource
|
|
359
389
|
access(contract) fun createNextNFT(): @{NonFungibleToken.NFT}
|
|
360
390
|
}
|
|
361
391
|
|
|
392
|
+
// Struct to wrap obtaining a Drop container. Intended for use with the ViewResolver contract interface
|
|
362
393
|
access(all) struct DropResolver {
|
|
363
394
|
access(self) let cap: Capability<&{ContainerPublic}>
|
|
364
395
|
|
|
@@ -380,7 +411,7 @@ access(all) contract FlowtyDrops {
|
|
|
380
411
|
access(all) fun getIDs(): [UInt64]
|
|
381
412
|
}
|
|
382
413
|
|
|
383
|
-
//
|
|
414
|
+
// Container holds drops so that one address can host more than one drop at once
|
|
384
415
|
access(all) resource Container: ContainerPublic {
|
|
385
416
|
access(self) let drops: @{UInt64: Drop}
|
|
386
417
|
|
|
@@ -413,6 +444,7 @@ access(all) contract FlowtyDrops {
|
|
|
413
444
|
self.drops.containsKey(id): "drop was not found"
|
|
414
445
|
}
|
|
415
446
|
|
|
447
|
+
emit DropRemoved(address: self.owner!.address, id: id)
|
|
416
448
|
return <- self.drops.remove(key: id)!
|
|
417
449
|
}
|
|
418
450
|
|
|
@@ -461,5 +493,7 @@ access(all) contract FlowtyDrops {
|
|
|
461
493
|
|
|
462
494
|
self.ContainerStoragePath = StoragePath(identifier: containerIdentifier)!
|
|
463
495
|
self.ContainerPublicPath = PublicPath(identifier: containerIdentifier)!
|
|
496
|
+
|
|
497
|
+
self.TotalMinted = 0
|
|
464
498
|
}
|
|
465
499
|
}
|
|
@@ -40,7 +40,14 @@ access(all) contract interface BaseNFT: ViewResolver {
|
|
|
40
40
|
]
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// In case the implementor of `NFT` wants to override resolved views,
|
|
44
|
+
// the actual logic to perform view resolution is done in another method,
|
|
45
|
+
// with this one calling directly into it.
|
|
43
46
|
access(all) fun resolveView(_ view: Type): AnyStruct? {
|
|
47
|
+
return self._resolveView(view)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
access(all) fun _resolveView(_ view: Type): AnyStruct? {
|
|
44
51
|
if view == Type<MetadataViews.Serial>() {
|
|
45
52
|
return MetadataViews.Serial(self.id)
|
|
46
53
|
}
|
|
@@ -94,6 +94,7 @@ access(all) contract NFTMetadata {
|
|
|
94
94
|
access(Owner) fun addMetadata(id: UInt64, data: Metadata) {
|
|
95
95
|
pre {
|
|
96
96
|
self.metadata[id] == nil: "id already has metadata assigned"
|
|
97
|
+
!self.frozen: "metadata is frozen and cannot be updated"
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
self.metadata[id] = data
|
package/flow.json
CHANGED
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
"source": "./contracts/flowty-drops/ContractManager.cdc",
|
|
360
360
|
"aliases": {
|
|
361
361
|
"testing": "0x0000000000000006",
|
|
362
|
-
"testnet": "
|
|
362
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
363
363
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
364
364
|
}
|
|
365
365
|
},
|
|
@@ -367,7 +367,7 @@
|
|
|
367
367
|
"source": "./contracts/flowty-drops/initializers/ContractInitializer.cdc",
|
|
368
368
|
"aliases": {
|
|
369
369
|
"testing": "0x0000000000000006",
|
|
370
|
-
"testnet": "
|
|
370
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
371
371
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
372
372
|
}
|
|
373
373
|
},
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
"source": "./contracts/flowty-drops/initializers/ContractBorrower.cdc",
|
|
376
376
|
"aliases": {
|
|
377
377
|
"testing": "0x0000000000000006",
|
|
378
|
-
"testnet": "
|
|
378
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
379
379
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
380
380
|
}
|
|
381
381
|
},
|
|
@@ -383,7 +383,7 @@
|
|
|
383
383
|
"source": "./contracts/flowty-drops/initializers/OpenEditionInitializer.cdc",
|
|
384
384
|
"aliases": {
|
|
385
385
|
"testing": "0x0000000000000006",
|
|
386
|
-
"testnet": "
|
|
386
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
387
387
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
388
388
|
}
|
|
389
389
|
},
|
|
@@ -391,7 +391,7 @@
|
|
|
391
391
|
"source": "./contracts/flowty-drops/nft/BaseCollection.cdc",
|
|
392
392
|
"aliases": {
|
|
393
393
|
"testing": "0x0000000000000006",
|
|
394
|
-
"testnet": "
|
|
394
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
395
395
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
396
396
|
}
|
|
397
397
|
},
|
|
@@ -399,7 +399,7 @@
|
|
|
399
399
|
"source": "./contracts/flowty-drops/nft/ContractFactoryTemplate.cdc",
|
|
400
400
|
"aliases": {
|
|
401
401
|
"testing": "0x0000000000000006",
|
|
402
|
-
"testnet": "
|
|
402
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
403
403
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
404
404
|
}
|
|
405
405
|
},
|
|
@@ -407,7 +407,7 @@
|
|
|
407
407
|
"source": "./contracts/flowty-drops/nft/ContractFactory.cdc",
|
|
408
408
|
"aliases": {
|
|
409
409
|
"testing": "0x0000000000000006",
|
|
410
|
-
"testnet": "
|
|
410
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
411
411
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
412
412
|
}
|
|
413
413
|
},
|
|
@@ -415,7 +415,7 @@
|
|
|
415
415
|
"source": "./contracts/flowty-drops/nft/OpenEditionTemplate.cdc",
|
|
416
416
|
"aliases": {
|
|
417
417
|
"testing": "0x0000000000000006",
|
|
418
|
-
"testnet": "
|
|
418
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
419
419
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
420
420
|
}
|
|
421
421
|
},
|
|
@@ -423,7 +423,7 @@
|
|
|
423
423
|
"source": "./contracts/flowty-drops/nft/UniversalCollection.cdc",
|
|
424
424
|
"aliases": {
|
|
425
425
|
"testing": "0x0000000000000006",
|
|
426
|
-
"testnet": "
|
|
426
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
427
427
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
428
428
|
}
|
|
429
429
|
},
|
|
@@ -431,7 +431,7 @@
|
|
|
431
431
|
"source": "./contracts/flowty-drops/nft/BaseNFT.cdc",
|
|
432
432
|
"aliases": {
|
|
433
433
|
"testing": "0x0000000000000006",
|
|
434
|
-
"testnet": "
|
|
434
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
435
435
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
436
436
|
}
|
|
437
437
|
},
|
|
@@ -439,7 +439,7 @@
|
|
|
439
439
|
"source": "./contracts/flowty-drops/nft/NFTMetadata.cdc",
|
|
440
440
|
"aliases": {
|
|
441
441
|
"testing": "0x0000000000000006",
|
|
442
|
-
"testnet": "
|
|
442
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
443
443
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
444
444
|
}
|
|
445
445
|
},
|
|
@@ -447,7 +447,7 @@
|
|
|
447
447
|
"source": "./contracts/flowty-drops/FlowtyDrops.cdc",
|
|
448
448
|
"aliases": {
|
|
449
449
|
"testing": "0x0000000000000006",
|
|
450
|
-
"testnet": "
|
|
450
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
451
451
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
452
452
|
}
|
|
453
453
|
},
|
|
@@ -455,7 +455,7 @@
|
|
|
455
455
|
"source": "./contracts/flowty-drops/DropFactory.cdc",
|
|
456
456
|
"aliases": {
|
|
457
457
|
"testing": "0x0000000000000006",
|
|
458
|
-
"testnet": "
|
|
458
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
459
459
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
460
460
|
}
|
|
461
461
|
},
|
|
@@ -463,7 +463,7 @@
|
|
|
463
463
|
"source": "./contracts/flowty-drops/FlowtyActiveCheckers.cdc",
|
|
464
464
|
"aliases": {
|
|
465
465
|
"testing": "0x0000000000000006",
|
|
466
|
-
"testnet": "
|
|
466
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
467
467
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
468
468
|
}
|
|
469
469
|
},
|
|
@@ -471,7 +471,7 @@
|
|
|
471
471
|
"source": "./contracts/flowty-drops/FlowtyPricers.cdc",
|
|
472
472
|
"aliases": {
|
|
473
473
|
"testing": "0x0000000000000006",
|
|
474
|
-
"testnet": "
|
|
474
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
475
475
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
476
476
|
}
|
|
477
477
|
},
|
|
@@ -479,7 +479,7 @@
|
|
|
479
479
|
"source": "./contracts/flowty-drops/FlowtyAddressVerifiers.cdc",
|
|
480
480
|
"aliases": {
|
|
481
481
|
"testing": "0x0000000000000006",
|
|
482
|
-
"testnet": "
|
|
482
|
+
"testnet": "0x5d06d5357cdf8063",
|
|
483
483
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
484
484
|
}
|
|
485
485
|
},
|
|
@@ -487,7 +487,7 @@
|
|
|
487
487
|
"source": "./contracts/flowty-drops/DropTypes.cdc",
|
|
488
488
|
"aliases": {
|
|
489
489
|
"testing": "0x0000000000000006",
|
|
490
|
-
"testnet": "
|
|
490
|
+
"testnet": "0xa8d83f18fd8ba80a",
|
|
491
491
|
"emulator": "0xf8d6e0586b0a20c7"
|
|
492
492
|
}
|
|
493
493
|
},
|