@flowtyio/flow-contracts 0.1.4 → 0.1.6
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/example/ExampleToken.cdc +47 -40
- package/contracts/flowty-drops/DropFactory.cdc +2 -2
- package/contracts/flowty-drops/DropTypes.cdc +19 -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
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import "FungibleToken"
|
|
2
|
-
import "FungibleTokenMetadataViews"
|
|
3
2
|
import "MetadataViews"
|
|
3
|
+
import "FungibleTokenMetadataViews"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
// THIS CONTRACT IS FOR TESTING PURPOSES ONLY!
|
|
7
|
-
access(all) contract ExampleToken {
|
|
5
|
+
access(all) contract ExampleToken: FungibleToken {
|
|
8
6
|
|
|
9
7
|
/// Total supply of ExampleTokens in existence
|
|
10
8
|
access(all) var totalSupply: UFix64
|
|
@@ -66,32 +64,36 @@ access(all) contract ExampleToken {
|
|
|
66
64
|
self.balance = balance
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
access(all) view fun
|
|
70
|
-
return self.balance
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
access(all) view fun getDefaultStoragePath(): StoragePath? {
|
|
74
|
-
return /storage/exampleTokenVault
|
|
67
|
+
access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
|
|
68
|
+
return amount <= self.balance
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
access(all) view fun
|
|
78
|
-
return
|
|
71
|
+
access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
|
|
72
|
+
return {self.getType(): true}
|
|
79
73
|
}
|
|
80
74
|
|
|
81
|
-
access(all) view fun
|
|
82
|
-
return
|
|
75
|
+
access(all) view fun isSupportedVaultType(type: Type): Bool {
|
|
76
|
+
if (type == self.getType()) { return true } else { return false }
|
|
83
77
|
}
|
|
84
78
|
|
|
85
|
-
access(all)
|
|
86
|
-
return
|
|
79
|
+
access(all) fun createEmptyVault(): @{FungibleToken.Vault} {
|
|
80
|
+
return <-create Vault(balance: 0.0)
|
|
87
81
|
}
|
|
88
82
|
|
|
89
|
-
///
|
|
90
|
-
|
|
83
|
+
/// Get all the Metadata Views implemented by ExampleToken
|
|
84
|
+
///
|
|
85
|
+
/// @return An array of Types defining the implemented views. This value will be used by
|
|
86
|
+
/// developers to know which parameter to pass to the resolveView() method.
|
|
87
|
+
///
|
|
88
|
+
access(all) view fun getViews(): [Type]{
|
|
91
89
|
return ExampleToken.getContractViews(resourceType: nil)
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
///
|
|
92
|
+
/// Get a Metadata View from ExampleToken
|
|
93
|
+
///
|
|
94
|
+
/// @param view: The Type of the desired view.
|
|
95
|
+
/// @return A structure representing the requested view.
|
|
96
|
+
///
|
|
95
97
|
access(all) fun resolveView(_ view: Type): AnyStruct? {
|
|
96
98
|
return ExampleToken.resolveContractView(resourceType: nil, viewType: view)
|
|
97
99
|
}
|
|
@@ -112,16 +114,6 @@ access(all) contract ExampleToken {
|
|
|
112
114
|
return <-create Vault(balance: amount)
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
|
|
116
|
-
return {
|
|
117
|
-
Type<@ExampleToken.Vault>(): true
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
access(all) view fun isSupportedVaultType(type: Type): Bool {
|
|
122
|
-
return type == Type<@ExampleToken.Vault>()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
117
|
/// deposit
|
|
126
118
|
///
|
|
127
119
|
/// Function that takes a Vault object as an argument and adds
|
|
@@ -139,8 +131,8 @@ access(all) contract ExampleToken {
|
|
|
139
131
|
destroy vault
|
|
140
132
|
}
|
|
141
133
|
|
|
142
|
-
access(
|
|
143
|
-
|
|
134
|
+
access(contract) fun burnCallback() {
|
|
135
|
+
ExampleToken.totalSupply = ExampleToken.totalSupply - self.balance
|
|
144
136
|
}
|
|
145
137
|
}
|
|
146
138
|
|
|
@@ -151,7 +143,7 @@ access(all) contract ExampleToken {
|
|
|
151
143
|
/// and store the returned Vault in their storage in order to allow their
|
|
152
144
|
/// account to be able to receive deposits of this token type.
|
|
153
145
|
///
|
|
154
|
-
access(all) fun createEmptyVault(): @Vault {
|
|
146
|
+
access(all) fun createEmptyVault(vaultType: Type): @Vault {
|
|
155
147
|
return <-create Vault(balance: 0.0)
|
|
156
148
|
}
|
|
157
149
|
|
|
@@ -227,6 +219,7 @@ access(all) contract ExampleToken {
|
|
|
227
219
|
}
|
|
228
220
|
}
|
|
229
221
|
|
|
222
|
+
/// Gets a list of the metadata views that this contract supports
|
|
230
223
|
access(all) view fun getContractViews(resourceType: Type?): [Type] {
|
|
231
224
|
return [Type<FungibleTokenMetadataViews.FTView>(),
|
|
232
225
|
Type<FungibleTokenMetadataViews.FTDisplay>(),
|
|
@@ -234,6 +227,21 @@ access(all) contract ExampleToken {
|
|
|
234
227
|
Type<FungibleTokenMetadataViews.TotalSupply>()]
|
|
235
228
|
}
|
|
236
229
|
|
|
230
|
+
access(all) fun mintTokens(amount: UFix64): @{FungibleToken.Vault} {
|
|
231
|
+
let admin = self.account.storage.borrow<&Administrator>(from: /storage/exampleTokenAdmin)!
|
|
232
|
+
let minter <- admin.createNewMinter(allowedAmount: amount)
|
|
233
|
+
|
|
234
|
+
let tokens <- minter.mintTokens(amount: amount)
|
|
235
|
+
destroy minter
|
|
236
|
+
|
|
237
|
+
return <- tokens
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Get a Metadata View from ExampleToken
|
|
241
|
+
///
|
|
242
|
+
/// @param view: The Type of the desired view.
|
|
243
|
+
/// @return A structure representing the requested view.
|
|
244
|
+
///
|
|
237
245
|
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
|
|
238
246
|
switch viewType {
|
|
239
247
|
case Type<FungibleTokenMetadataViews.FTView>():
|
|
@@ -244,15 +252,15 @@ access(all) contract ExampleToken {
|
|
|
244
252
|
case Type<FungibleTokenMetadataViews.FTDisplay>():
|
|
245
253
|
let media = MetadataViews.Media(
|
|
246
254
|
file: MetadataViews.HTTPFile(
|
|
247
|
-
url: "
|
|
255
|
+
url: "example.com"
|
|
248
256
|
),
|
|
249
257
|
mediaType: "image/svg+xml"
|
|
250
258
|
)
|
|
251
259
|
let medias = MetadataViews.Medias([media])
|
|
252
260
|
return FungibleTokenMetadataViews.FTDisplay(
|
|
253
|
-
name: "
|
|
261
|
+
name: "EXAMPLE Token",
|
|
254
262
|
symbol: "EXAMPLE",
|
|
255
|
-
description: "",
|
|
263
|
+
description: "This is an example token",
|
|
256
264
|
externalURL: MetadataViews.ExternalURL("https://flow.com"),
|
|
257
265
|
logos: medias,
|
|
258
266
|
socials: {
|
|
@@ -286,11 +294,10 @@ access(all) contract ExampleToken {
|
|
|
286
294
|
let vault <- create Vault(balance: self.totalSupply)
|
|
287
295
|
self.account.storage.save(<-vault, to: /storage/exampleTokenVault)
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
self.account.capabilities.publish(publicCap, at: /public/exampleTokenPublic)
|
|
297
|
+
let cap = self.account.capabilities.storage.issue<&{FungibleToken.Vault}>(/storage/exampleTokenVault)
|
|
298
|
+
self.account.capabilities.publish(cap, at: /public/exampleTokenReceiver)
|
|
299
|
+
self.account.capabilities.publish(cap, at: /public/exampleTokenBalance)
|
|
300
|
+
|
|
294
301
|
|
|
295
302
|
let admin <- create Administrator()
|
|
296
303
|
self.account.storage.save(<-admin, to: /storage/exampleTokenAdmin)
|
|
@@ -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
|
|
@@ -36,6 +36,7 @@ access(all) contract DropTypes {
|
|
|
36
36
|
access(all) let commissionRate: UFix64
|
|
37
37
|
access(all) let nftType: String
|
|
38
38
|
access(all) let creator: Address?
|
|
39
|
+
access(all) let paymentTokenTypes: {String: Bool}
|
|
39
40
|
|
|
40
41
|
access(all) let address: Address?
|
|
41
42
|
access(all) let mintedByAddress: Int?
|
|
@@ -58,7 +59,8 @@ access(all) contract DropTypes {
|
|
|
58
59
|
address: Address?,
|
|
59
60
|
phases: [PhaseSummary],
|
|
60
61
|
royaltyRate: UFix64,
|
|
61
|
-
creator: Address
|
|
62
|
+
creator: Address?,
|
|
63
|
+
paymentTokenTypes: {String: Bool}
|
|
62
64
|
) {
|
|
63
65
|
self.id = id
|
|
64
66
|
self.display = Display(display)
|
|
@@ -82,6 +84,7 @@ access(all) contract DropTypes {
|
|
|
82
84
|
self.blockHeight = b.height
|
|
83
85
|
self.blockTimestamp = UInt64(b.timestamp)
|
|
84
86
|
self.royaltyRate = royaltyRate
|
|
87
|
+
self.paymentTokenTypes = paymentTokenTypes
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
|
|
@@ -118,7 +121,7 @@ access(all) contract DropTypes {
|
|
|
118
121
|
access(all) let remainingForAddress: Int?
|
|
119
122
|
access(all) let maxPerMint: Int?
|
|
120
123
|
|
|
121
|
-
access(all) let
|
|
124
|
+
access(all) let quotes: {String: Quote}
|
|
122
125
|
|
|
123
126
|
init(
|
|
124
127
|
index: Int,
|
|
@@ -127,7 +130,7 @@ access(all) contract DropTypes {
|
|
|
127
130
|
totalMinted: Int?,
|
|
128
131
|
minter: Address?,
|
|
129
132
|
quantity: Int?,
|
|
130
|
-
|
|
133
|
+
paymentIdentifiers: [String]
|
|
131
134
|
) {
|
|
132
135
|
self.index = index
|
|
133
136
|
self.id = phase.uuid
|
|
@@ -155,19 +158,17 @@ access(all) contract DropTypes {
|
|
|
155
158
|
self.remainingForAddress = nil
|
|
156
159
|
}
|
|
157
160
|
|
|
158
|
-
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 = {}
|
|
159
163
|
|
|
160
|
-
|
|
161
|
-
let price = d.pricer.getPrice(num: quantity!, paymentTokenType: CompositeType(paymentIdentifier
|
|
162
|
-
|
|
163
|
-
self.quote = Quote(price: price, quantity: quantity!, paymentIdentifier: paymentIdentifier!, minter: minter)
|
|
164
|
-
} else {
|
|
165
|
-
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)
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
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? {
|
|
171
172
|
let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier")
|
|
172
173
|
let segments = nftTypeIdentifier.split(separator: ".")
|
|
173
174
|
let contractAddress = AddressUtils.parseAddress(nftType)!
|
|
@@ -206,7 +207,7 @@ access(all) contract DropTypes {
|
|
|
206
207
|
totalMinted: minter != nil ? dropDetails.minters[minter!] : nil,
|
|
207
208
|
minter: minter,
|
|
208
209
|
quantity: quantity,
|
|
209
|
-
|
|
210
|
+
paymentIdentifiers: paymentIdentifiers.length > 0 ? paymentIdentifiers : dropDetails.paymentTokenTypes.keys
|
|
210
211
|
)
|
|
211
212
|
phaseSummaries.append(summary)
|
|
212
213
|
}
|
|
@@ -231,13 +232,14 @@ access(all) contract DropTypes {
|
|
|
231
232
|
address: minter,
|
|
232
233
|
phases: phaseSummaries,
|
|
233
234
|
royaltyRate: royaltyRate,
|
|
234
|
-
creator: creator
|
|
235
|
+
creator: creator,
|
|
236
|
+
paymentTokenTypes: dropDetails.paymentTokenTypes
|
|
235
237
|
)
|
|
236
238
|
|
|
237
239
|
return dropSummary
|
|
238
240
|
}
|
|
239
241
|
|
|
240
|
-
access(all) fun getAllDropSummaries(nftTypeIdentifier: String, minter: Address?, quantity: Int?,
|
|
242
|
+
access(all) fun getAllDropSummaries(nftTypeIdentifier: String, minter: Address?, quantity: Int?, paymentIdentifiers: [String]): [DropSummary] {
|
|
241
243
|
let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier")
|
|
242
244
|
let segments = nftTypeIdentifier.split(separator: ".")
|
|
243
245
|
let contractAddress = AddressUtils.parseAddress(nftType)!
|
|
@@ -278,7 +280,7 @@ access(all) contract DropTypes {
|
|
|
278
280
|
totalMinted: minter != nil ? dropDetails.minters[minter!] : nil,
|
|
279
281
|
minter: minter,
|
|
280
282
|
quantity: quantity,
|
|
281
|
-
|
|
283
|
+
paymentIdentifiers: paymentIdentifiers.length > 0 ? paymentIdentifiers : dropDetails.paymentTokenTypes.keys
|
|
282
284
|
)
|
|
283
285
|
phaseSummaries.append(summary)
|
|
284
286
|
}
|
|
@@ -307,7 +309,8 @@ access(all) contract DropTypes {
|
|
|
307
309
|
address: minter,
|
|
308
310
|
phases: phaseSummaries,
|
|
309
311
|
royaltyRate: royaltyRate,
|
|
310
|
-
creator: creator
|
|
312
|
+
creator: creator,
|
|
313
|
+
paymentTokenTypes: dropDetails.paymentTokenTypes
|
|
311
314
|
))
|
|
312
315
|
}
|
|
313
316
|
|
|
@@ -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
|
},
|