How we built the technology behind Queens+Kings
We developed the entire tech for Hackatao’s Q+K from scratch. In this article, we explain in detail how smart contracts work in this project.
A few weeks ago, the entire Queens+Kings collection by artist duo Hackatao got sold out. After two successful drops, 6900 generative NFT avatars are now in the hands of thousands of collectors worldwide.
Now, a new exciting stage has begun for the project. Users are embarking on an incredible artistic journey creating rarer and more valuable variations of their NFT avatars.
For us at NFT Studios, building the entire technology behind Queens+Kings from scratch was as fun as it was challenging.
We developed the web3 platform, custom smart contracts to support the project, and a series of groundbreaking features and functions to enhance the whole experience, including:
- Modifying & re-minting avatars
- Linking NFT traits to avatars
- Late minting and batch minting features to reduce GAS costs
- NFT burning to prevent scams
- Avatar freezing
Currently, there’s no other NFT avatar project in the crypto-space that offers users such level of interactivity and complexity.
In this article, we explain how this technology works and how we managed to create a unique experience for users, including code snippets from our smart contracts commented on by our VP of Engineering, Santiago Carullo.
Come, see, and learn!
A new way of interacting with NFT avatars
Today, most NFT avatar projects share the same concept: simple artworks featuring varying rarity levels.
These projects offer little to no interaction whatsoever.
Hackatao wanted to create a project that allowed collectors not only to own rare pieces of art but have the freedom to design their own custom NFT avatars.
We aimed to ensure every layer of interactivity worked as smoothly as possible. We also focused on creating a simple, secure, and engaging experience for every user.
Here’s how we did it:
Hack The Royals: modifying & re-minting avatars
The Hack The Royals feature allows Queens+Kings collectors to modify their avatars by changing traits and creating new artworks with millions of possible combinations.
To make that possible from a technical standpoint, we had to create avatars and traits as separate ERC-721 NFTs and develop individual smart contracts for each group.
That way, we make sure the smart contracts communicate seamlessly and allow users to freely edit the PFP artworks.
function setTraitsToAvatar(uint256 _tokenId, uint16[] memory _traits) external onlyAvatarOwner(_tokenId) {
require(traitTypeToAddress.length == _traits.length, "Invalid amount of traits");
uint16 _iTokenId = getInternalMapping(_tokenId);
require(hasMintedTraits[_iTokenId], "Can not modify avatar until original traits are minted");require(!frozenAvatars[_iTokenId], "Can not change the traits of a frozen avatar");bool regenerate;
uint256[] memory traitsPreviousAvatar = new uint256[](_traits.length);
uint256 regeneratePreviousAvatarCounter;for (uint8 i; i < _traits.length; i++) {
if (_traits[i] == avatarTraits[_iTokenId][i]) {
continue;
}ITrait trait = ITrait(traitTypeToAddress[i]);require(_traits[i] == 0 || trait.ownerOf(_traits[i]) == msg.sender, "Caller is not the owner of the trait");uint16 newTraitCurrentAvatarId = trait.traitToAvatar(_traits[i]);if (newTraitCurrentAvatarId != 0 && newTraitCurrentAvatarId != _iTokenId) {
avatarTraits[newTraitCurrentAvatarId][i] = 0;traitsPreviousAvatar[regeneratePreviousAvatarCounter] = getExternalMapping(newTraitCurrentAvatarId);regeneratePreviousAvatarCounter++;
}if (avatarTraits[_iTokenId][i] != 0) {
regenerate = true;
trait.onTraitRemovedFromAvatar(avatarTraits[_iTokenId][i], ownerOf(_tokenId));
}avatarTraits[_iTokenId][i] = _traits[i];if (_traits[i] != 0) {
trait.onTraitAddedToAvatar(_traits[i], _iTokenId);
}
}for (uint256 i; i < regeneratePreviousAvatarCounter; i++) {
if (_exists(traitsPreviousAvatar[i])) {
regenerateAvatar(traitsPreviousAvatar[i]);
}
}if (regenerate) {
regenerateAvatar(_tokenId);
}
}
We did an internal and external mapping of IDs to be able to track every time traits are changed and what trait belongs to what avatar.
This method compares what traits the avatar already has and what traits it is trying to assign.
➔ If the trait is the same as the one the owner is assigning, no action is performed.
➔ If the trait is not the same and belongs to another avatar owned by the wallet, it’s removed from that other avatar and assigned to the new one. The avatar whose trait is being removed gets burned (a)
If an avatar is receiving a new trait it didn’t have (for example, it didn’t have a crown, and the owner is assigning one to it), then the avatar is not burned since we’re benefiting a potential buyer with a new trait.
The externalTokenId does not change, and if there is a pending sale on that avatar, the sale will continue with the same tokenId. [See ‘Protecting collectors from potential scams’ section below for more info]
If the owner removes/changes a trait, the avatar gets burned and regenerated. The same applies to the avatar the owner is extracting the trait from [see (a)]
Linking traits to avatars
To ensure NFT trading works as simple and secure as possible, the project needed each avatar to behave as a single collectible.
For that to work, we had to develop a function that linked traits to their respective avatars.
So, when a collector sells a Q+K avatar, it doesn’t have to trade all the NFTs individually. The assets are transferred automatically to the new owner in a single transaction.
Here’s how it works:
for (uint8 i; i < traitTypeToAddress.length; i++) {
if (avatarTraits[_iTokenId][i] == 0) {
continue;
}ITrait traitContract = ITrait(traitTypeToAddress[i]);
traitContract.onAvatarTransfer(from, to, avatarTraits[_iTokenId][i]);
}
When an avatar is about to be transferred, the code goes through the array of Trait Type contracts and checks if for each type of trait there’s an assignation in the ID mapping (if there’s a link between the avatar and a trait).
If that is the case, that trait is also transferred.
Dealing with high GAS costs
Since every avatar is made of many NFTs, each transaction on the blockchain inevitably involves high GAS fees.
To solve this issue, we implemented a batch minting function that allows users to perform several actions on their avatars in one single transaction, saving costs.
for (uint8 i; i < traitTypeToAddress.length; i++) {
if (avatarTraits[_iTokenId][i] == 0) {
continue;
}ITrait traitContract = ITrait(traitTypeToAddress[i]);
traitContract.onAvatarTransfer(from, to, avatarTraits[_iTokenId][i]);
}
This function is mainly used when minting traits for the first time. The traits that will be minted (and that the user will receive as NFTs) are determined by the metadata.
We’ve also generated a cryptographic signature in the backend to determine:
➔ Which traits are going to be minted
➔ Which tokenId will be the owner of the traitsThe signature is generated using NFTStudios’ private key. This way, we avoid any call to this method that has not been previously validated by us.
We also verify that the caller of this method is the owner of the tokenId from the traits that are going to be minted.
Once everything is validated, each Trait contract is called to mint the traits.
In addition to this, we decided not to force collectors to mint traits when minting an avatar for the first time. When acquiring a Q+K, collectors received a single token (avatar) and could mint the traits later when GAS fees are low.
function mint(uint16 _tokenId, address _to) external onlyMinter {
require(_tokenId > 0 && _tokenId <= totalTokens, "Token ID cannot be 0");
require(totalSupply < totalTokens, "Cannot mint more avatars");externalToInternalMapping[_tokenId] = _tokenId;
internalToExternalMapping[_tokenId] = _tokenId;totalSupply++;_mint(_to, _tokenId);
}
This function consists simply of minting an avatarId.
Since the traits are defined by the metadata and not in the smart contract, there is no need to store more information in the contract.
Protecting collectors from potential scams
Soon after we started developing Queens+Kings’ technology, we realized that due to the nature of the project (users modifying the avatars freely), trading could potentially be exploited by scammers.
Let’s see how:
For example, if an avatar receives an offer on OpenSea, the owner could modify the traits and then accept the transaction. That way, the buyer would be acquiring a different asset than the one it bid for.
To solve this problem, we developed a function in the smart contract that prevents these actions from happening.
Every time a user modifies an avatar (i.e., changes the traits), the NFT gets burned, and a new token is created with a different ID.
Since the original NFT avatar no longer exists, any offers on that asset are automatically canceled on OpenSea.
function regenerateAvatar(uint256 _tokenId) internal returns (uint256) {
address _owner = ownerOf(_tokenId);emit Transfer(_owner, address(0), _tokenId);
emit Transfer(address(0), _owner, latestExternalTokenId);uint16 _iTokenId = getInternalMapping(_tokenId);externalToInternalMapping[latestExternalTokenId] = _iTokenId;
internalToExternalMapping[_iTokenId] = latestExternalTokenId;delete externalToInternalMapping[_tokenId];latestExternalTokenId++;return latestExternalTokenId - 1;
}
When re-minting an avatar, a new tokenId is generated. This new ID is defined as externalTokenId and is related to the original tokenId.
Two Transfer events are generated in the blockchain:
➔ One to burn the old tokenId
➔ One to mint the new tokenIdAccording to the ERC-721 standard, this means: the old token is burned and a new token is created. So, any pending sale on the market is canceled.
More Queens+Kings highlights
For this project, we created a total of 13 smart contracts, including
- A smart contract for avatars
- A smart contract for the raffle system
- A smart contract for the avatar freezing feature
- 10 smart contracts for traits, one for each type
All these smart contracts are public, and you can check them right here: https://etherscan.io/address/0xbb869f884186c3cba0ffa89ab84980cb86f8744d#code.
Custom rules for creating Q+K avatars
There’s another aspect that made this project very challenging for our team and may go unnoticed by most people.
When making generative NFT art, there are usually a lot of conditions that need to be met to preserve the artwork’s identity and aesthetics.
For that reason, when we set out to create the original drop of 6900 avatars with Hackatao, we had to follow strict rules to ensure artistic harmony across the collection.
Some of those rules were:
- The color of the faces always has to match the color of the bodies.
- Body types always have to match the dresses.
- The ‘Hacked’ skin tone can’t go with the ‘Hacked’ background color.
- If the avatar has a beard, the beard’s color needs to match the color of the hair.
These rules limited the number of avatars that could be generated and made an even distribution of traits way harder because we couldn’t leave any traits unassigned.
Also, to preserve the random nature of the project, the entire process needed to occur autonomously.
Using a custom script we developed, we defined the rules and specific order of creation for the traits and avatars. Since this was made off-chain, these rules aren’t written in the smart contract.
Important: In the Hack The Royals feature, these rules are ignored. That means users have complete freedom to customize the avatars.
New upcoming Hack The Royals release
Together with Hackatao, we are developing a new iteration of Hack The Royals.
This update will allow collectors to play around with their avatars and test different combinations of traits to create any model they want without purchasing the parts on the market.
With this new simulator function, users will be able to try different variations using the entire collection of traits.
This feature will be available on the Queens+Kings platform this month.
Avatar freezing
One of the most unique features of this project is that collectors will be able to freeze their avatars forever, ensuring that no future owner can ever modify the traits of that NFT.
In addition to this, a collector owning a frozen avatar will also acquire the IP rights of that artwork and will be able to use it in their own brand and endeavors.
Once an avatar is frozen, the Queens+Kings web platform will run a copy check across the entire collection to block the possibility of a user freezing a new avatar with those same traits.
This feature is planned to be released in April 2022.
The future of this project
Over the following months, the Queens+Kings project will continue to grow and evolve, offering collectors new interactive experiences in the Metaverse and pushing the boundaries of NFT technology.
Here’s a quick look at what is coming next for this amazing project:
We hope Q+K becomes a starting point for other avatar projects to re-think how NFTs and blockchain technology can be leveraged.
We are excited to see the rise of new ambitious projects that set a higher bar for the crypto space.
Our aim is to encourage other creators to develop innovative projects. We are here to support the growth of the Metaverse through groundbreaking technology.