Understanding Bored Ape Yacht Club Source Code

Rahul Ravindran

Bored Ape Yacht Club is one of the most popular NFT collection. Many celebrities bought them, they were advertised in Time Square in New York, and recently they became even more expensive than Cryptopunks.

If you want a quick summary, checkout our video:

Introduction

The Bored Ape Yacht Club is a collection of 10,000 Non-Fungible Tokens (NFTs) styled as Apes created on the Ethereum blockchain. It takes inspiration from NFT projects like CryptoPunks, where each NFT looks totally unique and individual. Each Ape has a different rarity depending on what the Ape is wearing, doing, and its background.

The project was founded by Yuga Labs’ four pseudonymous founders: Gargamel, Gordon Goner, Emperor Tomato Ketchup, and No Sass. Since their initial mint, BAYC has created new lines of NFTs, NFT upgrades, and an exclusive club for BAYC holders. These developments have kept up an interest in the project, leading to incredible sale prices like $3.4 million (USD) for the NFT below at Sotheby’s.

Each Ape avatar is an ERC-721 NFT on the Ethereum blockchain. Each token is non-fungible, meaning that it is unique and not equally interchangeable with any other cryptocurrency or token. An Ape is made up of seven possible traits:

  1. Background
  2. Clothes
  3. Earring
  4. Eyes
  5. Fur
  6. Hat
  7. Mouth

Each attribute has a variety of different looks and styles. For example, you can see below that an Ape can have solid gold, trippy, and noise fur, among others. Each look is limited to a certain number, so some are statistically rarer.

Source Code

The smart contract of BAYC is not available on Github, but you can see it on Etherscan. When you read smart contracts on Etherscan, all the imported contracts and libraries are put in the same file.

[Source code Link][1]

For example, for BAYC, we find the code of ERC721. The following is the core contract:

contract BoredApeYachtClub is ERC721, Ownable {
    using SafeMath for uint256;

    string public BAYC_PROVENANCE = "";

    uint256 public startingIndexBlock;

    uint256 public startingIndex;

    uint256 public constant apePrice = 80000000000000000; //0.08 ETH

    uint public constant maxApePurchase = 20;

    uint256 public MAX_APES;

    bool public saleIsActive = false;

    uint256 public REVEAL_TIMESTAMP;

    constructor(string memory name, string memory symbol, uint256 maxNftSupply, uint256 saleStart) ERC721(name, symbol) {
        MAX_APES = maxNftSupply;
        REVEAL_TIMESTAMP = saleStart + (86400 * 9);
    }

    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        msg.sender.transfer(balance);
    }

    /**
     * Set some Bored Apes aside
     */
    function reserveApes() public onlyOwner {        
        uint supply = totalSupply();
        uint i;
        for (i = 0; i < 30; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    /**
     * DM Gargamel in Discord that you're standing right behind him.
     */
    function setRevealTimestamp(uint256 revealTimeStamp) public onlyOwner {
        REVEAL_TIMESTAMP = revealTimeStamp;
    } 

    /*     
    * Set provenance once it's calculated
    */
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        BAYC_PROVENANCE = provenanceHash;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

    /*
    * Pause sale if active, make active if paused
    */
    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    /**
    * Mints Bored Apes
    */
    function mintApe(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Ape");
        require(numberOfTokens <= maxApePurchase, "Can only mint 20 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_APES, "Purchase would exceed max supply of Apes");
        require(apePrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_APES) {
                _safeMint(msg.sender, mintIndex);
            }
        }

        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_APES || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        } 
    }

    /**
     * Set the starting index for the collection
     */
    function setStartingIndex() public {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");

        startingIndex = uint(blockhash(startingIndexBlock)) % MAX_APES;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if (block.number.sub(startingIndexBlock) > 255) {
            startingIndex = uint(blockhash(block.number - 1)) % MAX_APES;
        }
        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }

    /**
     * Set the starting index block for the collection, essentially unblocking
     * setting starting index
     */
    function emergencySetStartingIndexBlock() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");

        startingIndexBlock = block.number;
    }
}

The first three values we see are BAYC_PROVENANCE, MAX_APES, and REVEAL_TIMESTAMP. They have been assigned names in all caps with underscores, which according to the Solidity style guide, suggests they are constants. These are a good set of constants, because they suggest a fixed image set that will be revealed at a given time.

When buyers bought an ape during the primary sale, they didn’t know how it looked like. This is to avoid bidding wars for gas and introduce fairness.

Let’s now review how what each function does:

The function allows the owner of the contract to withdraw the ether of the primary sale:

function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        msg.sender.transfer(balance);
}

This function allows the owner of the contract to mint 30 apes for themselves, free of charge. This was very controversial at the time of launch.

function reserveApes() public onlyOwner {        
        uint supply = totalSupply();
        uint i;
        for (i = 0; i < 30; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

This function is to update the hash of the image containing all the apes. This image is generated and stored outside of the blockchain

  function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        BAYC_PROVENANCE = provenanceHash;
    }

This function is to update the URL of the metadata server, which contains the image of all the apes, as well json data which describes the attribute of each ape, like the hat, the fur or the eyes

  function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

This is to toggle on and off the primary sale. Apes can only be minted if primary sale is active.

 function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

This is the function called by users to mint each ape. It’s a payable function, because you have to send ether to buy an ape.

The code checks that:
– the primary sale is active
– the buyer doesn’t try to buy too many ape
– and enough ether was sent

After that, it mints the apes.

 function mintApe(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Ape");
        require(numberOfTokens <= maxApePurchase, "Can only mint 20 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_APES, "Purchase would exceed max supply of Apes");
        require(apePrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_APES) {
                _safeMint(msg.sender, mintIndex);
            }
        }

        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_APES || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        } 
    }

The starting index block is going to begin uninitialized, set to 0. In mintApe’s final if statement, the mint function will check if it’s sold out, or if the current time is after REVEAL_TIMESTAMP. If they are sold out or after the reveal, the contract is going to set the starting index block to the current block number (each block in the blockchain gets an ID number). So this function is used to generate a random ape.

function setStartingIndex() public {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");

        startingIndex = uint(blockhash(startingIndexBlock)) % MAX_APES;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if (block.number.sub(startingIndexBlock) > 255) {
            startingIndex = uint(blockhash(block.number - 1)) % MAX_APES;
        }
        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }

And finally, this function is used to reset the startingIndexBlock. It can only be called by the owner of the contract.

function emergencySetStartingIndexBlock() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");

        startingIndexBlock = block.number;
    }

Conclusion

Bored Ape Yacht Club not only dominated the NFT community’s “JPEG Summer”, but was the catalyst that started the summer 2021 PFP project mayhem. As Punks continued to moon throughout the first half of the year, and Punk derivative projects started to peter out, BAYC filled a demand for originality and substance within the collectible and art sections of the NFT market.

0 Comments

Leave a Reply

More great articles

Crypto 2021 Rewind – Glancing Through Key Happenings

2021 was a crazy year to say the least. Let’s pause for a moment to wrap things up and see…

Read Story

Introduction to Flow Blockchain

‍Flow is a fast, decentralized, and developer-friendly blockchain, designed as the foundation for a new generation of games, apps. Every…

Read Story

Dirt Cheap Gas Prices using Starknet

Overview As the number of people using Ethereum has grown, the blockchain has reached certain capacity limitations. This has driven…

Read Story

Never miss a minute

Get great content to your inbox every week. No spam.
[contact-form-7 id="6" title="Footer CTA Subscribe Form"]
Arrow-up