Hunting for Rare NFTs in the Ethereum Dark Forest

 
Exploring Ethereum mempool darkforest is represented by a forest Photo by Kristine Weilert on Unsplash


Ethereum mempool, a.k.a Dark Forest, is where most transactions are submitted before inclusion in the public blockchain. By analyzing its contents, you can react to pending transactions ahead of other bots and users. In this blog post, I’ll describe how you can leverage mempool analysis to snipe rare NFT tokens before the gas wars break out.

Read on even if NFTs are not your thing. The mempool scanning techniques described have a wide range of use cases for Ethereum blockchain projects.

Disclaimer: The information provided in this blog post is for educational purposes only and should not be treated as investment advice.

How to mint NFT tokens before they run out?

NFT mania is the main reason for Ethereum gas prices spiking to unreasonable values.

Ethereum gas prices spike

This blog post is by no means trying to be a comprehensive introduction to NFTs. Instead, I’ll briefly describe the very basics.

NFT stands for Non Fungible Tokens, i.e., tokens that are each unique and not interchangeable. NFTs are implemented with the so-called ERC721 standard.

The practical application is to represent a blockchain verifiable ownership of a digitized asset. The standard itself is agnostic about the nature of the asset. In practice, there’s metadata attached to the token. In the case of art, the metadata usually includes an IPFS link to the graphical representation of the asset.

New NFT tokens can be generated by a process called minting. To mint token, you have to interact with it’s Smart Contract and pay the minting fee. In the case of popular art collections, this fee is often an order of magnitude smaller than the potential market price of the new token. That’s why when the new tokens are open for minting, the whole Ethereum network gets clogged up, and gas prices go to the moon.

Introducing Dotdotdots

The NFT collection whose Smart Contract we’ll analyze in this tutorial is the mysterious Dotdotdots. You can check out this YouTube video for an explanation of why they are so mysterious and popular.

Dotdot sample art


This little guy above was initially minted for ~0.08 ETH, later sold for 0.4 ETH, and one more time resold for one full ETH. You can check out its transaction history for details.

As described in the linked video, this collection will be open for minting once again when Ethereum’s price exceeds its ATH value. Read on to learn how we can increase our chances of minting our very own Dotdotdot ahead of the competition.

Ethereum Mempool 101

Before we move on to sniping our NFTs, let’s cover the basics of the mempool.

After submitting a transaction to the Ethereum node, it is floating around in a mempool. Eventually, it is picked up by a miner for inclusion in the blockchain. Transaction gas price is the main factor determining how your transactions will be kept pending before getting mined.

Mempool is ephemeral. Transactions that got stuck for too long may eventually get dropped by all the nodes and effectively canceled. As long as a transaction is pending in the mempool, it is possible to cancel or speed it up by resubmitting it with the same nonce value but a higher gas price.

Metamask speed up or cancel transaction UI

Metamask provides the feature of canceling and speeding up the transactions while they are pending in the mempool.


It’s worth noting that mempool is here to stay after the ETH 2.0 merge. Miners will be replaced by Validators.

You can use a JSON RPC interface to explore the mempool contents if you’re running a full node. Please remember that light nodes do not contain mempool data. Due to the decentralized nature of the Ethereum network, a single definitive state of a mempool does not exist. It means that your node is not likely to advertise all the pending transactions. That’s why we’ll use 3rd party tools instead of proprietary nodes in this tutorial.

You can use the Etherscan Pending Transactions view for a quick overview of the current state of the mempool. There are over 160k pending transactions at the time of writing, some of them over ten days old.

As I wrote before, currently, “most” of the transactions enter the mempool. Free tools like Flashbots, TaichiNetwork RPC or @mevalphaleak BetaRPC allow sending transactions directly to the miners bypassing the mempool. This approach makes certain types of transactions safer by protecting them from so-called frontrunning, sandwiching, and other MEV (Miner/Maximum Extractable Value) techniques.

But that’s a story for another blog post. Let’s now move to the promised NFTs hunt!

Analyzing the NFT Smart Contract

Some NFT collections offer a fancy UI for minting. But, Dotdotdots are mysterious and can be minted only directly via its Smart Contract.

We’re interested in the mint function:

function mint(uint numberOfTokensMax5) public payable {
    require(saleIsActive, "Sale is not active at the moment");
    require(numberOfTokensMax5 > 0, "Number of tokens can not be less than or equal to 0");
    require(totalSupply().add(numberOfTokensMax5) <= MAX_SUPPLY, "Purchase would exceed max supply");
    require(numberOfTokensMax5 <= MAX_NFT_PURCHASE,"Can only mint up to 10 per purchase");
    require(NFT_PRICE.mul(numberOfTokensMax5) == msg.value, "Sent ether value is incorrect");

    for (uint i = 0; i < numberOfTokensMax5; i++) {
        _safeMint(msg.sender, totalSupply());
    }
}

You can see that several require statements have to be fulfilled so that the minting process can be successful. For example, apart from the correctness of our transaction data, the saleIsActive bool must be true, and totalSupply() plus our new tokens must not exceed current MAX_SUPPLY.

Only the contract owner can modify these state variables using setMaxTokenSupply and flipSaleState functions. For simplicity let’s assume that the contract owner will first increase the MAX_SUPPLY value and only later configure the sale as active.

