Skip to main content

Shard optimizations on TON

Architecture basics

TON processes countless transactions in parallel. This capability relies on the infinite sharding paradigm. When the load on a group of validators approaches its throughput limit, the system splits (shards) the group. Two groups of validators then independently and in parallel process this load. The system deterministically performs these splits, and the contract address associated with the transaction determines which group processes it. The system processes addresses close to each other (sharing the same prefix) in the same shard.TON processes countless transactions in parallel. This capability relies on the infinite sharding paradigm. When the load on a group of validators approaches its throughput limit, the system splits (shards) the group. Two groups of validators then independently and in parallel process this load. The system deterministically performs these splits, and the contract address associated with the transaction determines which group processes it. The system processes addresses close to each other (sharing the same prefix) in the same shard.

When one contract sends a message to another, two scenarios can occur: either both contracts reside in the same shard or in different shards. In the first scenario, the current group already holds all the necessary data and can process the message immediately. In the second scenario, the system must route the message from one group to another. To prevent message loss or double-processing, the system requires proper accounting. It achieves this by registering a queue of outgoing messages from the sender's shard in a MasterChain block. The receiver's shard must explicitly confirm that it has processed this queue. This overhead slows down cross-shard message delivery, as the system requires at least one MasterChain block between the block where the message was sent and the block where it was consumed. Typically, this delay lasts about 12-13 seconds.

Since the system processes all transactions for a single account within one shard, the transactions per second (TPS) speed for that account becomes limited. This limitation means that when designing an architecture for a new mass-scale protocol, you should aim to avoid central points. Furthermore, if a chain of transactions follows the same route, sharding will not speed up its processing. Each contract in the chain will face the same TPS limit, and delivery latency will increase the overall chain processing time.

In a mass-scale system, the trade-off between latency and throughput becomes the key factor separating good and great protocols.

To shard or not to shard

To enhance user experience and reduce processing time, protocols must identify which parts of their system can process transactions in parallel and benefit from sharding to improve throughput. They must also determine which parts are strictly sequential and will achieve lower latency if placed in the same shard.

A prime example of throughput optimization involves Jettons. Since transfers from A to B and C to D operate independently, the system can process them in parallel. By distributing all jetton wallets randomly and uniformly across the address space, the system achieves a balanced load distribution across the blockchain, enabling throughput of hundreds of transfers per second (and potentially thousands in the future) with acceptable latency.

On the other hand, consider a smart contract, contract A, that interacts with jettons. Suppose contract A performs an action when it receives jetton X, and its associated jetton wallet is contract B. Placing contract A and contract B in different shards will not increase throughput. Instead, each incoming transfer will follow the same chain of addresses, creating bottlenecks at each step. Placing contracts A and B in the same shard improves latency, reducing the overall chain processing time.

Practical conclusion for smart contract developers

If you rely on a single smart contract to execute business logic, consider deploying multiple instances of that contract to leverage TON’s parallelism. If this approach isn’t feasible and your smart contract interacts with a predefined set of other contracts (such as jetton wallets), aim to place them in the same shard. You can often achieve this off-chain by brute-forcing specific contract addresses to ensure all desired jetton wallets have neighboring addresses. In some cases, onchain brute-forcing is also acceptable.

Upcoming node and network performance improvements will likely boost the throughput of individual shards and reduce delivery latency. However, these advancements will coincide with a growing number of users. As more users join the network, shard optimization will become increasingly critical. Eventually, it will determine the success of mass-scale applications: users will naturally prefer the most convenient option, meaning the application with lower latency. Therefore, don’t delay shard-optimizing your application in anticipation of overall network improvements. Act now! In many cases, shard optimization may even outweigh gas optimization in importance.

Practical conclusion for services

Deposits

If you expect deposits at a rate exceeding, for example, 30 transfers per second, you should use multiple addresses to accept them in parallel and achieve high throughput. If you know the user’s deposit address — for instance, through a transaction initiated via TON Connect - select the deposit address closest to the user’s wallet address. Below is the ready-to-use TypeScript code for choosing the closest address:

import { Address } from "@ton/ton";

function findMatchingBits (a: number, b: number, start_from: number) {
let bitPos = start_from;
let keepGoing = true;
do {
const bitCount = bitPos + 1;
const mask = (1 << (bitCount)) - 1;
const shift = 8 - bitCount;
if(((a >> shift) & mask) == ((b >> shift) & mask)) {
bitPos++;
}
else {
keepGoing = false;
}
} while(keepGoing && bitPos < 7);

return bitPos;
}

