Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

tonutils-rs

CI crates.io codecov

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, and liteclient.
  • tl: TL types, LiteAPI request and response structures, and serialization.
  • tvm: cells, slices, builders, BoC, addresses, dictionaries, TL-B helpers, and TVM stack values. Enables tl.
  • adnl: ADNL types shared by transports. Enables tl.
  • adnl-tcp: native ADNL TCP transport. Enables adnl.
  • liteclient: LiteAPI client, LiteBalancer, and contract helpers. Enables adnl-tcp and tvm.
  • network-config: TON global config parsing and liteserver selection.
  • cli: command-line diagnostics. Enables liteclient, 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:

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_message failover 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 to N requests per second.
  • --global-rps <N>: throttle total LiteBalancer request attempts to N requests per second.
  • --num-servers <N>: number of allowed liteservers for high-level balancer commands after --exclude-ls filtering.
  • --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 a const &[u8] or expression such as include_bytes!("wallet.code.boc").
  • #[contract(code_hex = "...")] for inline BoC hex.
  • #[contract(code_file = "wallet.code.boc")] for include_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 raw result BoC 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, and JettonInternalTransferPayload cover TEP-74 transfer, burn, and wallet-to-wallet internal transfer bodies.
  • tonutils::nft::NftTransferPayload, NftOwnershipAssignedPayload, NftReportStaticDataPayload, and NftReportRoyaltyParamsPayload cover TEP-62 item transfer/static-data bodies and TEP-66 royalty reports.
  • inline_forward_payload and referenced_forward_payload select the TL-B Either Cell ^Cell branch 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: mainnet or testnet, defaulting to mainnet.
  • TON_GLOBAL_CONFIG_JSON: full TON global config JSON. Overrides public config download.
  • TON_LS_INDEX: liteserver index for single-peer examples, defaulting to 0.
  • TON_CONTRACT_ADDRESS: account address for contract examples. Mainnet contract examples default to UQBg0E2FCj7kkYWw-2yEcOHs7p1xtnqAoLIYBUG2AJ56eFNP.
  • TON_GET_METHOD: get-method name, defaulting to seqno.
  • TON_LITEAPI_REQUEST_HEX: serialized LiteAPI request bytes for raw queries, defaulting to serialized liteServer.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_info requires liteclient, network-config, and cli. It loads config from the live-network defaults, connects to TON_LS_INDEX, and prints the latest masterchain seqno.
  • liteclient_raw_query requires liteclient, network-config, and cli. It reads live-network defaults and optional TON_LITEAPI_REQUEST_HEX, sends already serialized LiteAPI bytes through query_raw, and prints the raw response as hex. Without TON_LITEAPI_REQUEST_HEX, it sends liteServer.getTime.
  • network_config requires network-config and cli. It reads live-network defaults, parses the liteserver list, and prints indexed socket addresses.
  • contract_get_state requires liteclient, network-config, and cli. It reads live-network defaults and optional TON_CONTRACT_ADDRESS, fetches latest account state, and prints block ids plus raw state length.
  • contract_get_method requires liteclient, network-config, and cli. It reads live-network defaults, optional TON_CONTRACT_ADDRESS, and optional TON_GET_METHOD, runs an empty-stack get-method, and prints the exit code plus raw result length. With TON_NETWORK=testnet, set TON_CONTRACT_ADDRESS; otherwise the example exits successfully because no stable default testnet seqno contract is defined.
  • litebalancer_failover requires liteclient, network-config, and cli. It reads live-network defaults, connects to all available liteservers from config, initializes LiteBalancer, performs get_masterchain_info, and prints seqno plus alive and archival peer counts.
  • adnl_ping requires adnl-tcp. It performs a loopback-safe ADNL handshake roundtrip in-memory (to_bytes + decrypt_from_raw) and prints sender and receiver identifiers.
  • tvm_cell_builder requires tvm. It builds a cell with fixed-width integer, big unsigned integer, and big signed integer values, reads them back via Slice, and prints decoded values.
  • tvm_boc_roundtrip requires tvm. It builds a small referenced cell graph, serializes it into BoC with CRC, deserializes back, and prints basic structure metadata.
  • tvm_stack_run_method requires liteclient, network-config, and cli. It reads live-network defaults, optional TON_CONTRACT_ADDRESS, and optional TON_GET_METHOD, calls run_get_method_by_name with an empty TvmStack, and prints exit code plus result size. With TON_NETWORK=testnet, set TON_CONTRACT_ADDRESS.
  • tlb_schema_codegen requires tvm. 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_decode requires tvm. It builds an offline Account::None fixture, encodes it as BoC, decodes it, and prints the root hash plus typed account view.
  • liteclient_account_state_decode requires liteclient. It decodes TON_ACCOUNT_STATE_BOC_HEX when set and otherwise exits cleanly using an offline Account::None fixture.
  • proof_verify requires tvm. It reads TON_MERKLE_PROOF_BOC_HEX when set and checks the exotic Merkle proof child-hash invariant. Without the environment variable, it uses a deterministic offline Merkle proof fixture.
  • tvm_dictionary_roundtrip requires tvm. It builds an offline compatibility Dict backed by HashmapE, serializes and deserializes it, and prints the key size, entry count, and root hash.
  • tlb_message_roundtrip requires tvm. 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_roundtrip requires tvm. 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_roundtrip requires tvm. It builds a full account with empty StateInit, storage, and balance fields, roundtrips it through TL-B, and prints the address hash, state, and balance.
  • tlb_block_wrapper_decode requires tvm. It builds the Phase 1 raw-cell Block wrapper with deterministic child cells, decodes it, and prints the global id plus child hashes.
  • tlb_config_params_wrapper requires tvm. It builds ConfigParams around a deterministic raw config dictionary root, roundtrips it through TL-B, and prints the config address and dictionary root hash.
  • tlb_parse_boc requires tvm. It decodes TON_TLB_BOC_HEX as a typed Account root, or uses an offline Account::None fixture, then prints typed data and hash roundtrip information.
  • tlb_read_tx_data requires tvm. It accepts TON_TRANSACTION_BOC_HEX or TON_TRANSACTION_BOC_BASE64, decodes a Transaction, and prints logical time, account hash, fees, statuses, message summary, and root hash. Without input it uses an offline deterministic transaction fixture.
  • tlb_custom_derive requires tlb-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_transfer requires tvm. 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. Enables tl.
  • adnl: ADNL types shared by transports. Enables tl.
  • adnl-tcp: native ADNL TCP transport. Enables adnl plus async transport dependencies.
  • liteclient: LiteAPI client, LiteBalancer, and contract helpers over ADNL TCP. Enables adnl-tcp and tvm.
  • network-config: TON global config parsing and liteserver selection helpers.
  • cli: command line interface for shell scripts and diagnostics. Enables liteclient and network-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 -> Account
  • decode_block_boc -> generated-backed Block wrapper
  • decode_config_params_boc -> ConfigParams
  • decode_shard_state_boc -> ShardState
  • decode_merkle_proof_boc and decode_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_block and raw_get_block_data
  • raw_get_block_header
  • get_account_state_typed, raw_get_account_state, and get_account_state_simple
  • raw_get_shard_info and raw_get_all_shards_info
  • get_one_transaction_typed, raw_get_transactions, and raw_get_block_transactions_ext
  • run_get_method_typed
  • get_config_all_typed and get_config_params_typed
  • get_libraries_typed and get_libraries_with_proof_typed
  • lookup_block_with_proof
  • list_block_transactions_ext
  • get_libraries_with_proof
  • get_shard_block_proof
  • get_out_msg_queue_sizes
  • get_block_out_msg_queue_size
  • get_dispatch_queue_info
  • get_dispatch_queue_messages
  • get_nonfinal_validator_groups
  • get_nonfinal_candidate
  • get_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: mainnet or testnet, defaulting to mainnet.
  • TON_GLOBAL_CONFIG_JSON: full TON global config JSON, overriding public config download.
  • TON_LS_INDEX: liteserver index for single-peer examples, defaulting to 0.
  • TON_CONTRACT_ADDRESS: account address for contract examples. Mainnet get-method examples default to UQBg0E2FCj7kkYWw-2yEcOHs7p1xtnqAoLIYBUG2AJ56eFNP; testnet get-method examples require an explicit address and exit successfully when it is absent.
  • TON_GET_METHOD: optional get-method name, defaulting to seqno.
  • TON_LITEAPI_REQUEST_HEX: serialized LiteAPI request bytes for raw queries, defaulting to serialized liteServer.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

  1. Architecture overview
  2. Feature matrix
  3. Source tracking
  4. Crypto primitives
  5. TL schema language
  6. LiteAPI schema
  7. ADNL TCP
  8. TVM cells
  9. BoC format
  10. TL-B data models
  11. Blockchain data model
  12. Blockchain TL-B coverage
  13. Block, config, and proof TL-B slice
  14. LiteClient request flow
  15. LiteClient rate limiting
  16. Smart-contract get-methods
  17. ABI data model
  18. Wallet V5R1
  19. Wallet V4R2 and TON mnemonics
  20. 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:

  1. Upstream ton-blockchain/ton schemas and C++ implementation.
  2. Official TON documentation and specs.
  3. Behavior observed from public liteservers with recorded fixtures.
  4. Mature SDK behavior such as tonutils-go, tongo, pytoniq, and pytoniq-core.
  5. 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:

  1. Resolve TON_NETWORK, defaulting to mainnet.
  2. Load TON_GLOBAL_CONFIG_JSON or download the public network config.
  3. Select TON_LS_INDEX, defaulting to 0.
  4. Connect with ADNL TCP.
  5. Fetch masterchain info.