Let’s now implement a simple bot that will monitor current state of the blockchain and trigger NFT minting transaction as soon as these conditions are met. The following example uses a popular ethers.js library. I’ve borrowed a few ideas on how to implement it from this Youtube tutorial so check it out for more detailed instructions.

const provider = new providers.InfuraProvider(1);
const dotdotContract = new Contract(DOT_CONTRACT_ADDRESS, DOT_CONTRACT_ABI, provider);
const wallet = new Wallet(PRIVATE_KEY);
const signer = wallet.connect(provider)
const mintOne = '0xa0712d680000000000000000000000000000000000000000000000000000000000000001'

provider.on('block', async (blockNumber) => {
  const maxSupply = parseInt(await dotdotContract.MAX_SUPPLY());
  const totalSupply = parseInt(await dotdotContract.totalSupply());
  const saleIsActive = await dotdotContract.saleIsActive();
  const canMint = maxSupply > totalSupply && saleIsActive;

  if(!canMint) {
    console.log('cannot mint :(');
    return;
  }

  await signer.sendTransaction({
    to: DOT_CONTRACT_ADDRESS,
    data: mintOne,
    type: 2,
    value: ETHER.div(100).mul(5),
    maxFeePerGas: GWEI.mul(350),
    gasLimit: 200000,
    maxPriorityFeePerGas: GWEI.mul(200),
    chainId: 1
  })
})
Import statements have been omitted for brevity


This piece of code monitors the Dotdotdots Smart Contract with every new block. Once the sale is active, it will send a transaction to mint a single NFT token. I’ve configured the gas prices based on values from the previous Dotdotdots gas wars. We have to bid aggressively because this contract is well-known. So there’s probably an army of other bots waiting to snipe these NFTs.

Watch out if you try to run this code in the mainnet! If you fail to mint an NFT, a miner might still include your reverted transaction, and you’ll have to pay the gas price. Using Flashbots would help you mitigate this risk, but this topic is out of the scope of this tutorial.

Let’s now discuss how mempool scanning can help you be one step ahead of other bots and significantly increase the chance of hunting your NFT prey.

Scanning mempool transactions with Blocknative SDK

Blocknative offers a free tier of its commercial mempool scanner. You can receive up to 1000 events a day without paying. For our use case, we only need to spot a single very specific transaction.

Using the Blocknative SDK, we’ll scan mempool looking for the transaction activating the Dotdotdots Smart Contract minting. It will be the execution of the flipSaleState function by the admin account. Later, by fine-tuning the gas pricing, you can position your minting transaction just after the flipSaleState transaction. It means that you could mint your NFT before other bots even know that minting is now possible. Thanks to the mempool, you’re one step or rather block ahead of the competition! In theory, it should let you mint your NFT before the gas wars even start.

Let’s see a sample code:

const options = {
  dappId: BLOCKNATIVE_API_KEY,
  networkId: 1,
  system: 'ethereum',
  ws: WebSocket,
  transactionHandlers: [(event) => mint(event.transaction)]
}

async function main() {
  const sdk = new BlocknativeSdk(options)
  await sdk.configuration({
    scope: DOT_CONTRACT_ADDRESS,
    abi: DOT_CONTRACT_ABI,
    filters: [
      { from:  DOT_ADMIN_ADDRESS },
      { "contractCall.methodName": "flipSaleState" },
      { status: "pending" }
    ],
    watchAddress: true
  })
}

main()
Import statements have been omitted for brevity


We initialize the SDK and configure it to monitor Dotdotdots address on the mainnet. It’s necessary to provide the ABI JSON so that the incoming data will contain the contract call details. Using filters, we scope monitored transactions only to ones initialized by an admin address and executing the flipSaleState function.

Our mint function will receive payload with all the data of a pending transaction initiated by Dotdotdot admin. We can use the following implementation to position our minting transaction just after. This technique is called backrunning:

async function mint(txData) {
  await signer.sendTransaction({
    to: DOT_CONTRACT_ADDRESS,
    data: mintOne,
    type: 2,
    value: ETHER.div(100).mul(5),
    maxFeePerGas: BigNumber.from(txData.maxFeePerGas).sub(1),
    maxPriorityFeePerGas: txData.maxPriorityFeePerGas,
    gasLimit: 200000,
    chainId: 1
  })
}

And that’s it! We’ve just completed a simple NFT hunter bot that’s scanning a mempool to gain a competitive edge.

Summary

Congrats on making it to the end of this lengthy post! But please don’t start counting your future NFT lambos just yet. Copy/pasting the above code samples is not likely to result in a stream of profit. From my limited experience with MEV extraction and NFT hunting, I know that the competition is brutal. These techniques are well known among MEV searchers, so you’d have to improve on them to start winning consistently. But, mempool scanning can be a powerful tool in your arsenal if you’re planning to compete in the blockchain bot wars.

I’ve used Dotdotdots Smart Contracts as a practical example of leveraging mempool because it’s significantly more straightforward than the Defi-related use cases. Ethereum mempool is a complex topic that has a tremendous impact on the whole Defi ecosystem. I hope that this post can serve as a starting point for your journey into exploring the Dark Forest.




Back to index