/ Tutorials

Coding a Mini Token Curated Registry

What is a Token Curated Registry? Before we begin this article I want to mention that a full-fledged TCR has many more features, but for the sake of this tutorial, I'm going to show you how to build the most fundamental components of the smart contract. If you would like a break down of the full 500+ line contract for TCR's or have any comments, please leave a comment here, or reach us on telegram or twitter.

A token curated registry or TCR is a smart contract game that allows users to create listings by staking some tokens on the legitimacy of their listing. Users of the TCR get to vote for or against new listings with the tokens of the registry. If a new listing is proposed and the majority of users reject it, the person who staked tokens to get listed loses their deposit and does not get listed. The staked money goes to those who voted against the listing. If the majority of users vote for a listing, the listing is approved and is displayed for all to see. Whenever the owner may like, they can withdraw their listing and get their deposit back, however, their listing will no longer be visible to the participants of the TCR.

The purpose of a TCR is to be able to maintain a quality public database of services and or products so that the end consumer may do due diligence in an extremely efficient manner. You may be thinking to yourself that this bears resemblance to platforms like Yelp that provide reviews on businesses, and while there are similarities, there are key differences that set the two apart.

  1. Businesses must put up some deposit or stake in order to have their listing reviewed. This means that a business that is doing shady things is less likely to to try to get listed in the first place as the majority of users would likely downvote them and the business would lose their deposit.

  2. There are no concepts of reviews, there is only a concept of listing which is binary. Either you are listed on the registry, or you aren't listed.

  3. Your listing can get removed at any time should a user challenge your worthiness on the list and a majority vote that you aren't worth of being on the list anymore, thus forfeiting your deposit.

  4. The more popular a list, the higher the cost of listing as token prices for that list increase. This means businesses will have to deposit more to be listed in a more desirable list.


Let's think of a simple example of a TCR in real life where we have the following entities:

  • Person A, B, C
  • Business D
  • TCR Food

People A, B and C are all avid restaurant goers. Business D is an Indian restaurant with great chicken tikka masala and naan. Business D buys 20,000 Food tokens and puts them up as a deposit to get listed on the Food TCR. Persons A, B and C see Business D is trying to get listed on TCR Food. Persons A and B who own Food TCR tokens both visit the Business D and find that both the service and food are good. After this experience, they both vote to get Business D listed on the TCR Food. A majority of users upvoted Busienss D so Business D gets listed on TCR Food.

There are other countless consumers who actively monitor TCR Food to find great places to dine, and to their delight find a new listing. Business D gets 30% more business overnight as these people are now aware of a vetted business. Business D is very happy with the investment in the 20,000 Food tokens as it has generated more consumer interest and sales.

Over the coming months, more and more consumers turn to the Food TCR to find great restaurants in their area. As a result more restaurant businesses want to get listed on Food TCR in order to increase their customer base and profits. As this Food TCR becomes more desirable, the tokens become more scarce and thus the deposit fee becomes more expensive.

adchain

An actual TCR that is being used and is live is AdChain that in their own words helps "provide advertisers with a list of websites that offer high - quality inventory for serving digital ads." You can view their registry here.
Now that we know what a TCR is and its purpose, let's code one in Solidity, the go-to smart contract language for Ethereum.
We need to make a few data structures.

  • A mapping to keep track of account's tokens

  • A mapping to keep track of deposited tokens

  • A mapping to keep track of addresses that have been used to create listings

  • A structure to store business information such as address and phone number

  • A mapping to store the index of a business within the TCR

      mapping (address => uint256) public tokenBalance;
      mapping (address => uint256) public tokenDeposited;
      mapping (address => bool) public isAddressUsed;
      struct Listing {
      string  businessName;
      string  businessAddress;
      int40   phoneNumber;
      bool    isListed;
      address businessOwner;    
      }
      mapping (uint8 => Listing) listed;
    

Now that we have the data structures, we need to start building out the logic so that users can interact with the contract.

We need a few functions:

A function to allow users and businesses to buy tokens. These two functions work in tandem to check that the user sent enough ether to buy a single token, and then send them those tokens. TransferTokens function sends tokens from 1 address to another. Later on we can implement safemath to prevent integer overflows, but for now, we want to make our functions as simple as possible.

