@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.
@@ -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 quote: Quote?
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
- paymentIdentifier: String?
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: {} as {String: AnyStruct})
161
+ self.maxPerMint = d.addressVerifier.getMaxPerMint(addr: self.address, totalMinted: totalMinted ?? 0, data: {})
162
+ self.quotes = {}
155
163
 
156
- if paymentIdentifier != nil && quantity != nil {
157
- let price = d.pricer.getPrice(num: quantity!, paymentTokenType: CompositeType(paymentIdentifier!)!, minter: minter)
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?, paymentIdentifier: String?): DropSummary? {
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
- paymentIdentifier: paymentIdentifier
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?, paymentIdentifier: String?): [DropSummary] {
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
- paymentIdentifier: paymentIdentifier
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 Minted(address: Address, dropID: UInt64, phaseID: UInt64, nftID: UInt64, nftType: String)
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
- access(all) fun getDetails(): DropDetails
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
- assert(phase.details.pricer.getPrice(num: amount, paymentTokenType: withdrawn.getType(), minter: receiverCap.address) * (1.0 - self.details.commissionRate) == withdrawn.balance, message: "incorrect payment amount")
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
- emit Minted(address: receiverCap.address, dropID: self.uuid, phaseID: phase.uuid, nftID: nft.id, nftType: nftType.identifier)
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
- let arr: [&{PhasePublic}] = []
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.append(ref)
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
- let arr: [&{PhasePublic}] = []
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.append(ref)
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
- // Contains drops.
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x772a10c786851a1b",
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": "0x22f23883bf122007",
490
+ "testnet": "0xa8d83f18fd8ba80a",
491
491
  "emulator": "0xf8d6e0586b0a20c7"
492
492
  }
493
493
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowtyio/flow-contracts",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "main": "index.json",
5
5
  "description": "An NPM package for common flow contracts",
6
6
  "author": "flowtyio",