DEV Community

Cover image for Build A Mutant Ape Yacht Club (MAYC) NFT Collection Clone
Avneesh Agarwal for thirdweb

Posted on • Originally published at blog.thirdweb.com

Build A Mutant Ape Yacht Club (MAYC) NFT Collection Clone

In this guide, we'll show you how to create a clone of the MAYC collection drop where users from an original collection will be airdropped serums that can be used to mint a MAYC NFT. When the user claims the NFT, the serum will be burned.

Before we get started, below are some helpful resources where you can learn more about the tools we will use in this guide.

Let's get started!

Setup

To build the smart contract we will be using Hardhat.

Hardhat is an Ethereum development environment and framework designed for full stack development in Solidity. Simply put, you can write your smart contract, deploy it, run tests, and debug your code.

Setting up a new hardhat project

Create a folder where the hardhat project and the Next.js app will go. To create a folder, open up your terminal and execute these commands

mkdir mayc-clone
cd mayc-clone
Enter fullscreen mode Exit fullscreen mode

Now, we will use the thirdweb CLI to generate a new hardhat project! So, run this command:

npx thirdweb create --contract
Enter fullscreen mode Exit fullscreen mode

When it asks what type of project, you must select an empty one!

Now you have a hardhat project ready to go!

Setting up the frontend

I am going to use the Next.js Typescript starter template for this guide.

If you are following along with the guide, you can create a project with the template using the thirdweb CLI:

npx thirdweb create --next --ts
Enter fullscreen mode Exit fullscreen mode

If you already have a Next.js app you can simply follow these steps to get started:

  • Install @thirdweb-dev/react and @thirdweb-dev/sdk and ethers.

  • Add MetaMask authentication to the site. You can follow this guide to add metamask auth.

Creating the Smart Contracts

Writing the smart contracts

BAYClone.sol

We will now write our smart contracts! Create a new file BAYClone.sol in the contracts folder. We are going to use the thirdweb contracts extend feature to create a new NFT drop. So, paste the following into it

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@thirdweb-dev/contracts/base/ERC721Drop.sol";

contract BAYClone is ERC721Drop {
    constructor(
        string memory _name,
        string memory _symbol,
        address _royaltyRecipient,
        uint128 _royaltyBps,
        address _primarySaleRecipient
    )
        ERC721Drop(
            _name,
            _symbol,
            _royaltyRecipient,
            _royaltyBps,
            _primarySaleRecipient
        )
    {}
}
Enter fullscreen mode Exit fullscreen mode

Here, we are importing the ERC721Drop contract from the thirdweb contracts package. This contract is a base contract that allows us to create a new NFT drop. We are also passing in the name, symbol, royalty recipient, royalty bps, and primary sale recipient to the constructor.

SerumClone.sol

Next, we will create a new contract that will be the collection of the serum. This will be an ERC1155 contract. Create a new file SerumClone.sol in the contracts folder. We will also use the thirdweb contracts extend feature to create a new ERC1155 collection. So, paste the following into it

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@thirdweb-dev/contracts/base/ERC1155Base.sol";

contract SerumClone is ERC1155Base {
    constructor(
        string memory _name,
        string memory _symbol,
        address _royaltyRecipient,
        uint128 _royaltyBps
    ) ERC1155Base(_name, _symbol, _royaltyRecipient, _royaltyBps) {}
}
Enter fullscreen mode Exit fullscreen mode

Here, just like the BAYC Clone, we are extending a base contract from the thirdweb contracts package. This time, we are extending the ERC1155Base contract. We are also passing in the name, symbol, royalty recipient, and royalty bps to the constructor.

MAYClone.sol

Finally, we will write our MAYC Clone, the most important contract. Here we are going to extend the ERC721Drop contract and add some extra functionality. Create a new file MAYClone.sol in the contracts folder. Paste the following into it

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@thirdweb-dev/contracts/base/ERC721LazyMint.sol";
import "@thirdweb-dev/contracts/base/ERC1155Base.sol";
import "@thirdweb-dev/contracts/base/ERC721Drop.sol";

