On October 7th, aibtc.dev dropped the largest airdrop in Bitcoin layers history, sending out nearly 800,000 NFTs. The final spend was about 100 STX excluding one big mint transaction that occupied almost a full block.
aibtc.dev is where hackers are uniting AI with Bitcoin. The airdrop was part of a marketing campaign to leverage NFTs as an ad network, where the image shows up in all existing wallets and can be updated by the team for new announcements.
Shortly after, Bitcoin Faces had a massive airdrop of their own, sending out nearly 800,000 custom NFTs to any wallet that has ever sent a transaction on Stacks, providing a custom PFP generated from their STX address.
To top it all off, aibtc.dev dropped another 136,000 NFTs to all deployed contracts and addresses who deployed a contract, resulting in over 1.7 million NFTs minted over just a few days for a total cost of 250 STX in fees. You can see that spike in activity in this chart:
And if you look at NFT collections by number of holders, you'll see us in the first spot:
How were we able to launch three massive airdrops back to back? Read on and find out.
The Technical Challenge
It all started with a simple question: how many NFTs can be minted in a single transaction?
Existing airdrops averaged about 500-1,000 NFTs per transaction, clogged the network due to their size, and the airdrop code was often included in the contract creating unnecessary bloat.
Thanks to the wizardry from Stacks community legend LNow and careful analysis of the Clarity costs contract, we found the answer: we could mint 14,995 NFTs in a single transaction with some creative code changes!
Now we had to determine how to demo it and prove the theory. We planned out three unique experiments:
- An NFT that serves as a "digital billboard" for aibtc.dev, where the image can be rotated out to display something in everyone's wallet
- An NFT that gives every address a unique PFP, powered by the art and tech behind Bitcoin Faces
- A second “digital billboard” sent to all deployed contracts and their deployers
Signal21 deserves a shout-out for providing the list of active Stacks addresses, which as of October 4th, 2024 totaled 792,096 unique entries. The list of contracts and deployers was sourced from the clarity-deployed-contracts GitHub repository maintained by Boom.
One beautiful thing about blockchains is how the data was already available on-chain, and it provided us a way to directly connect with users to send our message. Traditional methods to achieve the same result would be much more expensive.
In practice we tried to fill an entire Stacks block with the first transaction to prove the theory, however miners were not picking it up even with a hefty fee. We reduced the list size from 14,995 (max) to 12,000 addresses, paying 50 STX for the initial mint.
Following that first transaction, we kept the list size at 12,000 addresses for each one after, which allowed room for other mempool transactions to be picked up by miners. This reduced the pressure of high fee transactions competing for block space, and allowed us to continue minting without congesting the chain.
Stacks 2.1 definitely made this easier than past attempts at airdrops in the 2020-2021 era, and Stacks 3.0 (Nakamoto) with fast blocks would allow this process to take minutes instead of days!
About halfway through the minting process we switched from 12,000 to 6,000 addresses per transaction, allowing for even cheaper fees and up to two transactions getting included per block at a time. This felt like the “sweet spot” for minting such a large list.
Diving Into the Code: Anatomy of an Airdrop
In order to make the contract as efficient as possible, we knew that we had to keep the airdrop list separate from the contract, as the source code of the contract is loaded every time we make a transaction.
The NFT contract itself is extremely simple, with only 58 lines of code mostly implementing the SIP-009 standard. In addition to that we added a few key functions:
- <code-rich-text>airdrop<code-rich-text> which accepts three lists of 5,000, 5,000, and 4,995 principals (addresses) and iterates over them with <code-rich-text>fold<code-rich-text>
- <code-rich-text>drop<code-rich-text> which performs the mint for each entry on the list
- <code-rich-text>get-standard-caller<code-rich-text> which guards the protected functions with a slight twist
The <code-rich-text>airdrop<code-rich-text> and <code-rich-text>drop<code-rich-text> functions require the list(s) to be generated offline and provided through transactions made with the contract. This reduces bloat in the main contract and takes advantage of the cost savings using <code-rich-text>fold<code-rich-text> and <code-rich-text>is-err<code-rich-text> to ensure each mint is successful.
The <code-rich-text>get-standard-caller<code-rich-text> function serves as a guard for sensitive functions like <code-rich-text>mint<code-rich-text>, <code-rich-text>airdrop<code-rich-text>, and <code-rich-text>set-url<code-rich-text>. It allows some flexibility so that either the deployer can call the function, or another contract deployed by the same deployer can also call the function.
We achieved this by deconstructing the principal, whether it’s a standard address or a contract address, and returning either the address that called the function, or the address that deployed the contract that called the function.
The real magic is in squeezing everything possible out of the execution costs. There are only 15,000 read counts available per block, and:
- 3 reads are used to load the contract
- 1 read is used to read variable nextId
- 1 read is used to write new value of nextId
- 14,995 read counts remain for minting NFTs
As mentioned earlier, limiting this to a smaller list of 12,000 mints or less still allows for other transactions to be processed in the block, increasing chances of inclusion by miners.
As for the development process, that was straightforward thanks to Clarinet. Using the SDK we were able to check syntax, write tests, and evaluate costs while iterating on the contract code. We used unit tests to cover the success/failure paths of each function and <code-rich-text>::get_costs<code-rich-text> in the Clarinet console to review costs.
Clarity costs have five dimensions: runtime, read count, read length, write count and write length. Each has its own limit. Costs for the operations we perform in Clarity are outlined in SIP-006, and are currently defined by the costs-3 contract. More information on cost testing and optimization can be found in the Clarity Book and some archived Clarity Notes courtesy of LNow.
For the NFT metadata, we went a different route than other projects, and chose to go with a hosted solution because we want to be able to change the image for the aibtc.dev billboards. Each AIBTC.dev NFT points to an off-chain hosted image that we can update, as seen recently with the upcoming aibtc Champions Sprint.
For Bitcoin Faces, we also used a hosted image, powered by the same API endpoint that is used by the website to generate faces based on a string. This API serves identical images to the layers inscribed on Bitcoin for the original Ordinals collection. In the future we want to move this NFT contract to point to the inscribed Bitcoin Faces layers.
After the airdrop was complete, we served a total of 6M requests / 12gb of image data to indexers, and we were impressed with how fast the images for each NFT came online across wallets, marketplaces, and more!
Refreshing metadata was a little more of a challenge than we expected because we did not include the required print statement per SIP-019. However, we were able to use the reference contract from the SIP to submit an update, and the new image is being shown in most locations.
AI and BTC Come Together
Our aibtc.dev working group meets weekly on Thursday mornings, and anyone is welcome to join.
We have a special competition coming up on Monday, October 28th: the aibtc Champions Sprint, with the first sprint being focused on trading. Compete with others over 5 days and use our AI platform to generate the highest returns!