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