contract MAYClone is ERC721LazyMint {
    // Store constant values for the 2 NFT Collections:
    // 1. Is the BAYC NFT Collection
    ERC721LazyMint public immutable bayc;
    // 2. Is the Serum NFT Collection
    ERC1155Base public immutable serum;

    constructor(
        string memory _name,
        string memory _symbol,
        address _royaltyRecipient,
        uint128 _royaltyBps,
        address _baycAddress,
        address _serumAddress
    ) ERC721LazyMint(_name, _symbol, _royaltyRecipient, _royaltyBps) {
        bayc = ERC721LazyMint(_baycAddress);
        serum = ERC1155Base(_serumAddress);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, we are extending the ERC721LazyMint contract. We are also taking in the BAYC and Serum contract addresses as constructor arguments. Now, let's add the extra functions to the contract!

 function verifyClaim(address _claimer, uint256 _quantity)
        public
        view
        virtual
        override
    {
        // 1. Override the claim function to ensure a few things:
        // - They own an NFT from the BAYClone contract
        require(bayc.balanceOf(_claimer) >= _quantity, "You don't own enough BAYC NFTs");
        // - They own an NFT from the SerumClone contract
        require(serum.balanceOf(_claimer, 0) >= _quantity, "You don't own enough Serum NFTs");
    }

    function transferTokensOnClaim(address _receiver, uint256 _quantity) internal override returns(uint256) {
        serum.burn(
            _receiver,
            0,
            _quantity
        );

        // Use the rest of the inherited claim function logic
      return super.transferTokensOnClaim(_receiver, _quantity);
    }
Enter fullscreen mode Exit fullscreen mode

The verifyClaim function checks if the user owns enough BAYC and Serum NFTs. The transferTokensOnClaim function burns the Serum NFTs from the user and then calls the inherited transferTokensOnClaim function.

Now that we have written our smart contracts, we will go ahead and deploy our contract using deploy.

Deploying the contracts

npx thirdweb deploy
Enter fullscreen mode Exit fullscreen mode

This command allows you to avoid the painful process of setting up your entire project like setting up RPC URLs, exporting private keys, and writing scripts. Now, you will get options to choose which contract to deploy. We need to deploy all three of them so we will deploy them one by one.

Deploy

BAYClone

First, let's deploy the BAYC Clone contract. Choose the BAYC Clone contract after filling out the form with your values. Deploy the contract!

BAYC

Now, choose the network you want to deploy your contract to! I am going to use Goerli but you can use whichever one you like. Once you have selected your network click on Deploy now!

After the transactions are mined you will be taken to the dashboard which consists of many options. Feel free to explorer them but for now we will move on with other contracts!

SerumClone

Now, deploy the SerumContract in the same way as you did for the BAYC Clone contract.

MAYClone

Finally, deploy the MAYC Clone contract. The deployment process will be the same as the BAYC clone and Serum Clone but you need to pass in these two addresses as well!

Minting NFTs from BAYC Clone

We need to set up a claim phase so that people can claim our NFTs. We will claim one ourselves for testing as well. Go to the Claim Conditions and create a new claim phase with the parameters that you need!

Claim Conditions

Now, go to the NFTs tab. Let's batch upload some NFTs for the users to mint. For this guide, I am going to use the Shapes batch upload example.

Click on batch upload and upload the images and the CSV/JSON file.

Once they are uploaded you will be able to see the NFTs! To learn more about batch upload check out this guide.

Now, we need to claim some NFTs to test out the process. Go to the explorer tab and click on the claim option. Now, fill out the data I am using the following values:

  • _receiver: 0x477856f90EdE4D4669f222889947bE5EE43424Db
  • _quantity: 2
  • _currency: 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
  • _pricePerToken: 0
  • _allowlistProof: { "proof": ["0x0000000000000000000000000000000000000000000000000000000000000000"], "maxQuantityInAllowlist": 0 }
  • _data: []
  • Native Token Value: 0

Claim NFT

Click execute and confirm the transaction. Now, you can see that the NFTs have been claimed!

Airdropping NFTs from MAYC Clone

We need to add an erc 1155 token that will be airdropped to the owners of the BAYC collection, so go to the NFTs tab and click on mint. Fill out the details and click Mint!

To airdrop the serums we will create a script that will get all the owners of the BAYC NFTs and create a CSV file from it. Then we will use this CSV file to airdrop the serums. So, in our application folder, create a new file called scripts/getAll.mjs and add the following:

import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import fs from "fs";
import path from "path";

(async () => {
  const sdk = new ThirdwebSDK("goerli");
  const contract = await sdk.getContract(
    "0x3714e40A15Deffb8E43A58b018bD81C2c6AC2445"
  );

  if (!contract) {
    return;
  }

  const nfts = await contract?.nft?.query?.all();

  if (!nfts) {
    return;
  }

  const csv = nfts?.reduce((acc, nft) => {
    const address = nft.owner;
    const quantity = acc[address] ? acc[address] + 1 : 1;
    return { ...acc, [address]: quantity };
  }, {});

  const filteredCsv = Object.keys(csv).reduce((acc, key) => {
    if (key !== "0x0000000000000000000000000000000000000000") {
      return {
        ...acc,
        [key]: csv[key],
      };
    }
    return acc;
  }, {});

  const csvString =
    "address,quantity \r" +
    Object.entries(filteredCsv)
      .map(([address, quantity]) => `${address},${quantity}`)
      .join("\r");

  fs.writeFileSync(path.join(path.dirname("."), "nfts.csv"), csvString);
  console.log("Generated nfts.csv");
})();
Enter fullscreen mode Exit fullscreen mode

Update your contract address, and run the script. You will see a new nfts.csv file like this:

address,quantity
0xb371d1C5629C70ACd726B20a045D197c256E1054,1
Enter fullscreen mode Exit fullscreen mode

Once you are done updating/checking your CSV file, go back to the dashboard and click on your NFT. Go to the airdrop tab and upload your addresses. Once done, click deploy and the serums will be airdropped to the addresses!

Adding NFTs to MAYC Clone

Just like we added NFTs to the BAYC Clone, we need to add the NFTs to MAYC clone as well.

Building the frontend

Now, let's build our frontend so that people can claim the MAYC NFTs! To keep our code clean we will store the mayc and serum addresses as a variable in const/contractAddresses.ts:

export const MAYC_ADDRESS = "0xE0e3fd6782a3b87aB1bab5d78CDAF75cB453BCbe";
export const SERUM_ADDRESS = "0xDAcd1CDB5A144fC6fa3c55F290be778Ca47C5187";
Enter fullscreen mode Exit fullscreen mode

Now, in pages/index.tsx we will use the Web3Button component to allow users to claim the NFT:

return (
  <div>
    <Web3Button
      contractAddress={MAYC_ADDRESS}
      action={(contract) => mintMutantNft(contract)}
    >
      Mint Your Mutant NFT
    </Web3Button>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

The MAYC_ADDRESS will be imported from our contractAddresses file. And as you can see we also have a function called mintMutantNft so let's create that:

const mintMutantNft = async (maycContract: SmartContract<BaseContract>) => {
  // 1. Check the approval of the mayc contract to burn the user's serum tokens
  const hasApproval = await serumContract?.call(
    "isApprovedForAll",
    address,
    maycContract?.getAddress()
  );
  const balance = await serumContract?.call("balanceOf", address, 0);

  if (!hasApproval) {
    // Set approval
    await serumContract?.call(
      "setApprovalForAll",
      maycContract?.getAddress(),
      true
    );
  }

  if (balance < 1) {
    return alert("Not enough serum tokens");
  }

  await maycContract?.call("claim", address!, 1);
};
Enter fullscreen mode Exit fullscreen mode

Here, we are getting the serumContract and asking for approval first as the contract needs to burn the serum. To get the address and maycContract we will use some hooks:

const address = useAddress();
const { contract: serumContract } = useContract(SERUM_ADDRESS);
Enter fullscreen mode Exit fullscreen mode

Now, if we test out our app everything works! πŸ₯³

Conclusion

In this guide, we learnt how to use the bases extensions to create a MAYC clone. If you built the Dapp pat yourself on the back and share it with us on the thirdweb discord! If you want to take a look at the code, check out the GitHub Repository.

Top comments (1)

Collapse
 
sloan profile image
Sloan the DEV Moderator

This looks like a great article! πŸ™Œ

In fact, the topic of your post would also work really well in the Meta Punk Community too!

Metapunk Web3 Community πŸ¦™

We’re a community where blockchain builders and makers, web3 devs, and nft creators can connect, learn and share πŸ¦™

favicon metapunk.to

Meta Punk is a really cool international NFT and web3 community where artists, web3 developers, and traders can connect, learn, and share exciting discoveries and ideas. πŸ¦™

Would you consider posting this article there too? Because Meta Punk is built on the same platform as DEV (Forem) you can fairly easily copy the Markdown and post it there as well.

Really hope that you'll share this awesome post with the community there and consider browsing the other Forem communities out there!