Cadence Cookbook

Contribute

Create an NFT Listing

Create an NFT Listing

14 Oct 2022

Contributed by Flow Blockchain

Intermediate

List an NFT to be sold using the NFTStorefront contract.

Smart Contract Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 pub contract NFTStorefront { //More NFTStorefront contract code above .... pub struct SaleCut { // The receiver for the payment. // Note that we do not store an address to find the Vault that this represents, // as the link or resource that we fetch in this way may be manipulated, // so to find the address that a cut goes to you must get this struct and then // call receiver.borrow()!.owner.address on it. // This can be done efficiently in a script. pub let receiver: Capability<&{FungibleToken.Receiver}> // The amount of the payment FungibleToken that will be paid to the receiver. pub let amount: UFix64 // initializer // init(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) { self.receiver = receiver self.amount = amount } } // ListingDetails // A struct containing a Listing's data. // pub struct ListingDetails { // The Storefront that the Listing is stored in. // Note that this resource cannot be moved to a different Storefront, // so this is OK. If we ever make it so that it *can* be moved, // this should be revisited. pub var storefrontID: UInt64 // Whether this listing has been purchased or not. pub var purchased: Bool // The Type of the NonFungibleToken.NFT that is being listed. pub let nftType: Type // The ID of the NFT within that type. pub let nftID: UInt64 // The Type of the FungibleToken that payments must be made in. pub let salePaymentVaultType: Type // The amount that must be paid in the specified FungibleToken. pub let salePrice: UFix64 // This specifies the division of payment between recipients. pub let saleCuts: [SaleCut] // setToPurchased // Irreversibly set this listing as purchased. // access(contract) fun setToPurchased() { self.purchased = true } // initializer // init ( nftType: Type, nftID: UInt64, salePaymentVaultType: Type, saleCuts: [SaleCut], storefrontID: UInt64 ) { self.storefrontID = storefrontID self.purchased = false self.nftType = nftType self.nftID = nftID self.salePaymentVaultType = salePaymentVaultType // Store the cuts assert(saleCuts.length > 0, message: "Listing must have at least one payment cut recipient") self.saleCuts = saleCuts // Calculate the total price from the cuts var salePrice = 0.0 // Perform initial check on capabilities, and calculate sale price from cut amounts. for cut in self.saleCuts { // Make sure we can borrow the receiver. // We will check this again when the token is sold. cut.receiver.borrow() ?? panic("Cannot borrow receiver") // Add the cut amount to the total price salePrice = salePrice + cut.amount } assert(salePrice > 0.0, message: "Listing must have non-zero price") // Store the calculated sale price self.salePrice = salePrice } } ..... pub resource Listing: ListingPublic { // The simple (non-Capability, non-complex) details of the sale access(self) let details: ListingDetails // A capability allowing this resource to withdraw the NFT with the given ID from its collection. // This capability allows the resource to withdraw *any* NFT, so you should be careful when giving // such a capability to a resource and always check its code to make sure it will use it in the // way that it claims. access(contract) let nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}> // borrowNFT // This will assert in the same way as the NFT standard borrowNFT() // if the NFT is absent, for example if it has been sold via another listing. // pub fun borrowNFT(): &NonFungibleToken.NFT { let ref = self.nftProviderCapability.borrow()!.borrowNFT(id: self.getDetails().nftID) //- CANNOT DO THIS IN PRECONDITION: "member of restricted type is not accessible: isInstance" // result.isInstance(self.getDetails().nftType): "token has wrong type" assert(ref.isInstance(self.getDetails().nftType), message: "token has wrong type") assert(ref.id == self.getDetails().nftID, message: "token has wrong ID") return ref as &NonFungibleToken.NFT } // getDetails // Get the details of the current state of the Listing as a struct. // This avoids having more public variables and getter methods for them, and plays // nicely with scripts (which cannot return resources). // pub fun getDetails(): ListingDetails { return self.details } // purchase // Purchase the listing, buying the token. // This pays the beneficiaries and returns the token to the buyer. // pub fun purchase(payment: @FungibleToken.Vault): @NonFungibleToken.NFT { pre { self.details.purchased == false: "listing has already been purchased" payment.isInstance(self.details.salePaymentVaultType): "payment vault is not requested fungible token" payment.balance == self.details.salePrice: "payment vault does not contain requested price" } // Make sure the listing cannot be purchased again. self.details.setToPurchased() // Fetch the token to return to the purchaser. let nft <-self.nftProviderCapability.borrow()!.withdraw(withdrawID: self.details.nftID) // Neither receivers nor providers are trustworthy, they must implement the correct // interface but beyond complying with its pre/post conditions they are not gauranteed // to implement the functionality behind the interface in any given way. // Therefore we cannot trust the Collection resource behind the interface, // and we must check the NFT resource it gives us to make sure that it is the correct one. assert(nft.isInstance(self.details.nftType), message: "withdrawn NFT is not of specified type") assert(nft.id == self.details.nftID, message: "withdrawn NFT does not have specified ID") // Rather than aborting the transaction if any receiver is absent when we try to pay it, // we send the cut to the first valid receiver. // The first receiver should therefore either be the seller, or an agreed recipient for // any unpaid cuts. var residualReceiver: &{FungibleToken.Receiver}? = nil // Pay each beneficiary their amount of the payment. for cut in self.details.saleCuts { if let receiver = cut.receiver.borrow() { let paymentCut <- payment.withdraw(amount: cut.amount) receiver.deposit(from: <-paymentCut) if (residualReceiver == nil) { residualReceiver = receiver } } } assert(residualReceiver != nil, message: "No valid payment receivers") // At this point, if all recievers were active and availabile, then the payment Vault will have // zero tokens left, and this will functionally be a no-op that consumes the empty vault residualReceiver!.deposit(from: <-payment) // If the listing is purchased, we regard it as completed here. // Otherwise we regard it as completed in the destructor. emit ListingCompleted( listingResourceID: self.uuid, storefrontResourceID: self.details.storefrontID, purchased: self.details.purchased ) return <-nft } // destructor // destroy () { // If the listing has not been purchased, we regard it as completed here. // Otherwise we regard it as completed in purchase(). // This is because we destroy the listing in Storefront.removeListing() // or Storefront.cleanup() . // If we change this destructor, revisit those functions. if !self.details.purchased { emit ListingCompleted( listingResourceID: self.uuid, storefrontResourceID: self.details.storefrontID, purchased: self.details.purchased ) } } // initializer // init ( nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>, nftType: Type, nftID: UInt64, salePaymentVaultType: Type, saleCuts: [SaleCut], storefrontID: UInt64 ) { // Store the sale information self.details = ListingDetails( nftType: nftType, nftID: nftID, salePaymentVaultType: salePaymentVaultType, saleCuts: saleCuts, storefrontID: storefrontID ) // Store the NFT provider self.nftProviderCapability = nftProviderCapability // Check that the provider contains the NFT. // We will check it again when the token is sold. // We cannot move this into a function because initializers cannot call member functions. let provider = self.nftProviderCapability.borrow() assert(provider != nil, message: "cannot borrow nftProviderCapability") // This will precondition assert if the token is not available. let nft = provider!.borrowNFT(id: self.details.nftID) assert(nft.isInstance(self.details.nftType), message: "token is not of specified type") assert(nft.id == self.details.nftID, message: "token does not have specified ID") } } .... pub resource Storefront : StorefrontManager, StorefrontPublic { // The dictionary of Listing uuids to Listing resources. access(self) var listings: @{UInt64: Listing} // insert // Create and publish a Listing for an NFT. // pub fun createListing( nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>, nftType: Type, nftID: UInt64, salePaymentVaultType: Type, saleCuts: [SaleCut] ): UInt64 { let listing <- create Listing( nftProviderCapability: nftProviderCapability, nftType: nftType, nftID: nftID, salePaymentVaultType: salePaymentVaultType, saleCuts: saleCuts, storefrontID: self.uuid ) let listingResourceID = listing.uuid let listingPrice = listing.getDetails().salePrice // Add the new listing to the dictionary. let oldListing <- self.listings[listingResourceID] <- listing // Note that oldListing will always be nil, but we have to handle it. destroy oldListing emit ListingAvailable( storefrontAddress: self.owner?.address!, listingResourceID: listingResourceID, nftType: nftType, nftID: nftID, ftVaultType: salePaymentVaultType, price: listingPrice ) return listingResourceID } ..... } //More NFTStorefront contract code below }

When creating an NFT listing it will need a few parameters passed in, in order to make the listing go through.

First you will need to pass in a capability to the NFT that allows us to withdraw the NFT when it is being sold to the purchaser. Remember to link the capability in your private storage path as you do not want anyone to be able to withdraw any NFT from your account!

You'll also need to pass in the type of NFT that is being sold, as well as the NFT ID that is being sold. Afterwards you pass in the payment vault type for whichever crypto currency users will be purchasing the listing in.

You'll also include a struct called SaleCut. These Sales Cuts will determine the total price of your item. Let's say you want to have a marketplace fee implemented in your transaction. You would include a sales cut struct that would be passed in, as well as a sales cut that will go to the seller of the item. You can check out the example for multiple sales cuts to see how this would be implemented.

Lastly you would pass in the storefrontID which is the ID of your storefront containing the listing. Once all of this information is passed in, you have created a listing in your Storefront.

Afterwards you would link the public capability to the ListingPublic interface so that anyone can call the purchase function and buy your NFT.

Transaction Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import NFTStorefront from 0x03 import NonFungibleToken from 0x02 import ExampleNFT from 0x04 import FungibleToken from 0x01 // This transaction sets up account 0x01 for the marketplace tutorial // by publishing a Vault reference and creating an empty NFT Collection. transaction { let storefront: &NFTStorefront.Storefront let exampleNFTProvider: Capability<&ExampleNFT.Collection{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}> let tokenReceiver: Capability<&FungibleToken.Vault{FungibleToken.Receiver}> prepare(acct: AuthAccount) { self.storefront = acct.borrow<&NFTStorefront.Storefront>(from: NFTStorefront.StorefrontStoragePath) ?? panic("can't borrow storefront") if acct.getCapability<&ExampleNFT.Collection{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>(ExampleNFT.CollectionPrivatePath).check() == false { acct.link<&ExampleNFT.Collection{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>(ExampleNFT.CollectionPrivatePath, target: ExampleNFT.CollectionStoragePath) } self.exampleNFTProvider = acct.getCapability<&ExampleNFT.Collection{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>(ExampleNFT.CollectionPrivatePath)! assert(self.exampleNFTProvider.borrow() != nil, message: "Missing or mis-typed ExampleNFT.Collection provider") self.tokenReceiver = acct.getCapability<&FungibleToken.Vault{FungibleToken.Receiver}>(/public/MainReceiver)! assert(self.tokenReceiver.borrow() != nil, message: "Missing or mis-typed FlowToken receiver") let saleCut = NFTStorefront.SaleCut( receiver: self.tokenReceiver, amount: 10.0 ) self.storefront.createListing( nftProviderCapability: self.exampleNFTProvider, nftType: Type<@NonFungibleToken.NFT>(), nftID: 0, salePaymentVaultType: Type<@FungibleToken.Vault>(), saleCuts: [saleCut] ) log("storefront listing created") } }

Here we are defining initially the types that we are expected to return for each of the listed variables.

Firstly we borrow the storefront from the account that contains a storefront in it. Afterwards we need to get the capability to withdraw NFTS from the account so that the storefront can withdraw the correct NFT of the correct Type.

After that, we'll also get the Vault receiver capability so that it can be included in the sales cut struct. That way when it's time to pay out the earnings from the NFT, this vault will receive its cut.

Once that's all done, we take self.storefront and call the createListing function and pass in all the parameters need. Then we execute the transaction.


ProgressNFT Storefront Essentials

Up Next: Purchase NFT on Marketplace

67%


Related Recipes

14 Oct 2022
Create a Marketplace
Intermediate
01 Apr 2022
Purchase NFT on Marketplace
Intermediate