@flowtyio/flow-contracts 0.0.14 → 0.0.15

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.
@@ -0,0 +1,344 @@
1
+ import "FungibleToken"
2
+ import "NonFungibleToken"
3
+ import "DapperUtilityCoin"
4
+ import "MetadataViews"
5
+ import "Resolver"
6
+
7
+ // OffersV2
8
+ //
9
+ // Contract holds the Offer resource and a public method to create them.
10
+ //
11
+ // Each Offer can have one or more royalties of the sale price that
12
+ // goes to one or more addresses.
13
+ //
14
+ // Owners of NFT can watch for OfferAvailable events and check
15
+ // the Offer amount to see if they wish to accept the offer.
16
+ //
17
+ // Marketplaces and other aggregators can watch for OfferAvailable events
18
+ // and list offers of interest to logged in users.
19
+ //
20
+ pub contract OffersV2 {
21
+ // OfferAvailable
22
+ // An Offer has been created and added to the users DapperOffer resource.
23
+ //
24
+
25
+ pub event OfferAvailable(
26
+ offerAddress: Address,
27
+ offerId: UInt64,
28
+ nftType: Type,
29
+ offerAmount: UFix64,
30
+ royalties: {Address:UFix64},
31
+ offerType: String,
32
+ offerParamsString: {String:String},
33
+ offerParamsUFix64: {String:UFix64},
34
+ offerParamsUInt64: {String:UInt64},
35
+ paymentVaultType: Type,
36
+ )
37
+
38
+ // OfferCompleted
39
+ // The Offer has been resolved. The offer has either been accepted
40
+ // by the NFT owner, or the offer has been removed and destroyed.
41
+ //
42
+ pub event OfferCompleted(
43
+ purchased: Bool,
44
+ acceptingAddress: Address?,
45
+ offerAddress: Address,
46
+ offerId: UInt64,
47
+ nftType: Type,
48
+ offerAmount: UFix64,
49
+ royalties: {Address:UFix64},
50
+ offerType: String,
51
+ offerParamsString: {String:String},
52
+ offerParamsUFix64: {String:UFix64},
53
+ offerParamsUInt64: {String:UInt64},
54
+ paymentVaultType: Type,
55
+ nftId: UInt64?,
56
+ )
57
+
58
+ // Royalty
59
+ // A struct representing a recipient that must be sent a certain amount
60
+ // of the payment when a NFT is sold.
61
+ //
62
+ pub struct Royalty {
63
+ pub let receiver: Capability<&{FungibleToken.Receiver}>
64
+ pub let amount: UFix64
65
+
66
+ init(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
67
+ self.receiver = receiver
68
+ self.amount = amount
69
+ }
70
+ }
71
+
72
+ // OfferDetails
73
+ // A struct containing Offers' data.
74
+ //
75
+ pub struct OfferDetails {
76
+ // The ID of the offer
77
+ pub let offerId: UInt64
78
+ // The Type of the NFT
79
+ pub let nftType: Type
80
+ // The Type of the FungibleToken that payments must be made in.
81
+ pub let paymentVaultType: Type
82
+ // The Offer amount for the NFT
83
+ pub let offerAmount: UFix64
84
+ // Flag to tracked the purchase state
85
+ pub var purchased: Bool
86
+ // This specifies the division of payment between recipients.
87
+ pub let royalties: [Royalty]
88
+ // Used to hold Offer metadata and offer type information
89
+ pub let offerParamsString: {String: String}
90
+ pub let offerParamsUFix64: {String:UFix64}
91
+ pub let offerParamsUInt64: {String:UInt64}
92
+
93
+ // setToPurchased
94
+ // Irreversibly set this offer as purchased.
95
+ //
96
+ access(contract) fun setToPurchased() {
97
+ self.purchased = true
98
+ }
99
+
100
+ // initializer
101
+ //
102
+ init(
103
+ offerId: UInt64,
104
+ nftType: Type,
105
+ offerAmount: UFix64,
106
+ royalties: [Royalty],
107
+ offerParamsString: {String: String},
108
+ offerParamsUFix64: {String:UFix64},
109
+ offerParamsUInt64: {String:UInt64},
110
+ paymentVaultType: Type,
111
+ ) {
112
+ self.offerId = offerId
113
+ self.nftType = nftType
114
+ self.offerAmount = offerAmount
115
+ self.purchased = false
116
+ self.royalties = royalties
117
+ self.offerParamsString = offerParamsString
118
+ self.offerParamsUFix64 = offerParamsUFix64
119
+ self.offerParamsUInt64 = offerParamsUInt64
120
+ self.paymentVaultType = paymentVaultType
121
+ }
122
+ }
123
+
124
+ // OfferPublic
125
+ // An interface providing a useful public interface to an Offer resource.
126
+ //
127
+ pub resource interface OfferPublic {
128
+ // accept
129
+ // This will accept the offer if provided with the NFT id that matches the Offer
130
+ //
131
+ pub fun accept(
132
+ item: @AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
133
+ receiverCapability: Capability<&{FungibleToken.Receiver}>,
134
+ ): Void
135
+ // getDetails
136
+ // Return Offer details
137
+ //
138
+ pub fun getDetails(): OfferDetails
139
+ }
140
+
141
+
142
+ pub resource Offer: OfferPublic {
143
+ // The OfferDetails struct of the Offer
144
+ access(self) let details: OfferDetails
145
+ // The vault which will handle the payment if the Offer is accepted.
146
+ access(contract) let vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>
147
+ // Receiver address for the NFT when/if the Offer is accepted.
148
+ access(contract) let nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>
149
+ // Resolver capability for the offer type
150
+ access(contract) let resolverCapability: Capability<&{Resolver.ResolverPublic}>
151
+
152
+ init(
153
+ vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
154
+ nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
155
+ nftType: Type,
156
+ amount: UFix64,
157
+ royalties: [Royalty],
158
+ offerParamsString: {String: String},
159
+ offerParamsUFix64: {String:UFix64},
160
+ offerParamsUInt64: {String:UInt64},
161
+ resolverCapability: Capability<&{Resolver.ResolverPublic}>,
162
+ ) {
163
+ pre {
164
+ nftReceiverCapability.check(): "reward capability not valid"
165
+ }
166
+ self.vaultRefCapability = vaultRefCapability
167
+ self.nftReceiverCapability = nftReceiverCapability
168
+ self.resolverCapability = resolverCapability
169
+ var price: UFix64 = amount
170
+ let royaltyInfo: {Address:UFix64} = {}
171
+
172
+ for royalty in royalties {
173
+ assert(royalty.receiver.check(), message: "invalid royalty receiver")
174
+ price = price - royalty.amount
175
+ royaltyInfo[royalty.receiver.address] = royalty.amount
176
+ }
177
+
178
+ assert(price > 0.0, message: "price must be > 0")
179
+
180
+ self.details = OfferDetails(
181
+ offerId: self.uuid,
182
+ nftType: nftType,
183
+ offerAmount: amount,
184
+ royalties: royalties,
185
+ offerParamsString: offerParamsString,
186
+ offerParamsUFix64: offerParamsUFix64,
187
+ offerParamsUInt64: offerParamsUInt64,
188
+ paymentVaultType: vaultRefCapability.getType(),
189
+ )
190
+
191
+ emit OfferAvailable(
192
+ offerAddress: nftReceiverCapability.address,
193
+ offerId: self.details.offerId,
194
+ nftType: self.details.nftType,
195
+ offerAmount: self.details.offerAmount,
196
+ royalties: royaltyInfo,
197
+ offerType: offerParamsString["_type"] ?? "unknown",
198
+ offerParamsString: self.details.offerParamsString,
199
+ offerParamsUFix64: self.details.offerParamsUFix64,
200
+ offerParamsUInt64: self.details.offerParamsUInt64,
201
+ paymentVaultType: self.details.paymentVaultType,
202
+ )
203
+ }
204
+
205
+ // accept
206
+ // Accept the offer if...
207
+ // - Calling from an Offer that hasn't been purchased/desetoryed.
208
+ // - Provided with a NFT matching the NFT id within the Offer details.
209
+ // - Provided with a NFT matching the NFT Type within the Offer details.
210
+ //
211
+ pub fun accept(
212
+ item: @AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
213
+ receiverCapability: Capability<&{FungibleToken.Receiver}>,
214
+ ): Void {
215
+
216
+ pre {
217
+ !self.details.purchased: "Offer has already been purchased"
218
+ item.isInstance(self.details.nftType): "item NFT is not of specified type"
219
+ }
220
+
221
+ let resolverCapability = self.resolverCapability.borrow() ?? panic("could not borrow resolver")
222
+ let resolverResult = resolverCapability.checkOfferResolver(
223
+ item: &item as &AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
224
+ offerParamsString: self.details.offerParamsString,
225
+ offerParamsUInt64: self.details.offerParamsUInt64,
226
+ offerParamsUFix64: self.details.offerParamsUFix64,
227
+ )
228
+
229
+ if !resolverResult {
230
+ panic("Resolver failed, invalid NFT please check Offer criteria")
231
+ }
232
+
233
+ self.details.setToPurchased()
234
+ let nft <- item as! @NonFungibleToken.NFT
235
+ let nftId: UInt64 = nft.id
236
+ self.nftReceiverCapability.borrow()!.deposit(token: <- nft)
237
+
238
+ let initalDucSupply = self.vaultRefCapability.borrow()!.balance
239
+ let payment <- self.vaultRefCapability.borrow()!.withdraw(amount: self.details.offerAmount)
240
+
241
+ // Payout royalties
242
+ for royalty in self.details.royalties {
243
+ if let receiver = royalty.receiver.borrow() {
244
+ let amount = royalty.amount
245
+ let part <- payment.withdraw(amount: amount)
246
+ receiver.deposit(from: <- part)
247
+ }
248
+ }
249
+
250
+ receiverCapability.borrow()!.deposit(from: <- payment)
251
+
252
+ // If a DUC vault is being used for payment we must assert that no DUC is leaking from the transactions.
253
+ let isDucVault = self.vaultRefCapability.isInstance(
254
+ Type<Capability<&DapperUtilityCoin.Vault{FungibleToken.Provider, FungibleToken.Balance}>>()
255
+ )
256
+
257
+ if isDucVault {
258
+ assert(self.vaultRefCapability.borrow()!.balance == initalDucSupply, message: "DUC is leaking")
259
+ }
260
+
261
+ emit OfferCompleted(
262
+ purchased: self.details.purchased,
263
+ acceptingAddress: receiverCapability.address,
264
+ offerAddress: self.nftReceiverCapability.address,
265
+ offerId: self.details.offerId,
266
+ nftType: self.details.nftType,
267
+ offerAmount: self.details.offerAmount,
268
+ royalties: self.getRoyaltyInfo(),
269
+ offerType: self.details.offerParamsString["_type"] ?? "unknown",
270
+ offerParamsString: self.details.offerParamsString,
271
+ offerParamsUFix64: self.details.offerParamsUFix64,
272
+ offerParamsUInt64: self.details.offerParamsUInt64,
273
+ paymentVaultType: self.details.paymentVaultType,
274
+ nftId: nftId,
275
+ )
276
+ }
277
+
278
+ // getDetails
279
+ // Return Offer details
280
+ //
281
+ pub fun getDetails(): OfferDetails {
282
+ return self.details
283
+ }
284
+
285
+ // getRoyaltyInfo
286
+ // Return royalty details
287
+ //
288
+ pub fun getRoyaltyInfo(): {Address:UFix64} {
289
+ let royaltyInfo: {Address:UFix64} = {}
290
+
291
+ for royalty in self.details.royalties {
292
+ royaltyInfo[royalty.receiver.address] = royalty.amount
293
+ }
294
+ return royaltyInfo;
295
+ }
296
+
297
+ destroy() {
298
+ if !self.details.purchased {
299
+ emit OfferCompleted(
300
+ purchased: self.details.purchased,
301
+ acceptingAddress: nil,
302
+ offerAddress: self.nftReceiverCapability.address,
303
+ offerId: self.details.offerId,
304
+ nftType: self.details.nftType,
305
+ offerAmount: self.details.offerAmount,
306
+ royalties: self.getRoyaltyInfo(),
307
+ offerType: self.details.offerParamsString["_type"] ?? "unknown",
308
+ offerParamsString: self.details.offerParamsString,
309
+ offerParamsUFix64: self.details.offerParamsUFix64,
310
+ offerParamsUInt64: self.details.offerParamsUInt64,
311
+ paymentVaultType: self.details.paymentVaultType,
312
+ nftId: nil,
313
+ )
314
+ }
315
+ }
316
+ }
317
+
318
+ // makeOffer
319
+ pub fun makeOffer(
320
+ vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
321
+ nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
322
+ nftType: Type,
323
+ amount: UFix64,
324
+ royalties: [Royalty],
325
+ offerParamsString: {String:String},
326
+ offerParamsUFix64: {String:UFix64},
327
+ offerParamsUInt64: {String:UInt64},
328
+ resolverCapability: Capability<&{Resolver.ResolverPublic}>,
329
+ ): @Offer {
330
+ let newOfferResource <- create Offer(
331
+ vaultRefCapability: vaultRefCapability,
332
+ nftReceiverCapability: nftReceiverCapability,
333
+ nftType: nftType,
334
+ amount: amount,
335
+ royalties: royalties,
336
+ offerParamsString: offerParamsString,
337
+ offerParamsUFix64: offerParamsUFix64,
338
+ offerParamsUInt64: offerParamsUInt64,
339
+ resolverCapability: resolverCapability,
340
+ )
341
+ return <-newOfferResource
342
+ }
343
+ }
344
+
@@ -0,0 +1,70 @@
1
+ import "NonFungibleToken"
2
+ import "MetadataViews"
3
+ import "TopShot"
4
+
5
+ pub contract Resolver {
6
+ // Current list of supported resolution rules.
7
+ pub enum ResolverType: UInt8 {
8
+ pub case NFT
9
+ pub case TopShotEdition
10
+ pub case MetadataViewsEditions
11
+ }
12
+
13
+ // Public resource interface that defines a method signature for checkOfferResolver
14
+ // which is used within the Resolver resource for offer acceptance validation
15
+ pub resource interface ResolverPublic {
16
+ pub fun checkOfferResolver(
17
+ item: &AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
18
+ offerParamsString: {String:String},
19
+ offerParamsUInt64: {String:UInt64},
20
+ offerParamsUFix64: {String:UFix64}): Bool
21
+ }
22
+
23
+
24
+ // Resolver resource holds the Offer exchange resolution rules.
25
+ pub resource OfferResolver: ResolverPublic {
26
+ // checkOfferResolver
27
+ // Holds the validation rules for resolver each type of supported ResolverType
28
+ // Function returns TRUE if the provided nft item passes the criteria for exchange
29
+ pub fun checkOfferResolver(
30
+ item: &AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
31
+ offerParamsString: {String:String},
32
+ offerParamsUInt64: {String:UInt64},
33
+ offerParamsUFix64: {String:UFix64}): Bool {
34
+ if offerParamsString["resolver"] == ResolverType.NFT.rawValue.toString() {
35
+ assert(item.id.toString() == offerParamsString["nftId"], message: "item NFT does not have specified ID")
36
+ return true
37
+ } else if offerParamsString["resolver"] == ResolverType.TopShotEdition.rawValue.toString() {
38
+ // // Get the Top Shot specific metadata for this NFT
39
+ let view = item.resolveView(Type<TopShot.TopShotMomentMetadataView>())!
40
+ let metadata = view as! TopShot.TopShotMomentMetadataView
41
+ if offerParamsString["playId"] == metadata.playID.toString() && offerParamsString["setId"] == metadata.setID.toString() {
42
+ return true
43
+ }
44
+ } else if offerParamsString["resolver"] == ResolverType.MetadataViewsEditions.rawValue.toString() {
45
+ if let views = item.resolveView(Type<MetadataViews.Editions>()) {
46
+ let editions = views as! [MetadataViews.Edition]
47
+ var hasCorrectMetadataView = false
48
+ for edition in editions {
49
+ if edition.name == offerParamsString["editionName"] {
50
+ hasCorrectMetadataView = true
51
+ }
52
+ }
53
+ assert(hasCorrectMetadataView == true, message: "editionId does not exist on NFT")
54
+ return true
55
+ } else {
56
+ panic("NFT does not use MetadataViews.Editions")
57
+ }
58
+ } else {
59
+ panic("Invalid Resolver on Offer")
60
+ }
61
+
62
+ return false
63
+ }
64
+
65
+ }
66
+
67
+ pub fun createResolver(): @OfferResolver {
68
+ return <-create OfferResolver()
69
+ }
70
+ }
package/flow.json CHANGED
@@ -227,6 +227,46 @@
227
227
  "testnet": "0xbe4635353f55bbd4",
