tonutils-go: a pure Go SDK for the TON blockchain
TON SDK Library in pure Golang for interacting with The Open Network ecosystem using native protocols, such as ADNL, RLDP and etc.
At a glance
- What is it?
- tonutils-go implements TON's own network protocols in Go rather than wrapping a client binary, so a program can talk to lite servers directly over ADNL, DHT, RLDP, and overlays, and hold many of them at once. On top of that it covers wallets, NFTs, jettons, DNS records, contract get methods, cell serialisation, and Merkle proofs, with a connection pool that does the failover.
- Who is it for?
- tonutils-go fits a Go team that needs to read the TON chain, accept payments, mint an NFT, or run a lite server themselves, and that would rather implement the protocols than depend on a wrapper around a command line client. It does not fit a project that wants the simplest possible way to move a token, since the library expects you to know the contract interfaces, and it does not fit a build that cannot use a forked QUIC stack.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The protocols, implemented rather than wrapped
tonutils-go is a Go library for interacting with the TON blockchain, and the distinguishing claim is in the second sentence of the description: it is a native Go implementation of ADNL and the lite protocol, not a wrapper around something written in another language.
ADNL is the transport that carries everything else. The library implements the main TON protocols, and the list is ADNL, DHT, RLDP, and overlays. The DHT is the distributed hash table that lite servers use to find each other, RLDP is the reliable protocol for the transfers that carry the actual data, and overlays are the peer to peer layer that rides on top.
The architecture that follows from that is the connection pool. The library can be connected to multiple lite servers at the same time, and the balancing between them is done on the library side rather than in the calling code. It is also safe for concurrent use, and the documentation says it can be used from multiple goroutines under high workloads, which is the property that decides whether a library goes into a service or stays a script.
The alternative to all of this would be to run a node and talk to it over a local API. The library's pitch is that a Go program that already speaks its own wire protocols does not need a second process, does not need a node in Java, and does not need a JSON bridge between the two.
The project is MIT licensed, the releases move regularly, with a version on 2026-10-14, another on 2026-10-08, and a minor in July 2026, and the last push was on the same day as the latest release. There is a Telegram channel and a group for questions, and the repository holds an agent instruction file alongside the code, which is a newer convention that keeps automated contributions in the project's shape.
The coverage badge is at 81.3 percent, which for a library that talks to a live network is a reasonable number and an honest one.
Connecting, and the sticky context
The connection example is short, and the last line of it is where the design is.
client := liteclient.NewConnectionPool()
configUrl := "https://ton-blockchain.github.io/testnet-global.config.json"
err := client.AddConnectionsFromConfigUrl(context.Background(), configUrl)
if err != nil {
panic(err)
}
api := ton.NewAPIClient(client).WithRetryTimeout(0, 5*time.Second) // automatic retries with failover and a per-attempt timeoutThe pool is created empty, the servers are added from a configuration URL, and the API client is built on top of the pool with a retry timeout of zero and a per attempt timeout of five seconds. That single call is where the resilience lives: requests that fail are retried, and the retry goes to a different server.
The servers come from the public TON configurations, and there are two URLs, one for mainnet and one for testnet. The configuration is a JSON document whose liteservers section lists the servers, and the documentation notes that you need to convert the address from an integer to an IP and take the port and the key. The alternative is to run your own full node.
Now the problem the library acknowledges. Because the client is a pool, a block might not have been applied on one of the nodes yet, which leads to errors that are not the fault of the caller. The answer is a sticky context.
ctx := client.StickyContext(context.Background())You create that context and pass it to the methods, and every request of that operation is bound to a single node. The operation then sees a consistent view of the chain instead of a view that moves between servers underneath it. That is the kind of subtlety that only appears in production, and the fact that the library exposes the fix rather than hiding the behaviour is a point in its favour.
The reconnect policy is also configurable, and there is a section of the documentation for it, which matters for a long running service that has to survive a node going away.
Wallets, and the contract that gets deployed for you
The wallet section covers the everyday case, and there are two things to know about how it works.
You can use an existing wallet or generate a new one with the seed call, and the seed is a list of words. The wallet is initialised by the first message sent from it, and the library will deploy and initialise the wallet contract if it is not initialised yet. So the flow is: derive the addresses from the words, send the first message, and the contract appears on chain as part of that.
The example shows the sequence.
words := strings.Split("birth pattern ...", " ")
w, err := wallet.FromSeedWithOptions(api, words, wallet.V3)
if err != nil {
panic(err)
}
block, err := api.CurrentMasterchainInfo(context.Background())
if err != nil {
panic(err)
}
balance, err := w.GetBalance(context.Background(), block)
if err != nil {
panic(err)
}The version of the wallet contract is an option, and the third version is what the example uses. The balance is read against a block, and that is the pattern to notice: the current masterchain information is fetched first, and the balance call takes that block as an argument. It is a deliberate design, because a balance read at a specific block is a value that can be reasoned about, and one read against whatever the node happens to have is not.
The transfer follows the same shape.
addr := address.MustParseAddr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N")
err = w.Transfer(context.Background(), addr, tlb.MustFromTON("0.003"), "Hey bro, happy birthday!")The amount is given as a string with the unit, which means the library decides the decimals, and the comment field is part of the call.
Underneath the convenience method there is a general one. You can send any message to any contract with the send method, which takes an internal message structure, and the documentation points you at the transfer implementation as the place to read how the pieces fit together. The examples directory also has a version for building a transaction and sending it from somewhere else, which is the cold wallet pattern.
Contracts, get methods, and external messages
The contract support is the part that makes this a library rather than a wallet, because it lets you call an arbitrary contract.
The get method path starts with a contract that has a method like multiplying two integers and returning a cell, and the call is a single function.
res, err := api.RunGetMethod(context.Background(), block, addr, "mult", 7, 8)
if err != nil {
// if contract exit code != 0 it will be treated as an error too
panic(err)
}
slice, err := res.MustCell(0).BeginParse()
if err != nil {
panic(err)
}
val, err := slice.LoadUInt(64)Three details are worth pulling out. The method is called by name with the arguments as separate parameters, and the contract is free to declare them however it likes. A non zero exit code from the contract is treated as an error, so a contract that fails does not look like a contract that returned nothing. And the result is a slice of cells, so the first cell is unwrapped and parsed, and the value is read out of it with a width.
The write path is the external message.
data := cell.BeginCell().
MustStoreUInt(777, 64).
EndCell()
addr := address.MustParseAddr("kQBkh8dcas3_OB0uyFEDdVBBSpAWNEgdQ66OYF76N4cDXAFQ")
msg := &tlb.ExternalMessage{
DstAddr: addr,
Body: data,
}The body is built cell by cell, and the message carries a destination and a body. The documentation notes that the processing fees for such a message are taken from the contract rather than from the sender, which is the usual arrangement for a contract to contract call.
The examples cover the related ground as well: sending a message to a contract, deploying a contract, and emulating a transaction input, which is what you would use to check what a transaction would do before you send it.
The layers below: ADNL, DHT, RLDP, overlays
The repository layout is the protocol stack, and it is worth reading as a map of how the pieces relate.
There is a directory for the ADNL transport, one for the lite client that speaks to the servers, one for the high level API, one for the cell and TL-B serialisation, one for the type language bindings, one for address handling, one for the TVM, one for the CRC implementation, one for the Toncenter indexer integration, and one for internal helpers.
Several of the examples exist to test a single layer rather than to demonstrate a use case. There is one for the ADNL transport over UDP, one for the DHT with a server of its own, one for RLDP in a point to point test, one that resolves an ADNL address, one for a QUIC test, and one for a cell proof.
The dependency list explains the rest. The crypto is the Cloudflare library and the edwards25519 implementation, the compression is lz4, the cache for lite server data is a ristretto cache, and the QUIC stack is a fork maintained by the same author, which is the clearest signal in the list about how seriously this project takes being at the front of the protocol stack rather than behind a released library.
The Go requirement is 1.26, and the module is deliberately small in the sense that most of the heavy lifting comes from those few dependencies rather than from a large tree of transitive ones.
The example directory, and what it tells you to read
The examples are numbered by topic rather than by example, and the directory names are a table of contents for the library.
There is a simple example, a detailed one, and a set of account examples: reading account state, listing transactions, and accepting payments, which is the subscription of a wallet to incoming transfers. There is a high load wallet example, a transfer to a contract example, a send extra currency example, and a jetton transfer example.
The token side has its own group, with an NFT info example, an NFT mint example, and a deploy NFT collection example, alongside jettons examples for details and transfers. DNS has an example, and there is a block scanner, an external message example, a transaction input emulation example, and a URL to QR transfer example.
Then come the ones that show how far down you can go: a DHT server, a point to point RLDP test, an ADNL address resolution example, a QUIC test, a cell proof example, and a non final transaction example.
The documentation also has a page for the features that are not implemented yet, which is a detail worth thanking a maintainer for. A library that implements half of a protocol stack will otherwise leave you guessing which half, and a short list of what is missing is the cheapest documentation in the whole repository.
The one thing to know about the wallet examples is that they use two address forms, the user friendly form in the wallet example and the raw form in the contract example, and the library is strict about which is which. The addresses in the documentation are real ones from the examples, so they can be read against a chain, which makes them a better test than a placeholder would be.
Editorial conclusion
tonutils-go fits a Go team that needs to read the TON chain, accept payments, mint an NFT, or run a lite server themselves, and that would rather implement the protocols than depend on a wrapper around a command line client. It does not fit a project that wants the simplest possible way to move a token, since the library expects you to know the contract interfaces, and it does not fit a build that cannot use a forked QUIC stack. Verify first that the lite server configuration you point at is one the library can parse, that your node has applied the block you are reading before you trust the result, and that the contract addresses you call get methods on are the user friendly form the library expects, because the examples use both forms and they are not interchangeable.
Frequently asked questions
What is tonutils-go?
It is a Go library for working with the TON blockchain that implements the network protocols natively rather than wrapping a client binary. The ADNL transport, the DHT, RLDP, and overlays are all implemented, and the library can hold connections to many lite servers at once with the balancing done on its side.
How do I connect tonutils-go to a TON node?
Create a connection pool, add the servers from a public configuration URL for mainnet or testnet, and build an API client on top of it with a retry timeout for automatic retries with failover and a per attempt timeout. You can also run your own full node and point the pool at it instead.
What is the sticky context for in tonutils-go?
Because the client is a pool, a block may not yet be applied on one of the nodes, which produces errors that are not the caller's fault. Creating a sticky context and passing it to the methods binds every request of that operation to a single node, so the operation sees a consistent view of the chain.
Can I call a method on a TON contract with tonutils-go?
Yes. You fetch the current masterchain information, then call the method by name with its arguments, and a non zero exit code from the contract is treated as an error. The result comes back as a slice of cells, which you unwrap and parse, and a second path lets you send an external message to change a contract's state.
Does tonutils-go deploy a wallet contract for me?
It does. You can use an existing wallet or generate one from a seed, and the wallet is initialised by the first message sent from it, with the library deploying and initialising the wallet contract if it is not initialised yet. The wallet version, the third in the example, is passed as an option.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/xssnick-tonutils-go)Was this useful?