Smart Contract
SoulMadeMarketplace
A.9a57dfe5c8ce609c.SoulMadeMarketplace
1import NonFungibleToken from 0x1d7e57aa55817448
2import SoulMadeComponent from 0x9a57dfe5c8ce609c
3import SoulMadeMain from 0x9a57dfe5c8ce609c
4import FungibleToken from 0xf233dcee88fe0abe
5import FlowToken from 0x1654653399040a61
6
7/*
8 This contract is based on the Flovatar Marketplace contract
9 https://github.com/crash13override/flovatar/blob/main/contracts/FlovatarMarketplace.cdc
10*/
11
12pub contract SoulMadeMarketplace {
13
14 pub let CollectionPublicPath: PublicPath
15 pub let CollectionStoragePath: StoragePath
16
17 pub var SoulMadePlatformCut: UFix64
18
19 // The Vault of the Marketplace where it will receive the cuts on each sale
20 pub let marketplaceWallet: Capability<&FlowToken.Vault{FungibleToken.Receiver}>
21
22 pub event SoulMadeMarketplaceSaleCollectionCreated()
23
24 // Event that is emitted when a new NFT is put up for sale
25 pub event SoulMadeMainForSale(id: UInt64, price: UFix64, address: Address)
26 pub event SoulMadeComponentForSale(id: UInt64, price: UFix64, address: Address)
27
28 pub event SoulMadeForSale(id: UInt64, nftType: String, address: Address, saleData: SoulMadeSaleData)
29
30 // Event that is emitted when the price of an NFT changes
31 pub event SoulMadeMainPriceChanged(id: UInt64, newPrice: UFix64, address: Address)
32 pub event SoulMadeComponentPriceChanged(id: UInt64, newPrice: UFix64, address: Address)
33
34 // Event that is emitted when a token is purchased
35 pub event SoulMadeMainPurchased(id: UInt64, price: UFix64, from: Address, to: Address)
36 pub event SoulMadeComponentPurchased(id: UInt64, price: UFix64, from: Address, to: Address)
37
38 // Event that is emitted when a seller withdraws their NFT from the sale
39 pub event SoulMadeMainSaleWithdrawn(tokenId: UInt64, address: Address)
40 pub event SoulMadeComponentSaleWithdrawn(tokenId: UInt64, address: Address)
41
42 // Interface that users will publish for their Sale collection
43 // that only exposes the methods that are supposed to be public
44 pub resource interface SalePublic {
45 pub fun purchaseSoulMadeMain(tokenId: UInt64, recipientCap: Capability<&{SoulMadeMain.CollectionPublic}>, buyTokens: @FungibleToken.Vault)
46 pub fun purchaseSoulMadeComponent(tokenId: UInt64, recipientCap: Capability<&{SoulMadeComponent.CollectionPublic}>, buyTokens: @FungibleToken.Vault)
47 pub fun getSoulMadeMainPrice(tokenId: UInt64): UFix64?
48 pub fun getSoulMadeComponentPrice(tokenId: UInt64): UFix64?
49 pub fun getSoulMadeMainIDs(): [UInt64]
50 pub fun getSoulMadeComponentIDs(): [UInt64]
51 pub fun getSoulMadeMain(tokenId: UInt64): &{SoulMadeMain.MainPublic}
52 pub fun getSoulMadeComponent(tokenId: UInt64): &{SoulMadeComponent.ComponentPublic}
53 }
54
55 // NFT Collection object that allows a user to put their NFT up for sale
56 // where others can send fungible tokens to purchase it
57 pub resource SaleCollection: SalePublic {
58
59 // Dictionary of the NFTs that the user is putting up for sale
60 access(contract) let SoulMadeMainForSale: @{UInt64: SoulMadeMain.NFT}
61 access(contract) let SoulMadeComponentForSale: @{UInt64: SoulMadeComponent.NFT}
62
63 // Dictionary of the prices for each NFT by ID
64 access(contract) let SoulMadeMainPrices: {UInt64: UFix64}
65 access(contract) let SoulMadeComponentPrices: {UInt64: UFix64}
66
67 // The fungible token vault of the owner of this sale.
68 // When someone buys a token, this resource can deposit
69 // tokens into their account.
70 access(account) let ownerVault: Capability<&{FungibleToken.Receiver}>
71
72 init (ownerVault: Capability<&{FungibleToken.Receiver}>) {
73 self.SoulMadeMainForSale <- {}
74 self.SoulMadeComponentForSale <- {}
75 self.ownerVault = ownerVault
76 self.SoulMadeMainPrices = {}
77 self.SoulMadeComponentPrices = {}
78 }
79
80 // Gives the owner the opportunity to remove a SoulMadeMain sale from the collection
81 pub fun withdrawSoulMadeMain(tokenId: UInt64): @SoulMadeMain.NFT {
82 // remove the price
83 self.SoulMadeMainPrices.remove(key: tokenId)
84 // remove and return the token
85 let token <- self.SoulMadeMainForSale.remove(key: tokenId) ?? panic("missing NFT")
86
87 let vaultRef = self.ownerVault.borrow()
88 ?? panic("Could not borrow reference to owner token vault")
89 emit SoulMadeMainSaleWithdrawn(tokenId: tokenId, address: vaultRef.owner!.address)
90 return <- token
91 }
92
93 // Gives the owner the opportunity to remove a Component sale from the collection
94 pub fun withdrawSoulMadeComponent(tokenId: UInt64): @SoulMadeComponent.NFT {
95 // remove the price
96 self.SoulMadeComponentPrices.remove(key: tokenId)
97 // remove and return the token
98 let token <- self.SoulMadeComponentForSale.remove(key: tokenId) ?? panic("missing NFT")
99
100 let vaultRef = self.ownerVault.borrow()
101 ?? panic("Could not borrow reference to owner token vault")
102 emit SoulMadeComponentSaleWithdrawn(tokenId: tokenId, address: vaultRef.owner!.address)
103 return <- token
104 }
105
106 // Lists a SoulMadeMain NFT for sale in this collection
107 pub fun listSoulMadeMainForSale(token: @SoulMadeMain.NFT, price: UFix64) {
108 let id = token.id
109
110 // store the price in the price array
111 self.SoulMadeMainPrices[id] = price
112
113 let saleData: SoulMadeSaleData = SoulMadeSaleData(id: id, price: price, nftType: "SoulMadeMain", mainDetail: token.mainDetail, componentDetail: nil)
114
115 // put the NFT into the the forSale dictionary
116 let oldToken <- self.SoulMadeMainForSale[id] <- token
117 destroy oldToken
118
119 let vaultRef = self.ownerVault.borrow()
120 ?? panic("Could not borrow reference to owner token vault")
121
122 emit SoulMadeMainForSale(id: id, price: price, address: vaultRef.owner!.address)
123 emit SoulMadeForSale(id: id, nftType: "SoulMadeMain", address: vaultRef.owner!.address, saleData: saleData)
124
125 }
126
127 // Lists a Component NFT for sale in this collection
128 pub fun listSoulMadeComponentForSale(token: @SoulMadeComponent.NFT, price: UFix64) {
129 let id = token.id
130
131 // store the price in the price array
132 self.SoulMadeComponentPrices[id] = price
133
134 let saleData: SoulMadeSaleData = SoulMadeSaleData(id: id, price: price, nftType: "SoulMadeComponent", mainDetail: nil, componentDetail: token.componentDetail)
135
136 // put the NFT into the the forSale dictionary
137 let oldToken <- self.SoulMadeComponentForSale[id] <- token
138 destroy oldToken
139
140 let vaultRef = self.ownerVault.borrow()
141 ?? panic("Could not borrow reference to owner token vault")
142 emit SoulMadeComponentForSale(id: id, price: price, address: vaultRef.owner!.address)
143 emit SoulMadeForSale(id: id, nftType: "SoulMadeComponent", address: vaultRef.owner!.address, saleData: saleData)
144 }
145
146 // Changes the price of a SoulMadeMain that is currently for sale
147 pub fun changeSoulMadeMainPrice(tokenId: UInt64, newPrice: UFix64) {
148 self.SoulMadeMainPrices[tokenId] = newPrice
149
150 let vaultRef = self.ownerVault.borrow()
151 ?? panic("Could not borrow reference to owner token vault")
152 emit SoulMadeMainPriceChanged(id: tokenId, newPrice: newPrice, address: vaultRef.owner!.address)
153 }
154 // Changes the price of a Component that is currently for sale
155 pub fun changeSoulMadeComponentPrice(tokenId: UInt64, newPrice: UFix64) {
156 self.SoulMadeComponentPrices[tokenId] = newPrice
157
158 let vaultRef = self.ownerVault.borrow()
159 ?? panic("Could not borrow reference to owner token vault")
160 emit SoulMadeComponentPriceChanged(id: tokenId, newPrice: newPrice, address: vaultRef.owner!.address)
161 }
162
163 // Lets a user send tokens to purchase a SoulMadeMain that is for sale
164 pub fun purchaseSoulMadeMain(tokenId: UInt64, recipientCap: Capability<&{SoulMadeMain.CollectionPublic}>, buyTokens: @FungibleToken.Vault) {
165 pre {
166 self.SoulMadeMainForSale[tokenId] != nil && self.SoulMadeMainPrices[tokenId] != nil:
167 "No token matching this ID for sale!"
168 buyTokens.balance >= (self.SoulMadeMainPrices[tokenId] ?? 0.0):
169 "Not enough tokens to buy the NFT!"
170 }
171
172 let recipient = recipientCap.borrow()!
173
174 // get the value out of the optional
175 let price = self.SoulMadeMainPrices[tokenId]!
176
177 self.SoulMadeMainPrices[tokenId] = nil
178
179 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
180
181 let nft <- self.withdrawSoulMadeMain(tokenId: tokenId)
182
183 let marketplaceWallet = SoulMadeMarketplace.marketplaceWallet.borrow()!
184 let marketplaceAmount = price * SoulMadeMarketplace.SoulMadePlatformCut
185 let tempMarketplaceWallet <- buyTokens.withdraw(amount: marketplaceAmount)
186 marketplaceWallet.deposit(from: <- tempMarketplaceWallet)
187
188 // deposit the purchasing tokens into the owners vault
189 vaultRef.deposit(from: <- buyTokens)
190
191 // deposit the NFT into the buyers collection
192 recipient.deposit(token: <- nft)
193
194 emit SoulMadeMainPurchased(id: tokenId, price: price, from: vaultRef.owner!.address, to: recipient.owner!.address)
195 }
196
197 // Lets a user send tokens to purchase a Component that is for sale
198 pub fun purchaseSoulMadeComponent(tokenId: UInt64, recipientCap: Capability<&{SoulMadeComponent.CollectionPublic}>, buyTokens: @FungibleToken.Vault) {
199 pre {
200 self.SoulMadeComponentForSale[tokenId] != nil && self.SoulMadeComponentPrices[tokenId] != nil:
201 "No token matching this ID for sale!"
202 buyTokens.balance >= (self.SoulMadeComponentPrices[tokenId] ?? 0.0):
203 "Not enough tokens to buy the NFT!"
204 }
205
206 let recipient = recipientCap.borrow()!
207
208 // get the value out of the optional
209 let price = self.SoulMadeComponentPrices[tokenId]!
210
211 self.SoulMadeComponentPrices[tokenId] = nil
212
213 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
214
215 let nft <-self.withdrawSoulMadeComponent(tokenId: tokenId)
216
217 let marketplaceWallet = SoulMadeMarketplace.marketplaceWallet.borrow()!
218 let marketplaceAmount = price * SoulMadeMarketplace.SoulMadePlatformCut
219 let tempMarketplaceWallet <- buyTokens.withdraw(amount: marketplaceAmount)
220 marketplaceWallet.deposit(from: <-tempMarketplaceWallet)
221
222 // deposit the purchasing tokens into the owners vault
223 vaultRef.deposit(from: <- buyTokens)
224
225 // deposit the NFT into the buyers collection
226 recipient.deposit(token: <- nft)
227
228 emit SoulMadeComponentPurchased(id: tokenId, price: price, from: vaultRef.owner!.address, to: recipient.owner!.address)
229 }
230
231 // Returns the price of a specific SoulMadeMain in the sale
232 pub fun getSoulMadeMainPrice(tokenId: UInt64): UFix64? {
233 return self.SoulMadeMainPrices[tokenId]
234 }
235 // Returns the price of a specific Component in the sale
236 pub fun getSoulMadeComponentPrice(tokenId: UInt64): UFix64? {
237 return self.SoulMadeComponentPrices[tokenId]
238 }
239
240 // Returns an array of SoulMadeMain IDs that are for sale
241 pub fun getSoulMadeMainIDs(): [UInt64] {
242 return self.SoulMadeMainForSale.keys
243 }
244 // Returns an array of Component IDs that are for sale
245 pub fun getSoulMadeComponentIDs(): [UInt64] {
246 return self.SoulMadeComponentForSale.keys
247 }
248
249 // Returns a borrowed reference to a SoulMadeMain Sale
250 // so that the caller can read data and call methods from it.
251 pub fun getSoulMadeMain(tokenId: UInt64): &{SoulMadeMain.MainPublic} {
252 pre {
253 self.SoulMadeMainForSale[tokenId] != nil: "Main NFT doesn't exist"
254 }
255 let ref = (&self.SoulMadeMainForSale[tokenId] as auth &NonFungibleToken.NFT?)!
256 return ref as! &SoulMadeMain.NFT
257
258 }
259 // Returns a borrowed reference to a Component Sale
260 // so that the caller can read data and call methods from it.
261 pub fun getSoulMadeComponent(tokenId: UInt64): &{SoulMadeComponent.ComponentPublic} {
262 pre {
263 self.SoulMadeComponentForSale[tokenId] != nil: "Component NFT doesn't exist"
264 }
265 let ref = (&self.SoulMadeComponentForSale[tokenId] as auth &NonFungibleToken.NFT?)!
266 return ref as! &SoulMadeComponent.NFT
267 }
268
269 destroy() {
270 destroy self.SoulMadeMainForSale
271 destroy self.SoulMadeComponentForSale
272 }
273 }
274
275 pub struct SoulMadeMainSaleData {
276 pub let id: UInt64
277 pub let price: UFix64
278 pub let mainDetail: SoulMadeMain.MainDetail
279
280 init(
281 id: UInt64,
282 price: UFix64,
283 mainDetail: SoulMadeMain.MainDetail){
284 self.id = id
285 self.price = price
286 self.mainDetail = mainDetail
287 }
288 }
289
290 // Get a specific SoulMadeMain Sale offers for an account
291 pub fun getSoulMadeMainSale(address: Address, id: UInt64) : SoulMadeMainSaleData {
292 let account = getAccount(address)
293
294 let saleCollection = account.getCapability(self.CollectionPublicPath).borrow<&{SoulMadeMarketplace.SalePublic}>()!
295 let soulMadeMain = saleCollection.getSoulMadeMain(tokenId: id)
296 let price = saleCollection.getSoulMadeMainPrice(tokenId: id)
297 return SoulMadeMainSaleData(
298 id: id,
299 price: price!,
300 mainDetail: soulMadeMain.mainDetail
301 )
302 }
303
304 // Get all the SoulMadeMain Sale offers for a specific account
305 pub fun getSoulMadeMainSales(address: Address) : [SoulMadeMainSaleData] {
306 var saleData: [SoulMadeMainSaleData] = []
307 let account = getAccount(address)
308
309 let saleCollection = account.getCapability(self.CollectionPublicPath).borrow<&{SoulMadeMarketplace.SalePublic}>()!
310 for id in saleCollection.getSoulMadeMainIDs() {
311 let price = saleCollection.getSoulMadeMainPrice(tokenId: id)
312 let soulMadeMain = saleCollection.getSoulMadeMain(tokenId: id)
313 saleData.append(SoulMadeMainSaleData(
314 id: id,
315 price: price!,
316 mainDetail: soulMadeMain.mainDetail
317 ))
318 }
319 return saleData
320 }
321
322 // This struct is used to send a data representation of the Component Sales
323 pub struct SoulMadeComponentSaleData {
324 pub let id: UInt64
325 pub let price: UFix64
326 pub let componentDetail: SoulMadeComponent.ComponentDetail
327
328 init(
329 id: UInt64,
330 price: UFix64,
331 componentDetail: SoulMadeComponent.ComponentDetail){
332 self.id = id
333 self.price = price
334 self.componentDetail = componentDetail
335 }
336 }
337
338 // Get a specific Component Sale offers for an account
339 pub fun getSoulMadeComponentSale(address: Address, id: UInt64) : SoulMadeComponentSaleData {
340 let account = getAccount(address)
341
342 let saleCollection = account.getCapability(self.CollectionPublicPath).borrow<&{SoulMadeMarketplace.SalePublic}>()!
343 let soulMadeComponent = saleCollection.getSoulMadeComponent(tokenId: id)
344 let price = saleCollection.getSoulMadeComponentPrice(tokenId: id)
345 return SoulMadeComponentSaleData(
346 id: id,
347 price: price!,
348 componentDetail: soulMadeComponent.componentDetail
349 )
350 }
351
352 // Get all the Component Sale offers for a specific account
353 pub fun getSoulMadeComponentSales(address: Address) : [SoulMadeComponentSaleData] {
354 var saleData: [SoulMadeComponentSaleData] = []
355 let account = getAccount(address)
356
357 let saleCollection = account.getCapability(self.CollectionPublicPath).borrow<&{SoulMadeMarketplace.SalePublic}>()!
358 for id in saleCollection.getSoulMadeComponentIDs() {
359 let price = saleCollection.getSoulMadeComponentPrice(tokenId: id)
360 let soulMadeComponent = saleCollection.getSoulMadeComponent(tokenId: id)
361 saleData.append(SoulMadeComponentSaleData(
362 id: id,
363 price: price!,
364 componentDetail: soulMadeComponent.componentDetail
365 ))
366 }
367 return saleData
368 }
369
370 pub struct SoulMadeSaleData {
371 pub let id: UInt64
372 pub let price: UFix64
373 pub let nftType: String
374 pub let mainDetail: SoulMadeMain.MainDetail?
375 pub let componentDetail: SoulMadeComponent.ComponentDetail?
376
377 init(
378 id: UInt64,
379 price: UFix64,
380 nftType: String,
381 mainDetail: SoulMadeMain.MainDetail?,
382 componentDetail: SoulMadeComponent.ComponentDetail?){
383
384 self.id = id
385 self.price = price
386 self.nftType = nftType
387 self.mainDetail = mainDetail
388 self.componentDetail = componentDetail
389 }
390 }
391
392 pub fun getSoulMadeSales(address: Address) : [SoulMadeSaleData] {
393 var saleData: [SoulMadeSaleData] = []
394 let account = getAccount(address)
395
396 let saleCollection = account.getCapability(self.CollectionPublicPath).borrow<&{SoulMadeMarketplace.SalePublic}>()!
397
398 for id in saleCollection.getSoulMadeMainIDs() {
399 let price = saleCollection.getSoulMadeMainPrice(tokenId: id)
400 let soulMadeMain = saleCollection.getSoulMadeMain(tokenId: id)
401 saleData.append(SoulMadeSaleData(
402 id: id,
403 price: price!,
404 nftType: "SoulMadeMain",
405 mainDetail: soulMadeMain.mainDetail,
406 componentDetail: nil
407 ))
408 }
409
410 for id in saleCollection.getSoulMadeComponentIDs() {
411 let price = saleCollection.getSoulMadeComponentPrice(tokenId: id)
412 let soulMadeComponent = saleCollection.getSoulMadeComponent(tokenId: id)
413 saleData.append(SoulMadeSaleData(
414 id: id,
415 price: price!,
416 nftType: "SoulMadeComponent",
417 mainDetail: nil,
418 componentDetail: soulMadeComponent.componentDetail
419 ))
420 }
421
422 return saleData
423 }
424
425 pub fun convertSoulMadeMainSaleToSoulMadeSale(mainSale: SoulMadeMainSaleData): SoulMadeSaleData{
426 return SoulMadeSaleData(
427 id: mainSale.id,
428 price: mainSale.price,
429 nftType: "SoulMadeMain",
430 mainDetail: mainSale.mainDetail,
431 componentDetail: nil
432 )
433 }
434
435 pub fun convertSoulMadeComponentSaleToSoulMadeSale(componentSale: SoulMadeComponentSaleData): SoulMadeSaleData{
436 return SoulMadeSaleData(
437 id: componentSale.id,
438 price: componentSale.price,
439 nftType: "SoulMadeComponent",
440 mainDetail: nil,
441 componentDetail: componentSale.componentDetail
442 )
443 }
444
445 // Returns a new collection resource to the caller
446 pub fun createSaleCollection(ownerVault: Capability<&{FungibleToken.Receiver}>): @SaleCollection {
447 emit SoulMadeMarketplaceSaleCollectionCreated()
448 return <- create SaleCollection(ownerVault: ownerVault)
449 }
450
451 access(account) fun updatePlatformCut(platformCut: UFix64) {
452 self.SoulMadePlatformCut = platformCut
453 }
454
455
456 pub init() {
457 self.CollectionPublicPath = /public/SoulMadeMarketplace
458 self.CollectionStoragePath = /storage/SoulMadeMarketplace
459 self.SoulMadePlatformCut = 0.0
460
461 self.marketplaceWallet = self.account.getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver)
462 }
463}