Collect Monsters or How to implement non-fungible tokens on EOS
The Game
Monstereos is named the first game implemented on the EOS blockchain. It is a Tamagotchi alike game where the user has to care for their monsters. There are no creation fees for new monsters. The user has just to pay for the storage (< 400 bytes). Have a go, and try it out! Note that your monsters need to rest for 4 hours after birth and they can live for 20 hours without feeding. (This is also explained in the ricardian contract of the create action.)
With your monsters, you can now also play in a battle arena. You meet with another user, everyone chooses one of the monsters to play and then you start attacking each other. Depending on the properties of the monster you have a different choice of attack methods. If you know the properties of the other monsters you have higher chances to win the game. There are 109 different types of monsters and each monster becomes unique by its name and its id. The monster can there be identified globally as monstereosio:pets:2558
(first the contract account, then the place of storage/table name, then the id).
Non-fungible tokens
With the global identifier, monsters become non-fungible tokens. On the Ethereum blockchain, the properties of NFTs are defined in EIP-721 and the standard requires the following methods:
- balanceOf(owner)
- ownerOf(tokenId)
- *transferFrom(from, to, tokenId)
- .. and more methods about approved addresses
ownerOf
Non-fungible tokens (NFTs) have only one owner and you can find the owner by using e.g.
cleos get table monstereosio monstereosio pets -L 2558 -l 1
This means one row (-l 1
) starting from primary key with id 2558 (-L 2558
) is queried.
Hence, the owner can be found by querying the table and looking for property owner
in the result of get table
.
tokenOfOwner
On the other hand, the NFTs of a owner can be found with
cleos get table monstereosio monstereosio pets -L owner -U ownerX --key-type i64 --index 2
This call assumes that the second index of the table pets
is the index by owner. For the upper limit (-U)
a name should be used that follows owner
and preceeds the next user. As names are limited to 12 characters appending one letter to owner
like ownerX
is a good choice to just receive the NFTs of the owner.
transfer
When transferring a monster between users without payment it does not need more than an update of the table row in the transfernft(owner, newowner, tokenId)
action.
pets.modify(itr_pet, 0, [&](auto &r) {
r.owner = newowner;
});
The code is taken from github.com/leordev/monstereos
The ricardian contract for that action could contain a part like the following:
### Intent
The intent of the `{{ transfernft }}` action is to transfer ownership of the given digital asset to {{ newowner }}. Consequently move the cost for RAM of storing to the asset to {{ newowner }}. {{actor}} confirms that {{newowner}} has agreed to pay for these costs and adhere to the contracts associated with the digital asset.
### Term
This Contract expires after the code was executed.
The text was adapted from github.com/friedger/monstereos
Conclusion
Non-fungible tokens can be implemented using multi_index tables with the following conventions:
- Tokens are identified by
contract:table_name:id
- The token table has a column
owner
- The second index of the token table is by column
owner
- The contract has a method
transfernft
For Monstereos, the ricardian contract of the createpet
action defines the rights and obligations of a monster owner. If and how this is applicable to the general concept of NFTs needs to be discussed.
Futhermore, NFTs should be transferable with a value associated to the token. This becomes more tricky on the EOS blockchain. The contract has to listen for a certain eosio.token::transfer
action and then react on that. I will explain the solution for monstereos in the next article.