function chooseAddress(user: Address, contracts: Address[]) {
const maxBytes = 32;
let byteIdx = 0;
let bitIdx = 0;
let bestMatch: Address | undefined;

if(user.workChain !== 0) {
throw new TypeError(`Only basechain user address allowed:${user}`);
}
for(let testContract of contracts) {
if(testContract.workChain !== 0) {
throw new TypeError(`Only basechain deposit address allowed:${testContract}`);
}
if(byteIdx >= maxBytes) {
break;
}
if(byteIdx == 0 || testContract.hash.subarray(0, byteIdx).equals(user.hash.subarray(0, byteIdx))) {
let keepGoing = true;
do {
if(keepGoing && testContract.hash[byteIdx] == user.hash[byteIdx]) {
bestMatch = testContract;
byteIdx++;
bitIdx = 0;
if(byteIdx == maxBytes) {
break;
}
}
else {
keepGoing = false;
if(bitIdx < 7) {
const resIdx = findMatchingBits(user.hash[byteIdx], testContract.hash[byteIdx], bitIdx);
if(resIdx > bitIdx) {
bitIdx = resIdx;
bestMatch = testContract;
}
}
}
} while(keepGoing);
}
}
return {
match: bestMatch,
prefixLength: byteIdx * 8 + bitIdx
}
}

If you expect jetton deposits, you should not only create multiple deposit addresses but also shard-optimize them. Ensure each deposit address resides in the same shard as its corresponding jetton wallet. You can use a generator for such addresses, available here. Additionally, selecting the deposit address closest to the user’s wallet address will further improve efficiency.

Withdrawals

The same applies to withdrawals; if you need to send a large number of transfers per second, it is advisable to have multiple sending addresses and shard-optimize them with jetton wallets if necessary.

Shard optimizations 101

Explain shards like I'm from web 2

Like any other blockchain, the TON blockchain is a network, so it makes sense to try to explain it in web2 (IPv4) networking terms.

Endpoints

In general networking, the endpoint is a physical device; in blockchain, the endpoint is a smart contract.

Shards

In this analogy, a shard functions like a subnet. The key difference lies in the addressing scheme: IPv4 uses a 32-bit system, while TON employs a 256-bit one.
The shard prefix of a contract address identifies the group of validators responsible for computing the results of its incoming messages. From a networking perspective, a request within the same network segment processes faster than one routed elsewhere. This principle resembles using a CDN to host content closer to end users; in TON, we deploy contracts closer to the users.

When the load on a shard surpasses a certain threshold, the system splits the shard. This split allocates dedicated computational resources to the overloaded contract and isolates its impact on the broader network. Currently, the maximum shard prefix length is 4 bits, allowing the blockchain to split into a maximum of 16 shards, ranging from prefix 0 to 15.

Problems during shard optimizations

Let's get more practical.

Check if two addresses belong to the same shard

Since we know that the shard prefix is a maximum of 4 bits, a code snippet for it could look like this:

import { Address } from '@ton/core';
const addressA = Address.parse(...);
const addressB = Address.parse(...);

if((addressA.hash[0] >> 4) == (addressB.hash[0] >> 4)) {
console.log("Same shard");
} else {
console.log("Nope");
}

From the human perspective, the easiest way to check the address shard is to look at the address raw form. One could use the address page for it. Let's test it on the USDT address, for example: EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs. You will see 0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe as a raw representation, and the first 4 bits are essentially the first hexadecimal symbol - b. Now we know that the USDT minter is located in shard b (hex) or 11 (decimal).

How to deploy a contract to a specific shard

To understand how this works, you must first grasp how a contract address depends on its code and data. Essentially, the system generates the address by computing a SHA256 hash of the contract’s code and data at deployment time.

Given this, the only way to deploy a contract with the same code in a different shard is to manipulate its initial data. The data field influencing the resulting contract address is called a nonce. You can use any field that is either safe to update after deployment or does not directly affect contract execution for this purpose.

One of the earliest examples of this principle in action is the vanity contract. It includes a salt data field specifically designed to brute-force a value that produces the desired address pattern.

Placing a contract into a specific shard follows the same approach, except the prefix you need to match is much shorter. A simple starting point for this is the wallet contract.

  • The Wallet creation for different shard article illustrates the case where the public key is used as a nonce to put the wallet in a specific shard.
  • The other example is turbo-wallet using subwalletId for the same purposes. You can pretty quickly extend the ShardedContract interface using your contract constructor to make it sharded too.

Mass jetton distribution solutions

If you need to distribute jettons to tens or hundreds of thousands - or even millions - of users, refer to this post. We recommend leveraging existing, battle-tested services. Several of these solutions are deeply optimized, offering shard-optimized performance and lower costs compared to custom-built alternatives:

  • Mintless Jettons: During a Token Generation Event (TGE), you can enable users to claim a predefined airdrop directly from the jetton wallet contract. This approach is cost-effective, eliminates the need for additional transactions, and operates on-demand (only users ready to spend jettons will claim them). [LINK]
  • Tonapi Solution for Jetton Mass Sending: This solution distributes existing jettons by sending them directly to user wallets. Proven by Notcoin and DOGS (handling millions of transfers each), it optimizes latency, throughput, and costs. Mass jetton sending
  • TokenTable Solution for Decentralized Claim: This approach lets users claim jettons from specific claim transactions (users cover the fees). Tested by Avacoin and DOGS (millions of transfers), it maximizes throughput while managing costs. Introduction