Network Config

Expected flow:

  1. Resolve TON_NETWORK, defaulting to mainnet.
  2. Load TON_GLOBAL_CONFIG_JSON or download the public network config.
  3. Parse liteserver entries.
  4. Print indexed socket addresses for follow-up examples.

Raw LiteAPI

Expected flow:

  1. Load config through the live-network defaults.
  2. Select one liteserver with TON_LS_INDEX.
  3. Use TON_LITEAPI_REQUEST_HEX when provided.
  4. Otherwise serialize liteServer.getTime.
  5. Send bytes with query_raw and print raw response bytes as hex.

LiteBalancer

Expected flow:

  1. Build peer descriptors from config loaded through the live-network defaults.
  2. Connect or lazy-connect peers.
  3. Fetch version/time/masterchain info.
  4. Close background tasks.

Get-Method

Expected flow:

  1. Load config through the live-network defaults.
  2. Parse TON_CONTRACT_ADDRESS or use the documented mainnet default.
  3. When TON_NETWORK=testnet, require TON_CONTRACT_ADDRESS for default seqno get-method examples until a stable testnet contract is documented.
  4. Fetch latest block context.
  5. Build TVM stack.
  6. Run method by name.
  7. Check exit code.
  8. Decode typed stack result.

Send Message

Expected flow:

  1. Build external message cell.
  2. Serialize BoC.
  3. Send via LiteAPI.
  4. Track message hash until transaction appears.

