Collect Monsters or How to implement non-fungible tokens on EOS

in #eos6 years ago

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.)

monster-105.png

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.