function transferTokens(
                address sendfrom, 
                address sendto,
                uint amt) 
             internal
{
  require(tokenBalance[sendfrom] >= amt);
  tokenBalance[sendfrom] -= amt;
  tokenBalance[sendto] += amt;
}

function buyTokens() public payable
{
  require(msg.value >= 1000000000000000);
  uint256 amt = msg.value / 1000000000000000;
  require(msg.value != 0 && tokenBalance[address(0)] >= amt);
  transferTokens(address(0), msg.sender, amt);
}

We also need a function to allow a business to get listed on the TCR. This is the create listing function. This function relies on getFreeIndex function which iterates through the mapping and finds a free index to list your business. If there is no free index, we revert so that the code does not execute further. Writing loops in Solidity is generally considered bad practice as it can consume a lot of gas, however if we don't use a loop to find a free index, we would rely on an iterator value, which would break if someone frees a previously used index.

After we have finished finding a valid index, we subtract tokens from the business owner's balance and initialize a structure with the businesses information. We then set this used index to true so that other businesses cannot use this slot and thus overwrite another paying listed member.

function createListing(
                string memory _businessName,
                string memory _businessAddress,
                int40 _phoneNumber
                  ) public    
{        
//if there isn't a free index we revert
        uint8 index = getFreeIndex();
        require(isAddressUsed[msg.sender] == false);
        require(index < totalListings);
        require(!indexFree[index]);
        require(tokenBalance[msg.sender] >= depositPrice);
//set the current index to being used and withdraw tokens from person
        indexFree[index] = true;
        isAddressUsed[msg.sender] = true;
        tokenBalance[msg.sender] -= depositPrice;
        tokenDeposited[msg.sender] += depositPrice;

    listed[index] = Listing({businessName: _businessName,
        businessAddress: _businessAddress,
        phoneNumber: _phoneNumber,
        isListed: true, 
        businessOwner: msg.sender});    
}        
function getFreeIndex() public view returns (uint8)
 {
        for (uint8 i = 0; i < 10; i++)
        {
            if (indexFree[i] == false)
                return i;
        }
        //error code
        require(false, "Error, no free slots");
        return 255;
}

TCR's have a function to allow a business to get de-listed and retrieve their initial deposit so let's go ahead and code that. Remember when we defined our data structures before? We had a mapping that stored deposited tokens, and we need to use that.

function destroyListing(uint8 index) public
{
        require(index < totalListings);
        require(listed[index].businessOwner == msg.sender);
        require(listed[index].isListed == true);

        tokenBalance[msg.sender] += tokenDeposited[msg.sender];

        //We need to examine the assumptions made on this next line
        tokenDeposited[msg.sender] -= tokenDeposited[msg.sender];
        listed[index].isListed = false;
        indexFree[index] = false;
        isAddressUsed[msg.sender] = false;
}

Before we let someone destroy a listing at a specific index in the TCR, we need to first check that they own the listing they are attempting to destroy. We do this by checking that their address is the same as the one that was stored in the initial structure. We also want to make sure that the listing is active, because it does not make sense to let someone delist something that is already delisted. After we are done with validating and we know that this listing can be destroyed, we have to give the tokens back to their rightful owners. We add their tokens from the deposit mapping to their token balance. After we have given them their tokens back from deposit, we have to subtract their tokens from the deposit balance. I put a comment on this line because we make an assumption here that a single person will only register one business on this TCR. If you try to register more than one business under a single address, the contract will block you from doing this.

After we remove a listing, we reset the booleans in the structure and all mappings that check if a space is available to false to let the contract know that others can overwrite this slot.

The reason everything is set to false to indicate that it is not in use is because values are always 0 by default in Solidity, so it doesn't make sense for us to manually set everything to true to initialize the contract as that takes up a lot of gas, and it isn't possible to know what indexes of the mapping will be used in the future as new people add businesses.

That about wraps up everything for this miniature Token Curated Registry. There are other features we could add such as allowing users to vote on listings, however this adds on a lot of complexity and we will cover this in a future tutorial.


If you enjoyed this article, please leave some claps and share it with your fellow Blockchain Enthusiasts! Join the conversation on our Telegram at https://t.me/dennexus. Keep up to date with The DEN on Facebook & Twitter @dennexus, and visit us at https://www.theden.io for blockchain development classes, resources, and more information about all things decentralized.