Mempool Stream

Future flow:

  1. Build scanner from overlay/DHT config.
  2. Subscribe to pending messages.
  3. Filter by account or shard.
  4. Deduplicate by message hash.
  5. 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

FamilyExamplesRetry behavior
TL serializationunknown constructor, invalid bytes padding, EOFusually no
ADNL transportIO error, integrity error, EOF, invalid handshakeretry another peer
LiteAPI serverliteServer.errorusually no
TVM datamalformed BoC, cell overflow, invalid addressno until input changes
Contract executionnon-zero get-method exit codeno, semantic result
Proof verificationinvalid signature, invalid Merkle proofno; mark peer/data untrusted
Balancer stateno alive peers, no archival peerretry after reconnect

Design Rules

  • Do not map all errors to anyhow::Error in 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::ServerError does 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

FeaturePurposeExpected modules
stdStandard library supportall default builds
tlTL types and helperssrc/tl
tvmTVM primitivessrc/tvm, src/tlb
adnlADNL crypto and base typessrc/adnl without TCP runtime if split further
adnl-tcpasync TCP ADNLADNL peer, codec, handshake over Tokio
liteclientLiteAPI clientsrc/liteclient
network-configTON global config parsingsrc/network_config
clicommand line appsrc/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:name in 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

  • tl currently has response conversion helpers that mention LiteError.
  • adnl and adnl-tcp need a cleaner split if UDP support is added.
  • network-config parsing 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 .so dependency.
  • 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:

  1. Hashes, CRC, crypto helpers.
  2. TL primitive and boxed serialization.
  3. ADNL transport and ADNL message types.
  4. LiteAPI requests and responses.
  5. LiteClient request execution.
  6. LiteBalancer peer selection and failover.
  7. TVM cells, BoC, TLB, dictionaries, stack values.
  8. Smart-contract get-method and message APIs.
  9. Wallet, jetton, NFT, DHT, overlay, and mempool utilities.

Dependency Direction

Lower layers must not depend on higher layers.

Allowed directions:

  • liteclient may depend on adnl, tl, and tvm.
  • contracts may depend on liteclient and tvm.
  • network-config may depend on serde JSON support.
  • cli may depend on everything needed for user commands.