228
228
  "mainnet": "0x473d6a2c37eab5be"
229
229
  }
230
+ },
231
+ "TopShot": {
232
+ "source": "./contracts/dapper/TopShot.cdc",
233
+ "aliases": {
234
+ "emulator": "0xf8d6e0586b0a20c7",
235
+ "testnet": "0x877931736ee77cff",
236
+ "mainnet": "0x0b2a3299cc857e29"
237
+ }
238
+ },
239
+ "TopShotLocking": {
240
+ "source": "./contracts/dapper/TopShotLocking.cdc",
241
+ "aliases": {
242
+ "emulator": "0xf8d6e0586b0a20c7",
243
+ "testnet": "0x877931736ee77cff",
244
+ "mainnet": "0x0b2a3299cc857e29"
245
+ }
246
+ },
247
+ "DapperOffersV2": {
248
+ "source": "./contracts/dapper/offers/DapperOffersV2.cdc",
249
+ "aliases": {
250
+ "emulator": "0xf8d6e0586b0a20c7",
251
+ "testnet": "0x8a5f647e58dde1ee",
252
+ "mainnet": "0xb8ea91944fd51c43"
253
+ }
254
+ },
255
+ "OffersV2": {
256
+ "source": "./contracts/dapper/offers/OffersV2.cdc",
257
+ "aliases": {
258
+ "emulator": "0xf8d6e0586b0a20c7",
259
+ "testnet": "0x8a5f647e58dde1ee",
260
+ "mainnet": "0xb8ea91944fd51c43"
261
+ }
262
+ },
263
+ "Resolver": {
264
+ "source": "./contracts/dapper/offers/Resolver.cdc",
265
+ "aliases": {
266
+ "emulator": "0xf8d6e0586b0a20c7",
267
+ "testnet": "0x8a5f647e58dde1ee",
268
+ "mainnet": "0xb8ea91944fd51c43"
269
+ }
230
270
  }
231
271
  },
232
272
  "deployments": {
@@ -243,7 +283,12 @@
243
283
  "FlowStorageFees",
244
284
  "FeeEstimator",
245
285
  "LostAndFound",
246
- "LostAndFoundHelper"
286
+ "LostAndFoundHelper",
287
+ "Resolver",
288
+ "DapperOffersV2",
289
+ "OffersV2",
290
+ "TopShot",
291
+ "TopShotLocking"
247
292
  ],
248
293
  "emulator-ft": [
249
294
  "FungibleToken",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowtyio/flow-contracts",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "main": "index.json",
5
5
  "description": "An NPM package for common flow contracts",
6
6
  "author": "flowtyio",