Discover more from tactical_retreat’s stuff
Building a (basic) NFT bot
Beep boop part 2
I have… some experience with sending competitive transactions on Avalanche.
So I set out to build a NFT minting bot. In this article I’ll describe basically the path I took, how you can get started, and some of the pitfalls and optimizations to take into account.
There are a lot of topics to cover, so I’ve split it into a few parts. This part covers the bare minimum you need to get started. It probably won’t be enough to win you The Great Mint Race, but you gotta start somewhere.
I generally don’t publish my code for two reasons:
Fear of being shamed over trash code
Bad opsec; who knows what I’ve accidentally checked in
But fortunately there is lots of sample code on the internet you can adapt, and I will link to it when possible.
Everyone can learn to code, but this might be a bit much as a starting project. However, if you have any reasonable amount of existing experience, you can probably tackle this.
You don’t need to know any Solidity to do so, but a bit of basic knowledge will come in handy when you’re looking at contracts to decide how and when to call them. For me it also came in handy when running experiments against the blockchain.
You will obviously need to know at least one programming language, preferably one that is widely used for web3 (JS, Go, Python, etc). Even if you don’t know a web3 framework, it should be relatively easy to pick up, particularly if you know some of the basic web3 concepts (accounts, transactions, signatures, etc).
You should know at least a little about how NFTs work, and be familiar with the various things Snowtrace provides. For an overview of how to mint from contract, see this summary by @0x_pasta. You should understand the difference between reading from a contract and writing to one, how to look at the code for a verified contract, and how to find the ABI.
A simple NFT minting bot
My first attempt was relatively simple. I copy-pasted some code from my Crabada bot, hardcoded support for the Joepegs minting contract, and threw together a little script to wait until mint time and then send out a mint TX to the contract.
Want to give it a try? First set up a couple of things:
Pick a language and a library. Many languages have a ‘web3’ and an ‘ethers’ library. Personally I use web3py.
A burner account with a few AVAX. You’ll need to put that private key on disk to run your bot, so treat it as compromised.
Find a contract to mint and learn how to use Snowtrace. You’ll need to be able to identify the minting function and download the ABI.
Once you’ve got these items set up, a bare-bones bot consists of a few steps:
Load the private key into memory.
Connect to the AVAX RPC (https://api.avax.network/ext/bc/C/rpc).
Use language/framework specific code to create an interface to call functions on the contract (requires the address + ABI).
Wait until the mint opens.
Create a mint transaction by calling the appropriate function, sign it, send it, and wait for it to complete.
Easy right? To get you started, there is some excellent code in the Crabada.py project. They’re doing basically everything you’ll need to do, although they’re doing a lot of stuff you don’t need, and of course it’s focused on Crabada.
The code to load the private key, connect to the RPC, and call a function on a contract are all in there. You just need to extract it and clean it up a bit, and then write the wrapper script to mint.
An example contract
Lets go find an example on the Campfire minting page. You can usually find something with a very low price to use as a test bed. You could also clone one of these contracts to the Fuji testnet but that’s an unnecessary complication for now.
We found something cheap and mintable, lets click on the contract address to jump over to Snowtrace.
You’re going to spend a fair amount of time on this ‘Contract’ tab in Snowtrace. You can search for the mint function in the code box below, or switch over to the ‘Write Contract’ tab to look for it.
You can also scroll down a bit further and find the Contract ABI. You could download the ABI from here, particularly if you’re only trying to mint something that’s relatively static (e.g. the Joepegs or Kalao launches). But most contracts are bespoke, and therefore ABIs aren’t too reusable.
Pro-tip; Snowtrace offers an API to download the ABI of a contract. Set up your bot so that it fetches the ABI dynamically when it starts up to save you the hassle of downloading it in advance.
Knowing when to mint
Different contracts have different requirements that determine when minting can begin. This is where knowing how a bit of Solidity can come in handy. Sometimes it can be obvious from reviewing the ‘Read’ section of Snowtrace, but other times you need to peek into the source code. Generally contracts fall into two categories:
Time-gated mints. You can only mint when the block timestamp is greater than (or greater than/equal to) a specific timestamp. This timestamp might be hardcoded into the contract, or set by a separate transaction.
Event-gated mints. These depend on some privileged address calling a specific method to enable minting.
Be careful! It can sometimes be tricky to figure out what the conditions are. There could be multiple mint times (allowlist vs general minting). There could be multiple conditions (perhaps a ‘paused’ value AND an ‘allowlistOnly’ value) that need to pass. There could be even weirder stuff.
If you have to wait for a time, it’s relatively straightforward to know when to mint; do a busy loop with a brief sleep (.01s) until the time is passed.
If you have to wait for a condition, you’re best off scanning for new blocks in a slightly looser loop (.1s to prevent getting rate limited by the server) and examining the contents of the new blocks to see if the function got called.
Unless the mint will be uncontested (allowlisted) or weakly contested (obscure), do not try the following:
Don’t call the contract to get a value (e.g. ‘mintOpen’). The RPC endpoint will return cached values to you for longer than is competitive.
Don’t execute a dry-run mint call to determine if it’s open, this is very slow.
Knowing how to mint
You’ll need to know what method to call, and how to call it. Generally you can get this from the Write tab of the contract on Snowtrace, but occasionally you might need to review the code.
The key factors are:
Is the function payable? Then you need to provide a value, even if the value is 0. If it’s not payable, don’t supply a value.
Does the function require a quantity? If so, what quantity can you pass? Some contracts will only let you mint 1, some will let you mint 5 total with 1 per TX, others will let you mint 5 total with 5 per TX.
If a contract DOES let you mint a lot at a time, you will probably need to bump the gas limit up. Generally 300K is enough, but for something that mints 4-7 you might need to bump it to 500K or more.
Are there other, weirder arguments? Some mints might require a ‘proof’ that can only be obtained off-chain. Botting those mints is out of the scope of this article.
Dry run minting
I’ve mentioned this a few times already, but you can generally call() a method to check if it would succeed if submitted. For competitive mints this is not a viable option, but for some allowlisted or obscure mints it’s perfectly fine to test if the TX will succeed before submitting it.
So now you’re a hip and cool NFT botter. What’s cooler than botting with one account? Botting with lots of accounts. Here are a couple of ideas:
Load multiple private keys, and send multiple mint transactions in a row.
You probably want to check in your code to GitHub. Move your private keys out to a separate file, and remember to add it to .gitignore!
Maybe don’t store those private keys in plaintext. There is known malware that can scan for private keys. Do something to mangle them a bit, even if it’s just appending a few extra fake characters and ROT13 the string.
You’ll be single handedly minting things out in no time.
There are a lot of mistakes you can make at this point that will slow your bot down. You can probably still win some mints, but the goal is to win ALL the mints. Here are a couple of common pitfalls to avoid:
Don’t load all your accounts on the fly, particularly if you opt to store your key in a geth encrypted keyfile format, which is very slow to decrypt. Load them all into memory in advance.
Don’t wait for your transactions to get mined into the blockchain; send them all out at once, then start waiting for them.
Don’t send your TX single-threaded, you spend a fair amount of time IO blocked. If you’re using Python, use a ThreadPoolExecutor with at least 4 threads. But send too many TX too quickly and you might get rate limited by the endpoint.
Don’t leave the nonce field blank. You should cache it in advance before you start waiting. If you leave it blank, the library will fetch it before sending the TX.
Don’t leave the gas parameters blank (as above) or manually estimate them immediately before sending your TX. Always hardcode them.
Don’t try to call() the method to confirm it will succeed before sending the TX. This adds an excessive amount of delay.
Need a bit of help getting started?
I’ve created a really minimal NFT minting bot repo using Python. It executes the bare minimum functions necessary to mint an NFT, and should be relatively straightforward for you to read.
This bot is marginally better than clicking on the buttons in the UI yourself. You can apply the ideas in this article to up level it to a more competitive bot.
Get out there and mint, mint, mint!
This should be enough to get you a pretty mediocre NFT minting bot working. I actually used only the techniques described here for quite a while and had a lot of success, until competition over the free mints really heated up.
You probably won’t win a free public Joepegs mint with this. You definitely won’t win the minting competition. But you will probably beat out humans trying to allowlist mint, and even many other less popular mints.
In the next part I’ll discuss more advanced techniques to improve performance, like sharding, boosting, spamming, juicing, and how to dodge, duck, dip, dive and dodge.