Forbidden directions:

  • tvm must not depend on liteclient.
  • tl must not depend on liteclient except 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::utils to remove the tl -> liteclient dependency.
  • Add a shared request-executor trait so LiteClient and LiteBalancer do not duplicate every method.
  • Add contracts module 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#b8e48dfb and value_flow_v2#3ebf98b7.
  • shard_state#9023afe2 and split_state#5f327da5.
  • _ config_addr:bits256 config:^(Hashmap 32 ^Cell) = ConfigParams.
  • Exotic MERKLE_PROOF tag 0x03 and MERKLE_UPDATE tag 0x04.

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_bits must be 0..=60.
  • Block requires constructor tag 0x11ef55aa and four referenced children.
  • ValueFlow accepts only 0xb8e48dfb or 0x3ebf98b7.
  • ShardState accepts unsplit 0x9023afe2 or split 0x5f327da5.
  • 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.rs parses the Phase 1 schema slice and verifies the checked generated summary.
  • src/tlb/block.rs implements ShardIdent, ExtBlkRef, BlockIdExtTlb, Block, BlockExtra, ValueFlow, ShardState, ConfigParams, MerkleProof, and MerkleUpdate.
  • src/liteclient/boc.rs preserves raw LiteClient BoC bytes alongside decoded cells and typed views.
  • src/cli/mod.rs exposes 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$000 and storage_extra_info$001 dict_hash:uint256;
  • StorageInfo: used:StorageUsed, storage_extra:StorageExtraInfo, last_paid:uint32, and due_payment:(Maybe Grams);
  • AccountState: account_uninit$00, account_frozen$01 state_hash:bits256, and account_active$1 _:StateInit;
  • AccountStorage: last_trans_lt:uint64, balance:CurrencyCollection, and state:AccountState;
  • Account: account_none$0 or account$1 addr:MsgAddressInt storage_stat:StorageInfo storage:AccountStorage;
  • ShardAccount: account:^Account, last_trans_hash:uint256, and last_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:

  • AccountBlock maps upstream acc_trans#5 account_addr:bits256 transactions:(HashmapAug 64 ^Transaction CurrencyCollection) state_update:^(HASH_UPDATE Account). The transaction dictionary is a non-empty HashmapAug keyed by 64-bit logical time. Leaf values are referenced Transaction cells and leaf/fork augmentations are CurrencyCollection.
  • ShardAccountBlocks wraps HashmapAugE 256 AccountBlock CurrencyCollection, keyed by 256-bit account address hash. The top-level HashmapAugE extra 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 as Either<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 as Either<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 current OutAction.

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, and no_funds:Bool;
  • status_change:AccStatusChange, with tags 0, 10, and 11;
  • total_fwd_fees:(Maybe Grams) and total_action_fees:(Maybe Grams);
  • result_code:int32 and result_arg:(Maybe int32);
  • tot_actions:uint16, spec_actions:uint16, skipped_actions:uint16, and msgs_created:uint16;
  • action_list_hash:bits256;
  • tot_msg_size:StorageUsed, where StorageUsed is cells:(VarUInteger 7) and bits:(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) and gas_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, and exit_arg:(Maybe int32);
  • vm_steps:uint32;
  • vm_init_state_hash:bits256 and vm_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 familyRust modelCodec statusTests or examples
MsgAddress, Message, StateInittlb::message::*typedTL-B unit tests, tlb_message_roundtrip
Account, ShardAccounttlb::transaction::*typedTL-B unit tests, tlb_account_state_roundtrip
Transaction, phases, account blockstlb::transaction::*typedTL-B unit tests, tlb_transaction_roundtrip, tlb_read_tx_data
ShardIdent, ExtBlkRef, BlockIdExttlb::block::*typedblock unit tests
Blocktlb::Blocktyped root with referenced child cellstlb_block_wrapper_decode
BlockInfo, BlockPrevInfotlb::BlockInfo, tlb::BlockPrevInforaw-preserving wrappersschema summary check
ValueFlowtlb::ValueFlowconstructor-checked raw payloadblock unit tests
BlockExtra, McBlockExtratlb::BlockExtra, tlb::McBlockExtraraw-preserving wrappersschema summary check
ShardState, ShardStateUnsplittlb::ShardState, tlb::ShardStateUnsplitconstructor-checked raw payloadblock unit tests
ConfigParamstlb::ConfigParamstyped address, decoded Hashmap 32 ^Cell entries, raw-preserving wrappers for common param idstlb_config_params_wrapper, block unit tests
HASH_UPDATEtlb::HashUpdatetypedblock unit tests
MERKLE_PROOF, MERKLE_UPDATEtlb::MerkleProof, tlb::MerkleUpdateexotic-cell wrappers with virtual hash checksproof_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 through StoreBits<N> and LoadBits<N>. Unsigned primitive fields u8, u16, u32, u64, and u128 infer their natural width. Signed integer fields require bits; float primitive fields are rejected because the runtime does not define TL-B float semantics.
  • #[tlb(reference)] or #[tlb(ref)] on fields for ^T child-cell encoding.

