TON SDK Library in pure Golang for interacting with The Open Network ecosystem using native protocols, such as ADNL, RLDP and etc.
TON SDK Library in pure Golang for interacting with The Open Network ecosystem using native protocols, such as ADNL, RLDP and etc.
[![Based on TON][ton-svg]][ton] [![Telegram Channel][tgc-svg]][tg-channel]
Golang library for interacting with TON blockchain.
This library is native golang implementation of ADNL and lite protocol. It works as connection pool and can be connected to multiple lite servers in the same time, balancing is done on lib side.
It is concurrent safe and can be used from multiple goroutines under high workloads.
All main TON protocols are implemented: ADNL, DHT, RLDP, Overlays, etc.
If you love this library and want to support its development you can donate any amount of coins to this ton address ☺️
EQBx6tZZWa2Tbv6BvgcvegoOQxkRrVaBVwBOoW85nbP37_Go
You can find many usage examples in example directory
You could also join our Telegram channel and group, feel free ask any questions :)
You can use liteservers from TON configs:
https://ton-blockchain.github.io/global.config.jsonhttps://ton-blockchain.github.io/testnet-global.config.jsonfrom liteservers section, you need to convert int to ip and take port and key.
Or you can run your own full node, see TON docs.
You can connect like that:
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 timeoutSince client implements connection pool, it can be the chance that some block is not yet applied on one of the nodes, so it can lead to errors.
If you want to bound all requests of operation to single node, use
ctx := client.StickyContext(context.Background())And pass this context to methods.
You can use existing wallet or generate new one using wallet.NewSeed(), wallet will be initialized by the first message sent from it. This library will deploy and initialize wallet contract if it is not initialized yet.
You can also send any message to any contract using w.Send method, it accepts tlb.InternalMessage structure, you can dive into w.Transfer implementation and see how it works.
Example of basic usage:
…You can find full working example at example/wallet/main.go
Here is the description of features which allow us to trigger contract's methods
Let's imagine that we have contract with method
cell mult(int a, int b) method_id {
return begin_cell().store_uint(a * b, 64).end_cell();
}We can trigger it and get result this way:
…Using messages, you can interact with contracts to modify state. For example, it can be used to interact with wallet and send transactions to others.
You can send message to contract like that:
// message body
data := cell.BeginCell().
MustStoreUInt(777, 64).
EndCell()
// contract address
addr := address.MustParseAddr("kQBkh8dcas3_OB0uyFEDdVBBSpAWNEgdQ66OYF76N4cDXAFQ")
msg := &tlb.ExternalMessage{
DstAddr: addr,
Body: data,
}
// send external message, processing fees will be taken from contract
if err := api.SendExternalMessage(context.Background(), msg); err != nil {
panic(err)
}You can find full working example at example/external-message/main.go
Contracts can be deployed using wallet's method DeployContract,
you should pass 3 cells there: contract code, contract initial data, message body.
You can find example here
You can get full account information including balance, stored data and even code using GetAccount method. You can also get account's list of transactions with all details.
Example:
…You can find extended working example at example/account-state/main.go
You can load cells directly into Go structs using TLB tags and serialize them back using the same struct definition.
For most application-level code it is recommended to use TLB structs instead of manual raw cell assembly.
Raw cell.BeginCell() / Load* flows are still useful for low-level work, debugging, uncommon layouts and dynamic structures,
but if payload layout is known in advance, tlb.Parse (or tlb.LoadFromCell when you already have a *cell.Slice) and tlb.ToCell are usually simpler, shorter and less error-prone.
Supported tags:
tlb:"-" - skip field completelytlb:"#deadbeef" - exact hex magic / constructor prefix for _ tlb.Magictlb:"$1101" - exact binary magic / constructor prefix for _ tlb.Magictlb:"## N" - fixed-width integer in N bits. For small widths regular Go integer types can be used, for large widths use *big.Inttlb:"bits N" - exactly N bits into/from []bytetlb:"bool" - one-bit booleantlb:"addr" - TON address, usually *address.Addresstlb:"." - nested struct stored in the current celltlb:"^" - nested value stored in a referenced cell. If the field type is *cell.Cell, raw referenced cell is returned without nested parsingtlb:"maybe " - one presence bit followed by nested value only when present. Common examples: maybe ^, maybe .tlb:"either X Y" - one selector bit: 0 means parse/store as X, 1 means parse/store as Y. During serialization it tries X first, then Ytlb:"either leave {bits},{refs} X Y" - same as either, but chosen branch must still leave the requested number of free bits and refs in the current builder after serializationtlb:"dict N" - HashmapE dictionary with N-bit keystlb:"dict inline N" - inline Hashmap dictionary with N-bit keystlb:"?FieldName " - conditional field. It is loaded/stored only when previously declared boolean field FieldName is truetlb:"[TypeA,TypeB,...]" - interface/union field resolved by registered magic. Can be combined with other tags, for example ^ [ShardStateUnsplit,ShardStateSplit]Tags can be combined. Typical examples:
tlb:"maybe ^" - optional referencetlb:"maybe ." - optional inline nested structtlb:"?HasValue maybe ^" - conditional optional referencetlb:"^ [TypeA,TypeB]" - referenced union selected by magictlb:"dict inline 256" - inline dictionary with 256-bit keysCustom parsing/serialization hooks are also supported:
tlb.Unmarshaler, its LoadFromCell method is used by tlb.Parse and tlb.LoadFromCelltlb.Marshaller, ToCell calls its custom serializerFor exact behavior and edge cases, see comments on tlb.Parse, tlb.LoadFromCell and tlb.ToCell.
Example of parsing:
…Example of parsing your own payload:
…You can serialize the same structs back into cells using tlb.ToCell.
type ExamplePayload struct {
_ tlb.Magic `tlb:"#a1b2c3d4"`
QueryID uint64 `tlb:"## 64"`
Flags uint8 `tlb:"## 8"`
Body *cell.Cell `tlb:"^"`
}
payload := ExamplePayload{
QueryID: 123,
Flags: 7,
Body: cell.BeginCell().
MustStoreUInt(0xCAFE, 16).
EndCell(),
}
payloadCell, err := tlb.ToCell(payload)
if err != nil {
panic(err)
}
fmt.Println(payloadCell.Dump())Roundtrip example:
payloadCell, err := tlb.ToCell(payload)
if err != nil {
panic(err)
}
var decoded ExamplePayload
if err = tlb.Parse(&decoded, payloadCell); err != nil {
panic(err)
}
fmt.Println(decoded.QueryID) // 123See also build NFT mint message for a real-world serializer example.
You can mint, transfer, and get NFT information using nft.ItemClient and nft.CollectionClient, like that:
…You can find full examples at example/nft-info/main.go and example/nft-mint/main.go
You can get information about jetton, get jetton wallet and its balance, transfer jettons, and burn them using jetton.Client like that:
…You can find full example, which also contains transfer, at example/jettons/main.go
You can get information about domains, get connected wallet and any other records, transfer domain, edit and do any other nft compatible operations using dns.Client like that:
root, err := dns.GetRootContractAddr(context.Background(), api)
if err != nil {
panic(err)
}
resolver := dns.NewDNSClient(api, root)
domain, err := resolver.Resolve(context.Background(), "alice.ton")
if err != nil {
panic(err)
}
log.Println("domain points to wallet address:", domain.GetWalletRecord())You can find full example at example/dns/main.go
You could get any record of the domain using domain.GetRecord("record name") method, it will return cell that will contain data structure depends on record type.
Alternatively you can also use domain.GetWalletRecord() and domain.GetSiteRecord(), they will return already parsed data, like address for wallet, or slice of bytes with type for site.
To set domain records you can use domain.BuildSetRecordPayload("record name", dataCell), or predefined BuildSetSiteRecordPayload(adnlAddress), BuildSetWalletRecordPayload(walletAddress).
It will generate body cell that you need to send to domain contract from the owner's wallet.
Example:
…Work with cells is very similar to FunC cells:
…Load from cell:
slice, err := someCell.BeginParse()
if err != nil {
panic(err)
}
wc := slice.MustLoadUInt(8)
data := slice.MustLoadSlice(256)There are 2 types of methods Must and regular. The difference is that in case of error, Must will
No open issues yet, or sync has not completed.