On building super fast dApps on Solana

ChainCrunch Labs
5 min readApr 25, 2022

One of the main goals of Solana blockchain is to bring Web2 speed into the blockchain space. This has become possible thanks to multiple innovations in the architecture of the network validators and how the consensus mechanism works. But that’s not the end. dApp developers also need to make sure they are using the right APIs and data access patterns for providing a smooth experience to their users. In this article, we will share a few problems that we have encountered so far and how we managed to solve them. We assume you have some knowledge of Solana programming and how the RPC calls work.

But first, a little about what we have done so far:

We built ChainCrunch, an analytics platform where we store, parse, and query all the transactions on Solana.

We built the index, a prototype for a new generation of Solana RPC and APIs covering real-time and historical queries at superfast speed.

We built CoralCube, an NFT aggregator and marketplace, using Metaplex open-source auction house program and our own indexing infra to reach unprecedented speed.

This might sound like three very different projects, but there is a lot of overlap in the tech that we had to develop. We have collaborated with various projects along the way and analyzed their main issues. Here are some of the most common issues we see and how you might be able to solve them.

Issue #1: Too many RPC calls

If your user interface is showing various information from multiple sources you might end up making tons of requests to the RPC provider. This will definitely slow down your dApp. Fortunately, this is an easy issue to solve.

Solution: Batch your RPC requests

If you are trying to get multiple accounts data, there is a getMultipleAccounts RPC method that you can use to fetch all of the data with a single request. This can be used for up to 100 accounts in each request.

But there is a better, more general approach for batching requests which works for all sorts of RPC calls and is not limited to fetching account data. This is not very well documented in the Solana docs and there is no convenience function for it in the javascript web3 library, but we really recommend you implement and use it. There is a single line in the API documentation that states:

Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.

This means you can batch all your requests using the JSON-RPC batching protocol!

Here is a single curl request getting the current slot, the current balance of a single account, and the data for two other accounts all in one request:

curl https://rpc.theindex.io -X POST -H "Content-Type: application/json" -d '
[
{"jsonrpc":"2.0", "id":1, "method":"getSlot"},
{"jsonrpc":"2.0", "id":2, "method":"getBalance", "params":["BsvtXMu1eGKrAhpP636EnNG8LWddxqmCDq8zcEG8CwY3"]},
{
"jsonrpc": "2.0",
"id": 3,
"method": "getMultipleAccounts",
"params": [
[
"vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg",
"4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA"
]
]
}
]
'

Issue #2: Slow RPC calls

Some of the Solana RPC methods like getAccountInfo or even getMultipleAccounts are relatively cheap compared to other methods like getTransaction or getProgramAccounts

Of course, this is connected to how each of these APIs are implemented and what’s happening on the backend for fulfilling these requests. There is no easy solution for this issue on the client side. That’s why we built the index and solved it on the server side! Here is a little summary of what can be improved on the server side:

Indexing Historical Transactions:

If you want to show an activity history of what the users did in your dApp, you need to fetch their historical transactions. Since there is no filtering mechanism in the RPC method, you may fetch transactions that are irrelevant to your dApp like token transfers that you have to hide in the front-end. This adds a lot of complexity to your UI code in terms of filtering them out, pagination, etc. Another way to solve this issue is to create a backend service that indexes all your program transactions and exposes custom APIs that your user interface can use, this needs backend development, server maintenance, monitoring, etc. A third solution that we are working on is to provide APIs for fetching user transactions with a certain program mentioned in the transaction accounts. Stay tuned!

getProgramAccounts:

This is a tough one! By using PDAs you can limit your need for using getProgramAccounts but it will not eliminate it completely. Our solution for this problem is using the Solana geyser plugins. We’ve been using this architecture for a few months and we believe it has a lot of potential. Using this plugin you can extract all the accounts you need for your dApp and index them according to your access patterns. This will definitely make your dApp faster but also needs a lot of investment, you have to maintain Solana validators, the plugins, etc. That’s why we are providing faster getProgramAccounts out of the box on the index. We can create custom indexes on the fly and speed up the calls by orders of magnitude.

Issue #3: Latency in propagating changes

While building CoralCube we faced a trade-off between the consistency of data and the smoothness of UX. We had to make sure that our users’ transactions are finalized in order to have a consistent state of our database, on the other hand, we didn’t want our users to wait for ~30 seconds to see their listings on the website. This is a general problem for dApps that rely on custom data indexing, especially if you update your database based on the most recent transactions. If they use the data that is not finalized yet, they might end up with an inconsistent state and the need for reverting the state but using finalized data introduces a long lag. When you are using the RPC calls for fetching information, all of this is handled automatically, if the user transactions don’t get confirmed the state is automatically reverted by the validator and a page refresh will fix the issue but you have to handle it manually if you have your own indexing solution.

Solution: Trust, but verify

The main solution here is to apply the changes temporarily and revert them if they are not finalized in the blockchain. This is very similar to how Solana validators are working as well, they can keep multiple forks active, but in the end, only one will be used as the finalized version and the others will be ignored.

This can be done by subscribing to your program via WebSockets or the Geyser plugin to listen for the most recent changes and apply them, but don’t forget to revert if needed. Of course, this is a lot of extra work to do but we believe it is necessary to bring the next billion Web2 users to Solana!

Conclusion

Building slick dApp interfaces come with its own set of challenges and it’s not straightforward to create the same web2 experience with a blockchain as the backend, but we believe Solana architecture provides enough tooling and flexibility to make it possible.

We are still early and there is a lot of glass chewing left to be done! #solanaspeed

--

--