github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/main/cadence/nba/MarketTopshot.cdc (about) 1 /* 2 3 MarketTopShot.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 or exceed 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 holds the tokens that they are putting up for sale 19 20 They can give a reference to this collection to a central contract 21 so that it can list the sales in a central place 22 23 When a user creates a sale, they will supply three arguments: 24 - A FungibleToken.Receiver capability as the place where the payment for the token goes. 25 - A FungibleToken.Receiver capability specifying a beneficiary, where a cut of the purchase gets sent. 26 - A cut percentage, specifying how much the beneficiary will recieve. 27 28 The cut percentage can be set to zero if the user desires and they 29 will receive the entirety of the purchase. TopShot will initialize sales 30 for users with the TopShot admin vault as the vault where cuts get 31 deposited to. 32 */ 33 34 import FungibleToken from 0xee82856bf20e2aa6 35 import NonFungibleToken from 0xf8d6e0586b0a20c7 36 import TopShot from 0xf8d6e0586b0a20c7 37 38 pub contract Market { 39 40 // ----------------------------------------------------------------------- 41 // TopShot Market contract Event definitions 42 // ----------------------------------------------------------------------- 43 44 // emitted when a TopShot moment is listed for sale 45 pub event MomentListed(id: UInt64, price: UFix64, seller: Address?) 46 // emitted when the price of a listed moment has changed 47 pub event MomentPriceChanged(id: UInt64, newPrice: UFix64, seller: Address?) 48 // emitted when a token is purchased from the market 49 pub event MomentPurchased(id: UInt64, price: UFix64, seller: Address?) 50 // emitted when a moment has been withdrawn from the sale 51 pub event MomentWithdrawn(id: UInt64, owner: Address?) 52 // emitted when the cut percentage of the sale has been changed by the owner 53 pub event CutPercentageChanged(newPercent: UFix64, seller: Address?) 54 55 // SalePublic 56 // 57 // The interface that a user can publish a capability to their sale 58 // to allow others to access their sale 59 pub resource interface SalePublic { 60 pub var cutPercentage: UFix64 61 pub fun purchase(tokenID: UInt64, buyTokens: @FungibleToken.Vault): @TopShot.NFT { 62 post { 63 result.id == tokenID: "The ID of the withdrawn token must be the same as the requested ID" 64 } 65 } 66 pub fun getPrice(tokenID: UInt64): UFix64? 67 pub fun getIDs(): [UInt64] 68 pub fun borrowMoment(id: UInt64): &TopShot.NFT? { 69 // If the result isn't nil, the id of the returned reference 70 // should be the same as the argument to the function 71 post { 72 (result == nil) || (result?.id == id): 73 "Cannot borrow Moment reference: The ID of the returned reference is incorrect" 74 } 75 } 76 } 77 78 // SaleCollection 79 // 80 // This is the main resource that token sellers will store in their account 81 // to manage the NFTs that they are selling. The SaleCollection 82 // holds a TopShot Collection resource to store the moments that are for sale. 83 // The SaleCollection also keeps track of the price of each token. 84 // 85 // When a token is purchased, a cut is taken from the tokens 86 // and sent to the beneficiary, then the rest are sent to the seller. 87 // 88 // The seller chooses who the beneficiary is and what percentage 89 // of the tokens gets taken from the purchase 90 pub resource SaleCollection: SalePublic { 91 92 // A collection of the moments that the user has for sale 93 access(self) var forSale: @TopShot.Collection 94 95 // Dictionary of the low low prices for each NFT by ID 96 access(self) var prices: {UInt64: UFix64} 97 98 // The fungible token vault of the seller 99 // so that when someone buys a token, the tokens are deposited 100 // to this Vault 101 access(self) var ownerCapability: Capability 102 103 // The capability that is used for depositing 104 // the beneficiary's cut of every sale 105 access(self) var beneficiaryCapability: Capability 106 107 // The percentage that is taken from every purchase for the beneficiary 108 // For example, if the percentage is 15%, cutPercentage = 0.15 109 pub var cutPercentage: UFix64 110 111 init (ownerCapability: Capability, beneficiaryCapability: Capability, cutPercentage: UFix64) { 112 pre { 113 // Check that both capabilities are for fungible token Vault receivers 114 ownerCapability.borrow<&{FungibleToken.Receiver}>() != nil: 115 "Owner's Receiver Capability is invalid!" 116 beneficiaryCapability.borrow<&{FungibleToken.Receiver}>() != nil: 117 "Beneficiary's Receiver Capability is invalid!" 118 } 119 120 // create an empty collection to store the moments that are for sale 121 self.forSale <- TopShot.createEmptyCollection() as! @TopShot.Collection 122 self.ownerCapability = ownerCapability 123 self.beneficiaryCapability = beneficiaryCapability 124 // prices are initially empty because there are no moments for sale 125 self.prices = {} 126 self.cutPercentage = cutPercentage 127 } 128 129 // listForSale lists an NFT for sale in this sale collection 130 // at the specified price 131 // 132 // Parameters: token: The NFT to be put up for sale 133 // price: The price of the NFT 134 pub fun listForSale(token: @TopShot.NFT, price: UFix64) { 135 136 // get the ID of the token 137 let id = token.id 138 139 // Set the token's price 140 self.prices[token.id] = price 141 142 // Deposit the token into the sale collection 143 self.forSale.deposit(token: <-token) 144 145 emit MomentListed(id: id, price: price, seller: self.owner?.address) 146 } 147 148 // Withdraw removes a moment that was listed for sale 149 // and clears its price 150 // 151 // Parameters: tokenID: the ID of the token to withdraw from the sale 152 // 153 // Returns: @TopShot.NFT: The nft that was withdrawn from the sale 154 pub fun withdraw(tokenID: UInt64): @TopShot.NFT { 155 156 // Remove and return the token. 157 // Will revert if the token doesn't exist 158 let token <- self.forSale.withdraw(withdrawID: tokenID) as! @TopShot.NFT 159 160 // Remove the price from the prices dictionary 161 self.prices.remove(key: tokenID) 162 163 // Set prices to nil for the withdrawn ID 164 self.prices[tokenID] = nil 165 166 // Emit the event for withdrawing a moment from the Sale 167 emit MomentWithdrawn(id: token.id, owner: self.owner?.address) 168 169 // Return the withdrawn token 170 return <-token 171 } 172 173 // purchase lets a user send tokens to purchase an NFT that is for sale 174 // the purchased NFT is returned to the transaction context that called it 175 // 176 // Parameters: tokenID: the ID of the NFT to purchase 177 // butTokens: the fungible tokens that are used to buy the NFT 178 // 179 // Returns: @TopShot.NFT: the purchased NFT 180 pub fun purchase(tokenID: UInt64, buyTokens: @FungibleToken.Vault): @TopShot.NFT { 181 pre { 182 self.forSale.ownedNFTs[tokenID] != nil && self.prices[tokenID] != nil: 183 "No token matching this ID for sale!" 184 buyTokens.balance == (self.prices[tokenID] ?? UFix64(0)): 185 "Not enough tokens to buy the NFT!" 186 } 187 188 // Read the price for the token 189 let price = self.prices[tokenID]! 190 191 // Set the price for the token to nil 192 self.prices[tokenID] = nil 193 194 // Take the cut of the tokens that the beneficiary gets from the sent tokens 195 let beneficiaryCut <- buyTokens.withdraw(amount: price*self.cutPercentage) 196 197 // Deposit it into the beneficiary's Vault 198 self.beneficiaryCapability.borrow<&{FungibleToken.Receiver}>()! 199 .deposit(from: <-beneficiaryCut) 200 201 // Deposit the remaining tokens into the owners vault 202 self.ownerCapability.borrow<&{FungibleToken.Receiver}>()! 203 .deposit(from: <-buyTokens) 204 205 emit MomentPurchased(id: tokenID, price: price, seller: self.owner?.address) 206 207 // Return the purchased token 208 return <-self.withdraw(tokenID: tokenID) 209 } 210 211 // changePrice changes the price of a token that is currently for sale 212 // 213 // Parameters: tokenID: The ID of the NFT's price that is changing 214 // newPrice: The new price for the NFT 215 pub fun changePrice(tokenID: UInt64, newPrice: UFix64) { 216 pre { 217 self.prices[tokenID] != nil: "Cannot change the price for a token that is not for sale" 218 } 219 // Set the new price 220 self.prices[tokenID] = newPrice 221 222 emit MomentPriceChanged(id: tokenID, newPrice: newPrice, seller: self.owner?.address) 223 } 224 225 // changePercentage changes the cut percentage of the tokens that are for sale 226 // 227 // Parameters: newPercent: The new cut percentage for the sale 228 pub fun changePercentage(_ newPercent: UFix64) { 229 pre { 230 newPercent <= 1.0: "Cannot set cut percentage to greater than 100%" 231 } 232 self.cutPercentage = newPercent 233 234 emit CutPercentageChanged(newPercent: newPercent, seller: self.owner?.address) 235 } 236 237 // changeOwnerReceiver updates the capability for the sellers fungible token Vault 238 // 239 // Parameters: newOwnerCapability: The new fungible token capability for the account 240 // who received tokens for purchases 241 pub fun changeOwnerReceiver(_ newOwnerCapability: Capability) { 242 pre { 243 newOwnerCapability.borrow<&{FungibleToken.Receiver}>() != nil: 244 "Owner's Receiver Capability is invalid!" 245 } 246 self.ownerCapability = newOwnerCapability 247 } 248 249 // changeBeneficiaryReceiver updates the capability for the beneficiary of the cut of the sale 250 // 251 // Parameters: newBeneficiaryCapability the new capability for the beneficiary of the cut of the sale 252 // 253 pub fun changeBeneficiaryReceiver(_ newBeneficiaryCapability: Capability) { 254 pre { 255 newBeneficiaryCapability.borrow<&{FungibleToken.Receiver}>() != nil: 256 "Beneficiary's Receiver Capability is invalid!" 257 } 258 self.beneficiaryCapability = newBeneficiaryCapability 259 } 260 261 // getPrice returns the price of a specific token in the sale 262 // 263 // Parameters: tokenID: The ID of the NFT whose price to get 264 // 265 // Returns: UFix64: The price of the token 266 pub fun getPrice(tokenID: UInt64): UFix64? { 267 return self.prices[tokenID] 268 } 269 270 // getIDs returns an array of token IDs that are for sale 271 pub fun getIDs(): [UInt64] { 272 return self.forSale.getIDs() 273 } 274 275 // borrowMoment Returns a borrowed reference to a Moment in the collection 276 // so that the caller can read data from it 277 // 278 // Parameters: id: The ID of the moment to borrow a reference to 279 // 280 // Returns: &TopShot.NFT? Optional reference to a moment for sale 281 // so that the caller can read its data 282 // 283 pub fun borrowMoment(id: UInt64): &TopShot.NFT? { 284 let ref = self.forSale.borrowMoment(id: id) 285 return ref 286 } 287 288 // If the sale collection is destroyed, 289 // destroy the tokens that are for sale inside of it 290 destroy() { 291 destroy self.forSale 292 } 293 } 294 295 // createCollection returns a new collection resource to the caller 296 pub fun createSaleCollection(ownerCapability: Capability, beneficiaryCapability: Capability, cutPercentage: UFix64): @SaleCollection { 297 return <- create SaleCollection(ownerCapability: ownerCapability, beneficiaryCapability: beneficiaryCapability, cutPercentage: cutPercentage) 298 } 299 } 300