Smart Contract
MarketPlace
A.f5b0eb433389ac3f.MarketPlace
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import Collectible from 0xf5b0eb433389ac3f
4import Edition from 0xf5b0eb433389ac3f
5import NonFungibleToken from 0x1d7e57aa55817448
6import FUSD from 0x3c5959b568896393
7
8pub contract MarketPlace {
9
10 pub let CollectionStoragePath: StoragePath
11 pub let CollectionPublicPath: PublicPath
12
13 // Event that is emitted when a new NFT is put up for sale
14 pub event ForSale(id: UInt64, owner: Address, price: UFix64)
15
16 // Event that is emitted when the price of an NFT changes
17 pub event PriceChanged(id: UInt64, owner: Address, newPrice: UFix64, oldPrice: UFix64)
18
19 // Event that is emitted when a token is purchased
20 pub event TokenPurchased(id: UInt64, price: UFix64, from:Address, to:Address)
21
22 // Event that is emitted when a seller withdraws their NFT from the sale
23 pub event SaleWithdrawn(id: UInt64, owner: Address)
24
25 // Secondary commission events
26 pub event Earned(nftID: UInt64, amount: UFix64, owner: Address, type: String)
27 pub event FailEarned(nftID: UInt64, amount: UFix64, owner: Address, type: String)
28
29 pub resource interface SaleCollectionPublic {
30 pub fun purchase(
31 tokenID: UInt64,
32 recipientCap: Capability<&{Collectible.CollectionPublic}>,
33 buyTokens: @FUSD.Vault
34 )
35 pub fun idPrice(tokenID: UInt64): UFix64?
36 pub fun getIDs(): [UInt64]
37 pub fun borrowCollectible(id: UInt64): &Collectible.NFT?
38 }
39
40 // SaleCollection
41 //
42 // NFT Collection object that allows a user to put their NFT up for sale
43 // where others can send fungible tokens to purchase it
44 //
45 pub resource SaleCollection: SaleCollectionPublic {
46
47 // Dictionary of the NFTs that the user is putting up for sale
48 access(self) var forSale: @{UInt64: Collectible.NFT}
49
50 // Dictionary of the prices for each NFT by ID
51 access(self) var prices: {UInt64: UFix64}
52
53 // The fungible token vault of the owner of this sale.
54 // When someone buys a token, this resource can deposit
55 // tokens into their account.
56 access(account) let ownerVault: Capability<&FUSD.Vault{FungibleToken.Receiver}>
57
58 init (vault: Capability<&FUSD.Vault{FungibleToken.Receiver}>) {
59 self.forSale <- {}
60 self.ownerVault = vault
61 self.prices = {}
62 }
63
64 priv fun getEditionNumber(id: UInt64): UInt64? {
65 let ref = self.borrowCollectible(id: id)
66 if ref == nil {
67 return nil
68 }
69 return ref!.editionNumber
70 }
71
72 // withdraw gives the owner the opportunity to remove a sale from the collection
73 pub fun withdraw(tokenID: UInt64): @Collectible.NFT {
74 // remove the price
75 self.prices.remove(key: tokenID)
76
77 // remove and return the token
78 let token <- self.forSale.remove(key: tokenID) ?? panic("missing NFT")
79
80 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
81
82 return <-token
83 }
84
85 // listForSale lists an NFT for sale in this collection
86 pub fun listForSale(token: @Collectible.NFT, price: UFix64) {
87 pre {
88 price > 0.00 : "Price should be more than 0"
89 price <= 999999.99 : "Price should be less than 1 000 000.00"
90 }
91
92 let id = token.id
93
94 // store the price in the price array
95 self.prices[id] = price
96
97 // put the NFT into the the forSale dictionary
98 let oldToken <- self.forSale[id] <- token
99 destroy oldToken
100
101 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
102
103 emit ForSale(id: id, owner: vaultRef.owner!.address, price: price)
104 }
105
106 // changePrice changes the price of a token that is currently for sale
107 pub fun changePrice(tokenID: UInt64, newPrice: UFix64) {
108 pre {
109 self.prices[tokenID] != nil : "NFT does not exist on sale"
110 newPrice > 0.00 : "Price should be more than 0"
111 newPrice <= 999999.99 : "Price should be less than 1 000 000.00"
112 }
113
114 let oldPrice = self.prices[tokenID]!
115
116 self.prices[tokenID] = newPrice
117
118 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
119
120 emit PriceChanged(id: tokenID, owner: vaultRef.owner!.address, newPrice: newPrice, oldPrice: oldPrice)
121 }
122
123 // remove nft from sale. this uses is to differ between cancel sale and buy NFT
124 pub fun withdrawFromSale(tokenID: UInt64): @Collectible.NFT {
125 pre {
126 self.prices[tokenID] != nil : "NFT does not exist on sale"
127 }
128
129 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
130
131 emit SaleWithdrawn(id: tokenID, owner: vaultRef.owner!.address)
132
133 return <-self.withdraw(tokenID: tokenID)
134 }
135
136 // idPrice returns the price of a specific token in the sale
137 pub fun idPrice(tokenID: UInt64): UFix64? {
138 return self.prices[tokenID]
139 }
140
141 // getIDs returns an array of token IDs that are for sale
142 pub fun getIDs(): [UInt64] {
143 return self.forSale.keys
144 }
145
146 pub fun borrowCollectible(id: UInt64): &Collectible.NFT? {
147 if self.forSale[id] != nil {
148 let ref = &self.forSale[id] as auth &NonFungibleToken.NFT?
149 return ref as! &Collectible.NFT?
150 } else {
151 return nil
152 }
153 }
154
155 priv fun handlePayments(
156 tokenID: UInt64,
157 buyTokens: @FUSD.Vault,
158 price: UFix64
159 ) {
160 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
161
162 // this is validated during process of the cration NFT
163 let editionNumber = self.getEditionNumber(id: tokenID)!
164
165 let royaltyRef = MarketPlace.account.getCapability<&{Edition.EditionCollectionPublic}>(Edition.CollectionPublicPath).borrow()!
166
167 let royaltyStatus = royaltyRef.getEdition(editionNumber)!
168
169 for key in royaltyStatus.royalty.keys {
170 let commission = price * royaltyStatus.royalty[key]!.secondSalePercent * 0.01
171
172 let account = getAccount(key)
173
174 let vaultCap = account.getCapability<&FUSD.Vault{FungibleToken.Receiver}>(/public/fusdReceiver)
175
176 if(vaultCap.check()) {
177 let vaultCommissionRecepientRef = vaultCap.borrow()!
178
179 vaultCommissionRecepientRef.deposit(from: <- buyTokens.withdraw(amount: commission))
180
181 emit Earned(nftID: tokenID, amount: commission, owner: key, type: royaltyStatus.royalty[key]!.description)
182 } else {
183 emit FailEarned(nftID: tokenID, amount: commission, owner: key, type: royaltyStatus.royalty[key]!.description)
184 }
185 }
186
187 let amount = buyTokens.balance
188
189 // deposit the purchasing tokens into the owners vault
190 vaultRef.deposit(from: <- (buyTokens as! @FungibleToken.Vault))
191
192 emit Earned(nftID: tokenID, amount: amount, owner: vaultRef.owner!.address, type: "SELLER")
193 }
194
195 // purchase lets a user send tokens to purchase an NFT that is for sale
196 pub fun purchase(
197 tokenID: UInt64,
198 recipientCap: Capability<&{Collectible.CollectionPublic}>,
199 buyTokens: @FUSD.Vault
200 ) {
201 pre {
202 self.forSale[tokenID] != nil && self.prices[tokenID] != nil: "No token matching this ID for sale!"
203 buyTokens.balance == (self.prices[tokenID] ?? 0.0): "Not exact amount tokens to buy the NFT!"
204 }
205
206 let vaultRef = self.ownerVault.borrow() ?? panic("Could not borrow reference to owner token vault")
207
208 // get the value out of the optional
209 let price = self.prices[tokenID]!
210
211 self.prices[tokenID] = nil
212
213 let recipient = recipientCap.borrow() ?? panic("Could not borrow reference to buyer NFT storage")
214
215 // Send money to seller and share the secondary commission
216 self.handlePayments(tokenID: tokenID, buyTokens: <- buyTokens, price: price)
217
218 let token <- self.withdraw(tokenID: tokenID)
219
220 // deposit the NFT into the buyers collection
221 recipient.deposit(token: <- token)
222
223 emit TokenPurchased(id: tokenID, price: price, from: vaultRef.owner!.address, to: recipient.owner!.address)
224 }
225
226 destroy() {
227 destroy self.forSale
228 }
229 }
230
231 // structure for display NFTs data
232 pub struct SaleData {
233 pub let metadata: Collectible.Metadata
234 pub let id: UInt64
235 pub let price: UFix64?
236 pub let editionNumber: UInt64
237 init(metadata: Collectible.Metadata, id: UInt64, price: UFix64?, editionNumber: UInt64) {
238 self.metadata = metadata
239 self.id = id
240 self.price = price
241 self.editionNumber = editionNumber
242 }
243 }
244
245 // get info for NFT including metadata
246 pub fun getSaleDatas(address: Address) : [SaleData] {
247
248 var saleData: [SaleData] = []
249 let account = getAccount(address)
250
251 let CollectibleCollection = account.getCapability<&AnyResource{MarketPlace.SaleCollectionPublic}>(MarketPlace.CollectionPublicPath)
252 .borrow()
253 ?? panic("Could not borrow sale reference")
254
255 for id in CollectibleCollection.getIDs() {
256 var Collectible = CollectibleCollection.borrowCollectible(id: id)
257 var price = CollectibleCollection.idPrice(tokenID: id)
258 saleData.append(SaleData(
259 metadata: Collectible!.metadata,
260 id: id,
261 price: price,
262 editionNumber: Collectible!.editionNumber
263 ))
264 }
265
266 return saleData
267 }
268
269 // createCollection returns a new collection resource to the caller
270 pub fun createSaleCollection(ownerVault: Capability<&FUSD.Vault{FungibleToken.Receiver}>): @SaleCollection {
271 return <- create SaleCollection(vault: ownerVault)
272 }
273
274 init() {
275 self.CollectionPublicPath = /public/NFTbloctoXtinglesCollectibleSale
276 self.CollectionStoragePath = /storage/NFTbloctoXtinglesCollectibleSale
277 }
278}
279