NFT Code Review: Mutant Ape Yacht Club
We go line-by-line through NFT contract code.
Everyone sees the pfps, but few understand the code behind them.
In this series, we go line-by-line through NFT's contract code.
We must show you the brighter side of Yuga Labs, so we're going to take a look at their Mutant Ape Yacht Club contract!
Let's dive in 🧵👇
The MAYC contract interacts with both the BAYC contract and the BACC contract (Bored Ape Chemistry Club). We'll show you how this is done soon.
See the import statements on lines 9 and 10 in the `MutantApeYachtClub.sol` file. This is telling the file to import the abstract contracts.
`ownerOf` is the function the MAYC contract uses from the BAYC contract to get the ETH address for a specific BAYC `tokenId`.
This is how you can confirm the sender actually owns the bored ape they’re trying to mutate.
The MAYC contract uses the BACC contract to do two things.
`burnSerumForAddress` validates the sender owns the serum and then burns the serum after use.
`balanceOf` is used to get the count of a specific serum token the wallet owns.
Now to the contract.
There are a number of variables because there is a TON going on here. The inline comments in the code do a good job of explaining what they are all. We'll touch on them where they're used.
It's awesome to see the `MAYC_PROVENANCE` hardcoded into the contract 👏
Below you can see the `events` that the MAYC collection created. These are mostly used for transaction logs to make it easy to see what occurred.
There is no logic driven by these.
Here are two `modifier`s in the contract.
`modifier`s are used in a functions declaration to validate if the function can be called or not. Logically, it would be no different than adding these `require` calls within the functions themselves.
If they're used multiple times, it's nice for reusability.
The `constructor` is where the contract addresses are set for the abstract BAYC and BACC contracts. This is how the MAYC contract knows which contracts it should actually be interacting with.
The BAYC contract address is 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
The BACC contract address is 0x22c36bfdcef207f9c0cc941936eff94d4246d14a
`startPublicSale` and `pausePublicSale` are used to enable and disable the mint.
MAYC was a dutch auction, so the price decreased over time. That is why you see `publicSaleDuration`, `publicSaleMutantStartingPrice`, `publicSaleStartTime` etc.
Interestingly, it looks like they could have paused the contract at any time and restarted it with a new `saleDuration` and `saleStartPrice`.
Although, all of these actions would have been emitted as events.
`getElapsedSaleTime` and `getRemainingSaleTime` are helper functions used to calculate time related to the dutch auction.
`getMintPrice` is used to calculate the current mint price for the dutch auction. It's a function of time elapsed, the starting price, and the ending price.
The ending price is hardcoded in at 0.01 ETH. I believe the starting price was 3 ETH and didn't drop much before it was fully minted.
`withdraw` is standard. For those new to these threads, that's how the ETH paid minting gets sent to the owner's wallets.
`mintMutants` is the publicly accessible mint. The max mintable by the public was 10k.
The super cool thing @YugaLabs did here was on lines 194-196. If a minter accidentally paid too much ETH, they sent the ETH back to the minter.
A badass piece of code that shows the morals of the founding team 💪
`mutateApeWithSerum` is how BAYC and BACC holders minted their MAYC.
There is a lot here to unpack.
First, the function makes its validations that the serum mint is active, the sender owns the associated `apeId`, and the sender owns at least 1 of the serum type.
It then determines what type of mutation to do.
If it's a `MEGA_MUTATION_TYPE` it sets the `mutantId` with the `currentMegaMutationId`. There are only 8 possible mega mutants. They have token IDs 30,000 - 30,007.
If it's not the `MEGA_MUTATION_TYPE` it gets the mutant ID to use from the `getMutantId` function which takes the `serumTypeId` and `apeId` as parameters.
Finally, the serum is burned and the MAYC is minted.
Here is the implementation of `getMutantId`.
It is a function used to make sure there is no conflict in token IDs between the public mint, the serum type 1 mint, the serum type 2 mint, and the mega mutation mint.
`getMutantIdForApeAndSerumCombination` and `hasApeBeenMutatedWithType` are helper functions.
The former is used to get an ID for a specific mutant and the latter is used to see if an ape has already been mutated.
`isMinted` is a function to see if a specific token has been minted.
Here we have the functions to update and read the baseURI.
The provenance is hardcoded in the code, so the risk here is limited.
Here are a couple of toggles to enable/disable the minting for both the public mint and the mutation mint.
Finally, we have the function to `setStartingIndices` to make sure the mint is run fairly.
As you can see, @YugaLabs stepped up their dev game in a massive way from BAYC to MAYC.
One question to leave you with...
wen MAYC flip BAYC?
Happy #MutantMonday






















In the withdraw function, what is the purpose of storing the balance in a uint256 and passing that into the sendValue call rather than just putting address(this).balance as the value in the sendValue call?