LiteClient Module
The Ton.LiteClient module provides direct access to TON blockchain via the ADNL protocol. It's the recommended way for production applications.
Features
- ✅ Direct node communication via ADNL protocol
- ✅ Automatic load balancing across multiple servers
- ✅ Automatic reconnection on connection loss
- ✅ Query retry logic for reliability
- ✅ Type-safe API with frontend models
- ✅ No rate limits (depends on node capacity)
Quick Start
using Ton.LiteClient;
using Ton.Core.Addresses;
// Connect to TON mainnet
LiteClient client = await LiteClientFactory.CreateFromUrlAsync(
"https://ton.org/global-config.json"
);
// Get latest block
MasterchainInfo info = await client.GetMasterchainInfoAsync();
Console.WriteLine($"Latest block: {info.Last.Seqno}");
// Get account balance
Address address = Address.Parse("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");
AccountState state = await client.GetAccountStateAsync(address, info.Last);
Console.WriteLine($"Balance: {state.BalanceInTon} TON");
Factory Methods
CreateFromUrlAsync
Automatically connects to TON network from config URL:
// Mainnet
LiteClient client = await LiteClientFactory.CreateFromUrlAsync(
"https://ton.org/global-config.json"
);
// Testnet
LiteClient testnet = await LiteClientFactory.CreateFromUrlAsync(
"https://ton.org/testnet-global.config.json"
);
Behavior:
- Downloads and parses network config
- If 1 server → creates
LiteSingleEngine - If multiple servers → creates
LiteRoundRobinEngine - Automatically manages connections
Create
Connect to a single server:
LiteClient client = LiteClientFactory.Create(
host: "65.109.14.188",
port: 14432,
serverPublicKeyBase64: "aF91CuUHuuOv9rm2W5+O/4h38M3sRm40DtZRrwb6fJ4="
);
CreateRoundRobin
Explicitly use multiple servers:
var servers = new[]
{
("65.109.14.188", 14432, "aF91CuUHuuOv9rm2W5+O/4h38M3sRm40DtZRrwb6fJ4="),
("65.21.7.173", 50552, "QnGFe9kihW4YKBq1RQODjpL4f+j4OKvqYPNVXZaSkp0=")
};
LiteClient client = LiteClientFactory.CreateRoundRobin(
servers.Select(s => (s.Item1, s.Item2, s.Item3)).ToArray(),
reconnectTimeoutMs: 10000
);
Core Methods
GetMasterchainInfoAsync
Get latest masterchain state:
MasterchainInfo info = await client.GetMasterchainInfoAsync();
Console.WriteLine($"Latest block seqno: {info.Last.Seqno}");
Console.WriteLine($"Workchain: {info.Last.Workchain}");
Console.WriteLine($"Shard: {info.Last.Shard}");
Console.WriteLine($"Root hash: {Convert.ToHexString(info.Last.RootHash)}");
Console.WriteLine($"File hash: {Convert.ToHexString(info.Last.FileHash)}");
GetMasterchainInfoExtAsync
Get extended info with server capabilities:
MasterchainInfoExt info = await client.GetMasterchainInfoExtAsync();
Console.WriteLine($"Protocol version: {info.Version}");
Console.WriteLine($"Capabilities: {info.Capabilities}");
Console.WriteLine($"Last block time: {info.LastUtime}");
Console.WriteLine($"Server time: {info.Now}");
GetAccountStateAsync
Get complete account state at specific block:
Address address = Address.Parse("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");
BlockId block = info.Last;
AccountState state = await client.GetAccountStateAsync(address, block);
// Balance
Console.WriteLine($"Balance: {state.Balance} nanoTON");
Console.WriteLine($"Balance: {state.BalanceInTon:F4} TON");
// State
Console.WriteLine($"State: {state.State}");
Console.WriteLine($"Is active: {state.IsActive}");
Console.WriteLine($"Is contract: {state.IsContract}");
// Code and data (if contract)
if (state.Code != null)
{
Console.WriteLine($"Code size: {state.Code.Bits.Length} bits");
Console.WriteLine($"Code hash: {Convert.ToHexString(state.Code.Hash(0))}");
}
if (state.Data != null)
{
Console.WriteLine($"Data size: {state.Data.Bits.Length} bits");
}
// Last transaction
if (state.LastTransaction != null)
{
Console.WriteLine($"Last TX LT: {state.LastTransaction.Lt}");
Console.WriteLine($"Last TX hash: {Convert.ToHexString(state.LastTransaction.Hash)}");
}
LookupBlockAsync
Find block by seqno:
BlockId block = await client.LookupBlockAsync(
workchain: -1, // -1 = masterchain, 0 = basechain
shard: -9223372036854775808, // full shard
seqno: 1000000
);
Console.WriteLine($"Found block: {block.Seqno}");
LookupBlockByUtimeAsync
Find block by timestamp:
int timestamp = (int)DateTimeOffset.UtcNow.AddHours(-1).ToUnixTimeSeconds();
BlockId block = await client.LookupBlockByUtimeAsync(
workchain: -1,
shard: -9223372036854775808,
utime: timestamp
);
Console.WriteLine($"Block at timestamp {timestamp}: seqno {block.Seqno}");
LookupBlockByLtAsync
Find block by logical time:
BlockId block = await client.LookupBlockByLtAsync(
workchain: -1,
shard: -9223372036854775808,
lt: 1000000000
);
GetBlockHeaderAsync
Get block header with proof:
BlockHeader header = await client.GetBlockHeaderAsync(blockId);
Console.WriteLine($"Block: {header.Id.Seqno}");
Console.WriteLine($"Mode: {header.Mode}");
Console.WriteLine($"Proof size: {header.HeaderProof.Length} bytes");
// Parse proof (MerkleProof cell)
Cell proofCell = Cell.FromBoc(header.HeaderProof)[0];
Cell actualHeader = proofCell.UnwrapProof(); // Extract data from MerkleProof
GetAllShardsInfoAsync
Get all shard blocks for a masterchain block (with full shard description):
ShardDescr[] shards = await client.GetAllShardsInfoAsync(masterchainBlock);
Console.WriteLine($"Found {shards.Length} shard blocks:");
foreach (ShardDescr shard in shards)
{
Console.WriteLine(
$" Workchain: {shard.Workchain}, Shard: {shard.Shard}, Seqno: {shard.Seqno}, " +
$"StartLt: {shard.StartLt}, EndLt: {shard.EndLt}, GenUtime: {shard.GenUtime}");
}
ListBlockTransactionsAsync
List transactions in a block:
BlockTransactions txs = await client.ListBlockTransactionsAsync(
blockId,
count: 100, // max transactions to fetch
after: null // for pagination
);
Console.WriteLine($"Transactions: {txs.Transactions.Count}");
Console.WriteLine($"Incomplete: {txs.Incomplete}");
foreach (BlockTransaction tx in txs.Transactions)
{
Console.WriteLine($"Account: {Convert.ToHexString(tx.AccountHash)}");
Console.WriteLine($"LT: {tx.Lt}");
Console.WriteLine($"Hash: {Convert.ToHexString(tx.Hash)}");
}
// Pagination
if (txs.Incomplete && txs.Transactions.Count > 0)
{
var lastTx = txs.Transactions.Last();
var nextPage = await client.ListBlockTransactionsAsync(
blockId,
count: 100,
after: new LiteServerTransactionId3 {
Account = lastTx.AccountHash,
Lt = lastTx.Lt
}
);
}
GetConfigAsync
Get blockchain configuration:
ConfigInfo config = await client.GetConfigAsync(blockId);
Console.WriteLine($"Config from block: {config.Block.Seqno}");
Console.WriteLine($"State proof size: {config.StateProof.Length}");
Console.WriteLine($"Config proof size: {config.ConfigProof.Length}");
// Parse config from proof
Cell[] cells = Cell.FromBoc(config.ConfigProof);
// ... parse config parameters
GetTimeAsync
Get current server time:
DateTimeOffset serverTime = await client.GetTimeAsync();
Console.WriteLine($"Server time: {serverTime}");
GetVersionAsync
Get server version and capabilities:
(int version, long capabilities, int now) = await client.GetVersionAsync();
Console.WriteLine($"Version: {version}");
Console.WriteLine($"Capabilities: {capabilities}");
Console.WriteLine($"Server timestamp: {now}");
Engines
ILiteEngine Interface
All engines implement ILiteEngine:
public interface ILiteEngine : IDisposable
{
bool IsReady { get; }
bool IsClosed { get; }
Task<TResponse> QueryAsync<TRequest, TResponse>(
TRequest request,
Func<TLReadBuffer, TResponse> responseReader,
int timeout = 5000,
CancellationToken cancellationToken = default
) where TRequest : ILiteRequest;
Task CloseAsync();
event EventHandler? Connected;
event EventHandler? Ready;
event EventHandler? Closed;
event EventHandler<Exception>? Error;
}
LiteSingleEngine
Manages connection to a single lite server:
Features:
- Automatic connection on first query
- Automatic reconnection on failure
- Query queuing during reconnection
- Resends pending queries after reconnect
Events:
LiteSingleEngine engine = new("host", 1234, publicKey);
engine.Connected += (s, e) => Console.WriteLine("Connected!");
engine.Ready += (s, e) => Console.WriteLine("Ready!");
engine.Closed += (s, e) => Console.WriteLine("Closed");
engine.Error += (s, ex) => Console.WriteLine($"Error: {ex.Message}");
LiteRoundRobinEngine
Load balances queries across multiple engines:
Features:
- Distributes queries round-robin
- Automatically skips unavailable engines
- Retries on failure (up to 200 attempts for no engines, 20 for errors)
- 100ms delay between retries
- Tracks ready engines dynamically
ILiteEngine[] engines = {
new LiteSingleEngine("server1", port1, key1),
new LiteSingleEngine("server2", port2, key2),
new LiteSingleEngine("server3", port3, key3)
};
LiteRoundRobinEngine roundRobin = new(engines);
Console.WriteLine($"Total engines: {roundRobin.EngineCount}");
Console.WriteLine($"Ready engines: {roundRobin.ReadyEngineCount}");
Error Handling
Common Exceptions
try
{
AccountState state = await client.GetAccountStateAsync(address, block);
}
catch (TimeoutException)
{
// Query timed out (default 5000ms)
Console.WriteLine("Request timed out");
}
catch (InvalidOperationException ex)
{
// Engine closed or invalid state
Console.WriteLine($"Operation failed: {ex.Message}");
}
catch (IOException ex)
{
// Network/connection error
Console.WriteLine($"Connection error: {ex.Message}");
}
catch (Exception ex)
{
// Other errors
Console.WriteLine($"Error: {ex.Message}");
}
Timeout Configuration
// Per-method timeout
AccountState state = await client.GetAccountStateAsync(
address,
block,
timeout: 10000 // 10 seconds
);
// Cancellation token
CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));
AccountState state = await client.GetAccountStateAsync(
address,
block,
cancellationToken: cts.Token
);
Best Practices
1. Use Round-Robin for Production
// ✅ Good: Automatic load balancing
LiteClient client = await LiteClientFactory.CreateFromUrlAsync(
"https://ton.org/global-config.json"
);
// ❌ Avoid: Single point of failure
LiteClient client = LiteClientFactory.Create("single-server", 1234, key);
2. Reuse Client Instance
// ✅ Good: One client per application
public class MyService
{
private readonly LiteClient _client;
public MyService()
{
_client = await LiteClientFactory.CreateFromUrlAsync(...);
}
}
// ❌ Avoid: Creating client for each request
public async Task GetBalance(Address addr)
{
using var client = await LiteClientFactory.CreateFromUrlAsync(...);
// ...
}
3. Handle Errors Gracefully
// ✅ Good: Retry logic
int retries = 3;
for (int i = 0; i < retries; i++)
{
try
{
return await client.GetAccountStateAsync(address, block);
}
catch (TimeoutException) when (i < retries - 1)
{
await Task.Delay(1000);
}
}
4. Dispose Properly
// ✅ Good: Using statement
await using LiteClient client = await LiteClientFactory.CreateFromUrlAsync(...);
// ...
// Or: Explicit disposal
LiteClient client = await LiteClientFactory.CreateFromUrlAsync(...);
try
{
// ...
}
finally
{
client.Dispose();
}
Network Configs
Mainnet
https://ton.org/global-config.json
Testnet
https://ton.org/testnet-global.config.json
Custom Network
NetworkConfig config = new()
{
LiteServers = new List<LiteServerConfig>
{
new()
{
Ip = 1098487780, // IP as integer
Port = 14432,
Id = new PublicKeyConfig
{
Key = "aF91CuUHuuOv9rm2W5+O/4h38M3sRm40DtZRrwb6fJ4="
}
}
}
};
LiteClient client = LiteClientFactory.CreateFromConfig(config);