Runtime helpers added for macro and handwritten codecs:

  • CellRef<T> for referenced typed values.
  • RawCell for 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), or Opcode(u32).
  • Function kinds for get-methods, internal messages, and external messages.
  • Runtime values as AbiValue for 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 } and Uint { 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 } and Uint { bits } map to TvmStackEntry::Int, with declared width validation and signed or unsigned range checks.
  • Bool maps to TVM integer -1 for true and 0 for false. Decoding rejects all other integer values.
  • Bytes and String map to a Cell containing byte-aligned snake data. String decoding requires valid UTF-8.
  • Address maps to a Slice containing canonical MsgAddressInt::std(address) bytes and decodes only standard internal addresses without anycast.
  • Cell maps to TvmStackEntry::Cell.
  • Slice maps to TvmStackEntry::Slice.
  • Tuple maps to TvmStackEntry::Tuple and follows declared field order.
  • Array maps to TvmStackEntry::List.
  • Map maps to TvmStackEntry::Cell containing a local HashmapE key_bits ^AbiValueCell dictionary. Keys must be fixed-width uintN or intN; key_bits is inferred from N when omitted. Duplicate encoded keys are rejected, and decoded entries are returned in canonical dictionary key order.
  • Optional(None) maps to TvmStackEntry::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 } and Uint { bits } encode inline with the declared width.
  • Bool encodes as one inline bit.
  • Address encodes inline as a standard MsgAddressInt.
  • Bytes and String encode as referenced byte-aligned snake cells.
  • Cell and Slice encode as referenced cells.
  • Tuple encodes fields inline in declared order.
  • Map stores a reference to the same local HashmapE key_bits ^AbiValueCell dictionary root used by stack conversion.
  • Optional encodes a Maybe bit 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, and contracts,
  • contract name, optional methods, and optional events,
  • function name, kind, optional selector, optional inputs, and optional outputs,
  • event name, optional selector, and optional fields,
  • parameter name, type, and optional boolean optional.

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, returned output_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:

  • ToTvmStack and FromTvmStack for full get-method argument and result stacks,
  • ToTvmStackEntry and FromTvmStackEntry for one stack item,
  • TvmStackConversionError for 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,
  • shardblk execution 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 typed TlbSerialize state data,
  • code_boc() returns fixed code BoC bytes,
  • workchain() defaults to 0,
  • state_init() decodes code, serializes data, and fills StateInit.code and StateInit.data,
  • address() calls the shared address_from_state_init primitive,
  • bind() creates a normal address-bound Contract<'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 StateInit accessors,
  • 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, and run_get_method_by_name_latest_as,
  • raw external message BoC submission,
  • account transaction-history lookup,
  • StateInit address 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 ContractProvider behavior 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

  1. Build message cell.
  2. Serialize as BoC.
  3. Send liteServer.sendMessage.
  4. Return the opaque liteServer.SendMsgStatus.status submission 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:

  1. Track inclusion by message hash.
  2. Locate transaction in account history.
  3. 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 0x01 is decoded as off-chain URI content using snake bytes;
  • top-level 0x00 is decoded as on-chain HashmapE 256 ^Cell content keyed by SHA-256 field-name hashes;
  • on-chain values with in-value 0x00 decode as snake bytes;
  • on-chain values with in-value 0x01 decode as chunked HashmapE 32 ^Cell content 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_supply as a non-negative integer;
  • mintable as -1 for true and 0 for false;
  • admin_address as a standard internal address or addr_none;
  • jetton_content as a TEP-64 content cell;
  • jetton_wallet_code as 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() returns next_item_index, collection_content, and owner_address;
  • get_nft_data() returns init?, index, collection_address, owner_address, and individual_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

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, 390 iterations, first byte 0.
  • password seed-version check with PBKDF2-HMAC-SHA512, salt TON fast seed version, 1 iteration, first byte 1.
  • Ed25519 seed derivation with PBKDF2-HMAC-SHA512, salt TON default seed, 100000 iterations, 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 against pytoniq-core compatibility 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:bits512
  • wallet_id:(## 32)
  • valid_until:(## 32)
  • seqno:(## 32)
  • opcode:(## 32), currently 0
  • 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

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:

  • WalletV5R1Data serializes and deserializes the persistent storage data.
  • WalletV5R1WalletId packs and unpacks V5R1 wallet ids from a signed network global id and a V5R1 context.
  • WalletMessage builds standard action_send_msg actions with MessageRelaxed Any.
  • WalletV5R1 builds StateInit, derives addresses, creates external signed request bodies, signs their cell hash with Ed25519, and wraps them in external-in message BoCs.
  • With liteclient, WalletV5R1 provides get-method helpers for seqno, get_wallet_id, get_public_key, is_signature_allowed, and get_extensions over the existing ContractProvider trait.

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:W5InnerRequest
  • signature: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:MsgAddressInt
  • delete_extension#03 addr:MsgAddressInt
  • set_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, workchain 0, version 0, subwallet 0 gives 0x7fffff11.
  • testnet network_global_id = -3, workchain 0, version 0, subwallet 0 gives 0x7ffffffd.
  • workchain -1 with the same mainnet/testnet values gives 0x007fff11 and 0x007ffffd.

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

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

PrimitiveOutputUsed for
SHA-25632 bytescell representation hashes, ADNL addresses, ADNL packet hashes
SHA-51264 bytesEd25519 signing internals
CRC162 bytesuser-friendly address checksum, get-method name id helper
CRC32 / CRC32C4 bytesTL 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_raw only for protocol paths that explicitly sign raw bytes,
  • expose sign_tl for 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:

  1. state,
  2. archive requirement,
  3. masterchain freshness,
  4. EWMA latency,
  5. in-flight load,
  6. 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

  1. Decode proof BoC.
  2. Verify cell hashes against expected block ids.
  3. Verify Merkle proof exotic cells.
  4. Verify validator set and signatures.
  5. Verify shard inclusion in masterchain.
  6. 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 ShardAccounts dictionaries 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:

  • rps is the steady-state refill rate in whole requests per second.
  • burst is the maximum number of tokens and the number of immediate requests available after constructing the limiter.
  • rps == 0 and burst == 0 are 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 waitMasterchainSeqno prefix because the limiter is acquired before LiteClient::query_raw takes the pending seqno.
  • LiteBalancer global limiting counts actual attempts, including retries and each send_message peer 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.rs implements RequestRateLimit, validation, the async RateLimiter, and deterministic token-bucket tests.
  • src/liteclient/client.rs stores an optional limiter and acquires it at the start of query_raw.
  • src/liteclient/balancer.rs stores an optional global limiter and applies it in the shared execute_request path.
  • src/cli/mod.rs maps --rps to per-liteserver limits and --global-rps to 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

  1. Establish ADNL TCP connection.
  2. Acquire the optional local request-rate limiter.
  3. Wrap LiteAPI request into WrappedRequest.
  4. Serialize into liteServer.query.
  5. Serialize into adnl.message.query.
  6. Send through multiplexed ADNL stream.
  7. Receive adnl.message.answer.
  8. Decode answer bytes as Response.
  9. 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:

RangeSizeField
0..3232receiver ADNL address
32..6432sender public key
64..9632hash of plaintext AES params
96..256160encrypted 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.rs
  • src/adnl/primitives/codec.rs
  • src/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:

  1. Start from static DHT nodes from global config.
  2. Query closest known peers for a key.
  3. Validate returned nodes.
  4. Continue until enough close peers or value is found.
  5. 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: cli or 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

  1. Upstream ton-blockchain/ton schemas and implementation.
  2. Official TON docs.
  3. Captured behavior from public liteservers with fixtures.
  4. Mature SDKs such as tonutils-go, tongo, ton-rs, and tonlib-rs.
  5. 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-go
  • tongo: https://github.com/tonkeeper/tongo
  • tonstack/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.md or 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:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e with 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:0000000000000000000000000000000000000000000000000000000000000000 with 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 b5ee9c72 header 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 Any and MessageRelaxed Any with referenced children and exact trailing-data rejection,
  • StateInit,
  • CurrencyCollection with an extra-currency HashmapE 32,
  • Transaction and TransactionDescr,
  • Account,
  • ShardAccounts, AccountBlock, and ShardAccountBlocks,
  • standalone HashmapE canonical root-reference and label behavior,
  • standalone HashmapAugE empty 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, and Account fixtures.
  • 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 source and capture_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, and compat_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 source and capture_date,
  • fixture name,
  • input_stack_boc_hex,
  • root representation hash,
  • decoded stack entry shape,
  • captured_or_opt_in live-capture template metadata,
  • cross_sdk_vectors for 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.tl on 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.pendingShardBlocks
    • liteServer.nonfinal.getPendingShardBlocks
  • Synced liteServer.nonfinal.getValidatorGroups flag layout to upstream:
    • shard:mode.0?long (previous local snapshot used mode.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 typeWire meaning
int32-bit integer
long64-bit integer
int12816 bytes
int25632 bytes
byteslength-prefixed bytes with 4-byte alignment padding
stringsame binary family as bytes, interpreted as UTF-8 by convention
Boolboxed boolean constructors
vectorvector 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 N controls fields marked mode.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 for int256.
  • 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 -1 for masterchain and 0 for 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 -1 or 0..=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() and to_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) and to_non_bounceable(url_safe) override the bounceability flag for output,
  • to_test_only(url_safe) and to_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, or 0xd1,
  • 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, and addr_var models.
  • 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, mask 1..=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(), with deserialize_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_idx is set,
  • optional CRC32 trailers,
  • supported exotic cells with exact payload lengths and reference counts,
  • hex and standard base64 string wrappers through hex_to_boc() and base64_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_bytes or offset_bytes outside 1..=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() and boc_to_hex() wrap byte-level BoC conversion.
  • base64_to_boc() and boc_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 68ff65f3 and acc3a728.
  • 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:

  1. descriptors,
  2. top-up-padded data,
  3. reference depths,
  4. reference hashes,
  5. SHA-256.

Golden fixtures for ordinary cells are checked directly against this preimage, not against BoC bytes:

CellRepresentation preimageSHA-256 representation hash
Empty ordinary cell000096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7
One-bit ordinary cell containing 10001c07c6c1a965fd501d2938c2c0e06626bdaa3531357016e169070c9ef79c4c46bc0
Full-byte ordinary cell containing ab0002ab57c2a1a13baa2762109ed68be0c396f2303ce17e3dde7917d0e74b4072b1dbc7
32-bit ordinary cell containing 0000000f00080000000f57b520dbcb9d135863fc33963cde9f6db2ded1430d88056810a2c9434a3860f9
One-bit root containing 1, with refs to the empty and one-bit fixtures0201c00000000096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc77c6c1a965fd501d2938c2c0e06626bdaa3531357016e169070c9ef79c4c46bc0383598f93bde0afbe68b632ae75d5ffa6747df1284e2f4abb86cd2c5840514fe
One-bit middle containing 1, with ref to the empty fixture0101c0000096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79770d42f6d781e048a432b849b56d5329de4667b37cfb918429a23f90cb9884b
Full-byte root containing ab, with ref to the one-bit middle fixture0102ab00019770d42f6d781e048a432b849b56d5329de4667b37cfb918429a23f90cb9884b9f19f1fa052329a70f79c2adaef4e9f4e73eb88be389918473adc5f9a2801181
Full-byte root containing ab, with refs to the empty and one-bit middle fixtures0202ab0000000196a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79770d42f6d781e048a432b849b56d5329de4667b37cfb918429a23f90cb9884b6d112e22e9b4f47922b27cb78ffb8c4c3be4be304cdcb9ad24560e3104827eb6

The multi-level fixtures above keep ordinary-cell depth handling explicit:

  • the empty leaf has depth 0, descriptor 0000, and no child data,
  • the one-bit middle cell has depth 1, descriptor 0101, and child depth bytes 0000,
  • the full-byte chained root has depth 2, descriptor 0102, and child depth bytes 0001,
  • the two-reference root has depth 2, descriptor 0202, and child depth bytes 0000 0001 before 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:

TagKindPayloadReferencesDerived level
0x01Pruned branchtag, 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 bit0index of the most significant set mask bit plus one, therefore 1..=3
0x02Library referencetag plus a 32-byte library cell representation hash, exactly 264 bits00
0x03Merkle prooftag, one 32-byte proof hash, one two-byte big-endian proof depth, exactly 280 bits1max(ref.level - 1, 0)
0x04Merkle updatetag, old 32-byte proof hash, new 32-byte proof hash, old two-byte depth, new two-byte depth, exactly 552 bits2max(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 X
  • hme_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 X
  • hmn_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 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 = HashmapAugNode (n + 1) X Y
  • ahme_empty$0 extra:Y = HashmapAugE n X Y
  • ahme_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 n bits.
  • Key bits are stored MSB-first, and unused bits in the final byte are zero.
  • Empty dictionaries serialize as a single 0 bit and no reference.
  • Non-empty dictionaries serialize as 1 plus one root reference.
  • HashmapAug has no empty constructor; use HashmapAugE when an empty dictionary is valid.
  • HashmapAugE stores a top-level augmentation after the empty/root bit and optional root reference.
  • A leaf is valid only after exactly n key 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 Builder and &V,
  • the load callback receives &mut Slice and returns V.

The augmented APIs mirror this shape and add an augmentation callback:

  • Builder::store_hashmap_aug_with
  • Builder::store_hashmap_aug_e_with
  • Slice::load_hashmap_aug_with
  • Slice::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.rs
  • src/liteclient/client.rs get-method helpers

Missing Work

  • Fill checked captured_or_opt_in stack fixtures from successful live output.
  • Add cross_sdk_vectors with 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

TLTL-B
byte-level protocol serializationbit-level cell serialization
used for ADNL, LiteAPI, DHT, overlaysused for blocks, messages, accounts, state
constructor ids are 32-bit little-endian idsconstructors are fixed bit tags or implicit
vectors and bytes are common primitivesrefs, bits, Maybe, Either, HashmapE are common primitives
schema terms map to byte readers/writersschema 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:

  1. TlbSerialize writes a value into a mutable Builder.
  2. Builder::build produces a Cell.
  3. Slice reads a value from a cell for TlbDeserialize.
  4. 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] through StoreBits<N> and LoadBits<N>. Unsigned primitive fields u8, u16, u32, u64, and u128 infer their natural bit width when this attribute is omitted. Signed integer fields require an explicit bits attribute. Float primitive fields are rejected because the runtime does not define TL-B float semantics.
  • #[tlb(reference)] or #[tlb(ref)] stores a field as ^T using store_ref_tlb and load_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 X but 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: X follows 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 branch X.
  • 1: right branch Y.

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 a Hashmap n X root 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:

  • BitKey stores fixed-width key bits with canonical zeroed unused final-byte bits.
  • HashmapE<V> stores sorted BitKey entries and serializes them as canonical TL-B HashmapE.
  • Builder::store_hashmap_e_with and Slice::load_hashmap_e_with encode and decode values through callbacks, leaving concrete X codecs 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: canonical HashmapE and augmentation-preserving HashmapAug/HashmapAugE foundation.
  • src/tlb/mod.rs: public TL-B runtime with TlbSerialize, TlbDeserialize, TlbScheme, TlbError, fixed-tag helpers, exact decode checks, Maybe, Either, referenced value helpers, and canonical VarUInteger helpers.
  • 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:

  • Anycast with depth:(#<= 30) encoded in five bits and constrained to 1..=30.
  • MsgAddressInt constructors addr_std$10 and addr_var$11.
  • MsgAddressExt constructors addr_none$00 and addr_extern$01.
  • MsgAddress as an anonymous wrapper over MsgAddressInt or MsgAddressExt, without an additional tag.
  • Grams as canonical VarUInteger 16.
  • CurrencyCollection with HashmapE 32 (VarUInteger 32) extra currencies.
  • TickTock.
  • current upstream StateInit with fixed_prefix_length:(Maybe (## 5)), special:(Maybe TickTock), and code, data, library as Maybe ^Cell.
  • SimpleLib and current upstream StateInitWithLibs, using library:(HashmapE 256 SimpleLib).
  • CommonMsgInfo constructors int_msg_info$0, ext_in_msg_info$10, and ext_out_msg_info$11; the internal constructor uses current upstream extra_flags:(VarUInteger 16).
  • CommonMsgInfoRelaxed constructors int_msg_info$0 and ext_out_msg_info$11; there is no external-in relaxed constructor, and relaxed internal src is MsgAddress.
  • Message Any with explicit preservation of inline versus referenced StateInit and body cells.
  • MessageRelaxed Any with the same init and body placement rules and CommonMsgInfoRelaxed.
  • LibRef constructors libref_hash$0 and libref_ref$1.
  • The closed OutAction family: action_send_msg#0ec3c86d, action_set_code#ad4de08e, action_reserve_currency#36e6b809, and action_change_library#26fa1dd4.
  • OutList, using upstream linked-list constructors out_list_empty$_ = OutList 0 and out_list$_ {n:#} prev:^(OutList n) action:OutAction = OutList (n + 1). The Rust model exposes Vec<OutAction> in execution/schema order: the first vector item is deepest next to out_list_empty$_, and the last item is stored in the root node. Encoding and decoding enforce the 255-action TON limit.
  • AccStatusChange constructors acst_unchanged$0, acst_frozen$10, and acst_deleted$11.
  • StorageUsed as cells:(VarUInteger 7) and bits:(VarUInteger 7), with canonical variable-width integer payloads and a maximum payload length of six bytes per field.
  • TrActionPhase as the upstream implicit constructor tr_phase_action$_. It stores success, valid, no_funds, status_change, optional total_fwd_fees and total_action_fees, signed result_code, optional signed result_arg, four uint16 action counters, action_list_hash:bits256, and tot_msg_size:StorageUsed.
  • src/tlb/transaction.rs implements transaction-description phase models: TrStoragePhase, TrCreditPhase, ComputeSkipReason, TrComputePhase, TrBouncePhase, SplitMergeInfo, and the full TransactionDescr constructor family (trans_ord$0000, trans_storage$0001, trans_tick_tock$001, trans_split_prepare$0100, trans_split_install$0101, trans_merge_prepare$0110, and trans_merge_install$0111).
  • TransactionDescr.action:(Maybe ^TrActionPhase) maps to Option<TrActionPhase> but preserves the referenced child-cell placement and exact child decode semantics.
  • Account state models from upstream block.tlb: StorageExtraInfo with three-bit tags storage_extra_none$000 and storage_extra_info$001, StorageInfo with upstream field order used, storage_extra, last_paid, due_payment, AccountState, AccountStorage, AccountStatus, Account, and ShardAccount.
  • DepthBalanceInfo as depth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection, enforcing the five-bit 0..=30 split-depth bound.
  • ShardAccounts as HashmapAugE 256 ShardAccount DepthBalanceInfo.
  • Concrete HashUpdateAccount for update_hashes#72 old_hash:bits256 new_hash:bits256 = HASH_UPDATE Account.
  • Top-level transaction$0111, including the child reference that stores in_msg:(Maybe ^(Message Any)) and out_msgs:(HashmapE 15 ^(Message Any)). Outbound message dictionary keys are validated as 15-bit keys, and inbound/outbound messages are exact referenced Message Any payloads.
  • Split/merge install prepare_transaction:^Transaction fields map to Box<Transaction> to keep Rust layout finite while preserving exact referenced decoding.
  • AccountBlock as acc_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 exact Transaction payloads.
  • ShardAccountBlocks as HashmapAugE 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.md or in the model-specific documentation added with each schema family.

Missing Work

  • Expand tlb-derive with 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.