TypeScript: ton-lite-client
Connect ton-lite-client to a private TON liteserver over ADNL: engine setup, config IP conversion, masterchain info, account balance in GRAM, proofs.
ton-lite-client speaks the raw liteserver protocol (ADNL over TCP) directly from Node.js. No HTTP gateway, no JSON-RPC middleman — your process opens an encrypted TCP session to the liteserver and exchanges TL-serialized queries. This is the lowest-latency way to read TON state from TypeScript.
Install
npm install ton-lite-client @ton/core
@ton/core provides Address, Cell, and fromNano, which you will use alongside the client.
Connect to a private liteserver
A TONNode endpoint is a standard liteserver entry: host, port, and a base64 Ed25519 public key, all shown in the dashboard after checkout. It is drop-in for any global-config-compatible client.
LiteSingleEngine manages one connection. Pass the host with an explicit tcp:// prefix and the key as a Buffer:
import { LiteClient, LiteSingleEngine } from 'ton-lite-client';
const engine = new LiteSingleEngine({
host: 'tcp://<your-host>:41000', // from the dashboard
publicKey: Buffer.from('<base64-key>', 'base64'),
});
const client = new LiteClient({ engine });
LiteClient also accepts optional batchSize (default 100, max queries in flight per engine) and cacheMap (LRU cache sizing for blocks, headers, shards, and account states; default 1000 entries each).
Round-robin across endpoints
LiteRoundRobinEngine wraps several LiteSingleEngine instances and distributes queries across them. Use it when you have more than one endpoint or want failover:
import { LiteClient, LiteRoundRobinEngine, LiteSingleEngine, LiteEngine } from 'ton-lite-client';
const endpoints = [
{ host: 'tcp://<your-host>:41000', key: '<base64-key>' },
// additional liteservers, if any
];
const engines: LiteEngine[] = endpoints.map(
(e) =>
new LiteSingleEngine({
host: e.host,
publicKey: Buffer.from(e.key, 'base64'),
}),
);
const engine = new LiteRoundRobinEngine(engines);
const client = new LiteClient({ engine });
With a single private endpoint, LiteSingleEngine alone is fine — round-robin adds nothing until you have a second server.
Converting global-config IPs
Entries in a TON global config (liteservers[].ip) store the IPv4 address as a signed 32-bit integer, not a dotted string. If you mix a config-file entry into your engine list, convert it first:
function intToIP(int: number): string {
const part1 = int & 255;
const part2 = (int >> 8) & 255;
const part3 = (int >> 16) & 255;
const part4 = (int >> 24) & 255;
return `${part4}.${part3}.${part2}.${part1}`;
}
// global-config entry: { ip: <int>, port: <port>, id: { '@type': 'pub.ed25519', key: '<base64>' } }
const host = `tcp://${intToIP(server.ip)}:${server.port}`;
const publicKey = Buffer.from(server.id.key, 'base64');
Note the values can be negative — the config uses signed integers, and the bitwise math above handles that correctly. TONNode endpoints skip this step entirely: the dashboard gives you the hostname as-is.
Read masterchain info and an account balance
getMasterchainInfo() returns the latest masterchain block ID; getAccountState(address, blockId) returns the account at that block. Balance comes back in nanogram as a bigint — divide by 10⁹ (or use fromNano) to print GRAM:
import { LiteClient, LiteSingleEngine } from 'ton-lite-client';
import { Address, fromNano } from '@ton/core';
async function main() {
const engine = new LiteSingleEngine({
host: 'tcp://<your-host>:41000',
publicKey: Buffer.from('<base64-key>', 'base64'),
});
const client = new LiteClient({ engine });
const master = await client.getMasterchainInfo();
console.log('masterchain seqno:', master.last.seqno);
const address = Address.parse('UQD9EJ0Qwjrbaz1nSDwlvIBH0AS0y0PYY_TU5cJx6bSprYr0');
const account = await client.getAccountState(address, master.last);
console.log('balance:', fromNano(account.balance.coins), 'GRAM');
if (account.lastTx) {
console.log('last tx lt:', account.lastTx.lt.toString());
}
engine.close();
}
main();
getAccountState resolves to more than the balance: state (the parsed Account, or null if uninitialized), lastTx (lt and hash of the most recent transaction), raw (the state BoC), and the block references it was read against (block, shardBlock). Always call engine.close() when done, or the process will keep the TCP session open.
Merkle proofs
Liteserver responses carry Merkle proofs alongside the data: getAccountState exposes them as proof (account state against the shard block) and shardProof (shard block against the masterchain block). ton-lite-client returns these proof BoCs raw — it does not walk the chain of trust back to a trusted key block for you, the way the reference C++ lite-client does. For most backends reading from a liteserver they operate or rent under contract, that is acceptable. If you need trust-minimized reads (e.g., verifying state served by third parties), validate the proof cells against a pinned key block yourself before trusting the payload.
Production notes
Public-config liteservers are shared and routinely return ADNL timeouts (error 652) or not ready under load, which surfaces in ton-lite-client as rejected queries with no retry-after signal. The official TON docs recommend a dedicated liteserver for production traffic. A private endpoint with a guaranteed request rate — see plans from 10 req/s at $20/mo — removes that failure mode; the entry it gives you plugs into LiteSingleEngine exactly as shown above. Questions: t.me/tonnode_support.