@flowtyio/flow-contracts 0.0.14 → 0.0.16
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/dapper/TopShot.cdc +1686 -0
- package/contracts/dapper/TopShotLocking.cdc +165 -0
- package/contracts/dapper/offers/DapperOffersV2.cdc +238 -0
- package/contracts/dapper/offers/OffersV2.cdc +344 -0
- package/contracts/dapper/offers/Resolver.cdc +70 -0
- package/contracts/flow-utils/AddressUtils.cdc +109 -0
- package/contracts/flow-utils/ArrayUtils.cdc +69 -0
- package/contracts/flow-utils/StringUtils.cdc +97 -0
- package/flow.json +73 -1
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import "NonFungibleToken"
|
|
2
|
+
|
|
3
|
+
pub contract TopShotLocking {
|
|
4
|
+
|
|
5
|
+
// -----------------------------------------------------------------------
|
|
6
|
+
// TopShotLocking contract Events
|
|
7
|
+
// -----------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
// Emitted when a Moment is locked
|
|
10
|
+
pub event MomentLocked(id: UInt64, duration: UFix64, expiryTimestamp: UFix64)
|
|
11
|
+
|
|
12
|
+
// Emitted when a Moment is unlocked
|
|
13
|
+
pub event MomentUnlocked(id: UInt64)
|
|
14
|
+
|
|
15
|
+
// Dictionary of locked NFTs
|
|
16
|
+
// TopShot nft resource id is the key
|
|
17
|
+
// locked until timestamp is the value
|
|
18
|
+
access(self) var lockedNFTs: {UInt64: UFix64}
|
|
19
|
+
|
|
20
|
+
// Dictionary of NFTs overridden to be unlocked
|
|
21
|
+
access(self) var unlockableNFTs: {UInt64: Bool} // nft resource id is the key
|
|
22
|
+
|
|
23
|
+
// isLocked Returns a boolean indicating if an nft exists in the lockedNFTs dictionary
|
|
24
|
+
//
|
|
25
|
+
// Parameters: nftRef: A reference to the NFT resource
|
|
26
|
+
//
|
|
27
|
+
// Returns: true if NFT is locked
|
|
28
|
+
pub fun isLocked(nftRef: &NonFungibleToken.NFT): Bool {
|
|
29
|
+
return self.lockedNFTs.containsKey(nftRef.id)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// getLockExpiry Returns the unix timestamp when an nft is unlockable
|
|
33
|
+
//
|
|
34
|
+
// Parameters: nftRef: A reference to the NFT resource
|
|
35
|
+
//
|
|
36
|
+
// Returns: unix timestamp
|
|
37
|
+
pub fun getLockExpiry(nftRef: &NonFungibleToken.NFT): UFix64 {
|
|
38
|
+
if !self.lockedNFTs.containsKey(nftRef.id) {
|
|
39
|
+
panic("NFT is not locked")
|
|
40
|
+
}
|
|
41
|
+
return self.lockedNFTs[nftRef.id]!
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// lockNFT Takes an NFT resource and adds its unique identifier to the lockedNFTs dictionary
|
|
45
|
+
//
|
|
46
|
+
// Parameters: nft: NFT resource
|
|
47
|
+
// duration: number of seconds the NFT will be locked for
|
|
48
|
+
//
|
|
49
|
+
// Returns: the NFT resource
|
|
50
|
+
pub fun lockNFT(nft: @NonFungibleToken.NFT, duration: UFix64): @NonFungibleToken.NFT {
|
|
51
|
+
let TopShotNFTType: Type = CompositeType("A.TOPSHOTADDRESS.TopShot.NFT")!
|
|
52
|
+
if !nft.isInstance(TopShotNFTType) {
|
|
53
|
+
panic("NFT is not a TopShot NFT")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if self.lockedNFTs.containsKey(nft.id) {
|
|
57
|
+
// already locked - short circuit and return the nft
|
|
58
|
+
return <- nft
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let expiryTimestamp = getCurrentBlock().timestamp + duration
|
|
62
|
+
|
|
63
|
+
self.lockedNFTs[nft.id] = expiryTimestamp
|
|
64
|
+
|
|
65
|
+
emit MomentLocked(id: nft.id, duration: duration, expiryTimestamp: expiryTimestamp)
|
|
66
|
+
|
|
67
|
+
return <- nft
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// unlockNFT Takes an NFT resource and removes it from the lockedNFTs dictionary
|
|
71
|
+
//
|
|
72
|
+
// Parameters: nft: NFT resource
|
|
73
|
+
//
|
|
74
|
+
// Returns: the NFT resource
|
|
75
|
+
//
|
|
76
|
+
// NFT must be eligible for unlocking by an admin
|
|
77
|
+
pub fun unlockNFT(nft: @NonFungibleToken.NFT): @NonFungibleToken.NFT {
|
|
78
|
+
if !self.lockedNFTs.containsKey(nft.id) {
|
|
79
|
+
// nft is not locked, short circuit and return the nft
|
|
80
|
+
return <- nft
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let lockExpiryTimestamp: UFix64 = self.lockedNFTs[nft.id]!
|
|
84
|
+
let isPastExpiry: Bool = getCurrentBlock().timestamp >= lockExpiryTimestamp
|
|
85
|
+
|
|
86
|
+
let isUnlockableOverridden: Bool = self.unlockableNFTs.containsKey(nft.id)
|
|
87
|
+
|
|
88
|
+
if !(isPastExpiry || isUnlockableOverridden) {
|
|
89
|
+
panic("NFT is not eligible to be unlocked, expires at ".concat(lockExpiryTimestamp.toString()))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
self.unlockableNFTs.remove(key: nft.id)
|
|
93
|
+
self.lockedNFTs.remove(key: nft.id)
|
|
94
|
+
|
|
95
|
+
emit MomentUnlocked(id: nft.id)
|
|
96
|
+
|
|
97
|
+
return <- nft
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// getIDs Returns the ids of all locked Top Shot NFT tokens
|
|
101
|
+
//
|
|
102
|
+
// Returns: array of ids
|
|
103
|
+
//
|
|
104
|
+
pub fun getIDs(): [UInt64] {
|
|
105
|
+
return self.lockedNFTs.keys
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// getExpiry Returns the timestamp when a locked token is eligible for unlock
|
|
109
|
+
//
|
|
110
|
+
// Parameters: tokenID: the nft id of the locked token
|
|
111
|
+
//
|
|
112
|
+
// Returns: a unix timestamp in seconds
|
|
113
|
+
//
|
|
114
|
+
pub fun getExpiry(tokenID: UInt64): UFix64? {
|
|
115
|
+
return self.lockedNFTs[tokenID]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// getLockedNFTsLength Returns the count of locked tokens
|
|
119
|
+
//
|
|
120
|
+
// Returns: an integer containing the number of locked tokens
|
|
121
|
+
//
|
|
122
|
+
pub fun getLockedNFTsLength(): Int {
|
|
123
|
+
return self.lockedNFTs.length
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Admin is a special authorization resource that
|
|
127
|
+
// allows the owner to override the lock on a moment
|
|
128
|
+
//
|
|
129
|
+
pub resource Admin {
|
|
130
|
+
// createNewAdmin creates a new Admin resource
|
|
131
|
+
//
|
|
132
|
+
pub fun createNewAdmin(): @Admin {
|
|
133
|
+
return <-create Admin()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// markNFTUnlockable marks a given nft as being
|
|
137
|
+
// unlockable, overridding the expiry timestamp
|
|
138
|
+
// the nft owner will still need to send an unlock transaction to unlock
|
|
139
|
+
//
|
|
140
|
+
pub fun markNFTUnlockable(nftRef: &NonFungibleToken.NFT) {
|
|
141
|
+
TopShotLocking.unlockableNFTs[nftRef.id] = true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// unlocks all NFTs
|
|
145
|
+
pub fun unlockAll() {
|
|
146
|
+
TopShotLocking.lockedNFTs = {}
|
|
147
|
+
TopShotLocking.unlockableNFTs = {}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// -----------------------------------------------------------------------
|
|
152
|
+
// TopShotLocking initialization function
|
|
153
|
+
// -----------------------------------------------------------------------
|
|
154
|
+
//
|
|
155
|
+
init() {
|
|
156
|
+
self.lockedNFTs = {}
|
|
157
|
+
self.unlockableNFTs = {}
|
|
158
|
+
|
|
159
|
+
// Create a single admin resource
|
|
160
|
+
let admin <- create Admin()
|
|
161
|
+
|
|
162
|
+
// Store it in private account storage in `init` so only the admin can use it
|
|
163
|
+
self.account.save(<-admin, to: /storage/TopShotLockingAdmin)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import "OffersV2"
|
|
2
|
+
import "FungibleToken"
|
|
3
|
+
import "NonFungibleToken"
|
|
4
|
+
import "Resolver"
|
|
5
|
+
|
|
6
|
+
// DapperOffersV2
|
|
7
|
+
//
|
|
8
|
+
// Each account that wants to create offers for NFTs installs an DapperOffer
|
|
9
|
+
// resource and creates individual Offers for NFTs within it.
|
|
10
|
+
//
|
|
11
|
+
// The DapperOffer resource contains the methods to add, remove, borrow and
|
|
12
|
+
// get details on Offers contained within it.
|
|
13
|
+
//
|
|
14
|
+
pub contract DapperOffersV2 {
|
|
15
|
+
// DapperOffersV2
|
|
16
|
+
// This contract has been deployed.
|
|
17
|
+
// Event consumers can now expect events from this contract.
|
|
18
|
+
//
|
|
19
|
+
pub event DapperOffersInitialized()
|
|
20
|
+
|
|
21
|
+
/// DapperOfferInitialized
|
|
22
|
+
// A DapperOffer resource has been created.
|
|
23
|
+
//
|
|
24
|
+
pub event DapperOfferInitialized(DapperOfferResourceId: UInt64)
|
|
25
|
+
|
|
26
|
+
// DapperOfferDestroyed
|
|
27
|
+
// A DapperOffer resource has been destroyed.
|
|
28
|
+
// Event consumers can now stop processing events from this resource.
|
|
29
|
+
//
|
|
30
|
+
pub event DapperOfferDestroyed(DapperOfferResourceId: UInt64)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
// DapperOfferPublic
|
|
34
|
+
// An interface providing a useful public interface to a Offer.
|
|
35
|
+
//
|
|
36
|
+
pub resource interface DapperOfferPublic {
|
|
37
|
+
// getOfferIds
|
|
38
|
+
// Get a list of Offer ids created by the resource.
|
|
39
|
+
//
|
|
40
|
+
pub fun getOfferIds(): [UInt64]
|
|
41
|
+
// borrowOffer
|
|
42
|
+
// Borrow an Offer to either accept the Offer or get details on the Offer.
|
|
43
|
+
//
|
|
44
|
+
pub fun borrowOffer(offerId: UInt64): &OffersV2.Offer{OffersV2.OfferPublic}?
|
|
45
|
+
// cleanup
|
|
46
|
+
// Remove an Offer
|
|
47
|
+
//
|
|
48
|
+
pub fun cleanup(offerId: UInt64)
|
|
49
|
+
// addProxyCapability
|
|
50
|
+
// Assign proxy capabilities (DapperOfferProxyManager) to an DapperOffer resource.
|
|
51
|
+
//
|
|
52
|
+
pub fun addProxyCapability(
|
|
53
|
+
account: Address,
|
|
54
|
+
cap: Capability<&DapperOffer{DapperOffersV2.DapperOfferProxyManager}>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// DapperOfferManager
|
|
59
|
+
// An interface providing a management interface for an DapperOffer resource.
|
|
60
|
+
//
|
|
61
|
+
pub resource interface DapperOfferManager {
|
|
62
|
+
// createOffer
|
|
63
|
+
// Allows the DapperOffer owner to create Offers.
|
|
64
|
+
//
|
|
65
|
+
pub fun createOffer(
|
|
66
|
+
vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
|
|
67
|
+
nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
|
|
68
|
+
nftType: Type,
|
|
69
|
+
amount: UFix64,
|
|
70
|
+
royalties: [OffersV2.Royalty],
|
|
71
|
+
offerParamsString: {String:String},
|
|
72
|
+
offerParamsUFix64: {String:UFix64},
|
|
73
|
+
offerParamsUInt64: {String:UInt64},
|
|
74
|
+
resolverCapability: Capability<&{Resolver.ResolverPublic}>,
|
|
75
|
+
): UInt64
|
|
76
|
+
// removeOffer
|
|
77
|
+
// Allows the DapperOffer owner to remove offers
|
|
78
|
+
//
|
|
79
|
+
pub fun removeOffer(offerId: UInt64)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// DapperOfferProxyManager
|
|
83
|
+
// An interface providing removeOffer on behalf of an DapperOffer owner.
|
|
84
|
+
//
|
|
85
|
+
pub resource interface DapperOfferProxyManager {
|
|
86
|
+
// removeOffer
|
|
87
|
+
// Allows the DapperOffer owner to remove offers
|
|
88
|
+
//
|
|
89
|
+
pub fun removeOffer(offerId: UInt64)
|
|
90
|
+
// removeOfferFromProxy
|
|
91
|
+
// Allows the DapperOffer proxy owner to remove offers
|
|
92
|
+
//
|
|
93
|
+
pub fun removeOfferFromProxy(account: Address, offerId: UInt64)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
// DapperOffer
|
|
98
|
+
// A resource that allows its owner to manage a list of Offers, and anyone to interact with them
|
|
99
|
+
// in order to query their details and accept the Offers for NFTs that they represent.
|
|
100
|
+
//
|
|
101
|
+
pub resource DapperOffer : DapperOfferManager, DapperOfferPublic, DapperOfferProxyManager {
|
|
102
|
+
// The dictionary of Address to DapperOfferProxyManager capabilities.
|
|
103
|
+
access(self) var removeOfferCapability: {Address:Capability<&DapperOffer{DapperOffersV2.DapperOfferProxyManager}>}
|
|
104
|
+
// The dictionary of Offer uuids to Offer resources.
|
|
105
|
+
access(self) var offers: @{UInt64:OffersV2.Offer}
|
|
106
|
+
|
|
107
|
+
// addProxyCapability
|
|
108
|
+
// Assign proxy capabilities (DapperOfferProxyManager) to an DapperOffer resource.
|
|
109
|
+
//
|
|
110
|
+
pub fun addProxyCapability(account: Address, cap: Capability<&DapperOffer{DapperOffersV2.DapperOfferProxyManager}>) {
|
|
111
|
+
pre {
|
|
112
|
+
cap.borrow() != nil: "Invalid admin capability"
|
|
113
|
+
}
|
|
114
|
+
self.removeOfferCapability[account] = cap
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// removeOfferFromProxy
|
|
118
|
+
// Allows the DapperOffer proxy owner to remove offers
|
|
119
|
+
//
|
|
120
|
+
pub fun removeOfferFromProxy(account: Address, offerId: UInt64) {
|
|
121
|
+
pre {
|
|
122
|
+
self.removeOfferCapability[account] != nil:
|
|
123
|
+
"Cannot remove offers until the token admin has deposited the account registration capability"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let adminRef = self.removeOfferCapability[account]!.borrow()!
|
|
127
|
+
|
|
128
|
+
adminRef.removeOffer(offerId: offerId)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
// createOffer
|
|
133
|
+
// Allows the DapperOffer owner to create Offers.
|
|
134
|
+
//
|
|
135
|
+
pub fun createOffer(
|
|
136
|
+
vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
|
|
137
|
+
nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
|
|
138
|
+
nftType: Type,
|
|
139
|
+
amount: UFix64,
|
|
140
|
+
royalties: [OffersV2.Royalty],
|
|
141
|
+
offerParamsString: {String:String},
|
|
142
|
+
offerParamsUFix64: {String:UFix64},
|
|
143
|
+
offerParamsUInt64: {String:UInt64},
|
|
144
|
+
resolverCapability: Capability<&{Resolver.ResolverPublic}>,
|
|
145
|
+
): UInt64 {
|
|
146
|
+
let offer <- OffersV2.makeOffer(
|
|
147
|
+
vaultRefCapability: vaultRefCapability,
|
|
148
|
+
nftReceiverCapability: nftReceiverCapability,
|
|
149
|
+
nftType: nftType,
|
|
150
|
+
amount: amount,
|
|
151
|
+
royalties: royalties,
|
|
152
|
+
offerParamsString: offerParamsString,
|
|
153
|
+
offerParamsUFix64: offerParamsUFix64,
|
|
154
|
+
offerParamsUInt64: offerParamsUInt64,
|
|
155
|
+
resolverCapability: resolverCapability,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
let offerId = offer.uuid
|
|
159
|
+
let dummy <- self.offers[offerId] <- offer
|
|
160
|
+
destroy dummy
|
|
161
|
+
|
|
162
|
+
return offerId
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// removeOffer
|
|
166
|
+
// Remove an Offer that has not yet been accepted from the collection and destroy it.
|
|
167
|
+
//
|
|
168
|
+
pub fun removeOffer(offerId: UInt64) {
|
|
169
|
+
destroy self.offers.remove(key: offerId) ?? panic("missing offer")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// getOfferIds
|
|
173
|
+
// Returns an array of the Offer resource IDs that are in the collection
|
|
174
|
+
//
|
|
175
|
+
pub fun getOfferIds(): [UInt64] {
|
|
176
|
+
return self.offers.keys
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// borrowOffer
|
|
180
|
+
// Returns a read-only view of the Offer for the given OfferID if it is contained by this collection.
|
|
181
|
+
//
|
|
182
|
+
pub fun borrowOffer(offerId: UInt64): &OffersV2.Offer{OffersV2.OfferPublic}? {
|
|
183
|
+
if self.offers[offerId] != nil {
|
|
184
|
+
return (&self.offers[offerId] as &OffersV2.Offer{OffersV2.OfferPublic}?)!
|
|
185
|
+
} else {
|
|
186
|
+
return nil
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// cleanup
|
|
191
|
+
// Remove an Offer *if* it has been accepted.
|
|
192
|
+
// Anyone can call, but at present it only benefits the account owner to do so.
|
|
193
|
+
// Kind purchasers can however call it if they like.
|
|
194
|
+
//
|
|
195
|
+
pub fun cleanup(offerId: UInt64) {
|
|
196
|
+
pre {
|
|
197
|
+
self.offers[offerId] != nil: "could not find Offer with given id"
|
|
198
|
+
}
|
|
199
|
+
let offer <- self.offers.remove(key: offerId)!
|
|
200
|
+
assert(offer.getDetails().purchased == true, message: "Offer is not purchased, only admin can remove")
|
|
201
|
+
destroy offer
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// constructor
|
|
205
|
+
//
|
|
206
|
+
init() {
|
|
207
|
+
self.removeOfferCapability = {}
|
|
208
|
+
self.offers <- {}
|
|
209
|
+
// Let event consumers know that this storefront will no longer exist.
|
|
210
|
+
emit DapperOfferInitialized(DapperOfferResourceId: self.uuid)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// destructor
|
|
214
|
+
//
|
|
215
|
+
destroy() {
|
|
216
|
+
destroy self.offers
|
|
217
|
+
// Let event consumers know that this storefront exists.
|
|
218
|
+
emit DapperOfferDestroyed(DapperOfferResourceId: self.uuid)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// createDapperOffer
|
|
223
|
+
// Make creating an DapperOffer publicly accessible.
|
|
224
|
+
//
|
|
225
|
+
pub fun createDapperOffer(): @DapperOffer {
|
|
226
|
+
return <-create DapperOffer()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
pub let DapperOffersStoragePath: StoragePath
|
|
230
|
+
pub let DapperOffersPublicPath: PublicPath
|
|
231
|
+
|
|
232
|
+
init () {
|
|
233
|
+
self.DapperOffersStoragePath = /storage/DapperOffersV2
|
|
234
|
+
self.DapperOffersPublicPath = /public/DapperOffersV2
|
|
235
|
+
|
|
236
|
+
emit DapperOffersInitialized()
|
|
237
|
+
}
|
|
238
|
+
}
|