-
Notifications
You must be signed in to change notification settings - Fork 23
TON Kotlin documentation
TON-Kotlin is a cross-platform Software Development Kit for The Open Network (TON) written from scratch in Kotlin. It aims to provide developers with an easy way to interact with the network, the blockchain and its services while fully embracing concepts outlined in TON whitepaper and supporting documentation, namely TON Blockchain and TON Virtual Machine.
Abstract Datagram Networking Layer Protocol implementation
More about ADNL: TON Whitepaper, section 3.1
Contains all structures from TL scheme:
https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/ton_api.tl
TON Virtual Machine operates directly on individual binary digits (bits): 0 (false) and 1 (true). A finite string of
such values is called a bitstring, and is represented by the BitString
interface in ton-kotlin. This interface is
heavily used by more high-level abstractions, such as cells and bags-of-cells.
BitString(true, false, true) // Create from boolean values
BitString.binary("101") // Create from string representation of zeroes and ones
Bitstrings also have standard hex string representation. It correctly handles cases where number of bits is not
divisible by 8, unlike for example ton-crypto's hex()
. In TON's documentation they are denoted by x
prefix or being wrapped in x{
and }
.
BitString("B_").joinToStringBits() // "101"
BitString.binary("101") // "B_"
TVM sets maximum number of bits in one cell to 1023. Because of that bitstrings are also limited as it would be useless to have bitstrings bigger than what can fit in a cell.
BitString(List(1024) { false }) // throws BitStringOverflowException
It is important to note that bitstrings, as well as many other primitives are immutable, meaning that in order to modify
its value it is necessary to first convert it to a MutableBitString
, for example using the
BitString.toMutableBitString()
method.
BitString("B_")
.toMutableBitString()
.apply { // this: MutableBitString
add(false) // Append to the end
addAll(listOf(false, true)) // Same but for multiple values
set(1, true) // Set second element to 1
} // "E6_"
Avoid conversion to byte arrays wherever possible, as forgetting to specify correct number of bits may result in extra zeroes appended to the end of the new bitstring. Conversion to and from byte arrays is not portable, serialization to Bags-of-Cells should be used instead. See an entry on ton-boc.
val a = BitString("B_") // "B_" - original
val b = BitString(a.toByteArray()) // "A0" - size was not specified, extra zeroes appended to the end
require(a == b) // Fail, they're different bitstrings
val c = BitString(a.toByteArray(), a.size) // "B_" - only `a.size` bits were taken, rest was ignored
require(a == c) // Success
Contains all structures from TL-B scheme:
https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb
In order to store and transfer cells in a portable way, TON defines a collection of cells as bag-of-cells (BoC),
alongside an algorithm to serialize (write) and deserialize (read) it to/from a series of bytes. In ton-kotlin this
functionality is provided by the BagOfCells
interface.
BagOfCells(hex("b5ee9c72010101010003000001b0")).roots.first() // "x{B_}" - Loaded from byte array
BagOfCells(Cell("B_")).toByteArray() // b5ee9c72010101010003000001b0 - Serialized to byte array
TON Blockchain and Virtual Machine represent all data as so-called cells, where each cell consists of up to 1023 bits
of data and can contain up to 4 references to other cells, circular references are not allowed. In ton-kotlin, they are
implemented by the Cell
interface.
Cell(BitString("B_")) // Create from a bitstring
Cell("B_") // Same as the above
Cell("A_", Cell("B_")) // Cell containing a reference to another cell
CellSlice
is used to read data from cells as bits, bytes, integers, other cells, and data structures with TL-B
schema (see ton-tlb). Its interface is similar to FunC's
slice primitives.
val cs = Cell("2432B6363796102BB7B93632108000522AFFFFFFFFFFEB1E", Cell("B_"))
.beginParse() // Create cell slice instance
cs.preloadBit() // false - peek next bit without moving the cursor forward
cs.skipBits(1) // Skip one bit
cs.loadBits(13 * 8) // Load 13 bytes as a bitstring
.toByteArray() // Convert to bytes
.decodeToString() // "Hello, World!" - decode to string
cs.loadUInt(32) // 42069 - Load 32-bit unsigned integer
cs.loadInt(53) // -1337 - Load 53-bit signed integer
cs.loadBits(2) // "9_" - Load 2 bits
cs.loadRef() // "x{B_}" - Load cell reference
cs.endParse() // Finish parsing. This ensures that the entire slice was consumed
It is strongly advised to call endParse()
when done parsing, it will throw an exception in case some data left
unprocessed, which is generally a sign of incorrect/incomplete parsing algorithm. Use parse { }
when possible.
Cell("2432B6363796102BB7B93632108000522AFFFFFFFFFFEB1E", Cell("B_"))
.parse { // this: CellSlice
loadBit()
loadBits(13 * 8).toByteArray().decodeToString()
loadUInt(32)
loadInt(53)
loadBits(2)
loadRef()
} // Automatically calls endParse() at the end
CellBuilder
is used to construct new cells by storing bits, bytes, integers, cell slices, other cells, and data
structures with TL-B schema (see ton-tlb). Its interface is similar to
FunC's builder primitives.
CellBuilder.beginCell() // Create builder instance
.storeBit(false) // Store a single 0 bit
.storeBytes("Hello, World!".toByteArray()) // Store string's byte representation
.storeUInt(42069, 32) // Store 32-bit unsigned integer
.storeInt(-1337, 53) // Store 53-bit signed integer
.storeSlice(Cell("A_").beginParse()) // Store cell slice
.storeRef(Cell("B_")) // Store a reference to another cell
.endCell() // Assemble a cell
Slick kotlin-style builder interface is also available, it is functionally the same but calls beginCell()
and endCell()
automatically. Note how storeRef { }
creates a new builder to construct referenced cell. This makes it
easy to build complex nested cells.
CellBuilder.createCell { // this: CellBuilder
storeBit(false)
storeBytes("Hello, World!".toByteArray())
storeUInt(42069, 32)
storeInt(-1337, 53)
storeSlice(Cell("A_").beginParse())
storeRef { // this: CellBuilder - a new instance of a builder is created to construct this nested cell
storeBits(true, false, true)
}
}
The Open Network extensively relies on strong cryptography algorithms and data conversion methods. Ton-crypto module is aimed to put all the interfaces to these algorithms in one convenient package.
Note: no safety-critical algorithms are implemented as a part of this module, it simply serves as an interface to secure implementations provided by well-trusted libraries, such as Bouncy Castle.
These are basic helper functions for easy conversion of binary data (ByteArray
) into strings and back. Some examples
of their common uses in TON:
- base64 as defined in RFC 4648 §4
- addresses, such as
EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N
- liteservers' public keys, for example
VrBrkkxiB/EDNju0FpxMavfESFvtSk1uqZeNNhMT4rs=
- liteservers' public keys, for example
- addresses, such as
- base64url, as defined in RFC 4648 §5
- addresses, used more often than the plain base64 due to it being url-safe
- hex, a simple conversion of bytes to their hexadecimal string representation
- addresses, in their raw form:
-1:dd24c4a1f2b88f8b7053513b5cc6c5a31bc44b2a72dcb4d8c0338af0f0d37ec5
- bags-of-cells, it is common to store compiled smart-contract code as a hex string, such
as
B5EE9C72410101010044...
- addresses, in their raw form:
Note:
hex()
is only suitable for conversion of whole bytes. Conversion of arbitrary long bit sequences is done using ton-bitstring and is discussed further in this document.
Cyclic Redundancy Check algorithms used in TON blockchain to ensure validity of potentially corrupted data being read from file system or network. One of their prominent uses is in user-friendly addresses (base64/base64url-encoded), which prevents users from losing their funds in case of a typo.
A cryptographically-secure source of randomness used in various places, for example in generation of new wallet private keys. It is advised to use this object over those provided by the language as they might default to unsafe algorithms.
This module contains interfaces for working with dictionaries that are defined in hashmap.tlb .
Since ton-kotlin closely follows original schema, HashMapE
is actually represented as a tree rather than being
flattened to language-defined Map
on parsing. For ease of use HashMapE.toMap()
method is provided, it walks through
each node and assembles simpler key-value representation.
Implementing interfaces outlined in ton-lite-api, this module provides a way to interact with lite-servers over ADNL protocol, with many convenience functions added for most common scenarios.
Example usage:
val config = Json.decodeFromString<LiteClientConfigGlobal>(
URL("https://ton.org/global-config.json").readText()
)
val liteClient = LiteClient(config)
// Get last masterchain block ID
val lastBlockId = liteClient.getLastBlockId()
// Get block by ID until it appears in the database on the light server
val block: Block
while (true) {
block = liteClient.getBlock(lastBlockId) ?: continue
break
}
// Print block seq_no from TL-B scheme:
// https://github.com/ton-blockchain/ton/blob/36fbe3a2acda90fb92826b114e71ac08a8e53438/crypto/block/block.tlb#L442
println(block.info.seq_no)
Contains all structures from TL scheme:
https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/lite_api.tl
Generate random mnemonic:
val mnemonic = Mnemonic.generate()
println(mnemonic)
Generate random mnemonic with password:
val mnemonic = Mnemonic.generate(password = "password")
println(mnemonic)
Mnemonic to seed:
val seed = Mnemonic.toSeed(arrayOf("kangaroo", "hen", "toddler", "resist")
assertEquals(
hex("a356fc9b35cb9b463adf65b2414bbebcec1d0d0d99fc4fc14e259395c128022d"),
seed
)
Mnemonic to seed with password:
val seed = Mnemonic.toSeed(arrayOf("deal", "wrap", "runway", "possible"), "password")
assertEquals(
hex("3078a0d183d0f0e88c4f8a5979590612f230a3228912838b66bcc9e9053b2584"),
seed
)