Smart Contract
DisruptArtMarketplace
A.cd946ef9b13804c6.DisruptArtMarketplace
1// DisruptArt NFT Marketplace
2// Marketplace smart contract
3// NFT Marketplace : www.disrupt.art
4// Owner : Disrupt Art, INC.
5// Developer : www.blaze.ws
6// Version : 0.0.5
7// Blockchain : Flow www.onFlow.org
8
9
10// FUSD FUNGIBLE TOKEN mainnet
11//
12import FungibleToken from 0xf233dcee88fe0abe
13// Testnet wallet address
14import DisruptArt from 0xcd946ef9b13804c6
15
16// This contract allows users to list their DisruptArt NFT for sale.
17// Other users can purchase these NFTs with FUSD token.
18
19pub contract DisruptArtMarketplace {
20
21 // Event that is emitted when a new Disruptnow NFT is put up for sale
22 pub event ForSale(id: UInt64, price: UFix64)
23
24 // Event that is emitted when the NFT price changes
25 pub event PriceChanged(id: UInt64, newPrice: UFix64)
26
27 // Event that is emitted when a NFT token is purchased
28 pub event TokenPurchased(id: UInt64, price: UFix64)
29
30 // Event that is emitted when a seller withdraws their NFT from the sale
31 pub event SaleWithdrawn(id: UInt64)
32
33 // Market operator Address
34 pub var marketAddress : Address
35 // Market operator fee percentage
36 pub var marketFee : UFix64
37 // creator royality
38 pub var royality : UFix64
39
40 /// Path where the `SaleCollection` is stored
41 pub let marketStoragePath: StoragePath
42
43 /// Path where the public capability for the `SaleCollection` is
44 pub let marketPublicPath: PublicPath
45
46
47 // Interface public methods that are used for NFT sales actions
48
49 pub resource interface SalePublic {
50 pub fun purchase(id: UInt64, recipient: &{DisruptArt.DisruptArtCollectionPublic}, payment: @FungibleToken.Vault)
51 pub fun purchaseGroup(tokens: [UInt64], recipient: &{DisruptArt.DisruptArtCollectionPublic}, payment: @FungibleToken.Vault)
52 pub fun idPrice(id: UInt64): UFix64?
53 pub fun tokenCreator(id: UInt64): Address??
54 pub fun currentTokenOwner(id: UInt64): Address??
55 pub fun isResale(id: UInt64): Bool?
56 pub fun getIDs(): [UInt64]
57 }
58
59 // SaleCollection
60 //
61 // NFT Collection object that allows a user to list their NFT for sale
62 // where others can send fungible tokens to purchase it
63 //
64 pub resource SaleCollection: SalePublic {
65
66 // Dictionary of the NFTs that the user is putting up for sale
67 pub var forSale: @{UInt64: DisruptArt.NFT}
68
69 // Dictionary of the prices for each NFT by ID
70 pub var prices: {UInt64: UFix64}
71
72 // Dictionary of the prices for each NFT by ID
73 pub var creators: {UInt64: Address?}
74
75 // Dictionary of sale type (either sale/resale)
76 pub var resale: {UInt64: Bool}
77
78 // Dictionary of token owner
79 pub var seller : {UInt64: Address?}
80
81 // The fungible token vault of the owner of this sale.
82 // When someone buys a token, this resource can deposit
83 // tokens into their account.
84 access(account) let ownerVault: Capability<&AnyResource{FungibleToken.Receiver}>
85
86 init (vault: Capability<&AnyResource{FungibleToken.Receiver}>) {
87 self.forSale <- {}
88 self.ownerVault = vault
89 self.prices = {}
90 self.creators = {}
91 self.resale = {}
92 self.seller = {}
93 }
94
95 // Withdraw gives the owner the opportunity to remove a NFT from sale
96 pub fun withdraw(id: UInt64): @DisruptArt.NFT {
97 // remove the price
98 self.prices.remove(key: id)
99 self.creators.remove(key: id)
100 self.resale.remove(key: id)
101 self.seller.remove(key: id)
102 // remove and return the token
103 let token <- self.forSale.remove(key: id) ?? panic("missing NFT")
104 return <-token
105 }
106
107 // List NFTs in market collection
108 pub fun listForSaleGroup(sellerRef: &DisruptArt.Collection, tokens: [UInt64], price: UFix64) {
109
110 pre {
111 tokens.length > 0:
112 "No Token found. Please provide tokens!"
113 self.tokensExist(tokens: tokens, ids: sellerRef.getIDs()):
114 "Token is not available!"
115 }
116
117 var count = 0
118 while count < tokens.length {
119 let token <- sellerRef.withdraw(withdrawID: tokens[count]) as! @DisruptArt.NFT
120 self.listForSale(token: <- token, price: price)
121 count = count + 1
122 }
123 }
124
125 // lists an NFT for sale in this collection
126 pub fun listForSale(token: @DisruptArt.NFT, price: UFix64) {
127 let id = token.id
128
129 // Store the price in the price array
130 self.prices[id] = price
131
132 self.creators[id] = token.creator
133
134 self.seller[id] = self.owner?.address
135
136 self.resale[id] = (token.creator == self.owner?.address) ? false : true
137
138 // Put NFT into the forSale dictionary
139 let oldToken <- self.forSale[id] <- token
140 destroy oldToken
141
142 emit ForSale(id: id, price: price)
143 }
144
145 // Change price of a group of tokens
146 pub fun changePrice(tokens: [UInt64],newPrice: UFix64) {
147 pre {
148 tokens.length > 0:
149 "Please provide tokens!"
150 self.tokensExist(tokens:tokens, ids: self.forSale.keys):
151 "Tokens not available"
152 }
153 var count = 0
154 while count < tokens.length {
155 self.prices[tokens[count]] = newPrice
156 emit PriceChanged(id: tokens[count], newPrice: newPrice)
157 count = count + 1
158 }
159 }
160
161 pub fun tokensExist(tokens: [UInt64], ids: [UInt64]):Bool {
162 var count = 0
163 // let ids = self.forSale.keys
164 while count < tokens.length {
165 if(ids.contains(tokens[count])) {
166 count = count + 1
167 } else {
168 return false
169 }
170 }
171 return true
172 }
173
174 pub fun tokensTotalPrice(tokens: [UInt64]):UFix64 {
175 var count = 0
176 var price:UFix64 = 0.0
177 while count < tokens.length {
178 price = price + self.prices[tokens[count]]!
179 count = count + 1
180 }
181 return price
182 }
183
184 // Purchase : Allows am user send FUSD to purchase a NFT that is for sale
185 pub fun purchaseGroup(tokens: [UInt64], recipient: &{DisruptArt.DisruptArtCollectionPublic}, payment: @FungibleToken.Vault) {
186
187 pre {
188 tokens.length > 0:
189 "Please provide tokens!"
190 self.tokensExist(tokens: tokens, ids: self.forSale.keys):
191 "Tokens are not available"
192 payment.balance >= self.tokensTotalPrice(tokens:tokens) :
193 "Not enough FUSD to purchase this NFT token"
194 }
195
196 let totalTokens = UFix64(tokens.length)
197
198 var count = 0
199 while count < tokens.length {
200 let tokenCut <- payment.withdraw(amount: self.prices[tokens[count]]!)
201 self.purchase(id: tokens[count], recipient:recipient, payment: <-tokenCut)
202 count = count + 1
203 }
204
205 let disruptartvaultRef = getAccount(DisruptArtMarketplace.marketAddress)
206 .getCapability(/public/fusdReceiver)
207 .borrow<&{FungibleToken.Receiver}>()
208 ?? panic("failed to borrow reference to DisruptArtMarketplace vault")
209
210 disruptartvaultRef.deposit(from: <-payment)
211 }
212
213
214 // Purchase: Allows an user to send FUSD to purchase a NFT that is for sale
215 pub fun purchase(id: UInt64, recipient: &{DisruptArt.DisruptArtCollectionPublic}, payment: @FungibleToken.Vault) {
216 pre {
217 self.forSale[id] != nil && self.prices[id] != nil:
218 "No token matching this ID for sale!"
219 payment.balance >= self.prices[id]! :
220 "Not enough FUSD to purchase NFT"
221 }
222
223 let totalamount = payment.balance
224
225 let owner = self.creators[id]!!
226
227 // Disruptvalut reference
228 let disruptartvaultRef = getAccount(DisruptArtMarketplace.marketAddress)
229 .getCapability(/public/fusdReceiver)
230 .borrow<&{FungibleToken.Receiver}>()
231 ?? panic("failed to borrow reference to DisruptArtMarketplace vault")
232 // Creator vault reference
233 let creatorvaultRef = getAccount(owner)
234 .getCapability(/public/fusdReceiver)
235 .borrow<&{FungibleToken.Receiver}>()
236 ?? panic("failed to borrow reference to owner vault")
237
238 // Seller vault reference
239 let vaultRef = self.ownerVault.borrow()
240 ?? panic("Could not borrow reference to owner token vault")
241
242 let marketShare = self.flowshare(price:self.prices[id]!, commission:DisruptArtMarketplace.marketFee)
243 let royalityShare = self.flowshare(price:self.prices[id]!, commission:DisruptArtMarketplace.royality)
244
245 let marketCut <- payment.withdraw(amount: marketShare)
246 let royalityCut <- payment.withdraw(amount: royalityShare)
247
248 disruptartvaultRef.deposit(from: <-marketCut)
249 // Royalty to the original creator / minted user if this is a resale.
250 if(self.resale[id]!) {
251 creatorvaultRef.deposit(from: <-royalityCut)
252 } else {
253 disruptartvaultRef.deposit(from: <-royalityCut)
254 }
255
256 // After Marketplace fee & royalty fee deposit the balance FUSD to seller's wallet
257 vaultRef.deposit(from: <-payment)
258
259 // Deposit the NFT token into the buyer's collection storage
260 recipient.deposit(token: <-self.withdraw(id: id))
261
262 self.prices[id] = nil
263 self.creators[id] = nil
264 self.resale[id] = nil
265 self.seller[id] = nil
266
267 emit TokenPurchased(id: id, price: totalamount)
268 }
269
270 pub fun flowshare(price:UFix64, commission:UFix64):UFix64 {
271 return (price / 100.0 ) * commission
272 }
273
274 // Return the FUSD price of a specific token that is listed for sale.
275 pub fun idPrice(id: UInt64): UFix64? {
276 return self.prices[id]
277 }
278
279 pub fun isResale(id: UInt64): Bool? {
280 return self.resale[id]
281 }
282
283 // Returns the owner / original minter of a specific NFT token in the sale
284 pub fun tokenCreator(id: UInt64): Address?? {
285 return self.creators[id]
286 }
287
288 pub fun currentTokenOwner(id: UInt64): Address?? {
289 return self.seller[id]
290 }
291
292 // getIDs returns an array of token IDs that are for sale
293 pub fun getIDs(): [UInt64] {
294 return self.forSale.keys
295 }
296
297 // Withdraw NFTs from DisruptArtMarketplace
298 pub fun saleWithdrawn(tokens : [UInt64]) {
299 pre {
300 tokens.length > 0:
301 "Please provide NFT tokens!"
302 self.tokensExist(tokens:tokens, ids: self.forSale.keys):
303 "Tokens are not available"
304 }
305
306 var count = 0
307 let owner = self.seller[tokens[0]]!!
308 let receiver = getAccount(owner)
309 .getCapability(DisruptArt.disruptArtPublicPath)
310 .borrow<&{DisruptArt.DisruptArtCollectionPublic}>()
311 ?? panic("Could not get receiver reference to the NFT Collection")
312
313 while count < tokens.length {
314 // Deposit the NFT back into the seller collection
315 receiver.deposit(token: <-self.withdraw(id: tokens[count]))
316 emit SaleWithdrawn(id: tokens[count])
317 count = count + 1
318 }
319 }
320
321 destroy() {
322 destroy self.forSale
323 }
324 }
325
326 // createCollection returns a new collection resource to the caller
327 pub fun createSaleCollection(ownerVault: Capability<&AnyResource{FungibleToken.Receiver}>): @SaleCollection {
328 return <- create SaleCollection(vault: ownerVault)
329 }
330
331 pub resource Admin {
332 pub fun changeoperator(newOperator: Address, marketCommission: UFix64, royality: UFix64 ) {
333 DisruptArtMarketplace.marketAddress = newOperator
334 DisruptArtMarketplace.marketFee = marketCommission
335 DisruptArtMarketplace.royality = royality
336 }
337 }
338
339 init() {
340 self.marketAddress = 0x1592be4ab7835516
341 // 5% Marketplace Fee
342 self.marketFee = 5.0
343 // 10% Royalty reward for original creater / minter for every re-sale
344 self.royality = 10.0
345
346 self.marketPublicPath = /public/DisruptArtNFTSale
347
348 self.marketStoragePath = /storage/DisruptArtNFTSale
349
350 self.account.save(<- create Admin(), to:/storage/DisruptArtMarketAdmin)
351 }
352
353}
354