Smart Contract
CrowdflixMarket
A.5a35c571a4feae1e.CrowdflixMarket
1/**
2
3 CrowdflixMarket.cdc
4
5 Description: Contract definitions for users to sell their moments
6
7 Marketplace is where users can create a sale collection that they
8 store in their account storage. In the sale collection,
9 they can put their NFTs up for sale with a price and publish a
10 reference so that others can see the sale.
11
12 If another user sees an NFT that they want to buy,
13 they can send fungible tokens that equal the buy price
14 to buy the NFT. The NFT is transferred to them when
15 they make the purchase.
16
17 Each user who wants to sell tokens will have a sale collection
18 instance in their account that contains price information
19 for each node in their collection. The sale holds a capability that
20 links to their main moment collection.
21
22 They can give a reference to this collection to a central contract
23 so that it can list the sales in a central place
24
25 When a user creates a sale, they will supply four arguments:
26 - A Crowdflix.Collection capability that allows their sale to withdraw
27 a moment when it is purchased.
28 - A FungibleToken.Receiver capability as the place where the payment for the token goes.
29 - A FungibleToken.Receiver capability specifying a beneficiary, where a cut of the purchase gets sent.
30 - A cut percentage, specifying how much the beneficiary will recieve.
31
32 The cut percentage can be set to zero if the user desires and they
33 will receive the entirety of the purchase. Crowdflix will initialize sales
34 for users with the Crowdflix admin vault as the vault where cuts get
35 deposited to.
36**/
37
38// Testnet
39// import FungibleToken from 0x9a0766d93b6608b7
40// import NonFungibleToken from 0x631e88ae7f1d7c20
41// import MetadataViews from 0x631e88ae7f1d7c20
42// import Crowdflix from 0xa27ccee47a521b38
43// import FlowToken from 0x7e60df042a9c0868
44// Production
45import FungibleToken from 0xf233dcee88fe0abe
46import NonFungibleToken from 0x1d7e57aa55817448
47import MetadataViews from 0x1d7e57aa55817448
48import Crowdflix from 0xad9300d3ca41f63a
49import FlowToken from 0x1654653399040a61
50
51access(all) contract CrowdflixMarket {
52
53 // -----------------------------------------------------------------------
54 // Crowdflix Market contract Event definitions
55 // -----------------------------------------------------------------------
56
57 /// emitted when a Crowdflix moment is listed for sale
58 access(all) event MomentListed(id: UInt64, price: UFix64, seller: Address?)
59 /// emitted when a token is purchased from the market
60 access(all) event MomentPurchased(id: UInt64, price: UFix64, seller: Address?, momentName: String, momentDescription: String, momentThumbnailURL: String)
61 /// emitted when a moment has been withdrawn from the sale
62 access(all) event MomentWithdrawn(id: UInt64, owner: Address?)
63
64 /// Path where the `SaleCollection` is stored
65 access(all) let marketStoragePath: StoragePath
66
67 /// Path where the public capability for the `SaleCollection` is
68 access(all) let marketPublicPath: PublicPath
69
70 /// SaleCollection
71 ///
72 /// This is the main resource that token sellers will store in their account
73 /// to manage the NFTs that they are selling. The SaleCollection
74 /// holds a Crowdflix Collection resource to store the moments that are for sale.
75 /// The SaleCollection also keeps track of the price of each token.
76 ///
77 /// When a token is purchased, a cut is taken from the tokens
78 /// and sent to the beneficiary, then the rest are sent to the seller.
79 ///
80 /// The seller chooses who the beneficiary is and what percentage
81 /// of the tokens gets taken from the purchase
82 access(all) resource SaleCollection {
83
84 /// A collection of the moments that the user has for sale
85 access(self) var ownerCollection: Capability<auth(NonFungibleToken.Withdraw, NonFungibleToken.Update) &Crowdflix.Collection>
86
87 /// Dictionary of the low low prices for each NFT by ID
88 access(self) var prices: {UInt64: UFix64}
89
90 /// The fungible token vault of the seller
91 /// so that when someone buys a token, the tokens are deposited
92 /// to this Vault
93 access(self) var ownerCapability: Capability<&{FungibleToken.Receiver}>
94
95 /// The capability that is used for depositing
96 /// the beneficiary's cut of every sale
97 access(self) var beneficiaryCapability: Capability<&{FungibleToken.Receiver}>
98
99 /// The percentage that is taken from every purchase for the beneficiary
100 /// For example, if the percentage is 15%, cutPercentage = 0.15
101 access(all) var cutPercentage: UFix64
102
103 init (ownerCollection: Capability<auth(NonFungibleToken.Withdraw, NonFungibleToken.Update) &Crowdflix.Collection>,
104 ownerCapability: Capability<&{FungibleToken.Receiver}>,
105 beneficiaryCapability: Capability<&{FungibleToken.Receiver}>,
106 cutPercentage: UFix64) {
107 pre {
108 // Check that the owner's moment collection capability is correct
109 ownerCollection.check():
110 "Owner's Moment Collection Capability is invalid!"
111
112 // Check that both capabilities are for fungible token Vault receivers
113 ownerCapability.check():
114 "Owner's Receiver Capability is invalid!"
115 beneficiaryCapability.check():
116 "Beneficiary's Receiver Capability is invalid!"
117 }
118
119 // create an empty collection to store the moments that are for sale
120 self.ownerCollection = ownerCollection
121 self.ownerCapability = ownerCapability
122 self.beneficiaryCapability = beneficiaryCapability
123 // prices are initially empty because there are no moments for sale
124 self.prices = {}
125 self.cutPercentage = cutPercentage
126 }
127
128 /// listForSale lists an NFT for sale in this sale collection
129 /// at the specified price
130 ///
131 /// Parameters: tokenID: The id of the NFT to be put up for sale
132 /// price: The price of the NFT
133 access(all) fun listForSale(tokenID: UInt64, price: UFix64) {
134 pre {
135 self.ownerCollection.borrow()!.borrowMoment(id: tokenID) != nil:
136 "Moment does not exist in the owner's collection"
137 }
138
139 // Set the token's price
140 self.prices[tokenID] = price
141
142 emit MomentListed(id: tokenID, price: price, seller: self.owner?.address)
143 }
144
145 /// cancelSale cancels a moment sale and clears its price
146 ///
147 /// Parameters: tokenID: the ID of the token to withdraw from the sale
148 ///
149 access(all) fun cancelSale(tokenID: UInt64) {
150 // Remove the price from the prices dictionary
151 self.prices.remove(key: tokenID)
152
153 // Set prices to nil for the withdrawn ID
154 self.prices[tokenID] = nil
155
156 // Emit the event for withdrawing a moment from the Sale
157 emit MomentWithdrawn(id: tokenID, owner: self.owner?.address)
158 }
159
160 /// purchase lets a user send tokens to purchase an NFT that is for sale
161 /// the purchased NFT is returned to the transaction context that called it
162 ///
163 /// Parameters: tokenID: the ID of the NFT to purchase
164 /// buyTokens: the fungible tokens that are used to buy the NFT
165 ///
166 /// Returns: @Crowdflix.NFT: the purchased NFT
167 access(all) fun purchase(tokenID: UInt64, buyTokens: @FlowToken.Vault): @Crowdflix.NFT {
168 if self.prices[tokenID] != nil {
169 assert(
170 buyTokens.balance == self.prices[tokenID]!,
171 message: "Not enough tokens to buy the NFT!"
172 )
173
174 // Read the price for the token
175 let price = self.prices[tokenID]!
176
177 // Set the price for the token to nil
178 self.prices[tokenID] = nil
179
180 // Take the cut of the tokens that the beneficiary gets from the sent tokens
181 let beneficiaryCut <- buyTokens.withdraw(amount: price*self.cutPercentage)
182
183 // Deposit it into the beneficiary's Vault
184 self.beneficiaryCapability.borrow()!
185 .deposit(from: <-beneficiaryCut)
186
187 // Deposit the remaining tokens into the owners vault
188 self.ownerCapability.borrow()!
189 .deposit(from: <-buyTokens)
190
191 // Return the purchased token
192 let boughtMoment <- self.ownerCollection.borrow()!.withdraw(withdrawID: tokenID) as! @Crowdflix.NFT
193
194 let momentDisplay = boughtMoment.resolveView(Type<MetadataViews.Display>())! as! MetadataViews.Display
195 emit MomentPurchased(id: tokenID, price: price, seller: self.owner?.address, momentName: momentDisplay.name, momentDescription: momentDisplay.description, momentThumbnailURL: momentDisplay.thumbnail.uri())
196
197 return <-boughtMoment
198 }
199
200 panic("No token matching this ID for sale!")
201
202 }
203
204 /// changeOwnerReceiver updates the capability for the sellers fungible token Vault
205 ///
206 /// Parameters: newOwnerCapability: The new fungible token capability for the account
207 /// who received tokens for purchases
208 access(all) fun changeOwnerReceiver(_ newOwnerCapability: Capability<&{FungibleToken.Receiver}>) {
209 pre {
210 newOwnerCapability.borrow() != nil:
211 "Owner's Receiver Capability is invalid!"
212 }
213 self.ownerCapability = newOwnerCapability
214 }
215
216 /// changeBeneficiaryReceiver updates the capability for the beneficiary of the cut of the sale
217 ///
218 /// Parameters: newBeneficiaryCapability the new capability for the beneficiary of the cut of the sale
219 ///
220 access(all) fun changeBeneficiaryReceiver(_ newBeneficiaryCapability: Capability<&{FungibleToken.Receiver}>) {
221 pre {
222 newBeneficiaryCapability.borrow() != nil:
223 "Beneficiary's Receiver Capability is invalid!"
224 }
225 self.beneficiaryCapability = newBeneficiaryCapability
226 }
227
228 /// getPrice returns the price of a specific token in the sale
229 ///
230 /// Parameters: tokenID: The ID of the NFT whose price to get
231 ///
232 /// Returns: UFix64: The price of the token
233 access(all) view fun getPrice(tokenID: UInt64): UFix64? {
234 if let price = self.prices[tokenID] {
235 return price
236 }
237 return nil
238 }
239
240 /// getIDs returns an array of token IDs that are for sale
241 access(all) view fun getIDs(): [UInt64] {
242 return self.prices.keys
243 }
244
245 /// borrowMoment Returns a borrowed reference to a Moment for sale
246 /// so that the caller can read data from it
247 ///
248 /// Parameters: id: The ID of the moment to borrow a reference to
249 ///
250 /// Returns: &Crowdflix.NFT? Optional reference to a moment for sale
251 /// so that the caller can read its data
252 ///
253 access(all) view fun borrowMoment(id: UInt64): &Crowdflix.NFT? {
254 let ref = self.ownerCollection.borrow()!.borrowMoment(id: id)
255 return ref
256 }
257 }
258
259 /// createCollection returns a new collection resource to the caller
260 access(all) fun createSaleCollection(ownerCollection: Capability<auth(NonFungibleToken.Withdraw, NonFungibleToken.Update) &Crowdflix.Collection>,
261 ownerCapability: Capability<&{FungibleToken.Receiver}>,
262 beneficiaryCapability: Capability<&{FungibleToken.Receiver}>,
263 cutPercentage: UFix64,): @SaleCollection {
264
265 return <- create SaleCollection(ownerCollection: ownerCollection,
266 ownerCapability: ownerCapability,
267 beneficiaryCapability: beneficiaryCapability,
268 cutPercentage: cutPercentage)
269 }
270
271 init() {
272 self.marketStoragePath = /storage/CrowdflixMarketCollection
273 self.marketPublicPath = /public/CrowdflixMarketCollection
274 }
275}
276