Mitigating gas wars, bots and network congestion for Ethereum NFT projects

woof
5 min readSep 16, 2021

--

First of all, I’d like to thank @notchefbob for the inspiration and initial idea of our anti bot/gas war solution. Although it has been done before on other projects, in this article I will take a deeper dive into the technology behind it and how it could be improved to provide full transparency to all NFT purchases on launch.

The SVS Project

From the outset, our vision for SVS was to make all aspects of the project as fair as possible. Our aim was for as fair distribution as possible, with no bots, minimal whaling and no gas wars; the idea to give new holders a high chance to be able to get their hands on a Sneaky Vampire.

Although SVS is still in it’s early days, directly after the drop there was a 52% holder ratio, with little to no gas wasted due to our reservation system detailed below, demonstrating a success for our early vision from our release — which we plan to continue as we follow through our roadmap and bring more utility to SVS holders.

The SVS Contract

For the Sneaky Vampire Syndicate collection, we implemented a solution at the contract level: The public sale function. Along side the usual amount of tokens we passed our mint function a keccak256 hash, signature and nonce. The keccak256 hash contains the ABI encoded user address, quantity to mint, and nonce which is a randomly generated string with a length of 8. Our backend API signed the signature with a private key set in our environment variables. By using the OpenZeppelin ECDSA library, we had the ability to recover the public key that the signature belongs to. The public key was then matched to a _signerAddress variable in order to determine authenticity. The _signerAddress also included a setter function which gave us the ability to invalidate all of the existing signatures and do another launch for leftover reservations which weren’t purchased.

Signature, hash matching

If the recovered public key did not match the _signerAddress variable, the function would immediately revert execution because it wasn’t invoked through our flow, saving gas if someone did attempt to directly invoke the contract method. After which, the function checked whether the nonce already existed in the _usedNonces mapping. If it did, the execution would revert immediately. This is required to prevent someone from re-using the same signature and hash combination to mint again. The next step is where the contract verifies whether the keccak256 hash, passed as argument to the buy function, matches the hash generated by the hashTransaction function. This function hashes the same values as the server-side API, preventing a bad actor from manipulating the amount of tokens reserved for their purchase and/or attempting to use another wallet to mint with someone else’s reservation. Setting the nonce value as used in the _usedNonces mapping. After that, the function is like any other minting function.

The SVS Purchase Flow & Issues

Frontend purchase flow

We forced users through our website using the contract requirements stated above. In order to counter out any form of bot interaction with the web3 purchasing script, we also served the user a question challenge to solve, from an array of several different but easy questions. After solving correctly, an easy keccak256 POW challenge was solved by the browser to slow down someone running multiple browser automation tools (ie — selenium). We verified all of the entries coming in on our side and generated the hashes and signatures for each of the first reservations. This is where we ran into a few hiccups. Our database solution managing the entries could not keep up which slowed down our services. Entries submitted earliest wouldn’t get inserted or inserted later. If selected, the reserved address was allowed to purchase for 4 hours until we changed the _signerAddress variable invalidating all of the previous signatures, so we could do another wave.

Proof Of Work challenge to slow down reservations

A transparent and innovative way of reservations and raffling off entries

For our Generation 2 release we plan on using another chain with cheaper fees, such as Avalanche or Fantom. Because both of these chains support the EVM out of the box, it would be easy to write a reservation/raffle contract. The way that it would work is that the reservation API would check whether an address was already inserted, if not it inserts the reservation into a scalable message bus service, such as the Amazon SQS or Azure Service Bus.

A worker on the server-side will pull messages as soon as they come in from the messaging service and execute an on-chain (Avalanche or Fantom) transaction to the reservation contract. The requirements would only be to add enough AVAX or FTM on to the server-side wallet to send out the reservation requests, because the fees are cheap and transactions are fast. This would allow for 100% transparency on whether the entry has actually been processed as the process is executed fully on-chain. After the reservation period ends and is filled, the server pulls the list of reservations and can allow minting through the frontend.

A simplified server-controlled reservation contract example:

Simplified Solidity Reservation Contract

As everything happens on-chain, anyone who attempts to reserve will be able to see whether they were included or not, and all successful reservations are visible and fully immutable as well. The user will never have to touch the other chain (Avalanche/Fantom) where the reservation contract exists as the actual minting process will still be on Ethereum. The end result of these solutions will be a simplified user experience via a provably fair and immutable way of launching NFTs using a reservation/raffle system.

In my eyes, an NFT launch should be fair and open to every user; whether they have large amount of money or not. Using this solution: gas wars, bot attacks and a congested network can be mitigated. I think there should still be some sort of future development on a decentralised oracle serving signatures where contracts can call these and prevent bots from hitting them directly, but as of now with this solution, it requires a centralized entity for the launch.

Signed,

woof (ex sneaker dev who isn’t allowed into crypto apparently 🤫)

--

--