Tezos Explorer API Best Practices: #1 Don't request the same blockchain data until it actually changes

Tezos API: best practices and recommendations of using the TzKT API, the most reliable and powerful Tezos Explorer API by Baking Bad

Dmitry
Dmitry   Follow

Tezos ecosystem is growing very rapidly. Tezos Foundation continues to award grants to developers and recently announced the Fourth Cohort of Tezos Ecosystem Grants. Congratulations to all grant recipients! As the number of developers grows, so do the needs. One of them is related to obtaining data from the blockchain through an API. Whether you are a developer of Tezos wallet, explorer, exchange, baker, public delegation service or any DApp you need access to the blockchain data.

Ever since the first version of Tezos API TzKT was released, we have been constantly analyzing the API usage and various behavior patterns. We have seen both good and bad patterns, and sometimes even abusive ones, which not only created needless load on the API but also made client applications very inefficient.

Therefore we decided to start this series of articles where we will share best practices of using the Tezos explorer API, which we have successfully been using for a long time in all our projects.

# Table of contents

# How often do I need to make API requests?

This is the most important question that most users unfortunately do not know the right answer too.

For example, let's consider a simple application which sends notifications when the account balance changes. This hypothetical example actually demonstrates how some people use the TzKT Tezos API.

notifierApp() 
{
   var lastBalance = -1;
   
   while (true) 
   {
       var account = get('https://api.tzkt.io/v1/accounts/{address}');
       
       if (account.balance != lastBalance)
       {
           sendNotification("Balance has been changed!");
           lastBalance = account.balance;
       }
       
       sleep(1000);
   }
}

Obviously, this example is extremely inefficient because its hypothetical author couldn't correctly answer the question of how often data should be requested. Let's try to fix it!

First of all, we need to understand how often the data in Tezos blockchain is updated. And it's obvious that the data in the Tezos blockchain isn't updated more often than blocks are created. All the information we need (e.g. account transactions or balance updates) can only appear after the block has been added.

There is absolutely no need to request data every second, because the updated data cannot appear earlier than the new block. At least we can replace the /accounts/{address} request with the much simpler and faster /head request and track the level field to identify the new block, and when it is created we can request the /accounts/{address} endpoint.

However, this is not the most effective approach either. If we remember that blocks appear no more than once a minute (on mainnet), we can make our application much more effective. After requesting the /head endpoint, we can easily calculate estimated time of the next block, just by adding a minute to the last block. The new block will definitely not appear before, and it makes no sense to request /head again before this time.

So, let's create the checking method like this:

var localHead = null;
var nextBlockTime = now();

isNewBlock()
{
   if (now() >= nextBlockTime)
   {
       var remoteHead  = get('https://api.tzkt.io/v1/head');

       if (localHead == null || remoteHead.level != localHead.level) 
       {
           localHead = remoteHead;
           nextBlockTime = remoteHead.timestamp.addMinutes(1);
           return true;
       }
   }
   return false;
}

Now we can use this method as a kind of limiter to avoid excessive requests and excessive iterations of data processing.

In this way, our notifier application will look like this:

notifierApp() 
{
   var lastBalance = -1;
   while (true) 
   {
       //  if there was no new block, we don't do anything.
       if (isNewBlock())
       {
           // the request will be made no more than once a minute.
           var account = get('https://api.tzkt.io/v1/accounts/{address}');

           if (account.balance != lastBalance)
           {
               sendNotification("Balance has been changed!");
               lastBalance = account.balance;
           }
       }

       sleep(1000);
   }
}

As you can see, this small improvement helped us significantly reduce the number of API requests and save bandwidth and other resources.

# Tezos cycle-based data

Sometimes, we need to do something only when a new cycle comes, for example calculate and send Tezos staking rewards for the last cycle. In this case we can use the similar approach:

var localCycle= 0;
var nextCycleTime = now();

isNewCycle()
{
    if (now() >= nextCycleTime) 
    {
        var remoteHead  = get("https://api.tzkt.io/v1/head");
        var remoteCycle = (remoteHead.level - 1) / 4096;

        if (localCycle == 0 || remoteCycle != localCycle)
        {
            localCycle = remoteCycle;
            nextCycleTime = remoteHead.timestamp.AddMinutes(4096 - (remoteHead.level - 1) % 4096);
            return true;
        }   
   }

   return false;
}

rewardsApp()
{
    while(true)
    {
        if (isNewCycle())
        {
    	      var rewards = get("https://api.tzkt.io/v1/rewards/split/{address}/{localCycle - 1}");
            sendRewards(rewards);
        }
    }   
}

# Conclusion

So, every time you need to request data in a loop, first try to understand how often the data is updated. There's no reason to request balance or account transactions more than once a minute, just like there's no reason to request future rewards more than once per cycle, etc.

Try to cache key data locally, such as the time of the last block or the number of the last cycle, to understand when to expect the right data to be updated in the blockchain, and when to make the next request.

# What's next?

This is the first article of series "Tezos Explorer API Best Practices". In the next article we'll talk about data selection in API requests.

Stay tuned!