Skip to content

streamerd/nft-store-fullstack

 
 

Repository files navigation

Marketplace With Next.JS

Introduction

In this guide, you will learn how to create a marketplace like OpenSea on the Rinkeby Ethereum test network!

By the end, we'll implement the following features:

  • A marketplace where we can list NFTs for direct sale or for auction.
  • Allow users to make bids and buy our NFTs.

Check out the Demo here: https://marketplace-next-ts.vercel.app/

Tools

Using This Repo

  • Clone this repository.

  • Create your own Marketplace contract via the thirdweb dashboard. (Follow the steps in the guide below if you need extra help)!

  • Replace all instances of our Marketplace contract address with your own, (wherever you see the useMarketplace hook).

  • Install the required dependencies:

npm install
# or
yarn install
  • Run the development server:
npm run dev
# or
yarn dev

Need More help? Want to understand the code a bit more? Want to set the project up yourself? Follow the guide below! 👇


Creating A Marketplace

To create a marketplace contract:

  • Head to the thirdweb dashboard.
  • Click Create a new contract.
  • Click Setup Marketplace.
  • Configure & Deploy!

The Thirdweb Provider

The thirdweb React provider makes it straightforward to let your users connect their wallets to your website, and it abstracts away all the boilerplate you would usually have to write.

Open pages/_app.tsx we wrap all of our pages in the <ThirdwebProvider> component.

<ThirdwebProvider desiredChainId={ChainId.Rinkeby}>
  <Component {...pageProps} />
</ThirdwebProvider>

Signing Users In With Their Wallets

We connect user's wallets to our website by using the thirdweb React SDK's useMetamask hook.

const connectWithMetamask = useMetamask();

Displaying Listings On The Marketplace

On the index.tsx file, we're displaying all of the current active listings on the marketplace.

We're using React's useState to store the listings as well as a loading flag.

// Loading Flag
const [loadingListings, setLoadingListings] = useState<boolean>(true);
// Store Listings
const [listings, setListings] = useState<(AuctionListing | DirectListing)[]>(
  []
);

Then, we use the useMarketplace hook to connect to our smart contract via it's contract address.

const marketplace = useMarketplace("your-marketplace-address-here");

Once the marketplace is ready, we can use the useActiveListings hook to get all of the listings that are currently active (i.e. haven't expired or sold already).

const { data: listings, isLoading: loadingListings } =
  useActiveListings(marketplace);

Once we have the listings, we can display them to our users.

We'll leave the details of how best to display the listings up to you, but if you're looking for an example, check out the code in our index.tsx file file.

Listing Items on the marketplace

We have a page called create.tsx that lets users upload existing NFTs onto the marketplace.

If you don't have NFTs that you can list, you can create an NFT Collection via our dashboard.

Once again, we are using the useMarketplace hook to connect to our marketplace smart contract via it's contract address.

const marketplace = useMarketplace(
    "your-marketplace-address-here"
  );
};

Create Auction Type Listing:

async function createAuctionListing(
  contractAddress: string,
  tokenId: string,
  price: string
) {
  try {
    const transaction = await marketplace?.auction.createListing({
      assetContractAddress: contractAddress, // Contract Address of the NFT
      buyoutPricePerToken: price, // Maximum price, the auction will end immediately if a user pays this price.
      currencyContractAddress: NATIVE_TOKEN_ADDRESS, // NATIVE_TOKEN_ADDRESS is the cryptocurency that is native to the network. i.e. Rinkeby Ether
      listingDurationInSeconds: 60 * 60 * 24 * 7, // When the auction will be closed and no longer accept bids (1 Week)
      quantity: 1, // How many of the NFTs are being listed (useful for ERC 1155 tokens)
      reservePricePerToken: 0, // Minimum price, users cannot bid below this amount
      startTimestamp: new Date(), // When the listing will start (now)
      tokenId: tokenId, // Token ID of the NFT.
    });

    return transaction;
  } catch (error) {
    console.error(error);
  }
}

Create Direct Type Listing

async function createDirectListing(
  contractAddress: string,
  tokenId: string,
  price: string
) {
  try {
    const transaction = await marketplace?.direct.createListing({
      assetContractAddress: contractAddress, // Contract Address of the NFT
      buyoutPricePerToken: price, // Maximum price, the auction will end immediately if a user pays this price.
      currencyContractAddress: NATIVE_TOKEN_ADDRESS, // NATIVE_TOKEN_ADDRESS is the cryptocurency that is native to the network. i.e. Rinkeby Ether.
      listingDurationInSeconds: 60 * 60 * 24 * 7, // When the auction will be closed and no longer accept bids (1 Week)
      quantity: 1, // How many of the NFTs are being listed (useful for ERC 1155 tokens)
      startTimestamp: new Date(0), // When the listing will start (now)
      tokenId: tokenId, // Token ID of the NFT.
    });

    return transaction;
  } catch (error) {
    console.error(error);
  }
}

When you go to list your NFT, you'll be asked for two transactions:

  1. Approve the marketplace to sell your NFTs while the NFT still lives in your wallet. (setApprovalForAll)
  2. Create the listing on the marketplace (createListing)

If everything worked as planned, after you approve these two transactions, you should now see the listing you just created on the home page!

Viewing A Listing

On the home page, we provide a Link on each listing's name to a URL that looks like: /listing/${listing.id}. This is using Next JS's Dynamic Routes.

This way, each NFT navigates the user to a page that shows the details of the listing when they click on it, by taking them to the /listing/[listingId] page.

When the user visits the /listing/[listingId] page, we can fetch the information about the listing the user is looking at! E.g if the user visits /listing/1, we call marketplace.getListing(1) and load that listings information!

Fetching The Listing

const marketplace = useMarketplace("your-marketplace-address-here");

useEffect(() => {
  if (!listingId || !marketplace) {
    return;
  }
  (async () => {
    // Use the listingId from the router.query to get the listing the user is looking at.
    const l = await marketplace.getListing(listingId);

    setLoadingListing(false);
    setListing(l);
  })();
}, [listingId, marketplace]);

On the /listing/[listingId] page, we'll want users to also be able to place bids/offers on the listing, and also buy the listing!

Creating A Bid / Offer

async function createBidOrOffer() {
  try {
    // If the listing type is a direct listing, then we can create an offer.
    if (listing?.type === ListingType.Direct) {
      await marketplace?.direct.makeOffer(
        listingId, // The listingId of the listing we want to make an offer for
        1, // Quantity = 1
        NATIVE_TOKENS[ChainId.Rinkeby].wrapped.address, // Wrapped Ether address on Rinkeby
        bidAmount // The offer amount the user entered
      );
    }

    // If the listing type is an auction listing, then we can create a bid.
    if (listing?.type === ListingType.Auction) {
      await marketplace?.auction.makeBid(listingId, bidAmount);
    }
  } catch (error) {
    console.error(error);
  }
}

Buying the NFT

async function buyNft() {
  try {
    // Simple one-liner for buying the NFT
    await marketplace?.buyoutListing(listingId, 1);
  } catch (error) {
    console.error(error);
  }
}

We attach these functions to the onClick handlers of our Buy and Make Offer buttons. If you want to see how we do that, check out the code in our [listingId].tsx file page.

Note: For making offers, you'll need to have an ERC20 token. For our Rinkeby marketplace, that means you'll need to have wrapped ETH (wETH).

Join our Discord!

For any questions, suggestions, join our discord at https://discord.gg/thirdweb.

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 68.2%
  • CSS 31.2%
  • Other 0.6%