tonutils-rs
tonutils-rs publishes the tonutils crate: a pure Rust TON SDK inspired by
tonutils-go. It provides native TON primitives, LiteAPI clients, TVM cells,
BoC, TL, TL-B helpers, wallet utilities, and CLI diagnostics without depending
on third-party Rust TON SDK crates or native .so runtime libraries.
The crate is under active development. It is useful for offline TON data handling, LiteAPI experiments over native ADNL TCP, contract get-method calls, account and transaction inspection, wallet transfer construction, and scriptable diagnostics. It is not yet a complete proof-verifying production light client, DHT/overlay stack, mempool scanner, or full ABI layer.
Install
Add the crate from crates.io:
[dependencies]
tonutils = "1.0.0"
The default feature set enables the native LiteClient path:
tonutils = "1.0.0"
For offline TVM, BoC, address, dictionary, and TL-B work only:
tonutils = { version = "1.0.0", default-features = false, features = ["tvm"] }
For the CLI and public TON config parsing helpers:
tonutils = { version = "1.0.0", features = ["network-config", "cli"] }
Feature Flags
default:std,adnl-tcp, andliteclient.tl: TL types, LiteAPI request and response structures, and serialization.tvm: cells, slices, builders, BoC, addresses, dictionaries, TL-B helpers, and TVM stack values. Enablestl.adnl: ADNL types shared by transports. Enablestl.adnl-tcp: native ADNL TCP transport. Enablesadnl.liteclient: LiteAPI client, LiteBalancer, and contract helpers. Enablesadnl-tcpandtvm.network-config: TON global config parsing and liteserver selection.cli: command-line diagnostics. Enablesliteclient,network-config, and JSON ABI helpers.full: all crate features.
Keep heavyweight or optional integrations behind explicit features when adding new capabilities.
Quick Start
Check the crate and examples:
cargo check
cargo check --examples --all-features
Run common CLI diagnostics with the full feature set:
cargo run -F full --bin tonutils-rs -- --output json status
cargo run -F full --bin tonutils-rs -- --output json tvm schema check
cargo run -F full --bin tonutils-rs -- --output json tvm boc decode --hex '<boc-hex>' --tlb account
Run an offline TVM/BoC example:
cargo run -F tvm --example tvm_boc_roundtrip
Run a live LiteClient example using public mainnet config defaults:
cargo run -F full --example liteclient_masterchain_info
TON_NETWORK=testnet cargo run -F full --example liteclient_masterchain_info
Live examples can use TON_NETWORK, TON_GLOBAL_CONFIG_JSON, TON_LS_INDEX,
and task-specific variables documented in docs/examples.md.
Examples
The repository includes compile-checked examples for:
- LiteClient and LiteBalancer calls over native ADNL TCP.
- Network config loading and liteserver selection.
- Contract account state and get-method helpers.
- TVM cells, BoC, dictionaries, stack values, and addresses.
- TL-B account, message, transaction, block wrapper, and config wrapper roundtrips.
- Derive macros for custom TL-B and contract wrappers.
- Offline wallet address derivation and signed transfer BoC construction.
See docs/examples.md for the full list, feature requirements, and live-network environment variables.
Current Capabilities
- Native TL serialization and LiteAPI request/response structures.
- Native ADNL TCP transport and LiteClient request flow.
- LiteBalancer peer management and failover experiments.
- TON cells, slices, builders, BoC, addresses, dictionaries, and TVM stack values.
- TL-B models and checked fixture workflows for core account, message, transaction, block/config wrapper, and proof-related data.
- Contract account state loading and get-method calls.
- Wallet mnemonic, address, and offline signed transfer helpers.
- Scriptable CLI diagnostics for schema checks, BoC decoding, status checks, and live LiteClient workflows.
Known Limits
- Proof payloads and Merkle proof invariants are preserved and tested in focused areas, but full trust-level light-client proof verification is not complete.
- DHT, overlays, and mempool workflows are research or follow-up areas, not production SDK surfaces.
- LiteBalancer behavior is useful for experiments and diagnostics, but still needs broader live-network evidence before being treated as production infrastructure.
- The TL-B surface is intentionally growing from checked upstream-derived slices and fixtures; unsupported constructors should be treated as explicit gaps rather than inferred behavior.
- Live-network examples require network access and should not be run with real private keys, seed phrases, or production credentials.
Tracked follow-up work lives in TODO.md.
Documentation
HTML documentation is published through GitHub Pages at https://lapismyt.github.io/tonutils-rs/. Build the same site locally with:
scripts/build-docs-site.sh
- Getting started: feature selection and guide map.
- LiteClient: typed and raw LiteAPI workflows.
- LiteBalancer: multi-peer workflows and current limits.
- Contracts: account state and get-method wrappers.
- Wallets: mnemonic derivation, addresses, and signed transfer helpers.
- TVM primitives: cells, BoC, stack values, addresses, and dictionaries.
- TL and LiteAPI: constructors, serialization, and schema checks.
- Networking: ADNL TCP, network config, DHT, and overlay boundaries.
- CLI: commands, output formats, and exit behavior.
- Testing: local checks, fixtures, and live-test requirements.
- Internal dev docs: protocol and implementation notes.
Project direction is tracked in ROADMAP.md. Contributor and agent rules are in AGENTS.md.
Testing
Recommended local checks:
cargo fmt --check
cargo check
cargo test
cargo clippy -- -D warnings
Use cargo check --examples --all-features when examples, feature
declarations, or public docs change. Live tests require explicit environment
variables; see docs/testing.md.
Contributing
Contributions are welcome when they preserve the project direction: pure Rust
TON implementation work, no third-party Rust TON SDK crate dependencies, no
native .so runtime dependencies, feature-gated optional functionality, and
protocol facts grounded in upstream TON sources or checked fixtures.
Read CONTRIBUTING.md and CODE_OF_CONDUCT.md before opening a pull request.
Security
Do not open public issues for vulnerabilities. Use GitHub Private Vulnerability Reporting or a GitHub Security Advisory when possible, and never include real private keys, seed phrases, production credentials, or non-public user data in reports. See SECURITY.md.
Acknowledgments
This project uses these references for protocol research, behavior checks, and API comparisons. Thanks to their authors and maintainers:
- ton-blockchain/ton
- ton-blockchain/ton4j
- xssnick/tonutils-go
- tonkeeper/tongo
- ston-fi/ton-rs
- ston-fi/tonlib-rs
- RSquad/ton-rust-node
- nessshon/tonutils
- getgems-io/ton-grpc
- yungwine/ton-mempool
- yungwine/pytoniq-core
- yungwine/pytoniq
- yungwine/pytvm
LiteBalancer
LiteBalancer is the current multi-peer wrapper around connected
LiteClient instances. It is available with the liteclient feature and is
intended for callers that want the same LiteAPI helper surface with basic peer
selection and retry behavior.
Audience: applications that can connect to several liteservers and want basic
failover. Prerequisites: liteclient feature, live network access, and explicit
peer construction from config or socket/public-key pairs. This is not a
consensus or proof-verification layer.
Peer Setup
The balancer owns already constructed clients. When using public global config,
create one LiteClient per selected liteserver, then call start_up before
sending requests.
#![allow(unused)]
fn main() {
use std::str::FromStr;
use std::time::Duration;
use tonutils::liteclient::{
balancer::LiteBalancer,
client::LiteClient,
rate_limit::RequestRateLimit,
};
use tonutils::network_config::{ConfigGlobal, LiteServerBlacklist};
async fn example(config_json: &str) -> anyhow::Result<()> {
let config = ConfigGlobal::from_str(config_json)?;
let blacklist = LiteServerBlacklist::parse_tokens([
"index:0",
"id:AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=",
])?;
let mut peers = Vec::new();
for (_index, liteserver) in config.select_liteservers(3, &blacklist) {
peers.push(LiteClient::connect_liteserver(liteserver).await?);
}
let mut balancer = LiteBalancer::new(peers, Duration::from_secs(10))
.with_rate_limit_per_peer(RequestRateLimit::per_second(5)?)
.with_global_rate_limit(RequestRateLimit::per_second(12)?);
balancer.start_up().await?;
let info = balancer.get_masterchain_info().await?;
println!("{}", info.last.seqno);
balancer.close_all().await?;
Ok(())
}
}
start_up marks connected peers as alive, performs a best-effort archival
probe, starts the current health-check task, and records the balancer as
initialized. close_all aborts the health-check task and closes all owned
clients.
Request Routing
The balancer exposes typed helpers for common LiteAPI calls, including
masterchain info, version, time, block and state loading, block headers,
account state, get-methods, transactions, shard info, config, libraries, and
message sending. Typed methods such as raw_get_block,
get_account_state_simple, run_get_method_typed, and
get_config_params_typed delegate to the underlying LiteClient through the
same peer selection and retry path. Request routing builds a priority list from
alive peers, observed masterchain seqno, average response time, and current
in-flight request count.
For non-archival calls, no peer quorum is established. The first successful peer response is returned, and failed peers are marked dead for later requests. For calls that need archival data, the balancer uses peers detected by its archival probe.
Request Rate Limits
Per-peer limits throttle each owned LiteClient. Global limits throttle total
balancer attempts, including retries and each send_message peer attempt.
Neither limit is enabled by default.
Use per-peer limits for rented liteserver quotas that apply separately to every server. Use a global limit when an upstream account or proxy enforces an aggregate request budget.
Current Limits
This is a prototype balancer, not a production peer manager yet:
- peer transitions are represented, but timeout and reconnect state machines are still incomplete;
- reconnection uses no stored peer descriptors, exponential backoff, or jitter;
- latency scoring uses an arithmetic average rather than EWMA;
- stale seqno and in-flight penalties are basic;
send_messagefailover does not yet preserve every peer error for detailed diagnostics;- proof fields returned by LiteAPI calls are not verified.
Use it as a convenience layer over trusted liteserver connections. Do not treat multi-peer routing as proof verification or consensus validation.
CLI
The CLI is designed for shell scripts. Structured command results are written to stdout. Diagnostics and connection warnings are written to stderr.
Audience: operators, examples, and tests that need reproducible command-line
access to LiteClient, LiteBalancer, contract, TVM, BoC, and schema workflows.
Prerequisites: build with the cli feature. Network commands need live network
access; tvm commands are offline.
Global Options
--network mainnet|testnet: public config to download when no config is supplied.--config <path>: read TON global config JSON from a file.--config-json <json>: read TON global config JSON from an argument.--output human|json|pretty-json|raw|hex|base64: select stdout format.--rps <N>: throttle each selected liteserver toNrequests per second.--global-rps <N>: throttle total LiteBalancer request attempts toNrequests per second.--num-servers <N>: number of allowed liteservers for high-level balancer commands after--exclude-lsfiltering.--exclude-ls <index-or-id>: exclude liteservers from LiteBalancer paths. Repeat the flag or pass comma-separated values. Accepted forms are a decimal global-config index (3), an explicit index (index:3), an explicit public-key id (id:<hex|base64|base64url>), a bare 64-character hex Ed25519 public key, or a bare base64/base64url 32-byte Ed25519 public key.--single --ls-index <N>: use one reproducible liteserver instead of the default high-level balancer.
--config and --config-json are mutually exclusive. If neither is provided,
the CLI downloads the selected public config when the command needs a network
connection.
High-Level Network Commands
High-level commands use LiteBalancer by default, download the selected public
config when no config is supplied, and print compact human output. Use
--output json or --output pretty-json for complete structured output.
tonutils status
tonutils --exclude-ls 0 status
tonutils --exclude-ls index:0 --exclude-ls id:<public-key> status
tonutils account UQBg0E2FCj7kkYWw-2yEcOHs7p1xtnqAoLIYBUG2AJ56eFNP
tonutils --output json account UQBg0E2FCj7kkYWw-2yEcOHs7p1xtnqAoLIYBUG2AJ56eFNP
tonutils call '<addr>' seqno
tonutils call '<addr>' 85143 --arg int:1 --arg null
tonutils call '<addr>' balance --stack-json '[{"type":"int","value":"0"}]'
tonutils call '<addr>' nested --arg 'tuple:[{"type":"int","value":"1"}]'
tonutils call '<addr>' raw --arg unsupported:0a0b
tonutils transactions '<addr>' --count 20
tonutils block latest
tonutils block get '<wc:shard:seqno:root_hash:file_hash>'
tonutils config get
tonutils config get --params 0,17,34
call accepts typed stack arguments as null, int:<decimal>,
cell:<boc-hex>, slice:<boc-hex>, unsupported:<hex>,
tuple:<json-array>, and list:<json-array>, or one inline --stack-json
array. Tuple and list typed arguments use the same JSON stack entry objects as
--stack-json. --arg and --stack-json are mutually exclusive. Without
either option, it sends an empty stack.
account is best-effort after the strict LiteAPI response parse. If account
state TL-B decoding is incomplete, the command still prints byte lengths, root
hashes for successfully decoded BoCs, and decode_errors.
transactions needs the account’s last transaction hash. Until verified
ShardAccounts proof-path extraction lands, the high-level command can report
the current account LT but may return no history with a decode_error explaining
that the hash is unavailable. Use liteclient raw-get-transactions or
balancer raw-get-transactions with an explicit --lt and --hash when those
values are known.
Advanced LiteClient Commands
tonutils --output json liteclient masterchain-info --ls-index 0
tonutils --rps 5 --output json liteclient masterchain-info --ls-index 0
tonutils --output json liteclient version --ls-index 0
tonutils --output json liteclient time --ls-index 0
tonutils --output hex liteclient raw-query --ls-index 0 --hex '<request>'
tonutils --output json liteclient run-get-method --ls-index 0 --address '<addr>' --method seqno
Raw query input can be supplied with --hex, --base64, --file, or --stdin.
Advanced Contract Commands
Contract commands use the high-level contract API and the latest masterchain block reported by the selected liteserver.
tonutils --output json contract state --ls-index 0 --address '<addr>'
tonutils --output json contract run-get-method --ls-index 0 --address '<addr>' --method seqno
tonutils --output json contract run-get-method --ls-index 0 --address '<addr>' --method-id 85143
tonutils --output json contract run-get-method --ls-index 0 --address '<addr>' --method balance --arg int:0 --arg null
tonutils --output json contract run-get-method --ls-index 0 --address '<addr>' --method balance --stack-json '[{"type":"int","value":"0"}]'
tonutils --output json contract run-abi-get-method --ls-index 0 --address '<addr>' --abi-file contract.abi.json --contract Wallet --method seqno --arg 'owner="<addr>"'
JSON state output includes the masterchain block id, shard block id, proof byte
lengths, and raw state bytes as hex and base64. JSON get-method output includes
the execution block ids, exit code, proof byte lengths, raw result BoC, and a
decoded stack when the current stack decoder supports the returned shape.
contract run-get-method accepts the same typed --arg and --stack-json
stack inputs as high-level call. JSON stack entries use objects with type
set to null, int, cell, slice, tuple, list, or unsupported;
integer values are decimal strings and BoC or raw byte payloads are hex strings.
run-abi-get-method loads ABI JSON, encodes --arg name=json inputs through
the ABI metadata, and renders named decoded outputs. It accepts JSON integer
numbers or decimal/hex integer strings, booleans, strings, hex bytes, address
strings, tuple objects, arrays supported by the stack codec, and cell/slice BoC
hex strings. ABI maps and dictionaries use arrays of { "key": ..., "value": ... } entries and are limited to fixed-width integer keys.
Wallet Commands
Wallet commands do not store mnemonics or private keys. wallet generate is the
only command that prints a mnemonic. Other wallet commands read it from
--mnemonic-file <path>, --mnemonic-file - for stdin, or
--mnemonic-env <NAME>. The default wallet version is V5R1; pass
--version v4r2 for Wallet V4R2.
tonutils wallet generate
tonutils wallet address --mnemonic-file seed.txt
tonutils wallet address --version v4r2 --mnemonic-env TON_MNEMONIC
tonutils wallet seqno '<wallet-address>'
tonutils --output hex wallet prepare-transfer --mnemonic-file - --to '<addr>' --amount 100000000 --seqno 0
tonutils wallet send --mnemonic-env TON_MNEMONIC --to '<addr>' --amount 100000000 --deploy
Transfer options are --to <address>, --amount <nanotons>,
--comment <text>, --mode <u8> defaulting to 3, --timeout <seconds>
defaulting to 60, optional --seqno <u32>, optional --wallet-id <u32>,
--workchain <i8> defaulting to 0, and --deploy to include StateInit.
prepare-transfer is offline and requires --seqno; send fetches seqno
unless it is supplied. For wallet send --deploy, a missing seqno stack is
treated as seqno 0; other seqno decoding errors remain errors.
wallet send submits one serialized external-in message BoC through
liteServer.sendMessage and prints the opaque SendMsgStatus.status returned
by the liteserver. That status confirms LiteAPI submission only; it does not
prove transaction inclusion or final execution.
Advanced Balancer Commands
tonutils --output json balancer status --num-servers 3
tonutils --rps 5 --global-rps 12 --output json balancer masterchain-info --num-servers 3
tonutils --exclude-ls 0,4,<public-key> --output json balancer status --num-servers 3
Balancer commands construct multiple LiteClient peers from the selected config. They inherit the prototype balancer limits described in LiteBalancer.
--rps applies to every peer. --global-rps applies only to balancer commands
and counts retries and multi-peer message-send attempts as separate requests.
--num-servers selects the first N liteservers left after --exclude-ls
filtering. Connection warnings keep the original global-config index. Direct
LiteClient commands and high-level --single --ls-index commands are explicit
selections and are not blocked by --exclude-ls.
Offline TVM Commands
BoC decode and TL-B inspection commands do not connect to liteservers and do not require a global config:
tonutils --output json tvm boc decode --hex '<boc-hex>'
tonutils --output pretty-json tvm boc decode --base64 '<boc-base64>' --tlb account
tonutils --output json tvm boc decode --file state.boc --tlb block
tonutils --output json tvm boc decode --stdin --tlb proof --verify-proof
tonutils --output json tvm schema check
BoC input can be supplied with --hex, --base64, --file, or --stdin.
Known TL-B decode values are message, message-relaxed, transaction,
account, block, config, shard-state, proof, and merkle-update.
Proof verification flags only check the synthetic primitive invariant that an
exotic Merkle proof/update child hash equals the hash stored in the exotic
cell. They do not establish liteserver trust or validate a block against a
trusted masterchain root.
Exit Behavior
Successful commands exit with code 0. Command line parsing errors, network
errors, LiteAPI errors, and invalid input return nonzero exit codes through the
standard Rust error path. Structured command data is written to stdout; human
diagnostics and connection warnings should be treated as stderr.
Contracts
The contract API is available with the liteclient feature. It is a thin
wrapper over LiteAPI account-state and get-method requests, so it works with
both LiteClient and LiteBalancer. The optional contract-derive feature
adds #[derive(Contract)] for contracts defined by fixed code BoC bytes and
typed TL-B data.
Audience: callers that need derived contract addresses, account state,
get-method execution, or a typed wrapper before wallet and ABI builders land.
Prerequisites: liteclient, TVM stack familiarity for non-empty get-method
arguments, and live network access. Current helpers preserve proof bytes but do
not verify them.
Contract Blueprints
Use ContractBlueprint when a contract address is determined by code plus
typed data. With contract-derive, the root tonutils::Contract derive
implements the blueprint trait for a struct with exactly one named data
field.
#![allow(unused)]
fn main() {
use tonutils::Contract;
use tonutils::contracts::ContractBlueprint;
use tonutils::tlb::{Tlb, TlbSerialize};
use tonutils::tvm::TvmStack;
const WALLET_CODE_BOC: &[u8] = include_bytes!("wallet_v4r2.code.boc");
#[derive(Debug, Clone, Tlb)]
struct WalletData {
seqno: u32,
subwallet_id: u32,
public_key: [u8; 32],
}
#[derive(Debug, Clone, Contract)]
#[contract(code = WALLET_CODE_BOC, workchain = 0)]
struct WalletV4R2 {
data: WalletData,
}
async fn example<P: tonutils::contracts::ContractProvider>(
client: &mut P,
data: WalletData,
) -> anyhow::Result<()> {
let wallet = WalletV4R2 { data };
let address = wallet.address()?;
let mut contract = wallet.bind(client)?;
let seqno = contract
.run_get_method_by_name_typed_latest("seqno", TvmStack::empty())
.await?;
println!("{} {}", address.to_raw(), seqno.len());
Ok(())
}
}
Supported code attributes:
#[contract(code = WALLET_CODE_BOC)]for aconst &[u8]or expression such asinclude_bytes!("wallet.code.boc").#[contract(code_hex = "...")]for inline BoC hex.#[contract(code_file = "wallet.code.boc")]forinclude_bytes!generated by the macro.
workchain defaults to 0. The derived state_init() decodes the code BoC
root cell, serializes data with TlbSerialize, and stores both as referenced
StateInit cells. address() hashes the serialized StateInit, and bind()
returns a normal address-bound Contract<'a, P>.
Invalid code BoCs, data serialization failures, state-init serialization
failures, and invalid derived configurations are reported as
ContractBuildError. The derive rejects unnamed or unit structs, missing
data, extra fields, and multiple code sources at compile time.
Account State
#![allow(unused)]
fn main() {
use std::str::FromStr;
use tonutils::contracts::Contract;
use tonutils::liteclient::client::LiteClient;
use tonutils::network_config::ConfigGlobal;
use tonutils::tvm::Address;
async fn example(config_json: &str, address: &str) -> anyhow::Result<()> {
let config = ConfigGlobal::from_str(config_json)?;
let mut client = LiteClient::connect_config(&config, 0).await?;
let address = Address::from_str(address)?;
let mut contract = Contract::new(&mut client, address);
let state = contract.get_state_decoded_latest().await?;
let simple = state.simple();
println!("{:?} {}", simple.state, state.raw.state.len());
Ok(())
}
}
get_state_latest first reads getMasterchainInfo and then calls
getAccountState for the returned masterchain block. The response preserves the
raw account-state BoC and proof bytes. get_state_decoded_latest decodes the
account cell when present, and get_state_simple_latest returns a compact state
view with account state and last transaction logical time.
Active-account helpers return ContractError when the account is missing,
uninitialized, frozen, or lacks the requested field:
active_state_latest()balance_latest()code_latest()data_latest()
Get-Methods
#![allow(unused)]
fn main() {
use std::str::FromStr;
use tonutils::contracts::Contract;
use tonutils::liteclient::client::LiteClient;
use tonutils::network_config::ConfigGlobal;
use tonutils::tvm::Address;
async fn example(config_json: &str, address: &str) -> anyhow::Result<()> {
let config = ConfigGlobal::from_str(config_json)?;
let mut client = LiteClient::connect_config(&config, 0).await?;
let address = Address::from_str(address)?;
let mut contract = Contract::new(&mut client, address);
let seqno: u32 = contract
.run_get_method_by_name_latest_as("seqno", ())
.await?;
println!("seqno={seqno}");
Ok(())
}
}
Method names use the standard TON mapping:
(crc16(method_name) & 0xffff) | 0x10000. Numeric ids can be passed directly
with run_get_method or run_get_method_latest. Raw typed helpers return
decoded stack entries and turn non-zero get-method exit codes into
ContractError::NonZeroExitCode.
For wrapper code, prefer the conversion-trait helpers:
#![allow(unused)]
fn main() {
use tonutils::contracts::Contract;
use tonutils::tvm::Address;
async fn wallet_address<P: tonutils::contracts::ContractProvider>(
contract: &mut Contract<'_, P>,
owner: Address,
) -> Result<Address, tonutils::contracts::ContractError<P::Error>> {
contract
.run_get_method_by_name_latest_as("get_wallet_address", owner)
.await
}
}
ToTvmStack, FromTvmStack, ToTvmStackEntry, and FromTvmStackEntry
cover (), raw TvmStack, raw entry vectors, stack entries, signed and
unsigned Rust integers, BigInt, BigUint, bool, Address, Arc<Cell>,
Option<T>, and tuples up to eight fields. Address values are encoded as
standard internal-address stack slices. bool follows the TVM convention:
-1 is true and 0 is false. Conversion failures are reported as
ContractError::StackConversion.
Result Decoding
RunMethodResultExt exposes:
raw_result_boc(): borrowed rawresultBoC bytes.decode_result_stack(): attempts to decode supported stack values.result_stack_lossless(): returns decoded stack values or preserves the raw undecodable bytes with the decode error.
The current stack codec supports nulls, integers, cells, slices, tuples, lists, and explicit unsupported payloads in this crate’s internal representation. Stack compatibility with all liteserver return shapes is still being expanded. The CLI also exposes ABI get-method argument decoding; generic JSON stack input is tracked separately.
ABI Helpers
The tvm feature exposes tonutils::abi for ABI-driven local encoding and
decoding. encode_get_method_inputs converts ABI input values to TVM stack
entries, and decode_get_method_outputs converts returned stack entries back
to ABI values according to a GetMethod definition. Message helpers
encode_message_body and decode_message_body support internal and external
message bodies with optional 32-bit opcode prefixes.
Contract::run_abi_get_method and run_abi_get_method_latest combine those
local codecs with normal get-method execution. MethodId selectors are used
directly; functions without a selector use the standard TON method-name id
mapping. build_abi_external_message_body and
build_abi_internal_message_body build the body cell for ABI message
functions; they do not construct, sign, serialize, or send a full external
message BoC.
Enable abi-json to parse ABI documents:
#![allow(unused)]
fn main() {
use tonutils::abi::{AbiSelector, parse_abi_json_str};
fn example(json: &str) -> anyhow::Result<()> {
let abi = parse_abi_json_str(json)?;
let method = &abi.contracts[0].methods[0];
assert!(matches!(method.selector, AbiSelector::MethodId(_)));
Ok(())
}
}
The cli feature includes abi-json and exposes ABI get-method invocation:
tonutils --output json contract run-abi-get-method \
--address '<addr>' \
--abi-file contract.abi.json \
--contract Wallet \
--method seqno \
--arg 'owner="0:1111111111111111111111111111111111111111111111111111111111111111"'
If --contract is omitted, the ABI file must contain exactly one contract. If
--method is omitted, the selected contract must contain exactly one
get-method. CLI ABI arguments use name=json; map/dictionary values use arrays
of { "key": ..., "value": ... } entries and are limited to fixed-width
integer ABI keys.
Jetton And NFT Payloads
The tvm feature exposes typed message-body builders for common token
workflows:
tonutils::jetton::JettonTransferPayload,JettonBurnPayload, andJettonInternalTransferPayloadcover TEP-74 transfer, burn, and wallet-to-wallet internal transfer bodies.tonutils::nft::NftTransferPayload,NftOwnershipAssignedPayload,NftReportStaticDataPayload, andNftReportRoyaltyParamsPayloadcover TEP-62 item transfer/static-data bodies and TEP-66 royalty reports.inline_forward_payloadandreferenced_forward_payloadselect the TL-BEither Cell ^Cellbranch used by forwarded token payloads.
These helpers build body cells only. Use wallet helpers to wrap the body in an internal message and sign the external wallet request.
External Messages And Transactions
send_external_message_boc(body) submits an already serialized external
message BoC through LiteAPI sendMessage and preserves the bytes exactly. This
is not a wallet signing or deployment builder.
get_transactions(count, lt, hash) fetches account transaction history through
the same provider used by the contract wrapper.
address_from_state_init(workchain, state_init) is the lower-level primitive
used by ContractBlueprint::address(). It serializes the StateInit with the
crate TL-B codec, hashes the resulting root cell, and returns the standard
internal address.
Known Addresses
For already-known addresses, custom clients can own a Contract<'a, P> directly.
This is the lower-level escape hatch when code/data address derivation is not
needed.
#![allow(unused)]
fn main() {
use tonutils::contracts::{Contract, ContractProvider};
use tonutils::tvm::Address;
struct MyContract<'a, P: ContractProvider + ?Sized> {
inner: Contract<'a, P>,
}
impl<'a, P: ContractProvider + ?Sized> MyContract<'a, P> {
fn new(provider: &'a mut P, address: Address) -> Self {
Self { inner: Contract::new(provider, address) }
}
}
}
Proofs
LiteAPI proof fields are returned as raw bytes. This crate does not verify account-state, shard, or get-method proofs yet, so callers must not treat these helpers as a proof-verifying light client API.
Examples
Examples are compiled with explicit feature requirements in Cargo.toml. They
include offline TVM/TL-B examples and live-network examples. Live-network
examples default to public mainnet configuration so they can be run directly.
Environment variables remain overrides for local configs, testnet, selected
liteservers, contract addresses, methods, and raw requests.
Audience: users looking for copyable crate workflows and contributors checking public API examples. Prerequisites vary by example; each example below lists the required feature group. Offline examples do not need network access.
Compile Examples
cargo check --examples --all-features
Live-Network Defaults
By default, live examples download https://ton.org/global.config.json, select
liteserver index 0, and use mainnet. Set TON_NETWORK=testnet to download
https://ton.org/testnet-global.config.json instead. Set
TON_GLOBAL_CONFIG_JSON to bypass downloading and provide a full config JSON
string directly.
Common variables:
TON_NETWORK:mainnetortestnet, defaulting tomainnet.TON_GLOBAL_CONFIG_JSON: full TON global config JSON. Overrides public config download.TON_LS_INDEX: liteserver index for single-peer examples, defaulting to0.TON_CONTRACT_ADDRESS: account address for contract examples. Mainnet contract examples default toUQBg0E2FCj7kkYWw-2yEcOHs7p1xtnqAoLIYBUG2AJ56eFNP.TON_GET_METHOD: get-method name, defaulting toseqno.TON_LITEAPI_REQUEST_HEX: serialized LiteAPI request bytes for raw queries, defaulting to serializedliteServer.getTime.
Example commands:
cargo run -F full --example network_config
cargo run -F full --example liteclient_masterchain_info
TON_NETWORK=testnet cargo run -F full --example liteclient_masterchain_info
TON_LS_INDEX=2 cargo run -F full --example liteclient_raw_query
TON_CONTRACT_ADDRESS=EQ... cargo run -F full --example contract_get_method
Available Examples
liteclient_masterchain_inforequiresliteclient,network-config, andcli. It loads config from the live-network defaults, connects toTON_LS_INDEX, and prints the latest masterchain seqno.liteclient_raw_queryrequiresliteclient,network-config, andcli. It reads live-network defaults and optionalTON_LITEAPI_REQUEST_HEX, sends already serialized LiteAPI bytes throughquery_raw, and prints the raw response as hex. WithoutTON_LITEAPI_REQUEST_HEX, it sendsliteServer.getTime.network_configrequiresnetwork-configandcli. It reads live-network defaults, parses the liteserver list, and prints indexed socket addresses.contract_get_staterequiresliteclient,network-config, andcli. It reads live-network defaults and optionalTON_CONTRACT_ADDRESS, fetches latest account state, and prints block ids plus raw state length.contract_get_methodrequiresliteclient,network-config, andcli. It reads live-network defaults, optionalTON_CONTRACT_ADDRESS, and optionalTON_GET_METHOD, runs an empty-stack get-method, and prints the exit code plus raw result length. WithTON_NETWORK=testnet, setTON_CONTRACT_ADDRESS; otherwise the example exits successfully because no stable default testnetseqnocontract is defined.litebalancer_failoverrequiresliteclient,network-config, andcli. It reads live-network defaults, connects to all available liteservers from config, initializesLiteBalancer, performsget_masterchain_info, and prints seqno plus alive and archival peer counts.adnl_pingrequiresadnl-tcp. It performs a loopback-safe ADNL handshake roundtrip in-memory (to_bytes+decrypt_from_raw) and prints sender and receiver identifiers.tvm_cell_builderrequirestvm. It builds a cell with fixed-width integer, big unsigned integer, and big signed integer values, reads them back viaSlice, and prints decoded values.tvm_boc_roundtriprequirestvm. It builds a small referenced cell graph, serializes it into BoC with CRC, deserializes back, and prints basic structure metadata.tvm_stack_run_methodrequiresliteclient,network-config, andcli. It reads live-network defaults, optionalTON_CONTRACT_ADDRESS, and optionalTON_GET_METHOD, callsrun_get_method_by_namewith an emptyTvmStack, and prints exit code plus result size. WithTON_NETWORK=testnet, setTON_CONTRACT_ADDRESS.tlb_schema_codegenrequirestvm. It parses the local Phase 1 upstream-derived TL-B schema slice, regenerates the checked summary, and prints whether the checked-in output matches.tvm_boc_decoderequirestvm. It builds an offlineAccount::Nonefixture, encodes it as BoC, decodes it, and prints the root hash plus typed account view.liteclient_account_state_decoderequiresliteclient. It decodesTON_ACCOUNT_STATE_BOC_HEXwhen set and otherwise exits cleanly using an offlineAccount::Nonefixture.proof_verifyrequirestvm. It readsTON_MERKLE_PROOF_BOC_HEXwhen set and checks the exotic Merkle proof child-hash invariant. Without the environment variable, it uses a deterministic offline Merkle proof fixture.tvm_dictionary_roundtriprequirestvm. It builds an offline compatibilityDictbacked byHashmapE, serializes and deserializes it, and prints the key size, entry count, and root hash.tlb_message_roundtriprequirestvm. It builds deterministic internal messages with inline and referenced bodies, roundtrips them through TL-B, and prints body placement, value, and root hash.tlb_transaction_roundtriprequirestvm. It builds a minimal ordinary transaction with empty inbound and outbound messages, roundtrips it through TL-B, and prints logical time, statuses, and fee.tlb_account_state_roundtriprequirestvm. It builds a full account with emptyStateInit, storage, and balance fields, roundtrips it through TL-B, and prints the address hash, state, and balance.tlb_block_wrapper_decoderequirestvm. It builds the Phase 1 raw-cellBlockwrapper with deterministic child cells, decodes it, and prints the global id plus child hashes.tlb_config_params_wrapperrequirestvm. It buildsConfigParamsaround a deterministic raw config dictionary root, roundtrips it through TL-B, and prints the config address and dictionary root hash.tlb_parse_bocrequirestvm. It decodesTON_TLB_BOC_HEXas a typedAccountroot, or uses an offlineAccount::Nonefixture, then prints typed data and hash roundtrip information.tlb_read_tx_datarequirestvm. It acceptsTON_TRANSACTION_BOC_HEXorTON_TRANSACTION_BOC_BASE64, decodes aTransaction, and prints logical time, account hash, fees, statuses, message summary, and root hash. Without input it uses an offline deterministic transaction fixture.tlb_custom_deriverequirestlb-derive. It demonstrates a custom TEP-74-style jetton transfer struct with a hex constructor tag, inferred unsigned field width, wrapper TL-B fields, and generated roundtrip codecs.wallet_offline_transferrequirestvm. It derives V4R2 and V5R1 addresses from a fixed TON mnemonic and builds a signed V4R2 deployment transfer BoC without network access.
Remaining coverage gaps tracked in TODO.md: live proof capture and mempool
examples.
Getting Started
tonutils is a native Rust TON SDK. The current public surface is focused on
LiteAPI access over native ADNL TCP, TL serialization, TON cells/BoC primitives,
network config parsing, and a scriptable CLI.
Audience: first-time crate users and embedders choosing features. Prerequisite: basic Rust async familiarity for LiteClient examples. Live network access is only needed for LiteClient, LiteBalancer, contract, and config-download flows; TVM, BoC, TL-B schema, and many examples work offline.
Feature Selection
Default features enable the native LiteClient path over ADNL TCP:
tonutils = { path = "../tonutils-rs" }
The default feature set is std, adnl-tcp, and liteclient. Because
liteclient depends on tvm, adnl-tcp depends on adnl, and both tvm and
adnl depend on tl, the default build also compiles TL and TVM support.
Use narrower features when embedding only a part of the SDK:
tonutils = { path = "../tonutils-rs", default-features = false, features = ["tvm"] }
Enable config parsing and CLI support explicitly:
tonutils = { path = "../tonutils-rs", features = ["network-config", "cli"] }
Current Feature Map
std: standard library support. It is currently part of the default build.tl: TL types, LiteAPI request and response structures, and serialization helpers.tvm: cells, slices, builders, BoC, addresses, dictionaries, TL-B helpers, and TVM stack values. Enablestl.adnl: ADNL types shared by transports. Enablestl.adnl-tcp: native ADNL TCP transport. Enablesadnlplus async transport dependencies.liteclient: LiteAPI client, LiteBalancer, and contract helpers over ADNL TCP. Enablesadnl-tcpandtvm.network-config: TON global config parsing and liteserver selection helpers.cli: command line interface for shell scripts and diagnostics. Enablesliteclientandnetwork-config.
Future feature groups may add proof verification, wallets, DHT, overlays, mempool scanning, and optional TON emulator bindings.
Guide Map
- LiteClient: typed and raw LiteAPI workflows.
- LiteBalancer: multi-peer workflows and prototype limits.
- Contracts: account state and get-method wrappers.
- Wallets: mnemonic derivation, addresses, and signed transfer BoCs.
- TVM primitives: cells, BoC, stack values, addresses, and dictionaries.
- TL and LiteAPI: constructors, serialization, raw bytes, and schema checks.
- Networking: ADNL TCP, network config, and future protocol boundaries.
- CLI: shell commands, output formats, and exit behavior.
- Examples: compiling examples and live input variables.
- Testing: local checks, live tests, and fixture expectations.
LiteClient
LiteClient sends LiteAPI requests directly to TON liteservers over native
ADNL TCP. It is available with the liteclient feature, which also enables TL,
TVM, and ADNL TCP support. It supports typed request helpers and raw LiteAPI
bytes for methods that are not yet wrapped by a convenience method.
Audience: callers that already have a liteserver endpoint or TON global config.
Prerequisites: async Rust runtime, liteclient feature, and live network access.
For multi-peer retry behavior, see LiteBalancer. For shell
commands over the same APIs, see CLI.
Connect From Global Config
#![allow(unused)]
fn main() {
use std::str::FromStr;
use tonutils::liteclient::client::LiteClient;
use tonutils::network_config::ConfigGlobal;
async fn example(config_json: &str) -> anyhow::Result<()> {
let config = ConfigGlobal::from_str(config_json)?;
let mut client = LiteClient::connect_config(&config, 0).await?;
let info = client.get_masterchain_info().await?;
println!("{}", info.last.seqno);
Ok(())
}
}
Raw Query
Use query_raw when a LiteAPI constructor is known by schema but does not yet
have a typed Rust convenience method. The input must be an already serialized
LiteAPI request body. The output is the raw serialized LiteAPI response body.
#![allow(unused)]
fn main() {
use std::str::FromStr;
use tonutils::liteclient::client::LiteClient;
use tonutils::network_config::ConfigGlobal;
async fn example(config_json: &str, request: Vec<u8>) -> anyhow::Result<()> {
let config = ConfigGlobal::from_str(config_json)?;
let mut client = LiteClient::connect_config(&config, 0).await?;
let response = client.query_raw(request).await?;
println!("{}", hex::encode(response));
Ok(())
}
}
Request Rate Limits
LiteClient is unlimited by default. To stay within a provider quota, attach a
local token-bucket limiter before sending requests:
#![allow(unused)]
fn main() {
use tonutils::liteclient::{
client::LiteClient,
rate_limit::RequestRateLimit,
};
async fn example(mut client: LiteClient) -> anyhow::Result<()> {
client.set_rate_limit(RequestRateLimit::per_second(5)?);
let info = client.get_masterchain_info().await?;
println!("{}", info.last.seqno);
Ok(())
}
}
The limiter waits asynchronously instead of failing fast. Typed helpers and raw
queries share the same query_raw path, so one configured limit covers both.
Contract Helpers
tonutils::contracts::Contract reuses LiteClient::get_masterchain_info,
LiteClient::get_account_state, and LiteClient::run_get_method. It does not
change the LiteAPI trust model: proof fields are preserved, but proof
verification is not implemented yet.
Decoded BoC Helpers
tonutils::liteclient::boc contains offline decode helpers for LiteClient
payloads. Each helper preserves the raw BoC bytes and decoded root cell, then
adds a typed view where Phase 1 models exist:
decode_account_state_boc->Accountdecode_block_boc-> generated-backedBlockwrapperdecode_config_params_boc->ConfigParamsdecode_shard_state_boc->ShardStatedecode_merkle_proof_bocanddecode_merkle_update_boc-> exotic proof primitive wrappers
These helpers intentionally do not verify liteserver proofs by default. The
Merkle wrappers expose verify_virtual_hash and verify_virtual_hashes for
the local exotic-cell child-hash invariant only; callers must still anchor
proofs to trusted block ids before using decoded data as trusted state.
#![allow(unused)]
fn main() {
use tonutils::liteclient::boc::decode_account_state_boc;
fn example(raw_state_boc: &[u8]) -> anyhow::Result<()> {
let decoded = decode_account_state_boc(raw_state_boc)?;
println!("{}", decoded.boc.root_hash_hex());
println!("{:?}", decoded.account);
Ok(())
}
}
Typed Phase 1 Additions
Recent typed helpers added to LiteClient:
raw_get_blockandraw_get_block_dataraw_get_block_headerget_account_state_typed,raw_get_account_state, andget_account_state_simpleraw_get_shard_infoandraw_get_all_shards_infoget_one_transaction_typed,raw_get_transactions, andraw_get_block_transactions_extrun_get_method_typedget_config_all_typedandget_config_params_typedget_libraries_typedandget_libraries_with_proof_typedlookup_block_with_prooflist_block_transactions_extget_libraries_with_proofget_shard_block_proofget_out_msg_queue_sizesget_block_out_msg_queue_sizeget_dispatch_queue_infoget_dispatch_queue_messagesget_nonfinal_validator_groupsget_nonfinal_candidateget_nonfinal_pending_shard_blocks
Example:
#![allow(unused)]
fn main() {
use tonutils::liteclient::client::LiteClient;
use tonutils::tl::{BlockId, BlockIdExt};
async fn example(client: &mut LiteClient, block_id: BlockId, mc_block_id: BlockIdExt) -> anyhow::Result<()> {
let proof = client
.lookup_block_with_proof((), block_id, mc_block_id, None, None)
.await?;
println!("{}", proof.id.seqno);
Ok(())
}
}
Current Limits
The client has typed helpers for the common LiteAPI surface and a raw byte
escape hatch for missing constructors, including typed BoC decode wrappers for
block, account, transaction, shard, config, library, and get-method result
payloads. Full trust-level proof verification and full block.tlb expansion
are not implemented. Timeout configuration is currently limited, and
live-network behavior depends on the selected liteserver.
Nonfinal Typed Calls
Nonfinal constructors expose candidate and validator-group data that may change before finalization. They are useful for diagnostics and research flows and do not include proof verification or production-safety guarantees.
#![allow(unused)]
fn main() {
use tonutils::liteclient::client::LiteClient;
use tonutils::tl::NonfinalCandidateId;
async fn example(client: &mut LiteClient, candidate_id: NonfinalCandidateId) -> anyhow::Result<()> {
let groups = client.get_nonfinal_validator_groups(None).await?;
println!("{}", groups.groups.len());
let candidate = client.get_nonfinal_candidate(candidate_id).await?;
println!("{}", candidate.data.len());
Ok(())
}
}
Shell Equivalent
tonutils --output json liteclient masterchain-info --ls-index 0
tonutils --rps 5 --output json liteclient masterchain-info --ls-index 0
tonutils --output hex liteclient raw-query --ls-index 0 --hex '<serialized-request-hex>'
tonutils --output json contract run-get-method --address '<addr>' --method seqno
Networking
The current networking surface is native ADNL TCP for LiteAPI liteserver connections plus optional public network config parsing. ADNL UDP, DHT, overlays, and mempool networking are documented as future boundaries but are not public runtime APIs yet.
Audience: callers configuring transport features and contributors separating
current LiteAPI networking from future DHT, overlay, and mempool work.
Prerequisites: adnl-tcp for direct liteserver sockets, network-config for
global config parsing, and live network access for real liteserver calls.
Feature Boundaries
adnl: shared ADNL helper types and primitives.adnl-tcp: TCP transport, crypto handshake, frame codec, and peer wrapper.liteclient: LiteAPI client over ADNL TCP.network-config: TON global config JSON parsing and liteserver helpers.cli: downloads public configs and exposes shell commands.
The default feature set enables std, adnl-tcp, and liteclient.
network-config and cli must be requested explicitly.
ADNL TCP
LiteClient::connect accepts a socket address and liteserver public key. The
transport performs the native ADNL TCP handshake, then sends LiteAPI requests
through the framed encrypted stream.
#![allow(unused)]
fn main() {
use tonutils::liteclient::client::LiteClient;
async fn example(addr: &str, public_key: [u8; 32]) -> anyhow::Result<()> {
let mut client = LiteClient::connect(addr, public_key).await?;
let version = client.get_version().await?;
println!("{}", version.version);
Ok(())
}
}
Transport tests cover codec roundtrips, empty minimum-size payload frames, client/server key and nonce directionality, partial frames, multi-frame buffers, too-large payload rejection, tamper handling, and loopback handshake behavior. Timeout configuration and graceful close APIs are still being hardened.
Network Config
ConfigGlobal parses TON global config JSON and exposes liteserver entries:
#![allow(unused)]
fn main() {
use std::str::FromStr;
use tonutils::network_config::ConfigGlobal;
fn example(config_json: &str) -> anyhow::Result<()> {
let config = ConfigGlobal::from_str(config_json)?;
let first = config.first_liteserver()?;
println!("{}", first.socket_addr());
Ok(())
}
}
The config parser currently focuses on the liteservers section and Ed25519
public keys. It does not resolve DHT entries or overlay peers.
Future Protocols
ADNL UDP will be the lower-level datagram transport needed by DHT and overlays. DHT will resolve nodes and liteservers with signed peer records. Overlays will carry overlay queries and broadcasts, including future mempool workflows.
These protocols are intentionally separate from the current ADNL TCP LiteAPI path. Until they land, this crate cannot discover peers through DHT, join overlays, or stream pending external messages from the mempool.
Testing
The repository keeps deterministic checks separate from live-network workflows. Local checks should not require secrets, public liteserver availability, or external network access.
Audience: contributors adding code, fixtures, examples, or documentation that
could affect compile targets. Use this guide together with AGENTS.md for
change workflow and dev-docs/testing/fixtures.md for fixture metadata rules.
Local Checks
Run the minimum verification before merging SDK changes:
cargo check
cargo test --lib
When examples or feature documentation change, also compile the examples with the narrowest feature set they require:
cargo check --examples --all-features
Feature-gated work should add the matching checks, such as
cargo check --no-default-features, cargo check --all-features, or targeted
feature combinations.
Benchmark harnesses are compile-checked without running measurements with:
cargo bench --no-run
The deterministic offline harnesses can be run directly when measuring a local change:
cargo bench --bench wallet
cargo bench --bench protocol
Examples
Examples are written so they compile without live inputs. Runtime examples read
environment variables, but live-network examples now default to public mainnet
config download when TON_GLOBAL_CONFIG_JSON is absent. Offline examples use
deterministic fixtures when possible.
Current live example variables:
TON_NETWORK:mainnetortestnet, defaulting tomainnet.TON_GLOBAL_CONFIG_JSON: full TON global config JSON, overriding public config download.TON_LS_INDEX: liteserver index for single-peer examples, defaulting to0.TON_CONTRACT_ADDRESS: account address for contract examples. Mainnet get-method examples default toUQBg0E2FCj7kkYWw-2yEcOHs7p1xtnqAoLIYBUG2AJ56eFNP; testnet get-method examples require an explicit address and exit successfully when it is absent.TON_GET_METHOD: optional get-method name, defaulting toseqno.TON_LITEAPI_REQUEST_HEX: serialized LiteAPI request bytes for raw queries, defaulting to serializedliteServer.getTime.
Useful live smoke checks:
cargo run -F full --example network_config
cargo run -F full --example liteclient_masterchain_info
TON_NETWORK=testnet cargo run -F full --example liteclient_masterchain_info
cargo run -F full --example contract_get_method
Live-Network Tests
Live-network tests should be ignored by default and documented with exact inputs. They must not depend on repository-local secrets or a specific public liteserver staying healthy.
Use live tests for compatibility evidence, not as the only coverage for a protocol path. Add fixture-backed tests for TL bytes, BoC payloads, stack values, proofs, and transport frames whenever the behavior can be captured.
Fixtures
Binary fixtures should include source notes: upstream schema revision, command used to capture the data, network, block id, account address, and whether the data came from a live liteserver or from an upstream repository. Fixtures for malformed inputs should explain the exact invariant being violated.
Known fixture gaps remain for official HashmapE encodings, cell hashes, block proofs, account-state proofs, and successful live get-method stack shapes.
TL And LiteAPI
The tl feature exposes Type Language structures used by ADNL and LiteAPI.
The crate keeps local schema files under src/tl/schemas/ and maps the
implemented LiteAPI constructors into Rust enums and structs backed by
tl-proto serialization.
Audience: contributors adding LiteAPI constructors or callers using typed and
raw LiteAPI requests. Prerequisites: tl for schema types, liteclient for
network transport, and upstream TON schema evidence before changing wire types.
Schema Source
LiteAPI wire types are maintained from upstream TON schemas, primarily
src/tl/schemas/lite_api.tl. Request constructors live in
tonutils::tl::request; response constructors live in
tonutils::tl::response; common identifiers such as BlockIdExt, Int256,
and AccountId live in tonutils::tl::common.
The repository has a schema-check test that parses the local LiteAPI schema and compares computed constructor ids with the handwritten Rust ids. It is a drift check for implemented constructors, not a full code generator.
TL-B Schema Workflow
The tvm feature also exposes a small TL-B schema workflow in
tonutils::tlb::schema. Phase 1 keeps an upstream-derived slice of
ton-blockchain/ton crypto/block/block.tlb at
src/tlb/schemas/block_phase1.tlb and a checked generated summary at
src/tlb/generated/block_phase1.rs.
Run the deterministic check with:
tonutils tvm schema check
cargo test --lib tlb::schema
The current generator parses constructor tags, implicit tags, references,
grouped references, Maybe, Either, fixed-width integer/bit expressions,
VarUInteger, HashmapE, HashmapAug, HashmapAugE, and bounded constraint
text well enough to detect drift in the Phase 1 block/config/proof slice.
Message, account, and transaction types remain hand-written canonical public
models; the generated-backed slice covers block, config, shard-state, and
Merkle proof/update wrappers while deeper model generation remains tracked in
TODO.md.
Constructor Ids
TL constructor ids are 32-bit little-endian values on the wire. Public Rust
types use #[tl(id = "...")] attributes, and tests verify those ids against
the local schema text where coverage exists.
When adding a constructor:
- copy the upstream TL line into the local schema file;
- add or update the Rust type with the exact constructor id;
- include vectors, flags, optional fields, and boxed enums in roundtrip tests;
- document any missing or intentionally unsupported fields in
TODO.md.
Typed Requests
LiteClient::query_typed accepts a tonutils::tl::request::Request value and
decodes the response into a type that implements the crate’s response mapping.
The higher-level LiteClient helpers build these requests internally.
#![allow(unused)]
fn main() {
use tonutils::liteclient::client::LiteClient;
use tonutils::tl::request::Request;
use tonutils::tl::response::CurrentTime;
async fn example(client: &mut LiteClient) -> anyhow::Result<()> {
let time: CurrentTime = client.query_typed(Request::GetTime).await?;
println!("{}", time.now);
Ok(())
}
}
Raw Bytes
Use query_raw when the request is already serialized LiteAPI bytes or when a
constructor is known by schema but not yet modeled by the typed API.
#![allow(unused)]
fn main() {
use tonutils::liteclient::client::LiteClient;
async fn example(client: &mut LiteClient, bytes: Vec<u8>) -> anyhow::Result<()> {
let response = client.query_raw(bytes).await?;
println!("{}", hex::encode(response));
Ok(())
}
}
query_raw preserves unknown request and response bytes. It still wraps the
payload in the ADNL LiteAPI query envelope before transport.
Current Limits
The schema checker is active, but the local LiteAPI schema and handwritten Rust surface are not complete. Nonfinal candidate calls, queue helpers, and several proof helpers exist, but broader generated coverage and full golden binary fixtures remain follow-up work. DHT, overlay, and mempool TL types are future protocol work.
TVM Primitives
The tvm feature enables TON cells, builders, slices, BoC helpers, addresses,
dictionaries, and TVM stack values. It also enables tl because several public
types map cells and addresses into LiteAPI structures.
Audience: users working with offline cells, BoC payloads, TL-B models, addresses,
or get-method stack values. Prerequisites: tvm feature only; no live network
access is required unless the BoC or stack payload came from LiteClient calls.
Cells, Builders, And Slices
A cell stores up to 1023 bits and up to 4 references. Builder and
CellBuilder write values into a cell, and Slice reads them back in order.
#![allow(unused)]
fn main() {
use num_bigint::BigUint;
use tonutils::tvm::{Builder, Slice};
fn example() -> anyhow::Result<()> {
let value = BigUint::from(1u64) << 96;
let mut builder = Builder::new();
builder.store_uint::<u32>(0x12345678)?;
builder.store_uint_custom::<u8>(0b101, 3)?;
builder.store_big_uint(&value, 128)?;
let cell = builder.end_cell()?;
let mut slice = Slice::new(cell);
assert_eq!(slice.load_uint::<u32>()?, 0x12345678);
assert_eq!(slice.load_uint_custom::<u8>(3)?, 0b101);
assert_eq!(slice.load_big_uint(128)?, value);
Ok(())
}
}
Reads and writes are bounds checked. Loading too many bits or references returns an error instead of silently truncating data.
BoC
BoC helpers serialize and parse a root cell:
#![allow(unused)]
fn main() {
use tonutils::tvm::{Builder, deserialize_boc, serialize_boc};
fn example() -> anyhow::Result<()> {
let mut builder = Builder::new();
builder.store_byte(7)?;
let cell = builder.end_cell()?;
let boc = serialize_boc(&cell, true)?;
let decoded = deserialize_boc(&boc)?;
assert_eq!(decoded.hash(), cell.hash());
Ok(())
}
}
Convenience helpers convert BoC data to and from hex and base64. Current BoC support covers the crate’s ordinary-cell use cases; index table modes, cache bits, exotic cells, and official golden fixture coverage are still being expanded.
Addresses
Address parses raw workchain:hash strings and user-friendly base64 forms,
and can convert to LiteAPI AccountId:
#![allow(unused)]
fn main() {
use std::str::FromStr;
use tonutils::tvm::Address;
fn example(address: &str) -> anyhow::Result<()> {
let parsed = Address::from_str(address)?;
let account = parsed.to_account_id();
println!("{} {}", account.workchain, parsed.to_hex());
Ok(())
}
}
Address formatting exposes bounceable and test-only flags. Callers should keep test-only addresses out of mainnet workflows.
Dictionaries
HashmapE stores fixed-width BitKey values with callback-based value codecs.
The higher-level Dict wrapper supports integer keys and cell values for the
current public surface.
#![allow(unused)]
fn main() {
use tonutils::tvm::{Dict, DictValue};
fn example() -> anyhow::Result<()> {
let mut dict = Dict::new(16);
dict.set_int_key(7, DictValue::Uint(42, 32))?;
let root = dict.serialize()?;
assert!(root.is_some());
Ok(())
}
}
The dictionary encoder implements canonical labels and fork nodes. TL-B code can
use tlb::TlbHashmapE<T, N> when dictionary values implement the TL-B runtime
traits. Official TON golden fixtures and proof-friendly traversal remain TODO
items.
TL-B Models And Derive
The tvm feature exposes tonutils::tlb for typed cell models. Messages,
accounts, transactions, common transaction phases, block roots, config wrappers,
and proof wrappers are public APIs. Some deep block and shard-state families are
raw-preserving wrappers while the full upstream block.tlb generator is still
being expanded.
#![allow(unused)]
fn main() {
use tonutils::tlb::{Account, TlbDeserialize, TlbSerialize};
use tonutils::tvm::boc_to_hex;
fn example() -> anyhow::Result<()> {
let cell = Account::None.to_cell()?;
let decoded = Account::from_cell(cell.clone())?;
assert_eq!(decoded, Account::None);
println!("{}", boc_to_hex(&cell, false)?);
Ok(())
}
}
Custom TL-B structs can use the optional tlb-derive feature:
#![allow(unused)]
fn main() {
use tonutils::tlb::{Tlb, TlbDeserialize, TlbSerialize};
#[derive(Tlb)]
#[tlb(tag = "0x0f8a7ea5")]
struct Body {
query_id: u64,
}
}
The derive writes existing TlbSerialize and TlbDeserialize impls. Use
#[tlb(reference)] for ^T fields. Tags accept binary strings, 0b...,
0x..., and TL-B-style #... hex forms. Unsigned primitive fields u8,
u16, u32, u64, and u128 infer their natural bit width; use
#[tlb(bits = N)] when the TL-B width differs or for signed integer and hash
fields. Float primitive fields are rejected because the runtime does not define
TL-B float semantics.
Stack Values
TvmStack is used by contract get-method helpers. It supports nulls, integers,
cells, slices, tuples, lists, and explicit unsupported payloads for lossless
roundtrips.
#![allow(unused)]
fn main() {
use tonutils::tvm::{TvmStack, TvmStackEntry};
fn example() -> anyhow::Result<()> {
let stack = TvmStack::new(vec![TvmStackEntry::int(10)]);
let boc = stack.to_boc()?;
let decoded = TvmStack::from_boc(&boc)?;
assert_eq!(decoded.entries().len(), 1);
Ok(())
}
}
Compatibility with every liteserver runSmcMethod return shape is still being
verified with live and golden fixtures.
Wallets
tonutils::wallet provides offline helpers for Wallet V5R1 and V4R2. The
helpers derive StateInit addresses, build signed external message bodies,
serialize external-in message BoCs, and, with liteclient, submit those BoCs
through a provider. A submitted BoC is not proof of transaction inclusion.
Mnemonics
TonMnemonic uses 24 English BIP-39 words with TON seed-version checks and
derives an Ed25519 key using TON PBKDF2-HMAC-SHA512 parameters. Optional
mnemonic passwords are supported by the library and CLI through environment
variables, not positional arguments.
#![allow(unused)]
fn main() {
use tonutils::wallet::TonMnemonic;
let mnemonic = TonMnemonic::generate(None)?;
let public_key = mnemonic.public_key();
Ok::<(), anyhow::Error>(())
}
Addresses And Transfers
V5R1 is the recommended default. Mainnet V5R1 uses wallet id 0x7fffff11;
testnet uses 0x7ffffffd. V4R2 uses the common wallet id 0x29a9a317.
#![allow(unused)]
fn main() {
use tonutils::wallet::{MAINNET_GLOBAL_ID, WalletV5R1, WalletV5R1WalletId, wallet_v5r1_code};
let wallet_id = WalletV5R1WalletId::client(MAINNET_GLOBAL_ID, 0, 0, 0).pack()?;
let wallet = WalletV5R1::new(public_key, wallet_id, wallet_v5r1_code()?, 0);
let address = wallet.address()?;
Ok::<(), anyhow::Error>(())
}
valid_until is a Unix timestamp stored as uint32. seqno is replay
protection and must match the current wallet contract state. Include StateInit
only for deployment or first-message workflows.
With the liteclient feature, WalletV5R1::send_external_message and
WalletV4R2::send_external_message are accepted LiteAPI submission adapters.
They build and sign an external-in message, optionally include StateInit when
include_state_init is true, call ContractProvider::send_external_message_boc
once, and return the opaque liteServer.SendMsgStatus.status value. Provider
errors are surfaced as provider errors, build errors do not call the provider,
and the returned status must not be interpreted as transaction inclusion.
With the liteclient feature, WalletV5R1 also exposes typed get-method
helpers over any ContractProvider. The helpers read the latest masterchain
block from the provider, call the deployed wallet address derived from
WalletV5R1::address(), and decode successful TVM stack values for seqno,
get_wallet_id, get_public_key, is_signature_allowed, and
get_extensions.
extensions_raw_onchain preserves the exact get_extensions cell or slice
payload as Arc<Cell>. extensions_onchain decodes that payload as
WalletV5R1Extensions, a HashmapE 256 int1 wrapper keyed by 256-bit account
hash. Hash APIs are canonical; address helpers use only Address::hash_part and
do not include the workchain in dictionary keys.
Wallet V5R1 extended management actions are available through
WalletV5R1ExtendedAction and the explicit *_with_extended_actions body/BoC
builders. The ordinary transfer builders still work unchanged and serialize no
extended actions. The V5R1 limit is 255 total ordinary plus extended actions in a
single request.
TON Development Documentation
This directory is the internal technical reference for implementing tonutils. It is intentionally more implementation-oriented than general TON documentation: each page connects protocol facts to concrete Rust modules, invariants, tests, and missing work.
Human contributors and AI agents should read this directory before changing
protocol behavior. Public user guides belong in docs/; protocol evidence,
wire formats, invariants, source priorities, and crate mapping belong here.
Reading Order
- Architecture overview
- Feature matrix
- Source tracking
- Crypto primitives
- TL schema language
- LiteAPI schema
- ADNL TCP
- TVM cells
- BoC format
- TL-B data models
- Blockchain data model
- Blockchain TL-B coverage
- Block, config, and proof TL-B slice
- LiteClient request flow
- LiteClient rate limiting
- Smart-contract get-methods
- ABI data model
- Wallet V5R1
- Wallet V4R2 and TON mnemonics
- TEP metadata roadmap
Directory Map
architecture/: crate layers, features, errors, performance policy.api/: public API design, compatibility and ergonomics.blockchain/: blocks, accounts, transactions, messages, config params.crypto/: hashes, checksums, keys, signatures, encryption primitives.tl/: TL syntax, schema maintenance, LiteAPI types and function mapping.network/: ADNL transport, DHT, overlays, global config.tvm/: cells, BoC, addresses, dictionaries, TL-B, TVM stack.liteclient/: request flow, balancer, proof verification.contracts/: get-methods, external messages, high-level contract API.operations/: source tracking, diagnostics, maintenance workflow.research/: mempool scanning notes and future protocol investigations.testing/: fixtures, live tests, benchmarks.
Documentation Contract
Every topic file should answer:
- What TON subsystem does this describe?
- Which wire formats, constructor ids, byte order, limits, and flags matter?
- What invariants must code preserve?
- Which files in this crate implement or will implement it?
- Which tests or fixtures prove compatibility?
- What is still missing?
Repository text must stay English-only.
Source Of Truth Priority
When sources disagree, prefer this order:
- Upstream
ton-blockchain/tonschemas and C++ implementation. - Official TON documentation and specs.
- Behavior observed from public liteservers with recorded fixtures.
- Mature SDK behavior such as
tonutils-go,tongo, pytoniq, and pytoniq-core. - Existing crate behavior.
Use pytoniq and pytoniq-core for capability inspiration or comparison evidence
after upstream TON facts are established. They are not API or structure parity
targets. Record any deliberate protocol compatibility deviation in TODO.md
and in the relevant subsystem document.
API Examples To Maintain
This page defines examples that should exist in README, doctests, or examples/ once APIs stabilize.
LiteClient Connect
Expected flow:
- Resolve
TON_NETWORK, defaulting to mainnet. - Load
TON_GLOBAL_CONFIG_JSONor download the public network config. - Select
TON_LS_INDEX, defaulting to0. - Connect with ADNL TCP.
- Fetch masterchain info.
Network Config
Expected flow:
- Resolve
TON_NETWORK, defaulting to mainnet. - Load
TON_GLOBAL_CONFIG_JSONor download the public network config. - Parse liteserver entries.
- Print indexed socket addresses for follow-up examples.
Raw LiteAPI
Expected flow:
- Load config through the live-network defaults.
- Select one liteserver with
TON_LS_INDEX. - Use
TON_LITEAPI_REQUEST_HEXwhen provided. - Otherwise serialize
liteServer.getTime. - Send bytes with
query_rawand print raw response bytes as hex.
LiteBalancer
Expected flow:
- Build peer descriptors from config loaded through the live-network defaults.
- Connect or lazy-connect peers.
- Fetch version/time/masterchain info.
- Close background tasks.
Get-Method
Expected flow:
- Load config through the live-network defaults.
- Parse
TON_CONTRACT_ADDRESSor use the documented mainnet default. - When
TON_NETWORK=testnet, requireTON_CONTRACT_ADDRESSfor defaultseqnoget-method examples until a stable testnet contract is documented. - Fetch latest block context.
- Build TVM stack.
- Run method by name.
- Check exit code.
- Decode typed stack result.
Send Message
Expected flow:
- Build external message cell.
- Serialize BoC.
- Send via LiteAPI.
- Track message hash until transaction appears.
Mempool Stream
Future flow:
- Build scanner from overlay/DHT config.
- Subscribe to pending messages.
- Filter by account or shard.
- Deduplicate by message hash.
- Emit pending and finalized stages.
Public API Design
The SDK should expose both low-level protocol access and ergonomic high-level operations.
API Layers
Low-level:
- TL request and response structs,
- ADNL peer,
- TVM cells and BoC,
- raw LiteAPI query bytes.
Mid-level:
LiteClient,LiteBalancer,- typed LiteAPI methods,
- TVM stack values.
High-level:
Contract,- wallet helpers,
- jetton helpers,
- NFT helpers,
- mempool scanner.
Naming Rules
- Use TON protocol names when exposing wire-level types.
- Use Rust idioms for high-level builders and helpers.
- Avoid long boolean parameter lists for flag-heavy methods; prefer options structs.
Ownership Rules
- Use borrowed inputs for bytes and cells where it reduces allocations without complicating API.
- Return owned values from network boundaries.
- Preserve raw bytes alongside decoded values when decoding can be lossy or incomplete.
Compatibility
The crate is pre-stable, so breaking changes are acceptable. However, every breaking change should make the API closer to:
- explicit trust assumptions,
- fewer hidden allocations,
- better feature gating,
- schema compatibility.
Missing Work
- Options structs for LiteAPI flag-heavy methods.
- Shared trait for LiteClient and LiteBalancer contract execution.
- Raw response preserving wrappers.
Error Model
TON SDK errors should preserve the subsystem boundary where the error occurred. This is required for retry policy, user diagnostics, and safe proof handling.
Error Families
| Family | Examples | Retry behavior |
|---|---|---|
| TL serialization | unknown constructor, invalid bytes padding, EOF | usually no |
| ADNL transport | IO error, integrity error, EOF, invalid handshake | retry another peer |
| LiteAPI server | liteServer.error | usually no |
| TVM data | malformed BoC, cell overflow, invalid address | no until input changes |
| Contract execution | non-zero get-method exit code | no, semantic result |
| Proof verification | invalid signature, invalid Merkle proof | no; mark peer/data untrusted |
| Balancer state | no alive peers, no archival peer | retry after reconnect |
Design Rules
- Do not map all errors to
anyhow::Errorin public APIs. - Preserve server error code and message.
- Preserve ADNL error variants for balancer retry decisions.
- Preserve TVM decode context when decoding cells, BoC, stack, or TLB data.
- Contract execution errors must include exit code and raw result bytes if available.
Current Gaps
LiteError::ServerErrordoes not include a user-friendly display of code and message.- Some TVM APIs return
anyhow::Error. - Balancer retry policy only partially distinguishes transport and semantic errors.
- Proof verification error types do not exist yet.
Cargo Feature Matrix
The crate should compile in small configurations. Optional features must isolate dependencies that are not needed by core users.
Intended Features
| Feature | Purpose | Expected modules |
|---|---|---|
std | Standard library support | all default builds |
tl | TL types and helpers | src/tl |
tvm | TVM primitives | src/tvm, src/tlb |
adnl | ADNL crypto and base types | src/adnl without TCP runtime if split further |
adnl-tcp | async TCP ADNL | ADNL peer, codec, handshake over Tokio |
liteclient | LiteAPI client | src/liteclient |
network-config | TON global config parsing | src/network_config |
cli | command line app | src/cli, src/main.rs |
Default target:
default = ["std", "adnl-tcp", "liteclient"]
Dependency Policy
Always acceptable when needed:
- pure Rust crypto crates,
- pure Rust async crates,
- serialization crates,
- testing and fixture crates as dev-dependencies.
Avoid:
- native runtime libraries,
- third-party Rust TON SDK crates,
- mandatory HTTP clients in library default features,
- mandatory logging implementations.
Verification Matrix
Required local commands:
cargo check --no-default-features
cargo check
cargo check --all-features
cargo test
cargo test --all-features
CI should also run:
cargo fmt --check
Feature Design Rules
- A module behind a feature should not leak types into always-compiled public APIs.
- Optional dependencies should be marked with
dep:namein feature definitions. - Dev-dependencies are allowed for tests even if the corresponding runtime dependency is optional.
- Examples and binaries should use
required-features.
Known Gaps
tlcurrently has response conversion helpers that mentionLiteError.adnlandadnl-tcpneed a cleaner split if UDP support is added.network-configparsing and HTTP downloading should remain separate.
Architecture Overview
tonutils is a native Rust TON SDK. Its core design constraint is autonomy: implement TON-specific protocols in this repository instead of delegating to another Rust TON SDK.
Goals
- Pure Rust TON-specific implementation.
- No runtime
.sodependency. - High-performance serialization, hashing, and networking.
- Feature-gated optional modules.
- Low-level protocol access and ergonomic high-level APIs.
- Clear separation between transport errors, LiteAPI errors, TVM decoding errors, and proof verification failures.
Layer Stack
From bottom to top:
- Hashes, CRC, crypto helpers.
- TL primitive and boxed serialization.
- ADNL transport and ADNL message types.
- LiteAPI requests and responses.
- LiteClient request execution.
- LiteBalancer peer selection and failover.
- TVM cells, BoC, TLB, dictionaries, stack values.
- Smart-contract get-method and message APIs.
- Wallet, jetton, NFT, DHT, overlay, and mempool utilities.
Dependency Direction
Lower layers must not depend on higher layers.
Allowed directions:
liteclientmay depend onadnl,tl, andtvm.contractsmay depend onliteclientandtvm.network-configmay depend on serde JSON support.climay depend on everything needed for user commands.
Forbidden directions:
tvmmust not depend onliteclient.tlmust not depend onliteclientexcept current temporary response conversion helpers; this should be removed.- ADNL primitives must not depend on LiteAPI semantics.
Current Implementation Anchors
src/adnl/: ADNL crypto, handshake, codec, peer wrapper.src/tl/: TL request, response, common, ADNL message types.src/liteclient/: client, peer, balancer, service layers.src/tvm/: cells, BoC, addresses, dictionaries, stack.src/network_config/: global config parser.src/cli/: optional CLI.
Public API Principles
- Expose typed methods for stable, common workflows.
- Expose raw byte escape hatches for schema-forward compatibility.
- Preserve proof bytes even when full verification is not implemented.
- Do not silently trust data just because it came from a liteserver.
- Use explicit mode/flag builders where boolean argument lists become ambiguous.
Missing Architecture Work
- Split TL response conversion out of
tl::utilsto remove thetl -> liteclientdependency. - Add a shared request-executor trait so
LiteClientandLiteBalancerdo not duplicate every method. - Add
contractsmodule as a high-level API layer. - Define stable error enums per subsystem.
Block, Config, And Proof TL-B Slice
Purpose And Scope
This page records the Phase 1 TL-B coverage for block, config, shard-state,
and Merkle proof primitives. The source of truth is upstream
ton-blockchain/ton crypto/block/block.tlb. The crate keeps a small
upstream-derived slice in src/tlb/schemas/block_phase1.tlb and a checked
generated summary in src/tlb/generated/block_phase1.rs.
Wire Format And Data Model
Covered constructors include:
shard_ident$00 shard_pfx_bits:(#<= 60) workchain_id:int32 shard_prefix:uint64.ext_blk_ref$_ end_lt:uint64 seq_no:uint32 root_hash:bits256 file_hash:bits256.block_id_ext$_ shard_id:ShardIdent seq_no:uint32 root_hash:bits256 file_hash:bits256.block#11ef55aa global_id:int32 info:^BlockInfo value_flow:^ValueFlow state_update:^(MERKLE_UPDATE ShardState) extra:^BlockExtra.value_flow#b8e48dfbandvalue_flow_v2#3ebf98b7.shard_state#9023afe2andsplit_state#5f327da5._ config_addr:bits256 config:^(Hashmap 32 ^Cell) = ConfigParams.- Exotic
MERKLE_PROOFtag0x03andMERKLE_UPDATEtag0x04.
The current Rust types parse stable constructor boundaries and preserve deeper child cells by reference where full generated model expansion is still pending. This keeps BoC bytes, root hashes, references, and exact reserialization stable for LiteClient workflows.
Invariants And Edge Cases
ShardIdent.shard_pfx_bitsmust be0..=60.Blockrequires constructor tag0x11ef55aaand four referenced children.ValueFlowaccepts only0xb8e48dfbor0x3ebf98b7.ShardStateaccepts unsplit0x9023afe2or split0x5f327da5.- Merkle proof/update wrappers require exotic cells with one or two references.
- Proof helper verification only checks stored virtual hashes against child hashes. It is not a full liteserver trust check.
- Exact top-level TL-B decode rejects trailing data through
TlbDeserialize.
Current Crate Mapping
src/tlb/schema.rsparses the Phase 1 schema slice and verifies the checked generated summary.src/tlb/block.rsimplementsShardIdent,ExtBlkRef,BlockIdExtTlb,Block,BlockExtra,ValueFlow,ShardState,ConfigParams,MerkleProof, andMerkleUpdate.src/liteclient/boc.rspreserves raw LiteClient BoC bytes alongside decoded cells and typed views.src/cli/mod.rsexposes offline BoC/TL-B inspection and schema checks.
Missing Work
Full generated expansion of BlockInfo, ValueFlow, BlockExtra,
ShardStateUnsplit, McStateExtra, config params, and masterchain extra
families remains follow-up work. Live or upstream-captured BoCs for block,
config, account-state, and proof paths are still backlog evidence; required
Phase 1 tests remain offline and synthetic.
Config Parameters
TON stores network configuration on-chain. LiteAPI exposes config cells through getConfigAll and getConfigParams.
LiteAPI
liteServer.getConfigAll mode:# id:tonNode.blockIdExt = liteServer.ConfigInfo;
liteServer.getConfigParams mode:# id:tonNode.blockIdExt param_list:(vector int) = liteServer.ConfigInfo;
The response contains proof bytes and config proof bytes. The config itself is encoded in TVM cells.
Common Parameters
Commonly referenced config params include:
0: config smart contract address,1: elector smart contract address,2: minter smart contract address,15: validator election timing,17: validator stake limits,18: storage prices,20: masterchain gas prices,21: basechain gas prices,24: masterchain message prices,25: basechain message prices,32: previous validator set,34: current validator set,36: next validator set.
tlb::ConfigParams decodes the top-level config:^(Hashmap 32 ^Cell) into
raw parameter cells and exposes raw-preserving wrappers for the ids above.
Exact deep TL-B schemas for each parameter still require upstream source and
fixture evidence before replacing raw cells with semantic fields.
SDK Requirements
- Fetch config params by id.
- Decode config dictionary.
- Preserve common params behind typed ids while exact deep schemas are pending.
- Verify config proof against masterchain state.
- Keep unknown params as raw cells.
Missing Work
- Deep typed config param models.
- Validator set decoder.
- Config proof verifier.
TON Blockchain Data Model
TON is a sharded blockchain. A correct SDK must model masterchain, workchains, shardchains, blocks, accounts, transactions, messages, and state proofs.
Chains
Important chain ids:
-1: masterchain.0: basechain.- other signed values: additional workchains.
The masterchain stores global consensus data, validator-related data, config, and references to shardchain blocks.
Shards
A shard is identified by a signed 64-bit shard prefix. The full shard is commonly represented by:
0x8000000000000000
When stored as i64, this value is negative. APIs must preserve the exact bits and not reinterpret it as a decimal semantic value.
Block Ids
LiteAPI uses:
tonNode.blockId workchain:int shard:long seqno:int = tonNode.BlockId;
tonNode.blockIdExt workchain:int shard:long seqno:int root_hash:int256 file_hash:int256 = tonNode.BlockIdExt;
BlockIdExt is required for fetching and verifying concrete blocks because it contains both hashes.
Accounts
An account is identified by:
- workchain id,
- 256-bit account id.
Account state includes:
- account status,
- balance,
- last transaction logical time and hash,
- code cell,
- data cell,
- storage statistics.
The account TL-B slice in src/tlb/transaction.rs covers:
StorageExtraInfo:storage_extra_none$000andstorage_extra_info$001 dict_hash:uint256;StorageInfo:used:StorageUsed,storage_extra:StorageExtraInfo,last_paid:uint32, anddue_payment:(Maybe Grams);AccountState:account_uninit$00,account_frozen$01 state_hash:bits256, andaccount_active$1 _:StateInit;AccountStorage:last_trans_lt:uint64,balance:CurrencyCollection, andstate:AccountState;Account:account_none$0oraccount$1 addr:MsgAddressInt storage_stat:StorageInfo storage:AccountStorage;ShardAccount:account:^Account,last_trans_hash:uint256, andlast_trans_lt:uint64.
DepthBalanceInfo is implemented as
depth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection, with
split_depth encoded in five bits and constrained to 0..=30.
ShardAccounts wraps HashmapAugE 256 ShardAccount DepthBalanceInfo; its
empty form still carries the top-level DepthBalanceInfo augmentation.
Transactions
Transactions are ordered per account by logical time. A transaction can include:
- inbound message,
- outbound messages,
- total fees,
- state update,
- compute phase,
- action phase,
- bounce phase,
- storage phase,
- credit phase.
The current TL-B model slice includes schema-exact action phase metadata:
tr_phase_action$_ stores booleans for success, valid, and no_funds,
AccStatusChange, optional forward/action fees, signed result code and optional
argument, four uint16 counters, action_list_hash:bits256, and
tot_msg_size:StorageUsed. The action phase links to the produced action list
only by action_list_hash; it does not embed OutList.
Transaction-description TL-B models are implemented in src/tlb/transaction.rs.
They cover storage, credit, compute, bounce, split/merge info, and the complete
TransactionDescr constructor family:
- ordinary (
trans_ord$0000); - storage-only (
trans_storage$0001); - tick-tock (
trans_tick_tock$001); - split-prepare (
trans_split_prepare$0100); - split-install (
trans_split_install$0101); - merge-prepare (
trans_merge_prepare$0110); - merge-install (
trans_merge_install$0111).
action:(Maybe ^TrActionPhase) is represented as Option<TrActionPhase> and
encoded through an exact child reference when present. The recursive
prepare_transaction:^Transaction field in split/merge install descriptions is
represented as Box<Transaction> and decoded from an exact child reference.
Top-level transaction$0111 is implemented with exact child-reference layout:
the parent stores account hash, logical times, now:uint32, outmsg_cnt:uint15,
original/final AccountStatus, total fees, and references to
HASH_UPDATE Account and TransactionDescr. The message child reference stores
in_msg:(Maybe ^(Message Any)) followed by
out_msgs:(HashmapE 15 ^(Message Any)). Outbound dictionary keys must be
15 bits, and referenced message, state-update, and description cells are decoded
exactly.
update_hashes#72 old_hash:bits256 new_hash:bits256 = HASH_UPDATE Account is
represented as the concrete HashUpdateAccount type. A generic public
HashUpdate<T> is intentionally deferred until another schema slice needs it.
Transaction history pagination usually uses (account, lt, hash).
Account Blocks
Per-shard transaction collections use augmented dictionaries so validators can commit aggregate balance data at internal dictionary nodes without scanning all leaves:
AccountBlockmaps upstreamacc_trans#5 account_addr:bits256 transactions:(HashmapAug 64 ^Transaction CurrencyCollection) state_update:^(HASH_UPDATE Account). The transaction dictionary is a non-emptyHashmapAugkeyed by 64-bit logical time. Leaf values are referencedTransactioncells and leaf/fork augmentations areCurrencyCollection.ShardAccountBlockswrapsHashmapAugE 256 AccountBlock CurrencyCollection, keyed by 256-bit account address hash. The top-levelHashmapAugEextra is preserved even when the dictionary is empty.
The generic dictionary layer preserves decoded leaf, fork, and top-level augmentation values. It does not infer TON-specific aggregation rules; callers that construct augmented dictionaries must supply the augmentation values.
Messages
Message families:
- internal message,
- external inbound message,
- external outbound message.
Messages contain CommonMsgInfo, optional state init, and body.
State
Shard state contains account collections and metadata. Masterchain state contains global config and references to shard states. Proof verification requires decoding enough state structure to verify inclusion paths.
Crate Mapping
Current crate has low-level cell/address support plus focused TL-B models for
messages, outbound action lists, transaction action phase metadata, and
transaction descriptions, account state, ShardAccount, HASH_UPDATE Account,
top-level Transaction, augmented ShardAccounts, AccountBlock, and
ShardAccountBlocks. Full block headers, value flow, BlockExtra, shard-state
models, and config params should be introduced in future TL-B slices.
Missing Work
- TL-B definitions for full blocks, value flow, shard hashes, and shard state.
- Proof path extraction from shard state.
- Golden BoC fixtures for real account and transaction cells.
Messages And Transactions
Messages trigger account transactions and are the main object users send to contracts.
This page tracks the message and transaction-description subset currently mapped
in src/tlb/message.rs and src/tlb/transaction.rs.
The source of truth is upstream ton-blockchain/ton
crypto/block/block.tlb at
https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb,
especially the MsgAddressInt, MsgAddressExt, MsgAddress, Grams,
CurrencyCollection, StateInit, StateInitWithLibs, CommonMsgInfo,
CommonMsgInfoRelaxed, Message, MessageRelaxed, LibRef, OutAction,
TrStoragePhase, TrCreditPhase, TrComputePhase, TrBouncePhase,
SplitMergeInfo, and TransactionDescr definitions.
The overview page at
https://docs.ton.org/v3/documentation/data-formats/layout/messages is useful
for conceptual orientation, but constructor tags and field order come from the
upstream TL-B schema.
Message Families
TON messages are represented by TLB constructors. The major families are:
- internal messages between contracts,
- external inbound messages from outside the blockchain,
- external outbound messages emitted outside the blockchain.
The implemented CommonMsgInfo constructors are:
int_msg_info$0: internal message metadata.ext_in_msg_info$10: inbound external message metadata.ext_out_msg_info$11: outbound external message metadata.
The implemented CommonMsgInfoRelaxed constructors are:
int_msg_info$0: internal relaxed metadata.ext_out_msg_info$11: outbound external relaxed metadata.
Relaxed common info intentionally has no ext_in_msg_info$10 constructor.
Its internal constructor uses src:MsgAddress, so the source can be internal
or external, while dest remains MsgAddressInt.
Common Message Info
Common message info stores routing and fee metadata. Fields vary by message family but can include:
- source address,
- destination address,
- value,
- import fee,
- ihr fee,
- forward fee,
- creation logical time,
- creation unix time,
- bounce flags.
For current upstream internal messages, this crate stores
extra_flags:(VarUInteger 16) between value:CurrencyCollection and
fwd_fee:Grams. Older schema notes or SDKs may call this position ihr_fee;
that is not the field implemented here.
State Init
A message can carry StateInit to deploy a contract. State init can include:
- fixed prefix length,
- special tick/tock flags,
- code cell,
- data cell,
- libraries.
The implemented upstream form is:
fixed_prefix_length:(Maybe (## 5));special:(Maybe TickTock);code:(Maybe ^Cell);data:(Maybe ^Cell);library:(Maybe ^Cell).
StateInitWithLibs is also implemented for message validation paths. It uses
the same fixed prefix, special, code, and data fields as StateInit, but stores
library:(HashmapE 256 SimpleLib) instead of Maybe ^Cell.
SimpleLib is:
public:Bool;root:^Cell.
The 256-bit StateInitWithLibs.library key is the library hash. Values preserve
the referenced library root cell exactly.
Addresses
Internal message addresses support:
addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256;addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) workchain_id:int32 address:(bits addr_len).
addr_std maps to the existing Address type for the workchain and 256-bit
hash while preserving optional anycast. addr_var stores raw address bits and
the exact bit length.
External message addresses support:
addr_none$00;addr_extern$01 len:(## 9) external_address:(bits len).
The external model stores raw bits in Vec<u8> plus a bit length. It does not
use the older ExternalAddress helper because that helper is limited to a
u64 value and cannot represent arbitrary 511-bit external addresses.
MsgAddress wraps either MsgAddressInt or MsgAddressExt by preserving the
underlying address constructor. The first two bits therefore still select
00, 01, 10, or 11; there is no additional wrapper tag.
Values
Grams wraps nanograms$_ amount:(VarUInteger 16) = Grams. Encodings must be
canonical: zero uses a zero byte-length prefix, and non-zero values use the
shortest big-endian byte representation.
CurrencyCollection stores grams:Grams and an extra-currency dictionary:
HashmapE 32 (VarUInteger 32). Dictionary keys are fixed-width 32-bit currency
ids. Values are canonical VarUInteger 32 payloads.
Body
The implemented Message type models Message Any:
info: CommonMsgInfo;init:(Maybe (Either StateInit ^StateInit));body:(Either Cell ^Cell), represented asEither<Arc<Cell>, Arc<Cell>>.
Inline and referenced placement is preserved explicitly. Inline body decoding
consumes all remaining bits and references into a new cell. Referenced body
decoding loads exactly one child reference; Message::from_cell then rejects
trailing parent bits or references.
The implemented MessageRelaxed type models MessageRelaxed Any:
info: CommonMsgInfoRelaxed;init:(Maybe (Either StateInit ^StateInit));body:(Either Cell ^Cell), represented asEither<Arc<Cell>, Arc<Cell>>.
It uses the same inline versus referenced state-init and body placement rules as
Message Any. This is the form used by action_send_msg in transaction action
lists.
The body is usually a cell. For wallet messages it often contains:
- operation code,
- query id,
- transfer parameters,
- comments or payload references.
Out Actions
OutAction models the closed action family emitted by the TVM action phase:
action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any);action_set_code#ad4de08e new_code:^Cell;action_reserve_currency#36e6b809 mode:(## 8) currency:CurrencyCollection;action_change_library#26fa1dd4 mode:(## 7) libref:LibRef.
action_send_msg always stores the relaxed outbound message as an exact child
cell. action_change_library stores a seven-bit mode, so Rust serialization
rejects values above 127.
LibRef has two constructors:
libref_hash$0 lib_hash:bits256, mapped to[u8; 32];libref_ref$1 library:^Cell, preserving the referenced library cell.
OutList stores actions as the upstream recursive linked list:
out_list_empty$_ = OutList 0, encoded as an empty cell with no bits and no references;out_list$_ {n:#} prev:^(OutList n) action:OutAction = OutList (n + 1), encoded as one previous-list reference followed by the currentOutAction.
The Rust model exposes the list as Vec<OutAction> in schema/execution order.
The first vector item is stored deepest next to out_list_empty$_; the last
item is stored in the root node. TON limits action lists to 255 actions. The
codec rejects serialization and decoding above that limit and decodes each
previous-list reference exactly.
TrActionPhase stores the action phase result metadata, but it does not embed
OutList. The upstream field is action_list_hash:bits256, which is the hash
of the resulting action list. Decode OutList separately when a c5/action-list
cell is available, then compare or store its cell hash through
TrActionPhase.action_list_hash.
The implemented action phase fields are:
success:Bool,valid:Bool, andno_funds:Bool;status_change:AccStatusChange, with tags0,10, and11;total_fwd_fees:(Maybe Grams)andtotal_action_fees:(Maybe Grams);result_code:int32andresult_arg:(Maybe int32);tot_actions:uint16,spec_actions:uint16,skipped_actions:uint16, andmsgs_created:uint16;action_list_hash:bits256;tot_msg_size:StorageUsed, whereStorageUsediscells:(VarUInteger 7)andbits:(VarUInteger 7).
Transaction Phases
src/tlb/transaction.rs implements the transaction phases that are needed by
TransactionDescr:
tr_phase_storage$_ storage_fees_collected:Grams storage_fees_due:(Maybe Grams) status_change:AccStatusChange;tr_phase_credit$_ due_fees_collected:(Maybe Grams) credit:CurrencyCollection;tr_phase_compute_skipped$0 reason:ComputeSkipReason;tr_phase_compute_vm$1 success:Bool msg_state_used:Bool account_activated:Bool gas_fees:Grams ^[...];tr_phase_bounce_negfunds$00;tr_phase_bounce_nofunds$01 msg_size:StorageUsed req_fwd_fees:Grams;tr_phase_bounce_ok$1 msg_size:StorageUsed msg_fees:Grams fwd_fees:Grams.
ComputeSkipReason uses the upstream tags 00 (cskip_no_state), 01
(cskip_bad_state), 10 (cskip_no_gas), and 110
(cskip_suspended). The 111 bit pattern is invalid and is rejected.
The VM compute phase stores success, msg_state_used, account_activated,
and gas_fees in the parent cell, then stores the remaining VM metadata in an
exact child reference:
gas_used:(VarUInteger 7)andgas_limit:(VarUInteger 7), with at most six payload bytes;gas_credit:(Maybe (VarUInteger 3)), with at most two payload bytes;mode:int8,exit_code:int32, andexit_arg:(Maybe int32);vm_steps:uint32;vm_init_state_hash:bits256andvm_final_state_hash:bits256.
The child reference is decoded exactly. Trailing bits or references in the VM
tail are reported as an invalid TrComputePhase.vm reference payload.
Transaction Descriptions
The implemented TransactionDescr constructors are:
trans_ord$0000 credit_first:Bool storage_ph:(Maybe TrStoragePhase) credit_ph:(Maybe TrCreditPhase) compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) aborted:Bool bounce:(Maybe TrBouncePhase) destroyed:Bool;trans_storage$0001 storage_ph:TrStoragePhase;trans_tick_tock$001 is_tock:Bool storage_ph:TrStoragePhase compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) aborted:Bool destroyed:Bool;trans_split_prepare$0100 split_info:SplitMergeInfo storage_ph:(Maybe TrStoragePhase) compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) aborted:Bool destroyed:Bool;trans_split_install$0101 split_info:SplitMergeInfo prepare_transaction:^Transaction installed:Bool;trans_merge_prepare$0110 split_info:SplitMergeInfo storage_ph:TrStoragePhase aborted:Bool;trans_merge_install$0111 split_info:SplitMergeInfo prepare_transaction:^Transaction storage_ph:(Maybe TrStoragePhase) credit_ph:(Maybe TrCreditPhase) compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) aborted:Bool destroyed:Bool.
action:(Maybe ^TrActionPhase) maps to Option<TrActionPhase>, but it is
encoded through a referenced child cell exactly as the schema requires. A
present action stores a 1 bit and a child reference containing
TrActionPhase; an absent action stores only 0. Referenced action payloads
must consume all child bits and references.
SplitMergeInfo is an implicit constructor containing
cur_shard_pfx_len:(## 6), acc_split_depth:(## 6), this_addr:bits256, and
sibling_addr:bits256. Serialization rejects prefix lengths above 63.
The split-install and merge-install constructors currently preserve
prepare_transaction:^Transaction as typed Box<Transaction> values decoded
from exact child references. The box is only Rust layout indirection for the
recursive schema; it is not an extra TL-B layer.
Transaction Relation
Each transaction has at most one inbound message and zero or more outbound messages. To confirm a sent external message, locate the transaction whose inbound message hash matches the sent message hash.
SDK Requirements
- Build external inbound messages from the hand-written TL-B model.
- Build internal messages from the hand-written TL-B model.
- Compute message hash from the resulting cell.
- Decode inbound and outbound messages from transactions.
- Track sent message inclusion.
Missing Work
- Golden BoC fixtures for real upstream or liteserver messages.
- Typed message body wrappers for wallet, jetton, and contract-specific bodies.
- Wallet-specific message builders.
- Transaction location helpers.
- Fee estimation helpers.
Sharding
TON shards split account space so transactions can be processed in parallel.
Shard Identifier
Shard ids are 64-bit prefixes. The high bit is significant. The full shard is represented by:
0x8000000000000000
Do not format shard ids only as signed decimals in diagnostics; preserve hex output for clarity.
Account To Shard
An account belongs to a shard based on the prefix of its 256-bit account id. As shards split and merge, the shard covering an account can change over time.
Masterchain Relation
Masterchain blocks reference shardchain blocks. A light client verifies shard data by proving the shard block is referenced by a verified masterchain block.
LiteAPI Methods
Relevant methods:
liteServer.getShardInfo ...
liteServer.getAllShardsInfo ...
liteServer.getShardBlockProof ...
SDK Requirements
- Determine account shard at a given masterchain block.
- Fetch shard block proof.
- Verify shard inclusion.
- Handle shard split and merge history.
Missing Work
- Shard descriptor TLB decoder.
- Account-to-shard helper.
- Split/merge traversal helpers.
Blockchain TL-B Coverage
Purpose And Source
This page tracks the checked blockchain TL-B surface implemented in src/tlb.
The protocol source of truth is upstream ton-blockchain/ton
crypto/block/block.tlb; the local checked snapshot is
src/tlb/schemas/block.tlb.
The current snapshot is not yet the complete upstream file. It contains the families that are backed by typed codecs or raw-preserving public wrappers. Full upstream sync, constructor drift checks for every family, and fixture-backed block/shard/config/proof roundtrips remain active TODO items.
Coverage Matrix
| Upstream family | Rust model | Codec status | Tests or examples |
|---|---|---|---|
MsgAddress, Message, StateInit | tlb::message::* | typed | TL-B unit tests, tlb_message_roundtrip |
Account, ShardAccount | tlb::transaction::* | typed | TL-B unit tests, tlb_account_state_roundtrip |
Transaction, phases, account blocks | tlb::transaction::* | typed | TL-B unit tests, tlb_transaction_roundtrip, tlb_read_tx_data |
ShardIdent, ExtBlkRef, BlockIdExt | tlb::block::* | typed | block unit tests |
Block | tlb::Block | typed root with referenced child cells | tlb_block_wrapper_decode |
BlockInfo, BlockPrevInfo | tlb::BlockInfo, tlb::BlockPrevInfo | raw-preserving wrappers | schema summary check |
ValueFlow | tlb::ValueFlow | constructor-checked raw payload | block unit tests |
BlockExtra, McBlockExtra | tlb::BlockExtra, tlb::McBlockExtra | raw-preserving wrappers | schema summary check |
ShardState, ShardStateUnsplit | tlb::ShardState, tlb::ShardStateUnsplit | constructor-checked raw payload | block unit tests |
ConfigParams | tlb::ConfigParams | typed address, decoded Hashmap 32 ^Cell entries, raw-preserving wrappers for common param ids | tlb_config_params_wrapper, block unit tests |
HASH_UPDATE | tlb::HashUpdate | typed | block unit tests |
MERKLE_PROOF, MERKLE_UPDATE | tlb::MerkleProof, tlb::MerkleUpdate | exotic-cell wrappers with virtual hash checks | proof_verify |
Derive And Adapter Surface
The optional tlb-derive feature enables the tonutils-macros proc-macro
crate and re-exports tlb::Tlb and tlb::TlbDerive. The macro generates the
existing runtime traits, not a separate runtime. Supported attributes are:
#[tlb(tag = "101")],#[tlb(tag = "0b101")],#[tlb(tag = "0x5")], and#[tlb(tag = "#5")]on structs or enum variants for fixed constructor tags. Hex tags expand to four bits per digit.#[tlb(bits = N)]on integer/hash fields for exact-width encoding throughStoreBits<N>andLoadBits<N>. Unsigned primitive fieldsu8,u16,u32,u64, andu128infer their natural width. Signed integer fields requirebits; float primitive fields are rejected because the runtime does not define TL-B float semantics.#[tlb(reference)]or#[tlb(ref)]on fields for^Tchild-cell encoding.
Runtime helpers added for macro and handwritten codecs:
CellRef<T>for referenced typed values.RawCellfor intentionally opaque cell payloads.VarUInteger<N>for canonical variable-width unsigned integers.TlbHashmapE<T, N>for typed dictionary values using TL-B codecs.
Exact top-level decode continues to use TlbDeserialize::from_cell, which
rejects trailing bits and references. Referenced decode uses load_ref_tlb and
also requires exact child consumption.
Known Limits
The derive macro currently handles product structs and simple tagged enums. It
does not yet generate schema-driven dictionary adapters, parameterized TL-B
types, implicit CRC tags, ambiguous-prefix decision trees, or trybuild-style
negative tests. Those gaps are tracked in TODO.md.
ABI Data Model
The ABI module is the foundation for typed contract calls. It defines Rust structs and enums for describing contract methods, message handlers, events, parameters, selectors, and TON/TVM-oriented value types, plus scalar runtime value conversion to and from TVM stack entries.
The public module is tonutils::abi and the core model/codecs are available
behind the existing tvm Cargo feature because they depend on TVM cells,
slices, addresses, and stack values. JSON ABI loading is available behind the
narrower abi-json feature, which adds serde_json on top of tvm.
Scope
The current model covers:
- ABI documents as
AbiDefinition { name, version, contracts }. - Contract entries as
AbiContract { name, methods, events }. - Functions as
AbiFunction { name, kind, selector, inputs, outputs }. - Events as
AbiEvent { name, selector, fields }. - Parameters as
AbiParameter { name, ty, optional }. - Selectors as
None,MethodId(u64), orOpcode(u32). - Function kinds for get-methods, internal messages, and external messages.
- Runtime values as
AbiValuefor integers, booleans, bytes, strings, addresses, cells, slices, tuples, arrays, and optional values.
Validation is intentionally limited to local invariants:
- required names must be non-empty after trimming whitespace,
- signed and unsigned integer widths must be in
1..=257, - tuple, array, map, and optional types are validated recursively,
- unknown type spellings must be non-empty so they can be preserved safely.
Type Vocabulary
AbiType currently supports:
Int { bits }andUint { bits },Bool,Bytes,String,Address,Cell,Slice,Tuple(Vec<AbiParameter>),Array(Box<AbiType>),Map { key, value },Optional(Box<AbiType>),Unknown(String).
The 257 integer-width upper bound matches TVM integer capacity assumptions
used by TON stack values.
Stack Value Mapping
AbiValue::to_stack_entry, AbiValue::from_stack_entry, to_stack_entry,
and from_stack_entry convert values against an explicit AbiType. These
helpers are intentionally value-level only: they do not call contracts or
select network providers.
Current stack mappings:
Int { bits }andUint { bits }map toTvmStackEntry::Int, with declared width validation and signed or unsigned range checks.Boolmaps to TVM integer-1for true and0for false. Decoding rejects all other integer values.BytesandStringmap to aCellcontaining byte-aligned snake data.Stringdecoding requires valid UTF-8.Addressmaps to aSlicecontaining canonicalMsgAddressInt::std(address)bytes and decodes only standard internal addresses without anycast.Cellmaps toTvmStackEntry::Cell.Slicemaps toTvmStackEntry::Slice.Tuplemaps toTvmStackEntry::Tupleand follows declared field order.Arraymaps toTvmStackEntry::List.Mapmaps toTvmStackEntry::Cellcontaining a localHashmapE key_bits ^AbiValueCelldictionary. Keys must be fixed-widthuintNorintN;key_bitsis inferred fromNwhen omitted. Duplicate encoded keys are rejected, and decoded entries are returned in canonical dictionary key order.Optional(None)maps toTvmStackEntry::Null; present optional values map as their nested type.
Unknown returns an explicit unsupported conversion error. Map support is a
deterministic local ABI policy and is not yet upstream compatibility evidence.
encode_get_method_inputs validates that a function is a GetMethod with
either no selector or a MethodId, checks input arity, and converts each input
value into a TVM stack entry. decode_get_method_outputs applies the same
get-method selector checks to returned stack entries and decodes them in ABI
output order. Both helpers are local stack codecs; method-id routing and
network execution remain contract-wrapper responsibilities. Contract exposes
run_abi_get_method and run_abi_get_method_latest for this workflow: they
derive the method id from MethodId or the ABI function name, call the normal
typed get-method path, and return ABI output values.
Message Body Mapping
encode_message_body and decode_message_body support ABI input values for
InternalMessage and ExternalMessage functions. Opcode(u32) selectors are
encoded as the first 32 bits of the body. None selectors have no prefix.
GetMethod functions and MethodId selectors are rejected for message bodies.
Current body mappings:
Int { bits }andUint { bits }encode inline with the declared width.Boolencodes as one inline bit.Addressencodes inline as a standardMsgAddressInt.BytesandStringencode as referenced byte-aligned snake cells.CellandSliceencode as referenced cells.Tupleencodes fields inline in declared order.Mapstores a reference to the same localHashmapE key_bits ^AbiValueCelldictionary root used by stack conversion.Optionalencodes aMaybebit followed by the nested value when present.
Decoding is exact and rejects opcode mismatches or trailing bits/references.
Array and Unknown are intentionally unsupported for message bodies until a
sequence layout policy is documented.
encode_payload_components and decode_payload_components expose the same
component mapping without a selector prefix. encode_event_payload and
decode_event_payload apply that component mapping to AbiEvent fields:
Opcode(u32) selectors use the same 32-bit prefix as message bodies, None
has no prefix, and MethodId is rejected as get-method-only. This event
payload support is symmetric local-policy coverage only; checked bytes are not
yet claimed as upstream-captured event evidence.
JSON Loader
parse_abi_json_str and parse_abi_json_value are compiled with
abi-json. The loader accepts a local schema with:
- top-level
name,version, andcontracts, - contract
name, optionalmethods, and optionalevents, - function
name,kind, optionalselector, optionalinputs, and optionaloutputs, - event
name, optionalselector, and optionalfields, - parameter
name,type, and optional booleanoptional.
Function kinds use get_method, internal_message, or external_message
with short aliases get, internal, and external. Selectors are objects
with either method_id or opcode; numeric values may be JSON numbers,
decimal strings, or 0x hex strings. A selector object containing both
method_id and opcode is rejected as ambiguous.
Types may be strings such as uint64, int257, bool, bytes, string,
address, cell, slice, optional<uint32>, or array<cell>. Recursive
object forms are also accepted:
{ "tuple": [parameter, ...] },{ "array": type },{ "optional": type },{ "map": { "key": type, "value": type, "key_bits": optional_integer } },{ "unknown": "raw-spelling" }.
Map key types must be uintN or intN. When key_bits is omitted, it is
inferred from N; when provided, it must match the integer key width.
Diagnostics include JSON paths for missing fields, invalid JSON kinds,
ambiguous selectors, and known compatibility shapes that are not implemented
by the local loader yet. Local model validation still runs after parsing, so
integer-width and empty-name violations are reported through
AbiJsonError::Model.
CLI ABI Invocation
The cli feature includes abi-json. contract run-abi-get-method loads an
ABI JSON file, selects a contract and get-method, parses repeated
--arg name=json values, encodes get-method inputs to a TVM stack, executes
the get-method through the selected liteserver, and decodes returned stack
entries into named ABI output values. If --contract is omitted, the ABI file
must contain exactly one contract. If --method is omitted, the selected
contract must contain exactly one get-method.
CLI argument parsing accepts JSON integer numbers or decimal/hex integer
strings for ints and uints, JSON booleans and strings, hex strings for bytes,
TON address strings, tuple objects keyed by ABI field name, arrays for stack
types where the ABI stack codec already supports arrays, optional null, and
BoC hex strings for cells and slices. Map arguments use arrays of
{ "key": ..., "value": ... } entries and are encoded with the local fixed
integer-key dictionary policy.
Golden Fixtures
Checked ABI fixture metadata lives in fixtures/abi/contracts.json. The file
uses schema revision 1 with a top-level synthetic source note, capture date,
and a fixtures array. Each fixture stores evidence metadata
(evidence_kind, source_url, source_commit, network, account,
block_id, method_id, capture_command, and compat_reference), a local
ABI JSON document, input values in fixture-only JSON form, and the expected
offline wire artifacts:
- get-method fixtures store
input_stack_boc_hex,input_stack_root_hash, returnedoutput_stack_boc_hex,output_stack_root_hash, and expected decoded ABI outputs; - message-body fixtures store
message_body_boc_hex, the body root representation hash, and expected decoded inputs; - map fixtures store ABI JSON with fixed integer keys and fixture-only
{ "key": ..., "value": ... }entries for stack and message-body roundtrips. - event fixtures store
message_body_boc_hex, the event payload root representation hash, and expected decoded event fields generated from the local event payload policy.
The message-body BoCs are body-cell BoCs only. They are not full internal or
external message BoCs and do not include CommonMsgInfo, state init, fees, or
envelope metadata.
The current fixture set includes synthetic offline coverage generated from the
local ABI policy documented above and opt-in live capture templates for wallet
seqno and TEP-74 get_wallet_address(owner). The synthetic entries lock
deterministic behavior for already implemented JSON loading, get-method stack
conversion, message-body encode/decode, and local map/dictionary conversion.
The opt-in entries are templates only until stack/result bytes from the capture
command are checked in, so independent compatibility validation remains open.
Non-Goals
This step does not implement:
- independent upstream or live-captured ABI compatibility vectors.
The module should therefore not be described as ABI execution support. It is a stable Rust vocabulary and scalar stack conversion foundation for follow-up work.
Next Steps
Planned follow-up work:
- cross-check checked ABI fixtures against accepted TON protocol evidence and compatibility references.
Smart Contract Get-Methods
Get-methods are read-only TVM executions against account state.
LiteAPI Function
liteServer.runSmcMethod mode:# id:tonNode.blockIdExt account:liteServer.accountId method_id:long params:bytes = liteServer.RunMethodResult;
Method Id From Name
Common tooling maps names to ids:
(crc16(method_name) & 0xffff) | 0x10000
Input Stack
params must encode TVM stack values in the format expected by liteserver.
The internal stack codec preserves nulls, integers, cells, slices, tuples,
lists, and explicit unsupported payload bytes. The checked offline fixture
fixtures/tvm/stack.json records deterministic non-empty input stack BoCs,
root hashes, decoded entry shapes, and canonical reserialization checks for
scalar, deep stack-chain, nested tuple/list, huge integer, cell/slice, and
unsupported payload cases. This confirms the crate’s own offline format is
reproducible; successful live captures and cross-SDK comparisons are still
needed before claiming full TON node compatibility for every non-empty shape.
For opt-in live evidence, run the ignored
live_non_empty_stack_run_get_method_smoke test with
TON_GLOBAL_CONFIG_JSON, TON_STACK_TEST_CONTRACT_ADDRESS,
TON_STACK_TEST_METHOD defaulting to seqno, and TON_STACK_TEST_JSON.
TON_STACK_TEST_ACCEPT_EXIT_CODE may be set when the selected method is
expected to reject the supplied non-empty stack.
Successful exit_code == 0 runs print fixture JSON with params/result BoCs and
decoded stack output. Non-zero accepted runs remain smoke tests only and should
not be promoted to captured compatibility fixtures.
The public conversion layer is:
ToTvmStackandFromTvmStackfor full get-method argument and result stacks,ToTvmStackEntryandFromTvmStackEntryfor one stack item,TvmStackConversionErrorfor arity, type, integer range, bool, and address failures.
Built-in conversions cover (), TvmStack, Vec<TvmStackEntry>,
TvmStackEntry, signed and unsigned Rust integers, BigInt, BigUint,
bool, standard internal Address stack slices, Arc<Cell> cells,
Option<T> as Null or the inner entry, and tuples up to eight fields.
Result
Important result fields:
exit_code,- optional
result, - optional proof fields,
shardblkexecution context.
Non-zero exit_code is a contract result, not a transport failure.
High-Level API Design
The contracts module provides Contract<'a, P> over any ContractProvider.
ContractProvider is implemented for both LiteClient and LiteBalancer.
Contract<'a, P> remains the address-bound execution wrapper. The
ContractBlueprint trait models contracts whose address is derived from a
fixed code BoC and typed TL-B data:
data()returns the typedTlbSerializestate data,code_boc()returns fixed code BoC bytes,workchain()defaults to0,state_init()decodes code, serializes data, and fillsStateInit.codeandStateInit.data,address()calls the sharedaddress_from_state_initprimitive,bind()creates a normal address-boundContract<'a, P>.
The optional contract-derive feature re-exports tonutils::Contract as a
derive macro. The macro accepts #[contract(code = ...)],
#[contract(code_hex = "...")], or #[contract(code_file = "...")] and
rejects unnamed/unit structs, missing data, extra fields, and multiple code
sources.
The wrapper provides:
- account address,
- raw, decoded, and simple latest-block account-state fetch,
- active-account balance, code, data, and
StateInitaccessors, - get-method execution by numeric method id,
- get-method execution by method name,
- typed result helpers through
RunMethodResultExt, - high-level typed get-method helpers that fail on non-zero
exit_code, - conversion-trait helpers
run_get_method_as,run_get_method_by_name_as,run_get_method_latest_as, andrun_get_method_by_name_latest_as, - raw external message BoC submission,
- account transaction-history lookup,
StateInitaddress derivation from the serialized cell hash,- direct embedding of address-bound
Contract<'a, P>in typed clients.
Proof verification mode is not implemented yet. The wrapper preserves LiteAPI proof bytes in the response structures.
Error Semantics
High-level helpers return ContractError<P::Error>:
- provider failures preserve the original LiteClient or LiteBalancer error,
- non-zero get-method exit codes are
NonZeroExitCode, - TVM stack or TL-B decode failures are
Decode, - Rust value and TVM stack conversion failures are
StackConversion, - active-state helpers return missing-state variants for none, uninit, frozen, or absent code/data.
run_get_method remains a raw LiteAPI wrapper and returns non-zero exit codes
in RunMethodResult without treating them as transport errors.
Capability Acceptance
- LiteClient and LiteBalancer must expose the same
ContractProviderbehavior for account state, get-methods, raw external BoC submission, and account transactions. - Contract wrapper tests must prove provider routing, state decoding, active account field extraction, missing-state errors, method-name mapping, stack decoding, non-zero exit handling, raw external BoC preservation, transaction routing, state-init address derivation, blueprint state/address/bind semantics, conversion trait arity/range/type errors, latest-block typed helper routing, and address-bound typed-client delegation.
- Derive macro tests must cover supported code source attributes, default and explicit workchains, and rejected ambiguous struct shapes.
- Live-network tests remain ignored until checked fixtures or opt-in network configuration are available.
Missing Work
- Capture successful live non-empty stack fixtures.
- Compare non-empty stack serialization with tonutils-go and tonlib behavior.
- Expand result stack decoding against live liteserver fixtures.
- Add known contract fixtures.
- Add proof verification.
- Add wallet signing, deployment builders, and ABI-driven message bodies.
External Messages
External messages are used to send transactions to contracts, usually wallets.
LiteAPI Function
liteServer.sendMessage body:bytes = liteServer.SendMsgStatus;
body is a serialized external message BoC.
Required TLB Models
Message construction needs:
CommonMsgInfo,- external inbound message info,
- internal message info,
StateInit,- message body cell,
- wallet-specific signing payloads.
Send Flow
- Build message cell.
- Serialize as BoC.
- Send
liteServer.sendMessage. - Return the opaque
liteServer.SendMsgStatus.statussubmission status.
Wallet V5R1 and V4R2 helpers are accepted submission adapters for this flow:
they build and sign an external-in message, optionally include StateInit for
deploy or first-message workflows, submit exactly one BoC through
ContractProvider::send_external_message_boc, and surface the provider’s
status or error. They do not prove transaction inclusion.
Post-submit confirmation remains a separate flow:
- Track inclusion by message hash.
- Locate transaction in account history.
- Verify execution status and fees against the expected wallet/account state.
Missing Work
- Fee estimation helpers.
- Message tracking API.
- Post-send transaction lookup and inclusion verification.
TEP Metadata Roadmap
This page records the planned metadata parsing work for Phase 2 wrappers.
Implementation is tracked in TODO.md; this document keeps standards and
scope visible before parser code lands.
Standards
- TEP-64 defines token and NFT metadata content layouts, including off-chain URI content and on-chain dictionary content.
- TEP-74 defines jetton contract interfaces whose wrappers need metadata from jetton master data.
- TEP-62 defines NFT item and collection interfaces whose wrappers need collection and item metadata.
The parser should use upstream TON and official TEP behavior as protocol truth. SDKs such as pytoniq, pytoniq-core, tonutils-go, tongo, and STON.fi ton-rs are comparison references only.
Initial Parser Shape
The first metadata layer should be common and raw-preserving:
- Decode snake-cell byte strings.
- Decode chunked content dictionaries.
- Distinguish on-chain content, off-chain URI content, and unsupported content.
- Preserve raw cells and unknown keys so future TEP extensions do not become lossy decode failures.
- Surface malformed content as structured errors with enough context for users to inspect the original cell.
src/metadata.rs implements this common layer as parse_tep64_content:
- top-level
0x01is decoded as off-chain URI content using snake bytes; - top-level
0x00is decoded as on-chainHashmapE 256 ^Cellcontent keyed by SHA-256 field-name hashes; - on-chain values with in-value
0x00decode as snake bytes; - on-chain values with in-value
0x01decode as chunkedHashmapE 32 ^Cellcontent and concatenate chunks in ascending chunk-index order; - unsupported top-level tags and unknown dictionary keys preserve the raw cell;
- malformed on-chain field values are preserved as field diagnostics instead of dropping the entire dictionary.
The recognized key set currently covers common TEP-64 fields used by jettons and
NFTs: uri, name, description, image, image_data, symbol,
decimals, amount_style, render_type, content_url, and video.
Jetton and NFT wrappers should build on that common layer instead of duplicating metadata parsing.
Jetton Metadata
src/jetton.rs implements typed metadata support for TEP-74-compatible jetton
masters. The wrapper decodes the official get_jetton_data() stack layout:
total_supplyas a non-negative integer;mintableas-1for true and0for false;admin_addressas a standard internal address oraddr_none;jetton_contentas a TEP-64 content cell;jetton_wallet_codeas the returned wallet code cell.
JettonMasterData::metadata() maps the parsed TEP-64 content into
JettonMetadata fields for uri, name, description, image,
image_data, symbol, decimals, and amount_style. Unknown keys,
unsupported jetton-adjacent keys, raw content, and field-level malformed value
diagnostics remain inspectable. Off-chain JSON fetching is not implemented in
this layer; off-chain content only fills the discovered URI.
Required fixture coverage:
- Off-chain URI content: covered by deterministic unit tests.
- On-chain dictionary content: covered by deterministic unit tests.
- Unknown-key preservation: covered by deterministic unit tests.
- Malformed field diagnostics and top-level malformed content rejection: covered by deterministic unit tests.
The Contract helper jetton_master_data_latest() runs get_jetton_data at
the provider’s latest masterchain block and decodes successful stacks.
jetton_metadata_latest() returns the mapped metadata. Mock-provider tests
cover method-id routing, latest-block lookup, provider errors, and non-zero
exit code propagation.
Jetton Message Bodies
src/jetton/payload.rs implements typed TEP-74 message-body builders and
decoders for:
transfer#0f8a7ea5;burn#595f07bc;internal_transfer#178d4519;transfer_notification#7362d09c;excesses#d53276db.
The builders preserve message addresses as TL-B MsgAddress, encode jetton
amounts and forwarded Toncoin values as VarUInteger 16 through Grams, and
support both inline and referenced forward_payload branches. Custom payloads
are encoded as Maybe ^Cell, matching the standard message body shape.
Deterministic unit tests cover roundtrips, opcode rejection, empty excess
payloads, custom payload references, and inline versus referenced forward
payloads.
NFT Metadata
src/nft.rs implements typed metadata support for TEP-62-compatible NFT
collections and items. The wrapper decodes the official get-method stack
layouts:
get_collection_data()returnsnext_item_index,collection_content, andowner_address;get_nft_data()returnsinit?,index,collection_address,owner_address, andindividual_content;get_nft_content(index, individual_content)returns a full TEP-64 content cell for a collection-backed item.
NftCollectionData::metadata() maps collection content into NftMetadata.
Standalone item metadata is supported by nft_item_metadata_latest() when
collection_address is addr_none; collection-backed item metadata should be
resolved by running nft_full_item_metadata_latest(&item_data) on the
collection contract so merge behavior remains contract-defined.
Address stack entries accept addr_none as None and standard internal
addresses as Some(Address). External addresses, variable-length internal
addresses, anycast, trailing data, and malformed cells are structured decode
errors.
NftMetadata maps uri, name, description, image, image_data,
render_type, content_url, and video. Unknown keys and known but
NFT-unmapped jetton fields such as symbol, decimals, and amount_style
remain raw-preserved. Malformed known field values become field diagnostics
instead of invalidating the whole metadata object.
Required fixture coverage:
- Collection metadata: covered by deterministic unit tests.
- Item metadata: covered by deterministic unit tests.
- Individual-content merge behavior through
get_nft_content: covered by deterministic mock-provider tests. - Unknown-key preservation: covered by deterministic unit tests.
- Malformed content rejection and malformed field diagnostics: covered by deterministic unit tests.
NFT Message Bodies
src/nft/payload.rs implements typed TEP-62 and TEP-66 message-body builders
and decoders for:
transfer#5fcc3d14;ownership_assigned#05138d91;excesses#d53276db;get_static_data#2fcb26a2;report_static_data#8b771735;get_royalty_params#693d3950;report_royalty_params#a8cb00ad.
The transfer and ownership helpers share the same ForwardPayload branch type
as jettons. report_static_data encodes the NFT index as uint256 and accepts
addr_none for standalone items. report_royalty_params preserves the TEP-66
uint16 numerator and denominator fields. Deterministic tests cover each
message shape, query-only payloads, addr_none, forward payload branches, and
opcode rejection.
Current Limits
The common parser, jetton metadata mapper, and NFT metadata mapper do not fetch
off-chain JSON or merge semi-chain content locally. Jetton wallet-data
get-method helpers, jetton master get_wallet_address, NFT
get_nft_address_by_index, SBT extensions, indexer API integration, and full
wallet-send convenience flows remain out of scope for the current wrapper
layer.
Sources
- Official TON token metadata documentation for TEP-64 content markers, snake encoding, chunked encoding, and common jetton/NFT metadata keys: https://docs.ton.org/standard/tokens/metadata.
- Official TON jetton interface documentation for
get_jetton_data()stack fields,mintable-1/0semantics, and TEP-74 message bodies: https://docs.ton.org/standard/tokens/jettons/api. - Official TON NFT interface documentation for
get_collection_data(),get_nft_data(),get_nft_content()stack fields, and TEP-62/TEP-66 message bodies: https://docs.ton.org/standard/tokens/nft/api. - Official TON NFT reference documentation for contract-defined full item content composition: https://docs.ton.org/standard/tokens/nft/nft-reference.
Wallet V4R2 And TON Mnemonics
This page records the first Wallet V4R2 and mnemonic implementation scope for
src/wallet.rs.
Mnemonic Rules
TON mnemonic support uses:
- 24 English BIP-39 words.
- validation against the English word list.
- no-password seed-version check with PBKDF2-HMAC-SHA512, salt
TON seed version,390iterations, first byte0. - password seed-version check with PBKDF2-HMAC-SHA512, salt
TON fast seed version,1iteration, first byte1. - Ed25519 seed derivation with PBKDF2-HMAC-SHA512, salt
TON default seed,100000iterations, and the first 32 bytes as the Ed25519 seed. - PBKDF2 input password is
HMAC-SHA512(key = normalized mnemonic phrase, data = mnemonic password or empty). The key/data order and seed-version byte are checked againstpytoniq-corecompatibility fixtures.
The CLI never stores the mnemonic. It reads mnemonics from stdin, a file, or an environment variable.
Wallet V4R2
Persistent data:
seqno:(## 32)wallet_id:(## 32)public_key:(## 256)plugins:(HashmapE 256 int1)
The external simple-send body is:
signature:bits512wallet_id:(## 32)valid_until:(## 32)seqno:(## 32)opcode:(## 32), currently0- up to four
(mode:uint8, msg:^MessageRelaxed)entries
The signature is Ed25519 over the representation hash of the cell after the signature field.
With liteclient, WalletV4R2::send_external_message is an accepted
submission adapter over ContractProvider::send_external_message_boc. It builds
the same signed external-in message BoC as the offline helper, includes
StateInit only when requested for deploy or first-message workflows, submits
exactly one BoC through the provider, and returns the provider’s opaque
liteServer.SendMsgStatus.status value. Build failures, including the four
message action limit, happen before provider submission. Provider failures are
reported without treating BoC submission as transaction inclusion.
Code BoC Evidence
The embedded V4R2 code BoC is taken from @ton/ton WalletContractV4 package
source. The embedded V5R1 code BoC is taken from @ton/ton
WalletContractV5R1 package source. The checked tests pin the hashes produced
by this crate’s cell decoder.
As of 2026-05-12, the embedded wallet code hashes are reconciled with the current TON wallet-history docs:
- V4R2:
feb5ff6820e2ff0d9483e7e0d62c817d846789fb4ae580c878866d959dabd5c0. - V5R1:
20834b7b72b112147e1b2fb457b84e74d1a30f04f737d4f62a668e9552d2b72f.
fixtures/wallets/state_init_addresses.json records deterministic default
state-init/address fixtures. The V4R2 case uses wallet id 0x29a9a317,
workchain 0, and the same checked public key as the V5R1 fixtures. Tests load
the fixture JSON and compare the embedded code cell hash, serialized data cell
hash, StateInit cell hash, raw address, and non-bounceable URL-safe
user-friendly address.
The local BoC decoder currently verifies IEEE CRC32, while public TON code BoCs commonly use the CRC32C BoC flag. Wallet code loading strips that BoC transport checksum before cell decoding; the cell hash is independent of BoC transport checksum bytes.
Sources
- Official TON Wallets history page for V4R2 and V5R1 code hashes: https://docs.ton.org/standard/wallets/history.
- Official TON wallet interaction page for default V4R2 and V5 wallet ids: https://docs.ton.org/standard/wallets/interact.
Wallet V5R1
This page records the initial Wallet V5R1 implementation scope for
tonutils-rs. The public Rust surface lives in src/wallet.rs and is available
with the tvm feature.
Scope
The first milestone is offline-safe and deterministic:
WalletV5R1Dataserializes and deserializes the persistent storage data.WalletV5R1WalletIdpacks and unpacks V5R1 wallet ids from a signed network global id and a V5R1 context.WalletMessagebuilds standardaction_send_msgactions withMessageRelaxed Any.WalletV5R1buildsStateInit, derives addresses, creates external signed request bodies, signs their cell hash with Ed25519, and wraps them in external-in message BoCs.- With
liteclient,WalletV5R1provides get-method helpers forseqno,get_wallet_id,get_public_key,is_signature_allowed, andget_extensionsover the existingContractProvidertrait.
Live sending is an accepted adapter over
ContractProvider::send_external_message_boc. The wallet module must not hide
provider failures or treat BoC submission as proof that a transaction was
accepted on chain.
Wire Format
Official Wallet V5 documentation describes the persistent state as:
is_signature_allowed:(## 1)seqno:(## 32)wallet_id:(## 32)public_key:(## 256)extensions_dict:(HashmapE 256 int1)
The implemented WalletV5R1Data maps this directly. HashmapE 256 int1 is
decoded as HashmapE<bool> because int1 is a single bit. The public
WalletV5R1Extensions wrapper enforces the 256-bit key width and provides
hash-first insert, remove, contains, and iteration helpers.
Extension dictionary keys are 256-bit account hashes only. Address helpers on
WalletV5R1Extensions are convenience APIs that use Address::hash_part; they
do not encode or compare the workchain. This matches the V5 state layout and is
different from full MsgAddressInt serialization used by extended management
actions.
With liteclient, extensions_raw_onchain remains raw-preserving and returns
the stack cell or slice payload as Arc<Cell>. extensions_onchain layers typed
decoding on top of that payload and rejects malformed HashmapE 256 int1
encoding without changing the raw helper.
The external signed request body uses opcode 0x7369676e followed by:
wallet_id:(## 32)valid_until:(## 32)msg_seqno:(## 32)inner:W5InnerRequestsignature:bits512
The signature is Ed25519 over the representation hash of the signing cell that contains the opcode, wallet id, timeout, seqno, and inner request, but not the signature itself.
The implemented W5InnerRequest support covers ordinary outbound actions and
Wallet V5R1 extended management actions:
out_actions:(Maybe ^OutList)extended_actions:(Maybe W5ExtendedActionList)
WalletV5R1ExtendedAction supports the current V5R1 constructors:
add_extension#02 addr:MsgAddressIntdelete_extension#03 addr:MsgAddressIntset_signature_auth_allowed#04 allowed:Bool
The ordinary message API still serializes extended_actions as absent. The
explicit *_with_extended_actions builders accept management actions and enforce
the V5R1 255-action limit across ordinary and extended actions together.
Wallet Id
Wallet V5R1 ids are stored as raw 32 bits:
wallet_id = network_global_id XOR context_id
Client context is:
context_id_client$1 wc:int8 wallet_version:uint8 counter:uint15
The default vectors covered by tests are:
- mainnet
network_global_id = -239, workchain0, version0, subwallet0gives0x7fffff11. - testnet
network_global_id = -3, workchain0, version0, subwallet0gives0x7ffffffd. - workchain
-1with the same mainnet/testnet values gives0x007fff11and0x007ffffd.
Backoffice/custom context is preserved as a 31-bit value with leading bit 0.
Trust Assumptions
The wallet helper verifies only local serialization and local Ed25519 signature construction. It does not verify deployed wallet code, account state, seqno freshness, timeout acceptance, extension authorization, or transaction inclusion. The V5R1 get-method helpers decode successful TVM stack values from the wallet address derived locally, but they do not prove that the deployed code at that address is the embedded wallet code.
WalletV5R1::send_external_message builds the external-in BoC with the same
offline builder, includes StateInit only when requested, submits exactly one
BoC through the provider, and returns the provider’s opaque
liteServer.SendMsgStatus.status value. Build failures, including action-count
limits, happen before provider submission. Provider failures are reported
without retry or interpretation.
Address derivation is deterministic for the provided code cell and data cell.
This repo embeds a Wallet V5R1 code BoC from the @ton/ton
WalletContractV5R1 package source and pins its decoded cell hash in tests.
As of 2026-05-12, that hash is reconciled with the current TON wallet-history
table: 20834b7b72b112147e1b2fb457b84e74d1a30f04f737d4f62a668e9552d2b72f.
fixtures/wallets/state_init_addresses.json records deterministic V5R1
state-init/address fixtures for the default mainnet wallet id 0x7fffff11 and
default testnet wallet id 0x7ffffffd, both in workchain 0, using the same
checked public key. Tests load the fixture JSON and compare the embedded code
cell hash, serialized data cell hash, StateInit cell hash, raw address, and
non-bounceable URL-safe user-friendly address.
Tests
src/wallet.rs contains offline tests for:
- Wallet V5R1 wallet-id default vectors and unpacking.
- Data cell roundtrip and empty extension dictionary encoding.
- Address stability from the same
StateInit. - Signed external body construction and signature verification.
- Extension dictionary insertion, duplicate replacement, removal, key-width rejection, and empty/non-empty roundtrip.
- Extended action tags, multi-action snake-list encoding, mixed ordinary and extended signed bodies, and total action-count limit enforcement.
- External inbound message BoC decoding.
- Live send/deploy provider acceptance: deploy mode carries
StateInit, non-deploy mode omits it, successful and failed provider submissions receive exactly one external-in BoC, provider status is propagated opaquely, and build failures do not call the provider. - Embedded code BoC hash stability for the local decoder.
- Fixture-backed V5R1 mainnet/testnet default code, data, state-init, raw address, and user-friendly address derivation.
- Mock-provider coverage for V5R1 get-method routing, typed integer/public-key decoding, signature-auth status decoding, raw extension payload preservation, typed extension dictionary decoding, malformed extension dictionary rejection, non-zero exit codes, missing or undecodable stacks, wrong stack entry types, and provider error propagation.
Fee estimation, message tracking, and post-send transaction lookup remain tracked separately.
Sources
- Official TON Wallet V5 page for persistent state and default wallet-id semantics: https://docs.ton.org/standard/wallets/v5.
- Official TON Wallet V5 API page for TL-B message layout and signing flow: https://docs.ton.org/standard/wallets/v5-api.
- Official TON Wallets history page for the V5R1 code hash: https://docs.ton.org/standard/wallets/history.
- Official TON wallet interaction page for default V5 wallet ids: https://docs.ton.org/standard/wallets/interact.
- pytoniq/pytoniq-core and STON.fi ton-rs may be used as comparison evidence for behavior and API ergonomics, but implementation must stay native to this repository.
Cryptographic Primitives
TON protocols use several cryptographic primitives at different layers. This page records what the crate needs and where each primitive is used.
Hashes
| Primitive | Output | Used for |
|---|---|---|
| SHA-256 | 32 bytes | cell representation hashes, ADNL addresses, ADNL packet hashes |
| SHA-512 | 64 bytes | Ed25519 signing internals |
| CRC16 | 2 bytes | user-friendly address checksum, get-method name id helper |
| CRC32 / CRC32C | 4 bytes | TL constructor ids, BoC checksums depending on context |
Ed25519 Public Keys
Liteserver and ADNL identity commonly use Ed25519 public keys. The TL constructor for a public key is:
pub.ed25519 key:int256 = PublicKey;
The public key bytes are 32 bytes. The crate stores them as compressed Edwards-Y points when validation is needed.
ADNL Address Hash
For Ed25519 keys, ADNL short address is:
sha256(pub.ed25519_constructor_id_le || public_key_bytes)
Constructor id little-endian bytes are:
c6 b4 13 48
Signatures
ADNL and DHT signatures must cover the exact TL-serialized object expected by the schema. Signing raw bytes and signing TL objects are not interchangeable.
Implementation rule:
- expose
sign_rawonly for protocol paths that explicitly sign raw bytes, - expose
sign_tlfor TL object signatures, - tests must verify that raw and TL signatures are not accidentally treated as equivalent.
Shared Secrets
ADNL TCP handshake derives a shared secret from local secret key material and remote public key material. The current implementation uses curve operations from pure Rust dependencies and then derives AES-CTR parameters for the session.
AES-CTR
ADNL TCP frames are encrypted with AES-256-CTR. CTR mode is a stream cipher construction:
- encryption and decryption are the same keystream XOR operation,
- nonce/key reuse is dangerous,
- each direction must use the correct tx/rx key and nonce pair.
Randomness
Cryptographic randomness is required for:
- local ephemeral ADNL keys,
- AES session params,
- ADNL frame nonce bytes,
- query ids where unpredictability is useful.
Tests should use deterministic values only when cryptographic unpredictability is not part of the behavior under test.
Crate Mapping
src/adnl/crypto.rs: key types, signing, verification, shared secret helpers.src/adnl/helper_types.rs: ADNL address and AES params.src/crc/: CRC helpers.src/tvm/cell.rs: SHA-256 representation hash.
Missing Work
- Separate protocol-level signature coverage docs per DHT and overlay type.
- Add known-good signature fixtures from upstream implementations.
- Audit CRC32 vs CRC32C usage names and algorithms.
LiteBalancer
LiteBalancer routes LiteAPI requests across liteservers.
Peer Metadata
A complete peer record should include:
- liteserver socket address,
- public key,
- connection state,
- last masterchain seqno,
- latency EWMA,
- in-flight request count,
- last success time,
- last failure time,
- archival capability,
- reconnect attempt count.
Peer States
Healthy: normal candidate.Suspect: degraded candidate.Dead: not eligible.Recovering: reconnect or probe in progress.
Scoring
Priority should consider:
- state,
- archive requirement,
- masterchain freshness,
- EWMA latency,
- in-flight load,
- recent failures.
Retry Policy
Retry on:
- connection reset,
- timeout,
- ADNL transport error,
- end of stream.
Do not blindly retry on:
liteServer.error,- contract execution exit code,
- malformed local request,
- proof verification failure.
Current offline tests cover representative retry and non-retry paths with
in-memory peers: typed helper calls retry after ADNL transport errors, do not
retry liteServer.error, and do not retry local BoC decode failures. The same
tests pin the current failure bookkeeping behavior: failed attempts decrement
in-flight counters, update request statistics, remove the peer from the alive
set, and mark the peer Dead.
Rate Limiting
The balancer supports two independent limiter placements:
- per-peer limits stored on each owned
LiteClient; - a global limit stored on
LiteBalancer.
The global limiter is acquired in the shared request execution path after a
peer has been selected and before in-flight counters are incremented. Retries
therefore consume additional tokens. send_message also consumes one global
token per peer attempt, not one token for the high-level method call.
Missing Work
- Reconnect descriptors.
- EWMA implementation.
- Reconnect and timeout state-machine tests.
- Shared method dispatch with LiteClient.
- Live validation against rented liteserver quota behavior.
Light Client Proof Verification
LiteAPI can return proof bytes, but the SDK must verify them before claiming trustless correctness.
Trust Anchors
A light client needs:
- trusted zerostate,
- trusted or verified validator set,
- verified masterchain block chain,
- verified shard links.
Proof Types
Common proof material:
- block proof,
- shard block proof,
- account state proof,
- config proof,
- validator signatures.
LiteAPI proof byte fields are BoCs. Some account-state proof payloads have
multiple roots; for liteServer.getAccountState, the state byte field is the
actual TL-B Account cell, while proof contains pruned proof material that
must be combined with that state cell during verification.
Verification Outline
- Decode proof BoC.
- Verify cell hashes against expected block ids.
- Verify Merkle proof exotic cells.
- Verify validator set and signatures.
- Verify shard inclusion in masterchain.
- Verify account state in shard state.
Current State
The crate exposes proof bytes but does not yet fully verify them.
BoC diagnostics inspect single-root and multi-root proof payloads structurally
and report root counts and representation hashes. This inspection does not
construct TL-B proof objects and is not proof verification. Live LiteServer
getAccountState responses have been smoke-tested for multi-root proof BoC
structural inspection in the CLI account command.
No current LiteClient or CLI API should be described as trustless or verified unless its name and documentation explicitly say “verified”. The current APIs inspect, decode, and preserve proof material so future proof-specific work can anchor it correctly; full proof verification is intentionally deferred.
liteclient::boc::extract_verified_shard_account is the current checked
extraction boundary for high-level transaction unblocking. It accepts proof
material whose root has already been anchored to a verified shard-account path,
decodes ShardAccount, verifies the requested standard account hash, rejects
state/proof mismatches, and can check an independently verified shard root hash.
It is not yet a full ShardAccounts dictionary traversal or Merkle proof
verifier.
Missing Work
- Validator set TLB decoding.
- Signature set verification.
- Block proof path validation.
- Account proof validation.
- Traverse
ShardAccountsdictionaries from live account proofs after shard and Merkle proof roots are verified.
LiteClient Rate Limiting
Purpose And Scope
LiteAPI providers may enforce rented-liteserver quotas such as a fixed number
of requests per second. The crate models these quotas as local throttling, not
as LiteAPI protocol messages. LiteClient can throttle one connection, and
LiteBalancer can throttle either every owned peer or the balancer’s total
outgoing request attempts.
Data Model
RequestRateLimit { rps, burst } defines a token bucket:
rpsis the steady-state refill rate in whole requests per second.burstis the maximum number of tokens and the number of immediate requests available after constructing the limiter.rps == 0andburst == 0are invalid.
The limiter has no wire format. It runs before a serialized LiteAPI request is
sent through liteServer.query.
Invariants And Edge Cases
- Throttling waits asynchronously instead of returning a rate-limit error.
- Default clients and balancers are unlimited.
- A cancelled wait does not consume a
waitMasterchainSeqnoprefix because the limiter is acquired beforeLiteClient::query_rawtakes the pending seqno. LiteBalancerglobal limiting counts actual attempts, including retries and eachsend_messagepeer attempt.- Per-peer limiting is independent per
LiteClient; it does not cap aggregate balancer throughput unless every peer has the same cap and peer selection is balanced.
Current Crate Mapping
src/liteclient/rate_limit.rsimplementsRequestRateLimit, validation, the asyncRateLimiter, and deterministic token-bucket tests.src/liteclient/client.rsstores an optional limiter and acquires it at the start ofquery_raw.src/liteclient/balancer.rsstores an optional global limiter and applies it in the sharedexecute_requestpath.src/cli/mod.rsmaps--rpsto per-liteserver limits and--global-rpsto balancer-wide attempt limits.
Missing Work
- Live validation against tonconsole-style rented liteserver credentials.
- Optional metrics for limiter wait time and throttled attempt counts.
- Broader integration tests for retry-heavy balancer flows.
LiteClient Request Flow
LiteClient executes LiteAPI calls over ADNL TCP.
Flow
- Establish ADNL TCP connection.
- Acquire the optional local request-rate limiter.
- Wrap LiteAPI request into
WrappedRequest. - Serialize into
liteServer.query. - Serialize into
adnl.message.query. - Send through multiplexed ADNL stream.
- Receive
adnl.message.answer. - Decode answer bytes as
Response. - Convert response to typed output or return server error.
Query Ids
ADNL queries use query_id:int256. The current peer layer assigns random ids for multiplexing. Responses must match the original query id.
Wait Seqno
waitMasterchainSeqno is a prefix. It delays request execution until the liteserver catches up or times out.
The local rate limiter runs before waitMasterchainSeqno is consumed. If a
task is cancelled while waiting for a limiter token, the pending seqno remains
attached to the next request.
Rate Limiting
RequestRateLimit uses a local token bucket with whole-request rps and
burst values. It is not encoded into LiteAPI. When exhausted, query_raw
waits asynchronously and then sends the request, so typed helpers inherit the
same behavior through the shared raw path.
Raw Request Path
The crate should support a truly raw path:
- input: already serialized LiteAPI request bytes,
- output: raw response bytes,
- no attempt to decode request into known enum.
This is required for future schema compatibility.
Typed Request Path
Typed methods should exist for stable functions. Each method should:
- build request struct,
- set flags explicitly,
- call shared executor,
- convert response with exact expected type.
Missing Work
- Shared executor trait for LiteClient and LiteBalancer.
- Better timeout configuration.
- Live-network integration tests.
ADNL TCP
ADNL TCP is the first supported network transport in this crate. It is used for liteserver connections.
Identity
Liteserver configs provide an Ed25519 public key. The client computes the server ADNL address by hashing the TL public key constructor id and key bytes.
Handshake
Client sends a 256-byte packet:
| Range | Size | Field |
|---|---|---|
0..32 | 32 | receiver ADNL address |
32..64 | 32 | sender public key |
64..96 | 32 | hash of plaintext AES params |
96..256 | 160 | encrypted AES params |
Server decrypts AES params with a key derived from ECDH shared secret and the params hash. It responds with an encrypted empty packet.
Frame Body
After handshake, every frame has encrypted:
- little-endian length,
- random 32-byte nonce,
- payload bytes,
- SHA-256 hash over nonce and payload.
The length excludes the 4-byte length field and includes nonce and hash.
Client codecs encrypt with tx_key/tx_nonce and decrypt with
rx_key/rx_nonce; server codecs intentionally reverse those directions so a
client frame decodes with the server codec and a server frame decodes with the
client codec for the same session parameters.
Limits
- Minimum encrypted body length: 64 bytes.
- Maximum encrypted body length:
1 << 24. - Maximum payload length:
(1 << 24) - 64.
Security Properties
ADNL TCP gives encryption and integrity for the session. It does not verify blockchain correctness. LiteAPI proof verification is a separate layer.
Current Crate Mapping
src/adnl/primitives/handshake.rssrc/adnl/primitives/codec.rssrc/adnl/wrappers/peer.rs
Required Tests
- handshake success,
- handshake unknown receiver,
- codec roundtrip,
- empty payload/minimum frame body,
- client-to-server and server-to-client key/nonce directionality,
- partial frame,
- multi-frame buffer,
- too short frame,
- too long frame,
- tampered hash.
ADNL UDP
ADNL UDP is required for general TON peer-to-peer networking, DHT, overlays, and future mempool scanning. It is not the same implementation path as ADNL TCP liteserver connections.
Expected Responsibilities
UDP ADNL must handle:
- datagram boundaries,
- peer address lists,
- packet contents flags,
- public key identity,
- signatures,
- channel creation and confirmation,
- reinit dates,
- sequence numbers,
- packet parts for large messages.
Relevant TL Areas
ton_api.tl contains ADNL packet and message definitions such as:
adnl.packetContents,adnl.message.createChannel,adnl.message.confirmChannel,adnl.message.custom,adnl.message.query,adnl.message.answer,adnl.message.part,adnl.addressList,adnl.node.
Implementation Risks
- UDP packet loss and reordering.
- Large message fragmentation.
- NAT and address list freshness.
- Correct signature coverage.
- Interaction with DHT and overlay routing.
Crate Design
The future UDP implementation should not reuse TCP framing. It should share only crypto identity types and TL message types where protocol-compatible.
Missing Work
- Parse and document all relevant ADNL UDP TL constructors.
- Add packet fixtures from official nodes.
- Add deterministic simulated UDP tests.
TON DHT
The TON DHT is a distributed hash table used to discover peers and signed network values. It is required for autonomous network discovery beyond static global configs.
Core Concepts
- DHT node id: public key based identity.
- Address list: peer UDP/TCP addresses with version and expiration.
- Key description: describes what value is stored.
- Value: bytes with TTL and signature.
- K-bucket-like peer lookup behavior.
- Reverse ping: helps validate reachability.
Data Integrity
DHT values are signed. Implementation must verify:
- public key matches node id,
- signature covers the exact TL object required by the schema,
- TTL has not expired,
- address list version is current enough.
Discovery Flow
Typical lookup:
- Start from static DHT nodes from global config.
- Query closest known peers for a key.
- Validate returned nodes.
- Continue until enough close peers or value is found.
- Cache valid peers with expiration.
Crate Mapping
No DHT implementation exists yet. Future modules should live under src/dht or src/network/dht and depend on ADNL UDP.
Required Tests
- DHT node signature verification.
- Value signature verification.
- Expired value rejection.
- Closest-peer selection.
- Lookup convergence in simulated network.
Global Config And Liteserver Descriptors
TON global config JSON gives static entry points for clients. The crate currently parses liteserver entries.
Liteserver Descriptor
Fields:
ip: signed 32-bit integer containing IPv4 bits,port: TCP port,id: public key object.
Public key form:
{
"@type": "pub.ed25519",
"key": "base64..."
}
IP Conversion
The signed integer should be reinterpreted as u32 bits, then converted to Ipv4Addr.
Edge cases:
- negative input,
0.0.0.0,127.0.0.1,255.255.255.255.
Feature Boundary
- JSON parsing:
network-config. - HTTP download:
clior future optional HTTP feature.
Library users should be able to provide config JSON without enabling HTTP.
Tests
Required tests:
- base64 public key decode,
- invalid key rejection,
- signed IP roundtrip,
- socket address creation,
- serialization roundtrip.
TON Overlays
Overlays are logical peer groups over ADNL. They are used by validators and full nodes for shardchain communication, broadcasts, and peer exchange.
Why Overlays Matter
LiteAPI is request/response. Mempool and pending-message observation requires more direct network participation, which usually means DHT discovery and overlays.
Concepts
- overlay id,
- overlay node,
- overlay peer list,
- random peer exchange,
- broadcast,
- certificates,
- validator group overlays,
- shard overlays.
Implementation Requirements
An overlay implementation needs:
- ADNL UDP transport,
- DHT peer discovery,
- overlay TL types,
- peer score and expiration,
- query routing,
- broadcast validation,
- deduplication.
Mempool Relation
Pending external messages are propagated before final block inclusion. A scanner must distinguish:
- observed broadcast,
- candidate block data,
- included transaction,
- finalized transaction.
Missing Work
- Extract overlay constructors from
ton_api.tl. - Document overlay id derivation.
- Study validator shard overlays relevant to pending messages.
- Add captured fixtures.
Diagnostics And Observability
The SDK should provide useful diagnostics without forcing logging or metrics dependencies on all users.
Logging
Low-level logs should be structured enough to debug:
- peer address,
- ADNL connection phase,
- TL constructor name or id,
- request latency,
- liteserver error code,
- balancer peer state transition.
Avoid logging:
- private keys,
- shared secrets,
- AES keys and nonces,
- full message bodies by default.
Metrics
Optional metrics can include:
- requests started,
- requests succeeded,
- requests failed by family,
- ADNL reconnect count,
- peer state counts,
- latency histograms,
- bytes sent and received.
Metrics must be behind a feature gate.
Error Context
Errors should carry enough context to answer:
- which subsystem failed,
- whether retry is useful,
- whether data is untrusted,
- which peer produced the failure.
CLI network commands add operation context before returning errors. High-level
commands include the target address, block, method, or backend mode where that
context is available. LiteError::TlError displays the inner parse error, and
liteserver errors display both server code and message.
CLI Decode Policy
The CLI keeps LiteAPI TL response parsing strict: malformed or unexpected
LiteAPI responses fail the command. TL-B and BoC decoding of embedded account
state, proofs, and result payloads is best-effort for high-level inspection
commands. When a nested decode is unsupported or malformed, human output omits
raw bytes and reports byte lengths, root hashes for successfully decoded BoCs,
and decode_error lines. JSON and pretty JSON include the same decode_errors
array plus the structured fields that decoded successfully.
Structured command data is written to stdout. Connection warnings, peer startup failures, and diagnostics are written to stderr.
Live Smoke Coverage
On 2026-05-09, the account CLI command was smoke-tested against a live LiteServer response:
cargo run -F full -- account UQA_rW3Zvza4OcuW0yh4vH-cno3X0IcABYAX3whMjO5BSsQn
The run verified structural diagnostics for multi-root account proof BoCs. The
output reported shard_proof_root_count: 2, two shard_proof_root_hash lines,
proof_root_count: 2, and two proof_root_hash lines, with no proof-related
decode_error. This is evidence for CLI inspection behavior only; it is not
trustless account-state proof verification.
Missing Work
- Feature-gated metrics facade.
- Structured debug formatting for TL objects.
- Stable JSON error objects for CLI failures.
- Redaction helpers for sensitive protocol material.
Source Tracking
Protocol documentation and implementation must record where facts came from.
Source Priority
- Upstream
ton-blockchain/tonschemas and implementation. - Official TON docs.
- Captured behavior from public liteservers with fixtures.
- Mature SDKs such as
tonutils-go,tongo,ton-rs, andtonlib-rs. - Existing crate behavior.
Reference Catalog
Primary sources:
- Upstream TON implementation and schemas: https://github.com/ton-blockchain/ton
- Official TON documentation index for LLM-assisted research: https://docs.ton.org/llms.txt
Compatibility references:
tonutils-go: https://github.com/xssnick/tonutils-gotongo: https://github.com/tonkeeper/tongotonstack/lite-client: https://github.com/tonstack/lite-client- STON.fi
ton-rs: https://github.com/ston-fi/ton-rs - STON.fi
tonlib-rs: https://github.com/ston-fi/tonlib-rs - RSquad
ton-rust-node: https://github.com/RSquad/ton-rust-node nessshon/tonutils: https://github.com/nessshon/tonutils
Research references:
- TON mempool scanner behavior: https://github.com/yungwine/ton-mempool
These projects are references for protocol behavior, API ergonomics, and fixture comparison. They must not be treated as dependency approval, and Rust TON SDK crates must not be added as runtime dependencies.
What To Record
For each synced schema or fixture:
- source URL or local path,
- upstream commit if known,
- date fetched,
- relevant constructor names,
- expected ids or hashes,
- compatibility notes.
Local Schema Policy
Local TL schemas under src/tl/schemas/ and TL-B schemas under
src/tlb/schemas/ should have an adjacent note or generated metadata recording
upstream origin. src/tlb/schemas/block.tlb is currently a checked partial
snapshot of upstream crypto/block/block.tlb; the full upstream commit/date
sync remains tracked in TODO.md. The long-term goal is automated schema sync
validation for both TL and TL-B files.
Fixture Policy
Fixtures should include metadata files or comments explaining:
- whether they are synthetic or captured,
- exact input bytes,
- expected decoded values,
- source implementation used for comparison.
Missing Work
- Add
dev-docs/sources.mdor generated source metadata. - Add schema sync command.
- Add fixture metadata format.
Mempool Scanning Research
TON pending-message observation is not the same as reading finalized transactions. A scanner must expose uncertainty explicitly.
Data Sources
Potential sources:
- overlay broadcasts,
- nonfinal LiteAPI methods,
- dispatch queue LiteAPI methods,
- candidate block data,
- finalized block transaction lists.
Protocol Dependencies
Likely required:
- ADNL UDP,
- DHT discovery,
- overlay peer exchange,
- broadcast decoding,
- message BoC decoding,
- duplicate detection.
API Shape
Future scanner API should expose:
- async stream of pending items,
- message hash,
- raw BoC,
- first seen timestamp,
- source peer,
- shard hint if known,
- confidence or stage enum.
Suggested stages:
ObservedBroadcast,CandidateIncluded,FinalizedIncluded,DroppedOrExpired.
Safety Notes
Pending data can disappear. Do not expose pending observations as confirmed transactions. Users must opt into mempool semantics.
Research Tasks
- Study
yungwine/ton-mempool. - Identify overlay ids used for relevant traffic.
- Capture sample packets.
- Compare with nonfinal LiteAPI responses.
Benchmarks
Performance must be measured before optimizing.
Benchmark Targets
- Wallet and mnemonic hot paths:
- mnemonic import and Ed25519 key derivation from a fixed accepted phrase,
- TON default-seed PBKDF2 derivation,
- TON seed-version validation derivation,
- deterministic mnemonic generation with a seeded RNG,
- cached Wallet V4R2 and V5R1 code cell access,
- Wallet V4R2 and V5R1 address derivation,
- Wallet V4R2 and V5R1 signed transfer external-message BoC construction,
- standard wallet comment body construction.
- ADNL frame encode/decode.
- TL request/response encode/decode.
- BoC serialization and deserialization.
- Cell representation hash.
- Builder bit writes.
- Slice bit reads.
- TVM stack encode/decode.
- Balancer peer selection.
Metrics
Track:
- throughput,
- allocations,
- p50/p95/p99 latency for request paths,
- memory growth under long-running scans.
Rules
- Keep benchmarks deterministic.
- Avoid live network in normal benchmarks.
- Add live latency benchmarks as explicit opt-in.
- Record input sizes.
Commands
Run the deterministic wallet benchmark harness with:
cargo bench --bench wallet
All benchmarks in benches/wallet.rs are offline and use fixed mnemonics,
seeded RNGs, fixed addresses, and embedded wallet code cells.
Run deterministic protocol primitive benchmarks with:
cargo bench --bench protocol
benches/protocol.rs is offline and currently covers ADNL frame
encode/decode, TL request serialization/deserialization, cell hashing, BoC
serialization/deserialization, builder and slice bit operations, and nested TVM
stack BoC conversion. It uses fixed in-memory fixtures only. Balancer selection
benchmarks remain a separate follow-up because they need deterministic mock peer
state and request routing inputs.
When measuring CLI wallet commands, build and run the release binary. Timing
cargo run includes Cargo compilation and process setup noise:
cargo build --release --features cli
./target/release/tonutils-rs wallet address --help
Fixture Policy
Fixtures are required for compatibility with official TON behavior.
Fixture Metadata
Every fixture should document:
- source,
- date,
- upstream commit if known,
- schema file version,
- expected decoded structure,
- whether it is synthetic or captured.
Fixture Types
- TL binary constructors.
- ADNL frames.
- BoC files.
- Cell hashes.
- Account states.
- Block proofs.
- Get-method results.
Storage Rules
- Keep binary fixtures small.
- Prefer hex for very small values.
- Use files for larger BoC or network captures.
- Never include private keys or sensitive live credentials.
Current Embedded Fixtures
Address fixtures are embedded in src/tvm/address.rs because they are short
text vectors:
- TON Docs internal address formats page, accessed 2026-05-06:
raw address
0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3ewith bounceable, non-bounceable, bounceable test-only, and non-bounceable test-only user-friendly forms. These vectors validate tag handling, URL-safe and standard base64 alphabets, CRC16 validation, and raw conversion. - Zero-address fixture
0:0000000000000000000000000000000000000000000000000000000000000000with known bounceable and non-bounceable user-friendly encodings. This vector is a synthetic edge case generated from the TEP-0002 36-byte address layout documented by TON Docs.
BoC fixtures are embedded in src/tvm/boc.rs as small hex constants:
- empty ordinary cell: validates the generic
b5ee9c72header and zero-bit cell descriptors, - one-byte ordinary cell: validates byte-aligned cell payload descriptors,
- one-reference ordinary cell with and without an index table: validates root indexes, reference indexes, and canonical reserialization without index output,
- library-reference exotic cell: validates supported exotic tag
0x02, descriptor preservation, and exact payload length, - cache-bit variant of the empty-cell BoC: validates the crate policy that cache-bit BoCs are rejected until a lossless semantic need is proven.
The BoC byte vectors are synthetic but schema-derived from the TON Blockchain
paper serialized BoC constructor serialized_boc#b5ee9c72; no third-party Rust
TON SDK code or fixture dependency is used.
TL-B offline fixtures are embedded in src/tlb/mod.rs under
offline_fixture_tests. They use a small metadata harness with:
- fixture name,
- source note,
- hex or base64 BoC payload,
- expected root representation hash,
- expected decoded TL-B type.
The current TL-B fixture set is synthetic and schema-derived from the hand-written models already implemented in this crate. It covers:
Message AnyandMessageRelaxed Anywith referenced children and exact trailing-data rejection,StateInit,CurrencyCollectionwith an extra-currencyHashmapE 32,TransactionandTransactionDescr,Account,ShardAccounts,AccountBlock, andShardAccountBlocks,- standalone
HashmapEcanonical root-reference and label behavior, - standalone
HashmapAugEempty top-level extras, non-empty top-level extras, leaf extras, and fork extras.
These fixtures are intentionally offline-only. They lock canonical decode/encode/hash behavior for the current model surface without claiming that the values were captured from a live liteserver or copied from upstream test data.
Phase 1 milestone fixtures are also checked in under fixtures/phase1/ as JSON
metadata plus small BoC hex payloads. Normal cargo test --lib runs remain
fully offline: tests read these files with include_str!, decode the BoCs,
check source metadata, compare root representation hashes, decode the expected
TL-B type, and require canonical reserialization back to the exact fixture
bytes.
Current checked-in Phase 1 files:
fixtures/phase1/account_message_transaction.json:Message Any,MessageRelaxed Any,Transaction, andAccountfixtures.fixtures/phase1/transaction_descriptions.json: transaction-description fixtures for ordinary, tick-tock, split prepare, split install, merge prepare, and merge install constructors.
These fixtures are synthetic but upstream-schema-derived. They are generated from the local hand-written codecs that map directly to the documented upstream TL-B layouts. Live/public liteserver captures and upstream repository capture vectors remain useful as stronger evidence for later compatibility expansion, but they are not required by normal test runs and must never make CI depend on network access.
ABI golden fixtures are checked in under fixtures/abi/contracts.json as JSON
metadata and small BoC hex payloads. The schema records:
schema_revision,- top-level synthetic
sourceandcapture_date, - fixture
name,kind,source, ABI JSON document, and function name, - evidence metadata:
evidence_kind,source_url,source_commit,network,account,block_id,method_id,capture_command, andcompat_reference, - fixture-only ABI input values,
- expected stack BoC/root hashes for get-method inputs and outputs, or expected message-body BoC/root hash for body codecs,
- expected decoded ABI values.
Normal ABI fixture tests read the file with include_str!, load each ABI JSON
document through parse_abi_json_str, encode and decode through the existing
ABI stack/message-body helpers, and verify exact BoC hex plus root
representation hashes. The message-body entries are body-cell BoCs only, not
full message envelopes.
The current ABI fixture set is synthetic offline evidence plus
captured_or_opt_in templates. It covers one get-method stack vector with
address input and uint output, one opcode-prefixed external body vector with
scalar and referenced values, one no-selector internal body vector with tuple
and optional values, and local map/dictionary roundtrips for fixed integer-key
maps. The opt-in templates cover wallet seqno with no inputs and TEP-74
get_wallet_address(owner) with one address input; they are not claimed as
captured evidence until live stack/result bytes and block/account metadata are
filled in.
TVM stack golden fixtures are checked in under fixtures/tvm/stack.json. The
schema records:
schema_revision,- synthetic
sourceandcapture_date, - fixture
name, input_stack_boc_hex,- root representation hash,
- decoded stack entry shape,
captured_or_opt_inlive-capture template metadata,cross_sdk_vectorsfor tonutils-go raw params BoC matches or tonlib structural comparisons when those bytes are available.
The stack fixture set is synthetic offline evidence generated by the local
stack codec. It covers non-empty scalar stacks, linked stack chains beyond four
logical entries, nested tuple/list values, huge integers, cell/slice entries,
and unsupported raw bytes. Normal fixture tests decode each BoC, compare the
expected entries and root hash, and verify exact canonical reserialization.
Ignored live smoke tests can add transport evidence without making CI depend on
network access. The ignored non-empty stack test prints fixture JSON only after
exit_code == 0; non-zero exit-code smoke checks do not produce captured
fixture material.
Pending Captured Fixtures
Live or upstream-captured BoCs remain required before claiming broader wire-level compatibility for deep account-proof, block-proof, and config workflows. When added, captured fixtures should record:
- liteserver endpoint or upstream repository/commit,
- capture date,
- relevant schema revision,
- source command or script,
- expected decoded type,
- expected root hash and, when available, file hash,
- whether the fixture is required in normal test runs or kept as ignored captured evidence.
Testing Strategy
Testing must prove binary compatibility and prevent protocol drift.
Local Tests
Must not require network. Cover:
- TL roundtrip,
- ADNL codec,
- TVM cell and BoC,
- stack encoding,
- balancer scoring,
- address parsing.
Live Tests
Ignored by default. Cover:
- public config download,
- ADNL connect,
getMasterchainInfo,getVersion,- simple get-method.
Negative Tests
Required for:
- malformed TL bytes,
- malformed BoC,
- invalid address checksum,
- ADNL tampering,
- stale balancer peers,
- invalid proofs.
CI Commands
cargo fmt --check
cargo check --no-default-features
cargo check
cargo check --all-features
cargo test
cargo test --all-features
LiteAPI TL Schema
LiteAPI is defined in lite_api.tl in the upstream TON repository. Local schema copies live under src/tl/schemas/.
Schema Revision
- Synced local
src/tl/schemas/lite_api.tlon 2026-05-05 from:https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/lite_api.tl
- Added upstream nonfinal pending-shard constructors:
liteServer.nonfinal.pendingShardBlocksliteServer.nonfinal.getPendingShardBlocks
- Synced
liteServer.nonfinal.getValidatorGroupsflag layout to upstream:shard:mode.0?long(previous local snapshot usedmode.1)
Common Types
liteServer.accountId workchain:int id:int256 = liteServer.AccountId;
liteServer.transactionId3 account:int256 lt:long = liteServer.TransactionId3;
liteServer.libraryEntry hash:int256 data:bytes = liteServer.LibraryEntry;
AccountId is the LiteAPI form of an internal account address. It stores the workchain id and 256-bit account hash.
Masterchain Types
liteServer.masterchainInfo last:tonNode.blockIdExt state_root_hash:int256 init:tonNode.zeroStateIdExt = liteServer.MasterchainInfo;
liteServer.masterchainInfoExt mode:# version:int capabilities:long last:tonNode.blockIdExt last_utime:int now:int state_root_hash:int256 init:tonNode.zeroStateIdExt = liteServer.MasterchainInfoExt;
Use last as the reference for current chain state. state_root_hash identifies the current state root. init identifies zerostate.
Block Data And State
liteServer.blockData id:tonNode.blockIdExt data:bytes = liteServer.BlockData;
liteServer.blockState id:tonNode.blockIdExt root_hash:int256 file_hash:int256 data:bytes = liteServer.BlockState;
liteServer.blockHeader id:tonNode.blockIdExt mode:# header_proof:bytes = liteServer.BlockHeader;
Returned data and proof fields are usually BoC or serialized proof data. They are not automatically verified.
Account State
liteServer.accountState id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:bytes proof:bytes state:bytes = liteServer.AccountState;
Fields:
id: requested block id.shardblk: shard block containing the account state.shard_proof: proof linking shard to masterchain context.proof: account proof.state: account state data.
state is a BoC whose root is the TL-B Account value returned by the
liteserver. It is not a standalone ShardAccount; the ShardAccount appears
inside the account-proof path and must be extracted from the verified
ShardAccounts dictionary before its last_trans_hash can be trusted.
For full accounts, Account.storage.last_trans_lt is available directly from
the state cell.
The shard_proof and proof byte fields are BoCs used for proof material.
Account-state proof payloads can contain more than one BoC root; official proof
flow substitutes the returned state cell into the pruned proof tree before
checking hashes. Current typed helpers decode these roots for diagnostics but do
not claim proof validity.
Run Method
liteServer.runSmcMethod mode:# id:tonNode.blockIdExt account:liteServer.accountId method_id:long params:bytes = liteServer.RunMethodResult;
Result:
liteServer.runMethodResult mode:# id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:mode.0?bytes proof:mode.0?bytes state_proof:mode.1?bytes init_c7:mode.3?bytes lib_extras:mode.4?bytes exit_code:int result:mode.2?bytes = liteServer.RunMethodResult;
The exit_code is contract execution output, not a transport error.
Transaction Listing
liteServer.listBlockTransactions id:tonNode.blockIdExt mode:# count:# after:mode.7?liteServer.transactionId3 reverse_order:mode.6?true want_proof:mode.5?true = liteServer.BlockTransactions;
liteServer.listBlockTransactionsExt id:tonNode.blockIdExt mode:# count:# after:mode.7?liteServer.transactionId3 reverse_order:mode.6?true want_proof:mode.5?true = liteServer.BlockTransactionsExt;
Use after for pagination. incomplete in the response indicates that more data is available.
Proofs
liteServer.getBlockProof mode:# known_block:tonNode.blockIdExt target_block:mode.0?tonNode.blockIdExt = liteServer.PartialBlockProof;
liteServer.getShardBlockProof id:tonNode.blockIdExt = liteServer.ShardBlockProof;
Block proofs link known and target blocks. Shard block proofs link shard blocks to a masterchain block.
Queues And Pending Data
liteServer.getOutMsgQueueSizes mode:# wc:mode.0?int shard:mode.0?long = liteServer.OutMsgQueueSizes;
liteServer.getDispatchQueueInfo mode:# id:tonNode.blockIdExt after_addr:mode.1?int256 max_accounts:int want_proof:mode.0?true = liteServer.DispatchQueueInfo;
liteServer.getDispatchQueueMessages mode:# id:tonNode.blockIdExt addr:int256 after_lt:long max_messages:int want_proof:mode.0?true one_account:mode.1?true messages_boc:mode.2?true = liteServer.DispatchQueueMessages;
These are important for future pending-message and mempool-adjacent features.
Nonfinal Data
Current local schema snapshot (src/tl/schemas/lite_api.tl) includes nonfinal validator-group, candidate, and pending-shard APIs:
liteServer.nonfinal.getValidatorGroups mode:# wc:mode.0?int shard:mode.0?long = liteServer.nonfinal.ValidatorGroups;
liteServer.nonfinal.getCandidate id:liteServer.nonfinal.candidateId = liteServer.nonfinal.Candidate;
liteServer.nonfinal.getPendingShardBlocks mode:# wc:mode.0?int shard:mode.0?long = liteServer.nonfinal.PendingShardBlocks;
Implementation Checklist
- Keep schema copy synchronized with upstream.
- Add typed request and response structs.
- Add response conversion helpers.
- Add client method.
- Add TL roundtrip test.
- Add constructor id test.
- Add live test only if response can be stable enough.
TL Schema Language
TL, the Type Language, defines binary wire formats used by TON. Schemas define both data constructors and function constructors.
Constructor Shape
General form:
name field:type field2:type2 = ResultType;
Example:
tonNode.blockIdExt workchain:int shard:long seqno:int root_hash:int256 file_hash:int256 = tonNode.BlockIdExt;
Constructor Id
Boxed constructors carry a 32-bit id. It can be explicit:
liteServer.signatureSet.ordinary#f644a6e6 ... = liteServer.SignatureSet;
If omitted, the id is CRC32 of the normalized constructor string.
Implementation requirement:
- ids in Rust annotations must match schema ids,
- schema parser tests should recompute ids and fail on drift,
- string-based ids in macros need enough scheme context to compute correctly.
Primitive Types
| TL type | Wire meaning |
|---|---|
int | 32-bit integer |
long | 64-bit integer |
int128 | 16 bytes |
int256 | 32 bytes |
bytes | length-prefixed bytes with 4-byte alignment padding |
string | same binary family as bytes, interpreted as UTF-8 by convention |
Bool | boxed boolean constructors |
vector | vector constructor, count, then items |
Bytes Padding
TL bytes are padded to a 4-byte boundary. The payload length is not the same as the serialized field length. Nested bytes fields that contain serialized TL objects must preserve this wrapping exactly.
Boxed Vs Bare
Boxed values include constructor ids. Bare values do not. Enums are typically boxed. Struct fields are often bare unless the field type is an abstract result type.
Flags
Flags use:
mode:# field:mode.0?bytes other:mode.2?int = Type;
Rules:
mode:#serializes a 32-bit flags integer.- Bit
Ncontrols fields markedmode.N?. - Multiple fields can share the same bit.
- Optional fields are serialized in schema order only when present.
Functions
Functions appear after ---functions---. They are serialized as boxed constructors when called. Their output type tells the expected response abstract type.
LiteAPI wraps functions inside liteServer.query, then ADNL wraps that inside adnl.message.query.
Rust Mapping Checklist
- Choose exact signedness and width.
- Use
[u8; 32]or equivalent forint256. - Use
Vec<T>for vectors when allocation is acceptable. - Use
Option<T>for flag-controlled fields. - Use boxed enums for abstract result types.
- Add roundtrip and constructor id tests for every new type.
TON Addresses
Purpose And Scope
TON account addresses identify smart-contract accounts. This page documents the
address behavior currently implemented by src/tvm/address.rs for the Phase 1
TVM foundation slice. It covers internal standard addresses, raw text form,
user-friendly base64 forms, validation rules, and crate mapping. External
addresses remain a small value/bit-length helper and are not yet a complete
TL-B addr_extern implementation.
Data Model
The current Address stores:
workchain: i8, with-1for masterchain and0for basechain in normal usage,hash_part: [u8; 32], the 256-bit account id,is_bounceable: bool,is_test_only: bool.
Because the public type stores i8, the strict parser accepts -1 and
0..=127. Values below -1 are rejected as invalid workchain ids for this
type. Full TL-B addr_std can carry a signed 8-bit anycast-free workchain in
the serialized form and broader message-level address handling remains future
work.
Raw Format
Raw textual form is:
workchain:64_hex_chars
Examples:
0:0000000000000000000000000000000000000000000000000000000000000000
-1:2222222222222222222222222222222222222222222222222222222222222222
Validation rules:
- exactly one
:separator, - decimal workchain parseable as
i8, - workchain must be
-1or0..=127, - hash must be exactly 64 hexadecimal characters,
- invalid hex is reported as an address hash error.
to_raw() and to_hex() both produce this form. Display intentionally
continues to produce the user-friendly URL-safe form for compatibility with the
existing API.
User-Friendly Format
User-friendly addresses encode 36 bytes:
tag:1 | workchain:1 | account_id:32 | crc16:2
The CRC16 is computed over the first 34 bytes and stored in big-endian byte order. The supported tag values are:
0x11: bounceable,0x51: non-bounceable,tag | 0x80: test-only variant of either supported tag.
The parser accepts all four common base64 input encodings:
- URL-safe without padding,
- URL-safe with padding,
- standard base64 without padding,
- standard base64 with padding.
Formatting helpers:
to_base64()andto_user_friendly_url_safe()produce URL-safe base64 without padding using the stored flags,to_user_friendly_base64()produces standard base64 with padding using the stored flags,to_bounceable(url_safe)andto_non_bounceable(url_safe)override the bounceability flag for output,to_test_only(url_safe)andto_non_test_only(url_safe)override the test-only flag for output,- the legacy
to_string(user_friendly, url_safe, bounceable, test_only)helper is preserved.
Invariants And Edge Cases
The decoder rejects:
- base64 payloads that do not decode to exactly 36 bytes,
- tags other than
0x11,0x51,0x91, or0xd1, - CRC16 mismatches,
- workchain bytes that map to values below
-1, - malformed raw hash or workchain values.
The parser preserves parsed bounceable/non-bounceable and test-only flags in the
returned Address. Raw format has no flag storage, so raw parsing creates a
bounceable, non-test-only address by default.
Fixture Coverage
Current embedded fixture tests cover the TON Docs address example:
0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e
The test vectors include bounceable, non-bounceable, bounceable test-only, and non-bounceable test-only forms in both URL-safe and standard base64 alphabets. They validate tag parsing, flag preservation, CRC16 checking, and conversion back to raw form.
The zero account id is also covered as a synthetic edge fixture for known bounceable and non-bounceable encodings.
LiteAPI Mapping
LiteAPI uses:
liteServer.accountId workchain:int id:int256 = liteServer.AccountId;
Address::to_account_id() maps the stored i8 workchain into the LiteAPI
int field and copies the 32-byte hash into int256.
Missing Work
- Full TL-B
addr_none,addr_extern,addr_std, andaddr_varmodels. - Anycast support.
- Additional captured fixtures from upstream TON or pytoniq-core for non-zero masterchain addresses and address values seen in live contract workflows.
- Public address inspection/formatting CLI commands.
Bag Of Cells
Purpose And Scope
Bag of Cells serializes a directed acyclic graph of TON cells into bytes. This
page documents the ordinary-cell BoC baseline implemented in src/tvm/boc.rs.
The current implementation is intended to support future TL-B and contract work
without depending on a third-party Rust TON SDK.
This slice covers generic BoC decoding and encoding for ordinary and supported exotic cells, optional CRC32, optional index tables during decode, and string conversion helpers.
Wire Format
The supported generic BoC magic is:
b5 ee 9c 72
The generic header fields are read in this order:
magic:4
flags_and_size:1
offset_bytes:1
cells_count:size_bytes
roots_count:size_bytes
absent_count:size_bytes
cells_size:offset_bytes
root_index:size_bytes
index_table:cells_count * offset_bytes, only when has_idx is set
cells:cells_size
crc32:4, only when has_crc32 is set
flags_and_size contains:
- bit 7: index table flag,
- bit 6: CRC32 flag,
- bit 5: cache bits flag,
- low 3 bits:
size_bytes.
Integer fields are big-endian. The CRC32 trailer used by this crate is stored little-endian, matching the existing serializer behavior.
Cell Serialization
Each cell serializes as:
refs_descriptor:1
bits_descriptor:1
data:ceil(bits / 8)
ref_indexes:ref_count * size_bytes
The first descriptor stores reference count in bits 0..=2, the exotic flag in
bit 3, and level in bits 5..=6. Reserved bits must be unset. For ordinary
cells, the level is the maximum level of all references. For exotic cells, the
decoder derives the level from the parsed exotic kind and rejects a descriptor
whose level does not match.
The second descriptor is floor(bits / 8) + ceil(bits / 8). For partial-byte
cell data, the serialized data includes a top-up 1 bit immediately after the
last data bit and zero padding after that marker. The decoder removes the
top-up marker and restores the exact bit length.
Reference indexes are read using size_bytes, not a hard-coded one-byte index.
Indexes must point to a parsed cell index.
Supported exotic payloads:
- pruned branch: tag
0x01, mask1..=7, one hash and one depth per set mask bit, no references, - library reference: tag
0x02, one 32-byte hash, no references, - Merkle proof: tag
0x03, one proof hash and proof depth, one reference, - Merkle update: tag
0x04, old/new proof hashes and depths, two references.
The decoder preserves exotic cells as Cell::exotic_kind() instead of
rebuilding them as ordinary cells. Unsupported tags and invalid kind-specific
payloads fail decoding with an Invalid exotic cell error context.
Invariants And Edge Cases
The strict semantic decoders currently accept:
- one or more roots through
deserialize_boc_roots(), withdeserialize_boc()reserved for exactly one root and rejecting multi-root payloads, - zero absent cells,
- generic BoC magic
b5ee9c72, - ordinary cells with up to four references,
- optional index tables when
has_idxis set, - optional CRC32 trailers,
- supported exotic cells with exact payload lengths and reference counts,
- hex and standard base64 string wrappers through
hex_to_boc()andbase64_to_boc().
Both strict semantic decoders reject:
- unknown magic values,
- legacy indexed magic values that are not the generic BoC layout,
- truncated header or cell data,
size_bytesoroffset_bytesoutside1..=8,- cache-bit BoCs with the explicit error that cache bits are unsupported for ordinary-cell decoding,
- cell descriptors with reserved bits set,
- exotic-cell descriptor levels that do not match the level derived from the exotic payload and references,
- unsupported exotic-cell type tags,
- invalid exotic-cell payloads, including short type tags, invalid pruned masks, wrong payload lengths, and wrong reference counts,
- malformed index tables, including non-monotonic offsets or a final offset
that does not equal
cells_size, - root index out of range,
- reference index out of range,
- malformed partial-byte top-up markers,
- CRC32 mismatch,
- trailing bytes after the cell payload when CRC32 is absent, or after the CRC32 trailer when it is present.
deserialize_boc() additionally rejects otherwise valid BoCs that contain zero
roots or more than one root. Use deserialize_boc_roots() when a semantic
caller expects a strict multi-root cell set.
inspect_boc() is a proof-oriented structural path. It parses the same generic
header, root indexes, optional index table, CRC32 trailer, cell descriptors,
raw serialized cell payloads, and reference indexes, then computes root
representation hashes from the descriptor bytes, raw serialized data, child
depths, and child hashes. It does not construct Cell values and therefore
does not validate exotic-cell tags, exotic payload lengths, TL-B types, proof
paths, or trust roots. Structural failures such as invalid root indexes,
invalid reference indexes, CRC32 mismatches, truncated payloads, cache-bit
payloads, and reserved descriptor bits still fail.
Crate Mapping
serialize_boc(root, has_crc32)writes generic BoCs without an index table.deserialize_boc(data)reads strict single-root generic BoCs with or without index tables and preserves supported exotic-cell kinds.deserialize_boc_roots(data)returns all strict semantic root cells for multi-root payloads.inspect_boc(data)returns proof diagnostic root counts and hashes without requiring semantic proof verification.hex_to_boc()andboc_to_hex()wrap byte-level BoC conversion.base64_to_boc()andboc_to_base64()use standard base64 for BoC strings.
Fixture Coverage
Current embedded fixture tests cover:
- empty ordinary generic BoC,
- one-byte ordinary generic BoC,
- ordinary generic BoC with one reference, with and without an index table,
- exotic library-reference BoC,
- multi-root proof diagnostic inspection,
- malformed cache-bit BoC rejection.
These fixtures are intentionally small hex constants in src/tvm/boc.rs. They
are derived from the TON serialized_boc#b5ee9c72 layout and cross-check the
crate’s canonical serializer output for supported cases.
Phase 1 TL-B compatibility fixtures in fixtures/phase1/ add checked-in BoC
hex payloads with metadata for message, account, transaction, and
transaction-description roots. Their tests decode the BoC, compare the root
representation hash from the cell, decode the declared TL-B type, and require
canonical serializer output to match the fixture bytes exactly. They are
offline-only and do not use live network access.
Cache-Bit Policy
Cache-bit BoCs remain unsupported in this crate. The generic BoC header can signal cache bits, but the current SDK has no public representation for cached hash/depth material and no fixture proving that ignoring those bits is lossless for all supported cell kinds. Decoding such payloads by silently discarding the extra metadata would make compatibility ambiguous, especially for future proof and archive workflows.
Until a concrete upstream fixture requires cache-bit preservation, the decoder
rejects these BoCs with BoC cache bits flag is unsupported for ordinary-cell decoding. Serialization always writes the cache-bit flag as zero.
Missing Work
- Multi-level exotic hash/depth helper APIs for proof verification.
- Legacy indexed magic variants
68ff65f3andacc3a728. - Additional captured golden BoCs from upstream TON, pytoniq-core, or public liteservers for deep account-proof, block-proof, and config proof cells.
- Encoding with an index table when callers need that output mode.
- Full proof verification for account and block proof BoCs.
TVM Cells
Cells are the primary storage and serialization unit in TON.
Ordinary Cell Limits
- Data bits:
0..=1023. - References:
0..=4. - Level:
0..=3. - Exotic flag: false.
Descriptor Bytes
First byte:
refs_count + 8 * exotic + 32 * level
Second byte:
floor(bits / 8) + ceil(bits / 8)
Top-Up Bit
If data bit length is not divisible by 8, serialized data includes a 1 top-up bit immediately after the last data bit, then zero bits until byte boundary.
Fixed-Width Integer Bits
TLB uintN values are stored as exactly N bits in big-endian bit order, most significant bit first. When N is not divisible by 8, the first value bit still occupies the next available high bit in the target byte; unused bits in the final byte are zero in the in-memory cell data and are separate from the serialized top-up bit.
Unsigned fixed-width values must satisfy 0 <= value < 2^N. N = 0 is valid only for zero.
TLB intN values use fixed-width two’s-complement encoding. A signed value must be in:
-2^(N - 1) <= value <= 2^(N - 1) - 1
For N = 0, only zero is valid. Readers decode intN by reading the unsigned bit string, checking bit N - 1, and subtracting 2^N when the sign bit is set.
CellBuilder::store_uint::<T>, Builder::store_uint::<T>, and Slice::load_uint::<T> use the natural width of unsigned Rust primitives (u8, u16, u32, u64, and u128). Non-natural widths that fit in the chosen output type use store_uint_custom::<T>(value, bits) and load_uint_custom::<T>(bits), for example a TL-B uint5 field read as u8.
CellBuilder::store_big_uint, CellBuilder::store_big_int, Builder::store_big_uint, Builder::store_big_int, Slice::load_big_uint, and Slice::load_big_int support widths up to the ordinary cell data limit of 1023 bits. Signed primitive helpers keep their width argument and delegate to the same canonical encoding rules.
VarUInteger
TLB VarUInteger n stores an unsigned byte length in n bits, followed by exactly that many big-endian value bytes. Zero is encoded as a zero length and no value bytes. The byte length itself must fit in the n-bit length field.
TON coin amounts use VarUInteger 16, which means a 4-bit byte length followed by at most 15 value bytes. Values requiring 16 bytes are invalid even if they fit in Rust u128.
Depth
- No refs: depth
0. - With refs:
1 + max(ref.depth). - Serialized as two-byte big-endian values in representation hash calculation.
Representation Hash
For ordinary cells:
- descriptors,
- top-up-padded data,
- reference depths,
- reference hashes,
- SHA-256.
Golden fixtures for ordinary cells are checked directly against this preimage, not against BoC bytes:
| Cell | Representation preimage | SHA-256 representation hash |
|---|---|---|
| Empty ordinary cell | 0000 | 96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7 |
One-bit ordinary cell containing 1 | 0001c0 | 7c6c1a965fd501d2938c2c0e06626bdaa3531357016e169070c9ef79c4c46bc0 |
Full-byte ordinary cell containing ab | 0002ab | 57c2a1a13baa2762109ed68be0c396f2303ce17e3dde7917d0e74b4072b1dbc7 |
32-bit ordinary cell containing 0000000f | 00080000000f | 57b520dbcb9d135863fc33963cde9f6db2ded1430d88056810a2c9434a3860f9 |
One-bit root containing 1, with refs to the empty and one-bit fixtures | 0201c00000000096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc77c6c1a965fd501d2938c2c0e06626bdaa3531357016e169070c9ef79c4c46bc0 | 383598f93bde0afbe68b632ae75d5ffa6747df1284e2f4abb86cd2c5840514fe |
One-bit middle containing 1, with ref to the empty fixture | 0101c0000096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7 | 9770d42f6d781e048a432b849b56d5329de4667b37cfb918429a23f90cb9884b |
Full-byte root containing ab, with ref to the one-bit middle fixture | 0102ab00019770d42f6d781e048a432b849b56d5329de4667b37cfb918429a23f90cb9884b | 9f19f1fa052329a70f79c2adaef4e9f4e73eb88be389918473adc5f9a2801181 |
Full-byte root containing ab, with refs to the empty and one-bit middle fixtures | 0202ab0000000196a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79770d42f6d781e048a432b849b56d5329de4667b37cfb918429a23f90cb9884b | 6d112e22e9b4f47922b27cb78ffb8c4c3be4be304cdcb9ad24560e3104827eb6 |
The multi-level fixtures above keep ordinary-cell depth handling explicit:
- the empty leaf has depth
0, descriptor0000, and no child data, - the one-bit middle cell has depth
1, descriptor0101, and child depth bytes0000, - the full-byte chained root has depth
2, descriptor0102, and child depth bytes0001, - the two-reference root has depth
2, descriptor0202, and child depth bytes0000 0001before both child hashes.
Exotic Cells
The crate models supported exotic cells explicitly through ExoticCellKind.
Ordinary constructors such as Cell::new() and Cell::with_data() always
create ordinary cells. Exotic cells are constructed by BoC decoding or by
Cell::with_exotic_data(data, bit_len, references), which validates the tag,
payload length, reference count, and derived level.
The first byte of every exotic cell data payload is the type tag:
| Tag | Kind | Payload | References | Derived level |
|---|---|---|---|---|
0x01 | Pruned branch | tag, one-byte level mask 1..=7, one 32-byte hash for each set mask bit, then one two-byte big-endian depth for each set mask bit | 0 | index of the most significant set mask bit plus one, therefore 1..=3 |
0x02 | Library reference | tag plus a 32-byte library cell representation hash, exactly 264 bits | 0 | 0 |
0x03 | Merkle proof | tag, one 32-byte proof hash, one two-byte big-endian proof depth, exactly 280 bits | 1 | max(ref.level - 1, 0) |
0x04 | Merkle update | tag, old 32-byte proof hash, new 32-byte proof hash, old two-byte depth, new two-byte depth, exactly 552 bits | 2 | max(old_ref.level - 1, new_ref.level - 1, 0) |
The BoC descriptor still carries the exotic flag outside the data bits:
refs_count + 8 * 1 + 32 * derived_level
BoC decoding rejects an exotic cell if the descriptor level does not match the level derived from its kind-specific rules. The decoder also rejects unsupported exotic tags, tags missing from short payloads, invalid pruned branch masks, wrong reference counts, and wrong exact payload lengths.
Cell::hash() remains the SHA-256 representation hash of the serialized cell
representation, including the exotic descriptor bit and top-up-padded data.
Cell::depth() remains the representation depth of the decoded cell graph:
pruned and library cells have no references and therefore depth 0; Merkle
proof and Merkle update cells have graph depth based on their explicit
references. Kind-specific proof depths and pruned-branch depths are available
through ExoticCellKind; callers must use those fields for proof semantics
instead of treating Cell::depth() as the deleted subtree depth.
The implementation does not yet expose hash_i/depth_i multi-level helpers.
Proof verification code must add those helpers before validating higher hashes
with CHASHI/CHASHIX-style semantics.
Crate Mapping
src/tvm/cell.rs:Cell,CellBuilder.src/tvm/builder.rs: convenience builder.src/tvm/slice.rs: reader.
Tests Needed
- overflow and underflow tests.
- upstream or pytoniq-core golden fixtures for exotic cells and BoC bytes.
TON Dictionaries
Purpose And Scope
TON dictionaries are TL-B hashmaps over fixed-width bitstring keys. They are not equivalent to Rust HashMap serialization: the wire format is a canonical Patricia tree whose edges compress shared key prefixes.
This page documents the HashmapE n X, HashmapAug n X Y, and
HashmapAugE n X Y foundation implemented in src/tvm/dict.rs. It covers
fixed-width keys, canonical label serialization, fork nodes, augmentation
preservation, and callback-based value codecs. It does not cover Merkle proofs,
exotic cells, or full blockchain model decoding.
Wire Format And Data Model
HashmapE n X is defined by the upstream TON TL-B schema as:
hme_empty$0 = HashmapE n Xhme_root$1 root:^(Hashmap n X) = HashmapE n X
The referenced Hashmap n X is an edge:
hm_edge#_ label:(HmLabel ~l n) {n = (~m) + l} node:(HashmapNode m X) = Hashmap n X
The node is either a leaf or a fork:
hmn_leaf#_ value:X = HashmapNode 0 Xhmn_fork#_ left:^(Hashmap n X) right:^(Hashmap n X) = HashmapNode (n + 1) X
An edge label consumes a common prefix before the node. A fork consumes one additional key bit by selecting the left child for 0 and the right child for 1.
Augmented dictionaries use the same edge and label structure, but store augmentation values at every node:
ahm_edge#_ label:(HmLabel ~l n) node:(HashmapAugNode m X Y) = HashmapAug n X Yahmn_leaf#_ extra:Y value:X = HashmapAugNode 0 X Yahmn_fork#_ left:^(HashmapAug n X Y) right:^(HashmapAug n X Y) extra:Y = HashmapAugNode (n + 1) X Yahme_empty$0 extra:Y = HashmapAugE n X Yahme_root$1 root:^(HashmapAug n X Y) extra:Y = HashmapAugE n X Y
HashmapAug n X Y is non-empty. HashmapAugE n X Y may be empty, but the
empty constructor still stores the top-level extra:Y.
Labels
HmLabel has three valid encodings:
hml_short$0 len:(Unary ~n) s:(n * Bit)hml_long$10 n:(#<= m) s:(n * Bit)hml_same$11 v:Bit n:(#<= m)
The #<= m length field uses ceil(log2(m + 1)) bits. For example, a label whose maximum remaining length is 267 stores the length in 9 bits.
Serializers must emit a canonical label. This crate evaluates all valid encodings, chooses the shortest encoded bitstring, and on equal encoded length chooses the lexicographically smallest encoded bitstring. Deserializers accept all valid label forms and reject labels whose decoded length exceeds the current remaining key width.
Invariants And Edge Cases
- All keys in one dictionary have exactly
nbits. - Key bits are stored MSB-first, and unused bits in the final byte are zero.
- Empty dictionaries serialize as a single
0bit and no reference. - Non-empty dictionaries serialize as
1plus one root reference. HashmapAughas no empty constructor; useHashmapAugEwhen an empty dictionary is valid.HashmapAugEstores a top-level augmentation after the empty/root bit and optional root reference.- A leaf is valid only after exactly
nkey bits have been reconstructed. - A fork must have both child references.
- Duplicate decoded keys are rejected.
- Slice and reference underflow errors are propagated from
Slice. - Values are encoded by caller-provided callbacks; dictionary code does not infer
X. - Augmentation values are schema-specific. The dictionary layer preserves decoded leaf, fork, and top-level extras, but does not compute aggregate values for SDK-created dictionaries.
Current Crate Mapping
BitKey stores canonical fixed-width keys. HashmapE<V> stores entries in
BTreeMap<BitKey, V> so serialization is deterministic. HashmapAug<V, E>
stores a non-empty augmented tree plus key-ordered leaves, and
HashmapAugE<V, E> wraps an optional augmented root with the top-level extra.
Builder::store_hashmap_e_with and Slice::load_hashmap_e_with provide the generic codec surface:
- the store callback receives
&mut Builderand&V, - the load callback receives
&mut Sliceand returnsV.
The augmented APIs mirror this shape and add an augmentation callback:
Builder::store_hashmap_aug_withBuilder::store_hashmap_aug_e_withSlice::load_hashmap_aug_withSlice::load_hashmap_aug_e_with
Dict, DictKey, and DictValue remain compatibility wrappers. Integer keys use fixed-width BitKey conversion. Address keys serialize the full 267-bit addr_std form rather than truncating through u64.
Missing Work
- Proof-friendly dictionary traversal and path extraction.
- Golden fixtures compared against official TON implementations.
- Higher-level TL-B model codecs for full blocks, shard state, and config values.
- Cell capacity strategies for large inline values beyond the caller’s callback behavior.
TVM Stack
TVM stack values are used for smart-contract get-method parameters and results.
Required Entry Types
- null,
- integer,
- cell,
- slice,
- tuple,
- list,
- unsupported preserved value.
Integer Size
TVM integers are not limited to 64 bits. The current stack representation uses
num_bigint::BigInt, and checked fixtures include a decimal value wider than
i128 and u256.
Cells And Slices
Cells and slices should preserve:
- data bits,
- refs,
- current slice offset where applicable,
- BoC compatibility.
Tuples And Lists
Tuples and lists can nest. Implementation must avoid the four-reference direct-cell limit by using linked or referenced representation compatible with liteserver expectations.
LiteAPI Relation
liteServer.runSmcMethod sends params:bytes and receives result:mode.2?bytes. These bytes must match TON stack serialization, not an arbitrary SDK-local format.
The root VmStack cell starts with depth:(## 24), followed by the stack list
payload when depth is non-zero. Empty get-method calls therefore serialize to a
BoC whose root cell contains exactly 24 zero bits and no references.
Checked offline fixtures in fixtures/tvm/stack.json record deterministic
non-empty stack BoCs, root hashes, and decoded entry shapes generated by the
local codec. They cover scalar non-empty stacks, linked stack chains with more
than four logical entries, nested tuple/list values, huge integers, cell/slice
entries, and unsupported raw bytes. The fixture test decodes each BoC, compares
the expected entries and root hash, and verifies canonical reserialization to
the exact checked bytes.
An ignored live smoke test,
contracts::tests::live_non_empty_stack_run_get_method_smoke, can be run with
TON_GLOBAL_CONFIG_JSON, TON_STACK_TEST_CONTRACT_ADDRESS,
TON_STACK_TEST_METHOD defaulting to seqno, and TON_STACK_TEST_JSON. It
confirms transport success for a real liteserver call using a non-empty stack.
If the chosen method intentionally returns a non-zero exit code, set
TON_STACK_TEST_ACCEPT_EXIT_CODE to that value.
When the live call succeeds with exit_code == 0, the test prints a fixture
JSON object containing source tool/version, network, endpoint, block id,
account, method, input stack JSON, params BoC/root hash, result BoC/root hash,
decoded result, and compatibility-reference notes. Non-zero accepted smoke
tests do not print capture fixtures.
Current Crate Mapping
src/tvm/stack.rssrc/liteclient/client.rsget-method helpers
Missing Work
- Fill checked
captured_or_opt_instack fixtures from successful live output. - Add
cross_sdk_vectorswith tonutils-go params BoC bytes when available; for tonlib, keep structural decoded-result comparisons unless raw params BoC bytes are obtainable. - Expand result stack decoding against checked live response fixtures.
TL-B Data Models
Purpose And Scope
TL-B defines TON’s bit-level schema language for data stored in TVM cells. It is used for blocks, messages, accounts, transactions, config objects, smart-contract state, and many message body layouts. TL-B is separate from TL: TL serializes byte-level protocol messages for ADNL, LiteAPI, DHT, overlays, and related network APIs, while TL-B serializes structured values into cell bits and cell references.
This document fixes the crate direction for TL-B runtime traits, model codecs,
schema parsing, and macro support. The current implementation includes
hand-written codecs for the first blockchain model surface, raw-preserving
wrappers for deeper block families, a deterministic schema parser and
checked-summary workflow, and an optional proc-macro crate behind
tlb-derive.
TL Compared With TL-B
| TL | TL-B |
|---|---|
| byte-level protocol serialization | bit-level cell serialization |
| used for ADNL, LiteAPI, DHT, overlays | used for blocks, messages, accounts, state |
| constructor ids are 32-bit little-endian ids | constructors are fixed bit tags or implicit |
| vectors and bytes are common primitives | refs, bits, Maybe, Either, HashmapE are common primitives |
| schema terms map to byte readers/writers | schema terms map to Builder, Slice, and child cells |
Wire Format And Data Model
TL-B values are encoded as a sequence of bits in the current cell plus zero or more references to child cells. A cell can contain at most 1023 data bits and at most 4 direct references. Multi-cell structures must preserve the exact placement required by the schema: storing a value inline and storing it behind a reference are different encodings even if the child value is otherwise identical.
The initial Rust mapping should use src/tvm/builder.rs, src/tvm/slice.rs,
src/tvm/cell.rs, and src/tvm/dict.rs as the low-level runtime. Model code
should not build BoC bytes directly. The intended flow is:
TlbSerializewrites a value into a mutableBuilder.Builder::buildproduces aCell.Slicereads a value from a cell forTlbDeserialize.- BoC serialization remains a separate outer step.
The public TL-B module should eventually own model-level traits, error types,
and built-in schemas. Low-level bit, reference, integer, and dictionary
operations should stay in tvm.
Runtime Trait Shape
The intended minimal traits are:
#![allow(unused)]
fn main() {
pub trait TlbSerialize {
fn store_tlb(&self, builder: &mut Builder) -> Result<()>;
fn to_cell(&self) -> Result<Arc<Cell>> {
let mut builder = Builder::new();
self.store_tlb(&mut builder)?;
Ok(builder.build()?)
}
}
pub trait TlbDeserialize: Sized {
fn load_tlb(slice: &mut Slice) -> Result<Self>;
fn from_cell(cell: Arc<Cell>) -> Result<Self> {
let mut slice = Slice::new(cell);
let value = Self::load_tlb(&mut slice)?;
ensure_empty(&slice)?;
Ok(value)
}
}
}
The traits should remain object-agnostic and should not require allocation for
simple values. to_cell and from_cell are convenience wrappers around the
low-level builder and slice APIs, not the canonical implementation points.
TlbScheme currently holds descriptive constructor metadata: constructor name,
result name, and optional static tag bits. Runtime codecs must stay usable
without generated schema metadata.
Hand-written implementations remain preferred for protocol-critical built-in
models until schema generation is complete. The derive macro is available for
application-specific TL-B bodies and small local schemas; it emits the same
TlbSerialize and TlbDeserialize traits used by handwritten models.
Derive Macro Architecture
tonutils-macros is a workspace proc-macro crate enabled only through the
tlb-derive feature. The main crate re-exports tlb::Tlb and tlb::TlbDerive
under that feature. Default builds do not depend on syn, quote, or
proc-macro2 through this path.
Supported derive attributes:
#[tlb(tag = "0101")],#[tlb(tag = "0b0101")],#[tlb(tag = "0x5")], and#[tlb(tag = "#5")]write and check fixed constructor tags. Hex forms expand to four bits per digit.#[tlb(bits = N)]stores exact-width primitive integers and[u8; 32]throughStoreBits<N>andLoadBits<N>. Unsigned primitive fieldsu8,u16,u32,u64, andu128infer their natural bit width when this attribute is omitted. Signed integer fields require an explicitbitsattribute. Float primitive fields are rejected because the runtime does not define TL-B float semantics.#[tlb(reference)]or#[tlb(ref)]stores a field as^Tusingstore_ref_tlbandload_ref_tlb.
The macro currently supports structs and tagged enums. Enum variants must have
explicit tags. TlbDeserialize::from_cell remains the exact decode
entry point and rejects trailing bits or references after the generated decoder
returns.
Struct Mapping
A TL-B product constructor maps to a Rust struct. Fields are encoded in schema order and decoded in the same order. Field names in Rust should use normal snake case even when the source schema uses protocol-specific names.
Example shape:
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddressInt dest:MsgAddressInt value:CurrencyCollection
ihr_fee:Grams fwd_fee:Grams created_lt:uint64 created_at:uint32
= CommonMsgInfo;
The Rust model should encode the constructor tag first, then each field. Boolean
fields store one bit. Fixed-width unsigned integers use the exact bit width from
the schema and are encoded MSB-first. Signed integers use the TVM two’s-complement
integer APIs for the declared width. Big integer fields must use the explicit
large integer builder and slice APIs when the width exceeds u64.
No implementation should reorder fields for Rust convenience. No implementation should silently accept extra bits or refs for top-level exact decodes unless the caller explicitly asks for prefix decoding.
Enum And Constructor Mapping
A TL-B sum type maps to a Rust enum. Each variant corresponds to one constructor that returns the same TL-B result type. Decoding reads enough bits to distinguish the constructor, then delegates to the variant fields. Encoding writes the variant’s constructor tag and fields.
Constructor handling must support these cases:
- Fixed bit tags such as
$0,$10, or$110. - Hex tags such as
#_,#3, or longer schema tags when present in upstream definitions. - Implicit constructors, where the schema has no serialized tag and the context selects the only valid constructor.
- Parameterized constructors, where type-level parameters affect field widths or nested value codecs but are not necessarily serialized.
When variants have overlapping tag prefixes, decoding must use the complete constructor set for the result type and choose only an unambiguous full tag. A shorter prefix must not be accepted if a longer constructor could still match in that context. The future macro implementation should generate a compact tag decision tree and a validation test for ambiguous variants.
Cell References
^X means the current cell stores a reference to a child cell that encodes X.
Serialization must build the child cell with X::store_tlb, then store it as a
reference in the parent builder. Deserialization must load one reference, create
a slice over that child cell, decode X, and require the child slice to be
consumed for exact referenced values.
Reference errors must distinguish:
- The current slice has no reference to load.
- The referenced cell exists but cannot be parsed as
X. - The referenced cell parses as
Xbut has trailing bits or references where the schema requires exact consumption. - The parent builder would exceed the 4-reference cell limit.
Inline X and referenced ^X should use the same model type when possible, but
the wrapper position decides whether the codec operates on the current builder
or a child builder.
Maybe And Either
Maybe X stores a one-bit presence marker:
0: no value follows.1:Xfollows according to its own mapping.
Maybe X should map to Option<T> for owned values. Option<Cell> is valid
when the schema intentionally stores raw cells, but model types should use
Option<T> for typed data.
Either X Y stores a one-bit branch marker:
0: left branchX.1: right branchY.
Either X Y should map to an enum, not to two optional fields. For common
inline-or-reference patterns such as Either X ^X, the crate may provide a
small helper enum if it improves readability, but generated model code should
still make branch choice explicit.
VarUInteger
VarUInteger n stores a length prefix followed by an unsigned integer payload.
The prefix width is ceil(log2(n)) bits and the prefix value is the number of
payload bytes. The maximum payload length is n - 1 bytes. A zero prefix encodes
zero and stores no payload bytes.
VarUInteger 16, used by Grams, has a 4-bit length prefix and can encode up
to 15 payload bytes. The integer payload is big-endian and must be canonical:
non-zero values must use the shortest possible byte length, and zero must use
length 0. Deserialization should reject overlong encodings such as length 2 for
a value that fits in one byte, and length greater than the schema maximum.
The Rust mapping should use u64 only when the schema maximum is known to fit.
Currency values and generic VarUInteger n should use the crate’s big unsigned
integer APIs or a small wrapper that preserves canonical length constraints.
HashmapE
HashmapE n X stores an optional Patricia-tree dictionary:
0: empty dictionary.1: followed by one reference to aHashmap n Xroot edge.
Keys are fixed-width MSB-first bitstrings. Edges store compressed HmLabel
prefixes, and fork children are references selected by the next key bit.
src/tvm/dict.rs implements the current generic dictionary foundation:
BitKeystores fixed-width key bits with canonical zeroed unused final-byte bits.HashmapE<V>stores sortedBitKeyentries and serializes them as canonical TL-BHashmapE.Builder::store_hashmap_e_withandSlice::load_hashmap_e_withencode and decode values through callbacks, leaving concreteXcodecs to callers.
Serialization emits canonical HmLabel forms (hml_short, hml_long, or
hml_same). Deserialization accepts all valid label forms and rejects overlong
labels, missing fork references, duplicate decoded keys, and key-width
mismatches. Typed TL-B models should wrap these callback APIs rather than
duplicating dictionary traversal.
HashmapAug And HashmapAugE
HashmapAug n X Y uses the same fixed-width Patricia-tree key layout as
Hashmap n X, but every leaf and fork stores an augmentation value of type Y:
ahm_edge#_ label:(HmLabel ~l n) ... = HashmapAug n X Y;ahmn_leaf#_ extra:Y value:X = HashmapAugNode 0 X Y;ahmn_fork#_ left:^(HashmapAug n X Y) right:^(HashmapAug n X Y) extra:Y;ahme_empty$0 extra:Y = HashmapAugE n X Y;ahme_root$1 root:^(HashmapAug n X Y) extra:Y = HashmapAugE n X Y.
src/tvm/dict.rs provides HashmapAug<V, E> for non-empty dictionaries and
HashmapAugE<V, E> for optional-root dictionaries with a top-level extra.
Decoded leaf, fork, and top-level augmentation values are preserved. Canonical
construction from entries is available for tests and SDK-created values, but it
requires the caller to provide augmentation values because TON aggregation rules
are schema-specific.
The callback APIs mirror HashmapE: Builder::store_hashmap_aug_with,
Builder::store_hashmap_aug_e_with, Slice::load_hashmap_aug_with, and
Slice::load_hashmap_aug_e_with. Concrete models supply both value and
augmentation codecs.
Tag And Exact Decode Errors
TL-B decoding errors should be precise enough for fixture debugging and proof
verification. The intended TlbError cases include:
- Constructor tag mismatch, including the expected constructor or result type and the bits actually observed when available.
- Slice underflow for missing bits.
- Reference underflow for missing child cells.
- Builder overflow for more than 1023 bits or more than 4 references.
- Invalid reference payload for
^X. - Non-canonical integer,
VarUInteger, or dictionary encoding. - Unsupported exotic or proof-sensitive cell shape when a model requires an ordinary cell.
- Trailing bits or references after an exact decode.
The low-level tvm errors should remain reusable. TlbError can wrap them, but
model code should add schema context before returning an error where practical.
Schema And Macro Direction
Phase 1 macro/schema support is the checked schema workflow in
src/tlb/schema.rs:
- users can parse TL-B text with
parse_schema, - inspect constructor names, explicit tags, grouped references, field text, and result types,
- generate a deterministic checked summary with
generate_summary, - compare generated output against a checked-in snapshot for drift detection.
examples/tlb_schema_codegen.rs demonstrates this path with a small
user-defined schema snippet and separately verifies the built-in
BLOCK_PHASE1_TLB summary. The built-in Phase 1 block/config/proof schema
slice remains in src/tlb/schemas/block_phase1.tlb; its checked summary remains
in src/tlb/generated/block_phase1.rs.
The checked schema workflow remains the broad upstream-schema path. A separate
optional tonutils-macros proc-macro crate now covers hand-written Rust
structs and enums without adding compile cost for users who do not enable the
tlb-derive feature.
Macro work supports two complementary forms:
- A derive macro for manually written Rust structs and enums, where attributes provide constructor tags, field widths, references, and helper codecs.
- A schema macro or code generator that consumes checked upstream TL-B snippets and emits deterministic Rust models plus tests for constructor tags and field order.
The derive form is useful for stable public model names and hand-curated APIs.
The schema-driven form is useful for broad upstream coverage and drift checks.
Both forms must generate code that calls the same TlbSerialize and
TlbDeserialize traits, so hand-written and generated models compose.
Keep proc-macro support feature-gated so users who only need low-level TVM primitives do not pay macro compile cost.
Current Crate Mapping
Current implemented building blocks:
src/tvm/cell.rs: cell representation, hash/depth behavior, refs, and BoC integration.src/tvm/builder.rs: bit, integer, reference, and dictionary storage helpers.src/tvm/slice.rs: bit, integer, reference, and dictionary loading helpers.src/tvm/dict.rs: canonicalHashmapEand augmentation-preservingHashmapAug/HashmapAugEfoundation.src/tlb/mod.rs: public TL-B runtime withTlbSerialize,TlbDeserialize,TlbScheme,TlbError, fixed-tag helpers, exact decode checks,Maybe,Either, referenced value helpers, and canonicalVarUIntegerhelpers.tonutils-macros/: optional proc-macro crate for deriving the TL-B runtime traits on hand-written Rust structs and enums.
The first built-in hand-written blockchain model slice is implemented in
src/tlb/message.rs from the current upstream
ton-blockchain/ton crypto/block/block.tlb message definitions
(https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb).
It covers:
Anycastwithdepth:(#<= 30)encoded in five bits and constrained to1..=30.MsgAddressIntconstructorsaddr_std$10andaddr_var$11.MsgAddressExtconstructorsaddr_none$00andaddr_extern$01.MsgAddressas an anonymous wrapper overMsgAddressIntorMsgAddressExt, without an additional tag.Gramsas canonicalVarUInteger 16.CurrencyCollectionwithHashmapE 32 (VarUInteger 32)extra currencies.TickTock.- current upstream
StateInitwithfixed_prefix_length:(Maybe (## 5)),special:(Maybe TickTock), andcode,data,libraryasMaybe ^Cell. SimpleLiband current upstreamStateInitWithLibs, usinglibrary:(HashmapE 256 SimpleLib).CommonMsgInfoconstructorsint_msg_info$0,ext_in_msg_info$10, andext_out_msg_info$11; the internal constructor uses current upstreamextra_flags:(VarUInteger 16).CommonMsgInfoRelaxedconstructorsint_msg_info$0andext_out_msg_info$11; there is no external-in relaxed constructor, and relaxed internalsrcisMsgAddress.Message Anywith explicit preservation of inline versus referencedStateInitand body cells.MessageRelaxed Anywith the same init and body placement rules andCommonMsgInfoRelaxed.LibRefconstructorslibref_hash$0andlibref_ref$1.- The closed
OutActionfamily:action_send_msg#0ec3c86d,action_set_code#ad4de08e,action_reserve_currency#36e6b809, andaction_change_library#26fa1dd4. OutList, using upstream linked-list constructorsout_list_empty$_ = OutList 0andout_list$_ {n:#} prev:^(OutList n) action:OutAction = OutList (n + 1). The Rust model exposesVec<OutAction>in execution/schema order: the first vector item is deepest next toout_list_empty$_, and the last item is stored in the root node. Encoding and decoding enforce the 255-action TON limit.AccStatusChangeconstructorsacst_unchanged$0,acst_frozen$10, andacst_deleted$11.StorageUsedascells:(VarUInteger 7)andbits:(VarUInteger 7), with canonical variable-width integer payloads and a maximum payload length of six bytes per field.TrActionPhaseas the upstream implicit constructortr_phase_action$_. It storessuccess,valid,no_funds,status_change, optionaltotal_fwd_feesandtotal_action_fees, signedresult_code, optional signedresult_arg, fouruint16action counters,action_list_hash:bits256, andtot_msg_size:StorageUsed.src/tlb/transaction.rsimplements transaction-description phase models:TrStoragePhase,TrCreditPhase,ComputeSkipReason,TrComputePhase,TrBouncePhase,SplitMergeInfo, and the fullTransactionDescrconstructor family (trans_ord$0000,trans_storage$0001,trans_tick_tock$001,trans_split_prepare$0100,trans_split_install$0101,trans_merge_prepare$0110, andtrans_merge_install$0111).TransactionDescr.action:(Maybe ^TrActionPhase)maps toOption<TrActionPhase>but preserves the referenced child-cell placement and exact child decode semantics.- Account state models from upstream
block.tlb:StorageExtraInfowith three-bit tagsstorage_extra_none$000andstorage_extra_info$001,StorageInfowith upstream field orderused,storage_extra,last_paid,due_payment,AccountState,AccountStorage,AccountStatus,Account, andShardAccount. DepthBalanceInfoasdepth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection, enforcing the five-bit0..=30split-depth bound.ShardAccountsasHashmapAugE 256 ShardAccount DepthBalanceInfo.- Concrete
HashUpdateAccountforupdate_hashes#72 old_hash:bits256 new_hash:bits256 = HASH_UPDATE Account. - Top-level
transaction$0111, including the child reference that storesin_msg:(Maybe ^(Message Any))andout_msgs:(HashmapE 15 ^(Message Any)). Outbound message dictionary keys are validated as 15-bit keys, and inbound/outbound messages are exact referencedMessage Anypayloads. - Split/merge install
prepare_transaction:^Transactionfields map toBox<Transaction>to keep Rust layout finite while preserving exact referenced decoding. AccountBlockasacc_trans#5 account_addr:bits256 transactions:(HashmapAug 64 ^Transaction CurrencyCollection) state_update:^(HASH_UPDATE Account). The transaction dictionary is non-empty by construction and referenced transactions are exactTransactionpayloads.ShardAccountBlocksasHashmapAugE 256 AccountBlock CurrencyCollection.
The slice intentionally does not implement full block headers, value flow,
BlockExtra, shard-state models, config params, or schema-derived model
generation. TrActionPhase does not embed an OutList; it stores only the
256-bit action_list_hash produced from the action list. Golden fixture
coverage remains deferred.
Needed TON Models
Core models to implement next:
- Block header, value flow, extra data, and shard hashes.
- Config parameters needed by LiteClient and contract workflows.
Each model should cite the upstream TL-B schema revision used for its constructor definitions and should have fixture-backed roundtrip tests before it is used by high-level contract APIs.
Testing Strategy
TL-B tests should be layered:
- Unit tests for constructor tag matching,
Maybe,Either, references,VarUInteger, and dictionary callbacks. - Golden BoC fixture tests for known blocks, messages, accounts, config objects, and proofs.
- Roundtrip tests that decode a fixture, re-encode it, and compare cell hashes rather than relying only on byte-for-byte BoC equality.
- Negative tests for invalid tags, slice underflow, missing references,
non-canonical
VarUInteger, duplicate dictionary keys, and trailing data. - Cross-checks against upstream TON tools, tonutils-go, tongo, or pytoniq-core behavior where the protocol leaves room for implementation mistakes.
Invariants And Edge Cases
- Respect the 1023-bit and 4-reference cell limits at every encoding step.
- Treat bit widths as schema facts, not Rust type hints.
- Preserve inline versus referenced placement exactly.
- Reject non-canonical encodings for values where TON defines a canonical form.
- Do not accept ambiguous constructor tags.
- Require exact consumption for complete model decodes and referenced child values.
- Keep generated or macro-expanded code deterministic and formatted.
- Keep TL-B schema source tracking in
dev-docs/operations/source-tracking.mdor in the model-specific documentation added with each schema family.
Missing Work
- Expand
tlb-derivewith parameterized TL-B types, implicit or CRC tags if needed, ambiguous-prefix checks, and negative compile tests. - Add broader captured live/upstream golden fixtures for block, shard-state, config-param, Merkle proof, and Merkle update models.
- Replace raw-preserving block, shard-state, config, and proof wrappers with generated or handwritten typed models where stable.
Contributing
Thanks for improving tonutils-rs. The project aims to be a pure Rust TON SDK
with native protocol implementations, feature-gated optional functionality, no
third-party Rust TON SDK crate dependencies, and no native .so runtime
dependencies.
Local Setup
Install Rust with rustup before selecting a toolchain. On Unix-like systems,
including Linux, macOS, and WSL, run the official installer and follow the
prompts:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
On Windows, use the official installer from
https://www.rust-lang.org/tools/install. Reopen the shell if needed so the
~/.cargo/bin or %USERPROFILE%\.cargo\bin PATH update is visible, then
verify the Rust tools:
rustc --version
cargo --version
Install and select the stable Rust toolchain:
rustup toolchain install stable
rustup default stable
Rustup provides the cargo command, Rust compiler, formatter, test runner, and
Clippy checks used by this crate.
Install prek so you can run the configured pre-commit quality hooks locally
before commits and pull requests. The preferred path is the standalone installer
from the j178/prek releases:
# Linux / macOS
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/download/v0.4.1/prek-installer.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://github.com/j178/prek/releases/download/v0.4.1/prek-installer.ps1 | iex"
If you already have a recent Rust toolchain, you can alternatively build prek
from crates.io. Current upstream prek requires Rust 1.89 or newer for this
path:
cargo install --locked prek
CodeGraph is only needed when developing with agents. It gives agents a fast symbol and call-graph index so they can inspect Rust definitions, callers, and file structure without repeatedly scanning the checkout. Agent users need both the MCP server configuration and the CLI binaries.
Configure the CodeGraph MCP server with:
npx @colbymchenry/codegraph
If this fails with npx: command not found, install Node.js and npm first,
then reopen the shell and verify:
node --version
npm --version
npx --version
If npm is available but npx is not, use npm exec -- @colbymchenry/codegraph
or update the npm/Node.js installation. Modern npx is backed by npm exec,
so this uses the same package execution path.
Install the CodeGraph CLI binaries with the portable installer:
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.ps1 | iex
If you do not use the portable installer, install the npm-provided CLI binary:
npm i -g @colbymchenry/codegraph
After the MCP server and CLI binary are available, initialize the index from the repository checkout:
codegraph init -i
Verify the checkout:
cargo fmt --check
cargo check
cargo test
cargo clippy -- -D warnings
Run narrower checks while developing when they cover the changed surface. Before
opening a pull request, run the full relevant checks, and add
cargo check --examples --all-features when examples, feature declarations, or
public docs change.
Branches And Pull Requests
Create a focused topic branch for each change. Pull requests should describe the user-visible behavior, link related issues when applicable, and call out any feature flag, public API, protocol, trust-model, or live-network impact.
Protocol, serialization, network, trust-model, and public API changes should
include focused tests and relevant updates to docs/, dev-docs/, or
TODO.md.
Protocol Research
Do not invent TON behavior. Verify constructor names, ids, flags, numeric widths, byte order, hash rules, limits, and failure modes from upstream TON schemas and implementation, checked fixtures, or recorded live evidence.
Prefer upstream ton-blockchain/ton when references disagree. Use other SDKs
only for capability ideas and compatibility comparison, not as dependency
sources or parity targets. If evidence is incomplete, document the assumption
and keep the gap visible in TODO.md.
Documentation And TODOs
Keep repository text in English. Update public docs for user-facing behavior,
internal dev-docs/ for protocol or implementation details, and TODO.md for
known gaps, deferred work, or missing fixture evidence.
TODO.md follows todo-md/todo-md conventions: task lines begin with - [ ],
- [x], or - [-], active groups use ## headings, and postponed/completed
work belongs under # BACKLOG or # DONE.
Commits And Versions
Use Conventional Commits 1.0.0 summaries, such as:
feat: add wallet transfer helper
fix: reject invalid cell descriptors
docs: clarify LiteClient proof limits
The crate follows SemVer 2.0.0. Public API additions, deprecations, removals, or behavior changes may require a version update. Security fixes and release workflow changes should call out the expected version impact.
Live Tests And Secrets
Live-network tests and examples are opt-in and require explicit environment variables documented in docs/testing.md and docs/examples.md.
Never commit real private keys, seed phrases, live credentials, production configuration, or non-public user data. Use deterministic offline fixtures when possible, and sanitize live evidence before adding it to the repository.
Code Of Conduct
Our Pledge
We pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
Our Standards
Examples of behavior that contributes to a positive environment include:
- Using welcoming and inclusive language.
- Respecting differing viewpoints and experiences.
- Giving and gracefully accepting constructive feedback.
- Focusing on what is best for the community and the project.
Examples of unacceptable behavior include:
- Harassment, insults, trolling, or personal attacks.
- Public or private abuse, threats, or intimidation.
- Publishing others’ private information without explicit permission.
- Conduct that could reasonably be considered inappropriate in a professional setting.
Enforcement
Project maintainers may remove, edit, or reject comments, commits, issues, pull requests, and other contributions that do not align with this Code of Conduct. Maintainers may temporarily or permanently restrict participation for behavior they consider inappropriate, threatening, offensive, or harmful.
Report abusive, harassing, or otherwise unacceptable behavior through the repository maintainers’ GitHub contact channels. Maintainers will review reports privately and respond in a way that is appropriate to the circumstances.
Security Policy
Reporting A Vulnerability
Do not open public GitHub issues for vulnerabilities or suspected vulnerabilities.
Use GitHub Private Vulnerability Reporting or a GitHub Security Advisory for this repository so maintainers can investigate before details are public. If private reporting is unavailable, contact the maintainers through their GitHub profiles and include only the minimum information needed to establish a private reporting channel.
Helpful reports include:
- Affected crate version or commit.
- Affected feature flags and operating environment.
- Impact, expected attacker capability, and affected users.
- Reproduction steps, minimized proof-of-concept details, or malformed inputs.
- Whether live-network credentials, private keys, seed phrases, or user funds could be exposed or affected.
Never include real private keys, seed phrases, production credentials, non-public user data, or funds-bearing live wallet material in a report. If a report requires sensitive evidence, first establish a private maintainer channel and agree on a safe transfer method.
Supported Versions
Security fixes are prioritized for the latest published release and the current
main branch. Older releases may receive fixes when the issue is severe and a
small, low-risk backport is practical.
Handling Sensitive Material
Use deterministic test fixtures whenever possible. Do not attach production TON global configs, private liteserver credentials, mnemonic phrases, private keys, access tokens, logs containing secrets, or non-public account data.
If you accidentally disclose sensitive material in a report, say so immediately in the private thread so maintainers can help coordinate rotation, revocation, or repository cleanup.