Smart Contract

SoulMadeMarketplace

A.9a57dfe5c8ce609c.SoulMadeMarketplace

Deployed

2h ago
Feb 28, 2026, 06:38:20 PM UTC

Dependents

0 imports
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}