Freelancer developer, ENVIENTA activist, blogger
NFT is a new/old buzzword of the blockchain world. The NFT standard (EIP-721) was created in 2018, but after CrptoKitties this field was relatively silent.
Nowadays NFTs are back in fashion, and everybody wants to make NFT from everything. You can sell your arts or tweets, or anything that you can imagine in form of NFTs. But what is this mystic NFT thing?
NFT is not a big deal. As ERC20 (fungible) tokens, NFT (non-fungible tokens) are also simple “databases” on the blockchain.
While an ERC20 token contract stores balances of Ethereum addresses, the NFT contract stores unique ID -> Ethereum address assignments. When you mint an NFT to yourself, the token contract assigns the unique token ID to your Ethereum address.
Technically this unique ID is the NFT itself.
You can sell it, or buy other NFTs which is a reassignment of the ID. Metadata can be assigned to the ID (by a URI) that represents the thing behind the NFT (ex.: an image, a tweet, etc.). That’s all. Easy, right? I told, NFT is not a big deal…
Although an NFT is not a complicated thing, an NFT contract can be quite complex. If you take a look at 0xcert’s implementation, you will find a complex token logic and many internal state variables.
The problem with complexity is the high transaction cost. In this article, I will show how can you create a minimalistic but fully functional NFT contract on the Ethereum blockchain.
First of all, take a look at some of the state variables of 0xcert’s implementation:
mapping (address => uint256) internal ownerToIds; mapping (uint256 => uint256) internal idToOwnerIndex; mapping (uint256 => address) internal idToOwner; mapping (address => uint256) private ownerToNFTokenCount;
mapping contains the token IDs that are assigned to the Ethereum address,
is a token ID -> Ethereum address mapping,
assigns the token ID to the index of the owner’s token array, and finally,
assigns the owners Ethereum address to the token count (how many NFT are owned by the owner).
As you see, this data structure is strongly redundant, because every mapping could be calculated from the first ownerToIds structure.
All of these mappings are stored on the blockchain, and all of them have to be managed in the contract’s methods. Unfortunately, all of them are needed if we want to implement the ERC-721 token standard. So, what we can do to reduce the complexity and the transactional cost of method calls?
If ERC-721 compatibility is not needed then we can do a simple trick. In this case, we can store on the blockchain only the things that are absolutely necessary.
Every other thing can be stored in an external database. If we store the token balances, token lists, etc. in an external (non-blockchain) storage then we don’t need the redundant mappings. The contract will be simpler and the transactions will be cheaper. After the theory, let’s take a look at the code:
Only 24 lines of code. Not bad. But is it really a fully functional NFT contract? Let’s see. It has 2 methods: mint and transfer. The mint method creates a new NFT token. It has 2 parameters
. The first parameter is the target Ethereum address that will own the newly minted token.
The second parameter is a unique hash that points to the metadata on IPFS. In the original ERC-721 specification metadata is pointed by a
. Why do we use an IPFS hash instead? There are many reasons.
An IPFS hash is only 32 bytes, so writing it to the blockchain is cheaper than a URI string. The second reason is that IPFS is immutable. The content cannot be changed, because if it changes the IPFS hash would also change. So, storing the data on IPFS and writing the hash to the blockchain is something like writing the data itself to the blockchain, but much cheaper.
The mint method reads the current value of the
variable to get a unique token ID. The method increments this variable by every call, so the token ID will be absolutely unique. The method stores the token ID - Ethereum address assignment into the
mapping, which is the only one mapping what we store on the blockchain. In the last step, the method emits a Mint event, which will be very important later.
The transfer method has two parameters
. The first parameter is the target address that will own the NFT, and the second parameter is the ID of the token. The first line of the method checks the ownership of the token using the
mapping. Only the token owner can call the transfer method. If the token owned by the caller, then the method modifies the assignment in the mapping and emits a Transfer event.
This two method is enough for a fully functional NFT. But what about the balances, ownership queries, etc.? Here comes the external database. When a contract emits an event, it will be stored on the blockchain in the logs.
To show this, let’s check the ERC-721 methods and how an external database can provide the data.
- These can be easily calculated from Mint and Transfer events
- Our minimalistic NFT doesn’t provide approval logic, only the owner can transfer the tokens. If needed, this feature can be implemented by a second mapping.
- Transfer safety can be checked externally, and from parameter is meaningless here, because our tokens can be transferred only by the owner.
- The URI of the token can be generated from the IPFS hash part of the Mint event.
- These can be calculated easily from the Mint and Transfer events.
As you can see, our minimalistic NFT token can do everything that an ERC-721 token can do except the approval logic, but it can be implemented if needed.
Create your free account to unlock your custom reading experience.