Python: pytoniq
Connect pytoniq's LiteClient and LiteBalancer to a private TON liteserver: custom config dicts, async balance queries in GRAM, and proof trust levels.
pytoniq is a pure-Python TON SDK that speaks the native ADNL liteserver protocol. There is no HTTP gateway in the path: your process opens an encrypted connection straight to a liteserver, sends TL requests, and can cryptographically verify the responses. That makes it a natural fit for a private liteserver — you get the raw protocol at your guaranteed rate, with no shared API key ceilings in between.
Install
pip install pytoniq
This pulls in pytoniq-core (TL/TLB schemas, cells, proof verification) automatically.
Connect with your liteserver entry
Your TONNode dashboard gives you three values per liteserver: a host, a port, and a base64 Ed25519 public key. The LiteClient constructor takes exactly those — no global config file needed for a single endpoint:
from pytoniq import LiteClient
client = LiteClient(
host="<your-host>", # from the dashboard
port=41000, # from the dashboard
server_pub_key="<base64-key>", # base64 Ed25519 key from the dashboard
trust_level=2,
timeout=15,
)
trust_level=2 skips block-proof verification — reasonable when the liteserver is your own dedicated endpoint rather than an anonymous public node. See trust levels below before choosing a value for production.
Custom config dict vs from_mainnet_config
LiteClient.from_mainnet_config(ls_i=0, trust_level=0) downloads the public global config from ton.org and connects to one of the shared public liteservers listed there. It works for experiments, but those servers are rate-limited, shared with everyone, and prone to ADNL timeouts under load — the TON docs themselves recommend a dedicated liteserver for production.
To keep the config-file workflow but point it at your own infrastructure, build the same dict shape and pass it to from_config. Both LiteClient.from_config(config, ls_i=0, trust_level=2) and LiteBalancer.from_config(config, trust_level=2) accept it; the balancer connects to every entry in liteservers and spreads requests across them.
import socket
import struct
from pytoniq import LiteBalancer
def ip_to_int(host: str) -> int:
"""Global-config format stores IPv4 as a signed 32-bit integer."""
return struct.unpack(">i", socket.inet_aton(socket.gethostbyname(host)))[0]
config = {
"liteservers": [
{
"ip": ip_to_int("<your-host>"),
"port": 41000,
"id": {"@type": "pub.ed25519", "key": "<base64-key>"},
},
# add more entries here if your plan includes several endpoints
],
# Required by the parser. Copy the current "validator" block from
# https://ton.org/global.config.json — it is only *verified* when
# trust_level=0, but the key must be present.
"validator": {
"init_block": {
"workchain": -1,
"shard": -9223372036854775808,
"seqno": 27747086,
"root_hash": "<base64>",
"file_hash": "<base64>",
},
},
}
client = LiteBalancer.from_config(config, trust_level=2)
Two format quirks to note: the ip field is a signed 32-bit integer, not a dotted string (the helper above handles hostnames too), and config["validator"]["init_block"] must exist even at nonzero trust levels because from_config reads it unconditionally.
Fetch masterchain info and a balance
A complete, runnable example. get_account_state returns a SimpleAccount whose balance is an integer in nanogram — divide by 10^9 for GRAM (the native coin, formerly Toncoin; the network itself is still called TON).
import asyncio
from pytoniq import LiteClient
ADDRESS = "EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG"
async def main():
client = LiteClient(
host="<your-host>",
port=41000,
server_pub_key="<base64-key>",
trust_level=2,
timeout=15,
)
await client.connect()
info = await client.get_masterchain_info()
print("last masterchain seqno:", info["last"]["seqno"])
account = await client.get_account_state(ADDRESS)
if account.is_active():
print(f"balance: {account.balance / 10**9:.9f} GRAM")
else:
print("account is not active; balance:", account.balance)
await client.close()
asyncio.run(main())
get_account_state always returns a SimpleAccount, even for addresses that do not exist on-chain (balance 0, uninitialized state) — check is_active() / is_uninitialized() instead of catching exceptions. LiteClient also works as an async context manager (async with LiteClient(...) as client:), which closes the connection for you.
With LiteBalancer the calls are identical; start it with await client.start_up() and shut down with await client.close_all(). The balancer also exposes run_get_method(address=..., method=..., stack=[]) for contract getters.
Trust levels and proofs
The trust_level argument controls how much pytoniq verifies:
trust_level=0— full verification. pytoniq proves the block link from the config'sinit_blockall the way to the current masterchain block, then checks Merkle proofs on responses. The first run walks the key-block chain (slow); verified blocks are cached in a local.blockstoredirectory, so subsequent runs are fast. Requires a realinit_block, and pytoniq logs a warning if that block is not one of the known mainnet/testnet init blocks — verify its hash yourself before trusting it.trust_level=1— response proofs are checked, but the chain-of-trust walk from the init block is skipped.trust_level=2— no proof verification; responses are trusted as-is.
Against anonymous public liteservers, run trust_level=0. Against your own private endpoint the threat model is different — you are trusting your provider either way — so trust_level=2 is a pragmatic default, and trust_level=0 remains available if you want end-to-end proofs regardless.
At trust_level=0, keep the .blockstore cache on persistent storage in containerized deployments, or every cold start repeats the initial proof walk.
Rate limits
pytoniq applies no client-side throttling — it sends requests as fast as you issue them. On a TONNode private liteserver that is the point: plans guarantee 10 to 400 req/s (pricing), and pay-per-request bursts to 500 req/s, so a LiteBalancer over your endpoints can run at full speed without the 429-style backoff logic public APIs force on you. If a request hangs, lower timeout and let the balancer retry on another peer rather than blocking the event loop. Questions about limits or extra endpoints: t.me/tonnode_support.