Hot Dogs, Merkle Trees, and NFTs

10/22/2022 - originally posted on mirror.xyz

The half-joke long before I left Coinbase in June 2022 was that I was going to quit to sell hot dogs. I was a senior software engineer, burnt out, and more than ready to do anything else; so long as it was in web3 anyways.

So, as early as March of 2022, a couple friends and I decided the world needed more hot dogs. The “plan” was to sell hot dog NFTs. One friend would do the designs, another the scripting work + marketing, and I’d write the contracts + handle IPFS. This was obviously a full-proof plan bound to make us millions. Just look at our early designs:

"Hot dog with nips" - April 4th, 2022

I wanted to call our project “Not Hot Dog” and go all in with marketing against all other NFT projects.

Oh you’ve got a Bored Ape? That’s not a hot dog

Fortunately for everyone involved, we went a different route. The project was ultimately called “Hot Dog Time Machine”.

go watch Silicon Valley

Along the way we asked the important questions:

Is a hot dog a sandwich or a taco? A: a taco

What would be a funny hot dog tattoo?

Do the hot dogs need noses?

We also made sure to pull inspiration from the top minds of the internet.

https://www.youtube.com/watch?v=WLfAf8oHrMo

Fun fact #1: On average, each American consume ~70 hot dogs per year

https://www.thehotdog.org/ultimate-hot-dog-consumption/

https://www.youtube.com/watch?v=HgqbCq_sxmo?t=46


Anyways… after rescheduling our internal launch data several times and establishing a Twitter presence, we were finally ready to launch.

https://twitter.com/HotDogHoncho/status/1581035951177879552?s=20&t=__Mqc-76V6ZA8Kf9OUKVug

Fun fact #2: Doodles spent >2 ETH in gas setting up their whitelist

We were giving away [pictures of] hot dogs for free and had 3 different whitelists totaling over 2000 wallet addresses across multiple phases. And I had failed to estimate the gas costs of actually adding those on-chain. 🤦‍♀️ Our original contract code for whitelisting looked something like this:

function inviteToParty(address[] calldata _partyPeople, uint8 _partyPhase, uint8 _maxPer) external onlyOwner {
    for (uint256 i = 0; i < _partyPeople.length; i += 1) {
        partyPeople[_partyPeople[i]][_partyPhase] = _maxPer;
    }
}

And our whitelist check was simply:

function isInvited(address _partyPerson, uint8 _phase) public view returns(bool) {
    return partyPeople[_partyPerson][_phase] > 0;
}

Yes, we deployed our contract before realizing it was going to cost us 1 ETH to add our whitelist addresses. We knew we weren’t actually going to make money from this project (it’s free pictures of hot dogs after), but we figured we’d bite the bullet and pay up the funds. This was when the price of gas was at only ~20 gwei - it then proceeded to jump to over 170 gwei before we could finalize the whitelists. So we ultimately gave ourselves the day to find cheaper gas (we submitted the first list at 25 gwei hoping it would clear soon) and let the hungry fans know.

https://twitter.com/HotDogHoncho/status/1582403220348891136?s=20&t=__Mqc-76V6ZA8Kf9OUKVug

Merkle Trees to the Rescue

While waiting for gas to lower again, we started exploring other options. We stumbled on this post about Merkle trees from about a year ago. And although we hadn’t seen this used in the wild, we figured we would try it while waiting.

Turns out, Merkle trees are fantastic and OpenZepplin makes them easy to implement in Solidity (along with everything else). Instead of adding every address on our lists to the contract, we did the following:

  1. Create Merkle trees from our addresses; one per set per phase where an address represents a “leaf”

  2. Grab the root hash and put that on-chain

  3. At mint time, the site would fetch the “proof” of an address being in the Merkle tree from a server we control

  4. That proof would be sent to the mint function of the contract where it would be verified against the root hash we added in Step 2

Now the code for our whitelisting looked like this:

function inviteToParty(bytes32[] memory _merkleVIPs, uint8 _partyPhase) external onlyOwner {
    merkleVIPs[_partyPhase] = _merkleVIPs[0];
}

function addPlusOnes(bytes32[] memory _merkleVIPs, uint8 _partyPhase) external onlyOwner {
    merklePlusOnes[_partyPhase] = _merkleVIPs[0];
}

(some addresses could mint 2 instead of 1 - hence the merklePlusOnes)

Checking against our whitelist became slightly more involved, but only slightly:

function isInvited(address _partyPerson, bytes32[] calldata _ticket, uint8 _phase) public view returns(bool) {
    bytes32 leaf = keccak256(abi.encodePacked(_partyPerson));
    return MerkleProof.verify(_ticket, merkleVIPs[_phase], leaf);
}

The gist is instead of checking if the sender’s address was in a list, we checked if the sender’s address + proof (_ticket) could be verified against our Merkle root.

And that’s it! We ended up rolling with the new Merkle tree setup and launched to far more success than we were actually expecting. Not only did we mint out, but we also had over 7 ETH in secondary sales with a floor price well under 0.01 ETH.

If you’re curious, you can check out our new contract here, our collection on Gem.xyz, and our project website.

hot dogs is what has convinced my sister to install metmask

That’s success 💪