Guidance for coding agents working in github.com/xssnick/tonutils-go.
This document is intentionally repo-specific. Follow the existing code and package boundaries here before applying generic Go advice.
tonutils-go is split by protocol layer and domain:
address: TON address parsing, formatting, flags, bit helpers.tl: TL schema registration, loader/serialization primitives.tlb: TLB loaders/serializers built on top of cells and struct tags.tvm/cell: core cell, slice, builder, dict, proof, BoC primitives.tvm,tvm/vm,tvm/op/*,tvm/tuple,tvm/vmerr: TVM emulator and opcode implementation.adnl,adnl/dht,adnl/overlay,adnl/rldp: transport and overlay protocols.liteclient: liteserver connection pool, balancing, sticky contexts, config loading.ton: high-level blockchain API on top ofliteclient, proofs, block/account/transaction access.ton/wallet,ton/nft,ton/jetton,ton/dns: domain clients built onton.toncenter: HTTP-based client as a separate access path.example: runnable user-facing examples, kept small and practical.
When adding code, keep it at the lowest layer that owns the responsibility. Do not pull high-level TON client concerns into tl, tlb, tvm/cell, or transport packages.
-
Prefer linear APIs.
-
Do not use tri-state returns like
(value, ok, err)in storage and domain code. -
When data may be absent, use
(value, error)and return a dedicated not-found error such asErrNotFound. -
If
err == nil, the returned value must already be valid and ready to use. -
Do not design APIs where
err == nilbut the caller still has to inspectokor check the value fornil. -
Keep boolean returns only when they represent a real property or business flag, not presence or absence of data.
-
Do not add wrapper APIs, aliases, or renames that do not simplify the code.
-
Avoid patterns like:
type X = Ytype Options = ImplOptionsOpen -> OpenImpl
-
Name public types and functions correctly once and use them directly.
-
Do not introduce internal conversion helpers like
fromXandtoXunless there is a real format boundary, protocol boundary, or external API boundary. -
Inside the project, prefer using the actual types directly.
-
Add empty lines between logical blocks inside functions.
-
Do not make excessive nil checks.
-
Check only what can really be nil logically. Do not check obvious input values for nil, which are intended to have a value.
-
Do not check obvious value inputs for nil.
-
Do not add defensive nil/self-guard checks for required inputs, even when they are pointers. If a pointer is part of an internal invariant and the caller must provide it, use it directly.
-
Required fields should not get fallback encoding or "just in case" nil handling.
-
Treat low-level code as potentially hot. Add runtime checks only when they protect a real boundary, enforce an invariant that can be violated in normal use, or prevent a meaningful safety issue. Avoid defensive checks that duplicate earlier validation or guard states that cannot happen through the package API.
-
Keep code simple and optimized.
-
Add abstractions only when they remove real duplication or represent a real boundary.
-
Prefer straightforward Go-style code.
-
Keep the control flow linear and easy to read.
-
Avoid unnecessary indirection.
-
Beware of fallbacks, each fallback must have a strong reason why it is necessary and commented.
-
Do not leave useless short wrapper methods, less calls are better unless it is big piece of code which is used in many places
-
Do not handle both
Tand*Tin type switches "just in case". Pick the concrete representation owned by that layer, convert once at a real boundary, and pass that concrete type through the rest of the code. Existing protocol-boundary compatibility helpers are exceptions, not patterns to copy.
- Any type used in
tl.Register(...)must be public and named with a capital letter.
- Keep test-only constants, hooks, and helpers in
_test.gofiles. - Do not leave test-only code in normal production files.
- Treat
tvm/cell,tl, andtlbas foundational packages. They should stay reusable and mostly independent from higher-level TON client logic. - Keep transport and business logic separate.
liteclienthandles connectivity, node selection, stickiness, retries/timeouts at transport level;tonhandles proof-aware blockchain semantics on top. - Put domain-specific smart-contract clients under
ton/*packages, not inliteclientor low-level protocol packages. - Keep compatibility wrappers when API evolution is cheap. This repo already preserves deprecated aliases, wrapper methods, and compatibility files instead of breaking callers aggressively.
- File naming usually mirrors protocol version or variant:
client-v2.go,client-v3.go,v5r1.go,overlay-adnl.go,item-editable.go. Follow that pattern instead of inventing abstract names.
- Use idiomatic Go with
gofmt. Do not mass-reformat unrelated files just to normalize import groups or spacing. - Keep comments and docs in English. Existing README, examples, and code comments are English-first.
- Prefer small exported constructors and wrappers with familiar names:
NewXFromXWithXMustX
- Safe methods usually return
error; panic-based variants are explicitly namedMust*. - Panic is acceptable for programmer errors or impossible internal states, not normal runtime failures.
- Public APIs favor straightforward structs and functional options over deep builder frameworks.
- Errors are usually wrapped with precise context using
fmt.Errorf("failed to ...: %w", err). - Prefer early returns and explicit branching over clever control flow.
- Short local names are normal when context is obvious:
ctx,addr,b,res,api,cfg,st. - Keep wire/protocol registration near the owning package.
init()is used here for TL registration and similar setup, so adding another localizedinit()is acceptable when it matches existing patterns. - Preserve concurrency guarantees. Networking code relies on
context,sync,sync/atomic, and careful request bookkeeping; avoid changes that weaken thread safety.
- If you add a convenience API, keep the lower-level path available too.
- If behavior is configurable, prefer wrapper/option style already used in the repo:
WithRetryWithTimeoutWithAPIWithSigner
- If a type has both ergonomic and strict forms, mirror the existing duality:
- safe method returning
error Must*helper panicking on misuse
- safe method returning
- For public-facing TON clients, preserve the current feel: practical, direct, and easy to use from examples.
- Use the standard
testingpackage only. This repo does not rely ontestify-style assertion libraries. - Prefer direct assertions with
t.Fatal/t.Fatalf/t.Errorf. - Use
t.Runfor grouped cases and table-driven tests where it improves clarity. - Add regression tests close to the touched package, especially for:
- serialization/deserialization
- proof validation
- dictionary logic
- stack/opcode behavior
- network failover/sticky behavior
- Keep unit tests deterministic when possible.
- Integration tests exist and often touch live TON infrastructure. Do not silently turn ordinary unit coverage into network-dependent tests.
example/programs are part of the public face of the library. Keep them concise, runnable, and focused on one task.- When a change affects user workflow, update or add an example if the package is example-driven.
- Prefer practical comments that explain protocol nuance, invariants, or surprising behavior. Avoid boilerplate commentary.
- Make narrow changes that respect package ownership.
- Avoid introducing upward dependencies from low-level packages into high-level ones.
- Preserve backward compatibility where reasonable; this repo often keeps deprecated entry points instead of removing them immediately.
- When changing serialization, proof, or protocol code, add round-trip or regression tests in the same area.
- When changing high-level APIs in
ton/*, verify the surrounding wrappers and examples still make sense.
Typical checks for this repo:
go build -v ./...go test -v -failfast $(go list ./... | grep -v /example/)- targeted package tests such as
go test ./ton/...orgo test ./tvm/cell
CI currently builds the whole repo and runs tests excluding example/ packages, so mirror that unless your change is intentionally example-only.
- Large cross-package refactors without a protocol or API reason.
- New abstractions that hide simple request/response flows.
- Pulling high-level TON logic into foundational packages.
- Replacing explicit error handling with magical helpers.
- Introducing third-party test/assert dependencies for ordinary package tests.