TOLK lang
807 subscribers
15 links
Channel devoted to TOLK — "next-generation FunC" — a language for writing smart contracts in TON
Download Telegram
Channel created
Tolk is a language for writing smart contracts in TON. Think of Tolk as the "next‑generation FunC". Tolk compiler is literally a fork of FunC compiler, introducing familiar syntax similar to TypeScript, but leaving all low-level optimizations untouched.

Tolk was announced at the TON Gateway in Dubai in November 2024.

This channel will contain updates and releases of Tolk, highlights of planned features, and occasional posts about compilers and language design in general.

Useful links:
- Documentation
- GitHub (Tolk is a part of ton-blockchain repo)
- IDE plugins: "TON" in JetBrains, "Tolk" in VS Code
- Read an interview about TOLK history and plans
- Watch announcement on YouTube
🔥9👍3
Two months of absence: what was going on?

Two months have passed since the announcement of Tolk. You might be wondered, what was going on and why there we no releases yet.

Throughout all November, I've been working on the vision of the future. My goal was to "visualize" what Tolk v1.0 should look like. What's the language we're all targeting to, so that it solves lots of practical problems, avoids manual cells/slices manipulation, provides sufficient mechanisms for ABI generation, but still being zero overhead. I have created a giant roadmap (40 PDF pages!) describing the vision, and how, step by step, we're going to reach it.

Throughout all December, I've been constantly working on the compiler's kernel. As you know, Tolk is a fork of FunC. FunC compiler internals are very challenging to be extended and modified. The way FunC looks like is just a mirror of its internal implementation. Heading towards the future, I had to partially "untangle" this "legacy FunC core", so that in the future, it will be able to "interbreed" with features it was not originally designed for.

Currently I am done with this preparation. Tolk v0.7 has just been released. It contains a fully rewritten semantic analysis kernel (though almost invisible to the end user, huh).

Notable changes in Tolk v0.7:
1. Under the hood: refactor and revamp compiler internals. AST-level semantic analysis kernel
2. Under the hood: rewrite the type system from Hindley-Milner to static typing
3. Clear and readable error messages on type mismatch
4. Generic functions fun f<T>(...) and instantiations like f<int>(...)
5. The bool type
6. Type casting via value as T

PR on GitHub with detailed info. IDE plugins are updated accordingly.

BTW, a new version of blueprint was also released. You can now update compilers (Tolk / FunC / Tact) independently, they became peer dependencies.

P.S. I'll uncover the details about planned Tolk v1.0 quite soon.
🔥12🎉85
The Hindley-Milner type system, and Why Tolk decided to avoid it

You know, that FunC is "functional C". But do you know, what makes it "functional"? Not its low-level nature. Not its peculiar syntax. And even not the ~ tilda. "Functional" is mostly about the Hindley-Milner type system.

The Hindley-Milner type system is a common approach for functional languages, where types are inferred from usage through unification. As a result, type declarations are not necessary:

() f(a, b) {
return a + b; // a and b now int, since `+` (int, int)
}


For example,

() f(slice s) {}

var s = null;
f(s); // infer s as slice, since f accepts slice


For example,

int f(x) {
(a, b) = (0, x);
return a + b; // x becomes int, since x and b edge
}


This "unification", looking pretty at first glance, arises problems, if we actually do not want types to unify. Imagine, we want to have nullable types: int (not nullable) and int? (nullable), so that we can assign null only to int?. What would Hindley-Milner think about this?

var x = 0; // unify(Hole, Int) = Int
...
x = null; // unify(Int, Nullable<Hole>) = Nullable<Int>


Instead of an error, Hindley-Milner would perform unification and result in x: int?. Not as we wanted to, right? (while it can be "fixed", it would step away from HM's nature)

A fun fact: you don't notice these problems in FunC. Because FunC's type system is very limited. But Tolk will have bool, fixed-width integers, nullability, smart casts, structures, and generics — these problems will become significant. Hindley-Milner will clash with structure methods, struggle with proper generics, and become entirely impractical for union types (despite theoretical claims that it was "designed for union types").

The goal is to have predictable, explicit, and positionally-checked static typing. While Hindley-Milner is powerful, it's actually "type inference for the poor" — simple to implement when there's no time to fundamentally design the language.

By the way, unreadable type errors also stem from Hindley-Milner:

error: function return type (int, int) cannot be unified with implicit end-of-block return type (int, ()): cannot unify type () with int


What the programmer actually wants to see is:

1) can not assign `(int, slice)` to variable of type `(int, int)`
2) can not call method for `builder` with object of type `int`
3) missing `return`


That's why Tolk v0.7 contains a fully rewritten type system, encoupled with clear error messages and an IDE plugin with type inference included. It's the groundwork for future enhancements.
🔥72
Tolk v0.8: preparation for structures

A new version of Tolk was released several days ago. We're starting a way to eventually implement structures with auto packing to/from cells. This will take several steps (each publicly released), it's the first one.

Notable changes in Tolk v0.8:
1. Syntax tensorVar.0 and tupleVar.0, both for reading and writing
2. Allow cellslice, etc. to be valid identifiers

PR on GitHub with detailed info.

Using syntax `tensorVar.{i}` and `tupleVar.{i}`, you can access tensors/tuples by indices without unpacking them.

It works for tensors:

var t = (5, someSlice, someBuilder); // 3 stack slots
t.0 // 5
t.0 = 10; // t is now (10, ...)
t.0 += 1; // t is now (11, ...)
increment(mutate t.0); // t is now (12, ...)
t.0.increment(); // t is now (13, ...)

t.1 // slice
t.100500 // compilation error


It works for tuples (does asm INDEX/SETINDEX under the hood):

var t = [5, someSlice, someBuilder]; // 1 tuple on a stack with 3 items
t.0 // "0 INDEX", reads 5
t.0 = 10; // "0 SETINDEX", t is now [10, ...]
t.0 += 1; // "0 INDEX" to read 10, "0 SETINDEX" to write 11
increment(mutate t.0); // also, the same way
t.0.increment(); // also, the same way

t.1 // "1 INDEX", it's slice
t.100500 // compilation error


It works for nesting var.{i}.{j}. It works for nested tensors, nested tuples, tuples nested into tensors. It works for mutate. It works for globals.

Why is this essential?

In the future, we'll have structures, declared like this:

struct User {
id: int;
name: slice;
}


Structures will be stored like tensors on a stack:

var u: User = { id: 5, name: "" };
// u is actually 2 slots on a stack, the same as
var u: (int, slice) = (5, "");

fun getUser(): User { ... }
// on a stack, the same as
fun getUser(): (int, slice) { ... }


It means, that `obj.{field}` is exactly the same as `tensorVar.{i}`:

var u: User = ...; // u: (int, slice) = ...
u.id; // u.0
u.id = 10; // u.0 = 10


Same goes for nested objects:

struct Storage {
lastUpdated: int;
owner: User;
}

s.lastUpdated // s.0
s.owner.id // s.1.0


So, implementing indexed access for tensors/tuples covering all scenarios is a direct step towards structures.
🔥15👍8🗿3
𒀟 Tolk v0.9: nullable types, null safefy, control flow, smart casts

Tolk v0.9 introduces nullable types: int?, cell?, and T?, bringing null safety to your code. The compiler prevents using nullable values without checks, but thanks to smart casts, this feels smooth and natural.

Notable changes in Tolk v0.9:

1. Nullable types int?, cell?, etc.; null safety
2. Standard library updated to reflect nullability
3. Smart casts, like in TypeScript in Kotlin
4. Operator ! (non-null assertion)
5. Code after throw is treated unreachable
6. The never type

PR on GitHub with detailed info.

Nullable types and null safety

In FunC, null was implicitly assignable to any primitive type — too permissive. A variable declared as int could still hold null at runtime, leading to TVM exceptions if used incorrectly.

Tolk now forces you to explicitly mark nullable values. This aligns with TypeScript T | null and Kotlin, preventing unintended null usage.


value = x > 0 ? 1 : null; // int?

value + 5; // error
s.storeInt(value); // error

if (value != null) {
value + 5; // ok
s.storeInt(value); // ok
}


* any type can be nullable: cell?, [int, slice]?, (int, cell)?
* no more unexpected TVM exceptions due to null
* at runtime, int? and cell? occupy just one stack slot — zero overhead

Smart casts (via control flow graph)

Once a nullable value is checked, the compiler automatically refines its type:

if (lastCell != null) {
// here lastCell is `cell`, not `cell?`
}


or:

if (lastCell == null || prevCell == null) {
return;
}
// both lastCell and prevCell are `cell`


or:

var x: int? = ...;
if (x == null) {
x = random();
}
// here x is `int`


Smart casts ensure code is safe while remaining gas-efficient (compile-time only).

Operator `!` (non-null assertion)

If you know a value can't be null, use the ! operator to bypass nullability checks:

// this key 100% exists, make it `cell`, not `cell?`
validators = getBlockchainConfigParam(16)!;


It's useful for low-level TVM functions (dicts, particularly), when you have guarantees outside the code. Use with care!

The `never` type

Now, you can declare "always-throwing functions":

fun alwaysThrows(): never {
throw 123;
}

fun f() {
...
    alwaysThrows();  // no return needed after this
}


never also occurs implicitly when a condition is impossible:

var v = 0;
// compiler warning: `int` can never be `null`
if (v == null) {
    // v is `never`
}


... this is just the beginning! Nullable tensors, tricky smart casts, and low-level null safety details — all explained in the PR.

🌳 Null safety is smooth, intuitive, and enforced at compile time — no runtime cost, no extra gas, just safer code.
7👍6🔥4👎1
🫧 Tolk v0.10: preparing for serialization — intN, bytesN, coins

This update lays the foundation of future auto-packing to/from cells by solving one critical question:
How should fields be serialized?

Notable changes in Tolk v0.10:

1. Fixed-size integer types: int32, uint64, etc.
2. Type coins and function ton("0.05")
3. Types bytesN and bitsN (backed by slices at TVM)
4. Replace "..."c postfixes with stringCrc32("...") functions
5. Trailing comma support

PR on GitHub with detailed info.

Fixed-size integers? In TVM? What?

Imagine Tolk already has structures, and we define an incoming message:

struct CounterIncrement {
counter_id: int;
inc_by: int;
}


A client sends this message following the TL/B schema:

counterIncrement
counter_id:int32
inc_by:int64
= CounterIncrement;


But how do we tell the compiler that counter_id is int32 and inc_by is int64? This information is missing in the struct definition.

Rejected approaches: why they fail

Several syntax ideas were considered:


// type casting?
counter_id: int as int32;
inc_by: int as int64;

// inline annotations?
counter_id: int @int32;
inc_by: int @int64;

// annotations above fields?
@serialize(int32)
counter_id: int;
@serialize(int64)
inc_by: int;


Each of these quickly breaks down when handling more complex cases.

For example, how would we handle TL-B Maybe int32? Would we write:

// this?
inc_by: (int as int32)?;
// or this?
inc_by: int? as int32?;
// or this?
inc_by: Maybe<int> as Maybe<int32>;


And what about TL/B Both (Maybe int32) int64?

// this?
my_data: Both<Maybe<int as int32>, int as int64>;
// or this?
my_data: Both<Maybe<int>, int> as Both<Maybe<int32>, int64>;
// or how??


With every new case, the syntax becomes more complex, ambiguous, and error-prone.

The solution: `int32` as a first-class type


struct CounterIncrement {
counter_id: int32;
inc_by: int64;
}


No annotations. No confusing as syntax. No ambiguity.

This scales perfectly:

struct MyMsg {
inc_by: int32?;
my_data: (int32?, int64);
}


These are distinct types. A variable can be int32 and similar:

var op: int32 = ...;
var query_id: uint64 = ...;


This makes serialization predictable, structured, and error-free.

What about overflow?

A reasonable question: what happens if a value exceeds the limit?

var v: uint8 = 255;
v += 1; // ???


Answer: no runtime overflow or clamping! It's just int at TVM.

* arithmetic works normally – v becomes 256
* no extra gas cost – no runtime bounds checks
* overflow will only happen at serialization


struct Resp {
outValue: uint8;
}

resp.outValue = v; // 256
resp.toCell(); // a runtime "overflow" error


Why is this the best approach?

Think of smart contracts as a black box:
- inputs are encoded (int32, uint64, etc.)
- inside the contract, arithmetic uses full 257-bit precision
- outputs are serialized again — overflow happens only at this stage

This is similar to how mulDivFloor(x,y,z) uses 513-bit precision internally. Your contract keeps precision internally and only enforces constraints at the border with an outside world.

🌳 Tolk will follow a type-based philosophy

This post covered the foundation of automatic serialization. The right way is to have a rich type system. Having nested types, having generics, having aliases — will allow to describe every practical TL/B case, but at a language level.

In v0.10, we introduce intN (fixed integers), bytesN (definite slices), coins (variadic integers), and some more additions. Read the details in the PR.

How will Either L R and even more complex TL/B structures be expressed?
Stay tuned for the next update...
6🔥4👍3🤔1
🫧 Tolk v0.11: type aliases, union types, and pattern matching

This update might confuse you at first. You may wonder: "Why do we need this? How will it be useful?". But in the end, you'll see how everything comes together bringing seamless and generalized message handling.

Notable changes in Tolk v0.11:

1. Type aliases type NewName = <existing type>
2. Union types T1 | T2 | ...
3. Pattern matching for types
4. Operators is and !is
5. Pattern matching for expressions
6. Semicolon for the last statement in a block can be omitted

PR on GitHub with detailed info.

Type aliases

Tolk now supports type aliases, similar to TypeScript and Rust.


type UserId = int32;
type MaybeOwnerHash = bytes32?;


An alias creates a new name for an existing type but remains interchangeable with it. No performance overhead — a compile-time feature.

Union types `T1 | T2 | ...`

They now allow a variable to hold multiple possible types.


fun whatFor(a: bits8 | bits256): slice | UserId { ... }

var result = whatFor(...); // slice | UserId


Nullable types T? are now formally T | null.

At the TVM level, union types work as tagged unions — similar to Rust enums but more flexible.

Pattern matching

The only way to work with union types is matching them:


match (result) {
slice => { /* result is slice here */ }
UserId => { /* result is UserId here */ }
}


Matching is based on smart casts — inside each branch, the variable is automatically narrowed to the matched type.
 
It can also be used as an expression:


type Pair2 = (int, int);
type Pair3 = (int, int, int);

fun getLast(tensor: Pair2 | Pair3) {
return match (tensor) {
Pair2 => tensor.1,
Pair3 => tensor.2,
}
}


So, `match` + smart casts are our way for union types. You may notice that it's close to enums in Rust. But we don't have enum. Union types are more general and powerful.

`match` for expressions


val nextValue = match (curValue) {
1 => 0,
0 => 1,
else => -1
};


As you see, match also works for constant expressions, similar to switch in other languages.

Union types and TL/B `Either`

T1 | T2 will be directly mapped to TL-B (Either T1 T2).
Look how clean this is: (Either SmallPayload LargePayload) becomes


struct StoragePart {
data: SmallPayload | LargePayload;
// (de)serialized as '0' + ... or '1' + ...
}

match (s.data) {
SmallPayload => ...
LargePayload => ...
}


No need to manually handle bits from the slice — it's naturally expressed in the type system!

Union types and TL/B constructors

T1 | T2 | ... is a typed way to describe multiple constructors from TL/B. Generally, they can be used anywhere inside a storage or a message.

Moreover — handling incoming messages is beautifully expressed with union types.

Union types and future structures

The ultimate goal? You'll describe each incoming message as a struct, create a union type for them, parse a slice, and just match over variants:


// don't mind about opcodes yet
struct CounterIncBy { byValue: int32 }
struct CounterReset {}
struct ... other messages

type IncomingMessage = CounterIncBy | CounterReset | ...;

// ... after parsing a message
match (msg) {
CounterIncBy => {
newCounter = curCounter + msg.byValue
}
CounterReset => {
newCounter = 0
}
...
}


🌳 So, union types (that perfectly work with tensors) will seamlessly work with structures. With union types, you will declare both Either and different kinds of messages. Combined with intN and other types, they will allow to express (almost) any practical TL/B construction. They are not limited to message routing — in other words, message routing does not differ from handling any field, any storage, or any cell in general.
👍189🔥9
🫧 Tolk v0.12: structures, generics, and methods

Finally! Or maybe not yet?

This update brings Tolk one step closer to its final form by introducing:

Notable changes in Tolk v0.12:

1. Structures
2. Generics
3. Methods
4. Stdlib with short naming
5. Fift output enhancements

PR on GitHub with detailed info.

Structures

Looks like TypeScript — but works in TVM!


struct Point {
x: int;
y: int;
}

fun calcMaxCoord(p: Point) {
return p.x > p.y ? p.x : p.y;
}

// declared like a JS object
var p: Point = { x: 10, y: 20 };

// called like a JS object
calcMaxCoord({ x: 10, y: 20 });


Point a just named tensor — identical to (int, int) at the TVM level.

Key features:
- Compiler guesses what you mean — automatically:

fun loadData(): StoredInfo {
return {
counterValue: ...,
ownerAddress: ...,
}
}


- Works with shorthand syntax { x, y }
- Default values for fields are supported
- Nested objects are supported
- No overhead for single-field structs over a plain value
- Nullability, smart casts, union types, all language features — everything works

Generics

They exist only at the type level (no runtime cost):

struct Nullable<T> {
item: T? = null;
}


The type system becomes powerful enough to express complex scenarios:

struct Ok<TResult> { result: TResult }
struct Err<TError> { err: TError }

type Response<R, E> = Ok<R> | Err<E>;

match (r) {
Ok => { r.result }
Err => { r.err }
}


Methods — for any types

For structures:

fun Point.getX(self) {
return self.x
}

fun Point.create(x: int, y: int): Point {
return { x, y }
}


Or extend built-in types:

fun slice.load32(mutate self): int32 {
return self.loadInt(32);
}


Or — tensors, unions, generics, even "any receiver". A method is just an extension function with predictable resolution rules.

stdlib — with short methods naming


// before
someCell.cellHash();
someTuple.tupleSize();
someBuilder.getBuilderBitsCount();

// now
someCell.hash();
someTuple.size();
someBuilder.bitsCount();


Even more — some global functions are now static methods:

// before
getMyAddress();
setContractData(c);
getLogicalTime();

// now
contract.getAddress();
contract.setData(c);
blockchain.logicalTime();


It looks pretty, and since IDE suggest only available methods after dot, it's extremely useful. For a full list of renamings, consider documentation.

A long-awaited bonus for Fift ninja

Look at the PR and enjoy how readable Fift assembler output is. Yes, it contains original .tolk lines as comments! And yes, it works perfectly.

🌳 You now see how Tolk's features are aligning together towards automatic serialization, fully described by the type system. Cell references, constructor prefixes, message sending, and other use cases will be expressed with the features above.
🔥11👍76💘2
🫧 Tolk v0.13 (Release candidate): Auto-packing to/from cells

We've finally reached the point we've been working toward for the past six months: automatic (de)serialization of anything into cells. From simple structs to unions, generics, and even nested references — it just works.

The goal? To replace TL/B entirely with declarative, type-safe definitions. The compiler takes care of packing, loading, estimating, error handling, and more.

Notable changes in Tolk v0.13:

1. Auto-packing to/from cells/slices/builders
2. Type address
3. Lateinit variables, default parameters, and other minor features

PR on GitHub with detailed info.

Auto-serialization: short demo


struct Point {
x: int8;
y: int8;
}

var value: Point = { x: 10, y: 20 };

// makes a cell containing "0A14"
var c = value.toCell();
// back to { x: 10, y: 20 }
var p = Point.fromCell(c);


Key features:

* supports all types: unions, tensors, nullables, generics, atomics, ...
* allows to specify prefixes (particularly, opcodes)
* allows to manage cell references and when to load them
* lets you control error codes and other behavior
* unpacks data from a cell or a slice, mutate it or not
* packs data to a cell or a builder
* warns if data potentially exceeds 1023 bits
* more efficient than manual serialization
* will seamlessly integrate with message sending/receiving in Tolk v1.0

Pack or unpack — anywhere

T.fromCell, T.fromSlice, slice.loadAny<T>, builder.storeAny<T>, and other methods give both high-level and low-level API.


// low-level is allowed:
beginCell()
.storeUint(1, 32) // mix manual
.storeAny(myStruct) // with auto


Serialization prefixes and opcodes


struct (0x7362d09c) TransferNotification {
queryId: uint64;
...
}


They are not limited to 32 bits — you can describe any TL/B constructors:

struct (0b001) AssetSimple { ... }
struct (0b100) AssetBooking { ... }
type Asset = AssetSimple | AssetBooking | ...;


Typed cells


struct A {
    ref1: cell;         // untyped ref
    ref2: Cell<Inner>;  // typed ref
    ref3: Cell<int256>?; // maybe ref
}


Typed cells give you full control over when content is unpacked — nothing is loaded unless you call .load() manually:

a.ref2.field // error
a.ref2.load().field // ok


// Yes, point.toCell() really gives you Cell<Point>

Granular options

Accurately adjust serialization behaviour (for example, error codes):


Point.fromSlice(s, {
assertEndAfterReading: false
})


Built-in type `address`


struct Wallet {
owner: address;
}


* integrated with auto-serialization, while still a slice under the hood
* comparable: if (senderAddress == wallet.owner)
* introspectable: isInternal(), getWorkchain(), etc.

Some other features

Described in detail in the full changelog and mirrored in the documentation.

🌳 We're just a couple of steps away from Tolk v1.0 — with message sending and handling, gas optimizations, and one secret feature. There is still a lot of work ahead, but today we're closer to the release than ever before.
🔥13❤‍🔥5🎉3🤔1
🫧 Tolk v0.99: universal message composition

This update introduces a high-level, type-safe way to compose and send messages to other contracts.

No more manual beginCell().storeUint(...).storeRef(...) boilerplate. Just describe the message in a literal, and let the compiler do the rest.

Notable changes in Tolk v0.99:

1. Universal createMessage – control body, extra currency, stateInit, and more
2. Universal createExternalLogMessage — emit logs efficiently
3. Sharding — calculate addresses "close to another contract"

PR on GitHub with detailed info.

createMessage: short demo


val reply = createMessage({
bounce: false,
value: ton("0.05"),
dest: senderAddress,
body: RequestedInfo { ... }
});
reply.send(SEND_MODE_REGULAR);


Key features:

1. Supports extra currencies
2. Supports stateInit with automatic address computation
3. Supports different workchains
4. Supports sharding (formerly splitDepth)
5. Integrated with auto-serialization of body
6. Automatically detects "body ref or not"

Union types as the foundation

Almost every field is a union — flexible API while staying strictly typed:


// value: either tons, or tons + extra currencies
value: ton("1.5")
value: (ton("1.5"), extraDict)

// destination: various forms
dest: someAddress
dest: (workchain, hash)


Body: inline or ref?

Don't think about refs — the compiler does:
- small structs are inlined
- large ones are wrapped in a ref
- detected at compile-time — no runtime checks


// any serializable struct — no toCell!
body: RequestedInfo { ... }


Don't need body? Just leave it out.

Deployment: a variant of "destination"


dest: {
    workchain: BASECHAIN,
    stateInit: { code, data },
}


The beauty of TON is that it doesn't have a dedicated deployment mechanism. You just send a message to some void — and if this void doesn't exist, but you've attached a way to initialize it — it's initialized immediately, and accepts your message.

Sharding!

Built-in features to calculate an address "in the shard of contract B."


dest: {
...
toShard: {
fixedPrefixLength: 8,
closeTo: ownerAddress,
}
}


Perfect for optimized sharded jettons. All the hashing, StateInit packing, and bit-level work is done automatically.

⚙️ What about gas costs?

Even with all this flexibility, createMessage is not bloated. The compiler unfolds all union branches and constant conditions at compile time, producing flat, minimal code — often more optimal than hand-crafted cell composition.

v0.99? So, what's next?

Yes, it's the last version before the final release. Tolk now covers the entire initial roadmap and is ready for real-world use. On top of that, we'll add several compiler optimizations and rewrite standard contracts from FunC to Tolk — to demonstrate how exactly everything ties together.
🔥2518🍾11😱3🆒3
🫧 Tolk v1.0: lazy loading, partial updating, AST inlining — and extremely low gas fees

This is happening today.

I managed not only to finish up the compiler, but also to migrate standard contracts from FunC to Tolk.

I covered every contract with gas metrics to check whether it's more efficient than FunC or not. I didn't believe my eyes. I double-checked, triple-checked, but nothing changed. The results are correct. And now I am ready to present them to you.

🧪 Benchmarks!

Every contract has several metrics. For example, "basic jetton — mint".
FunC: 19278. Tolk: 11611.
Gas savings: -39.77%

|————————————————–-
| 01 — Jetton (mint, transfer, burn, discover)
| -39.77% | -30.32% | -33.66% | -26.75%
|————————————————–-
| 02 — NFT (deploy, transfer, onchain get)
| -43.79% | -37.47% | -44.28%
|————————————————–-
| 03 — Notcoin
| -35.72% | -27.98% | ...
|————————————————–-
| 04 — tgBTC
| -23.94% | -26.99% | ...
|————————————————–-
| 05 — Wallet v5
| -21.66% | -22.92% | ...
|————————————————–-
| 06 — Vesting
| -45.30% | -29.61% | ...
|————————————————–-
| 07 — Telegram gifts
| -54.81% | -39.39% | ...
|————————————————–-

I have migrated 7 contracts — preserving the original behavior and passing the same tests.

All benchmarks are open. No cheating. Just a smart compiler — and a language designed to be optimized.

Even wallet?! But it's very low-level and tricky!

Even wallet. You just express your thoughts without any tricks — and it works automatically.

But how? How is it possible to achieve such numbers?

Of course, I've put a lot of smartness into the compiler. A strong type system. Automatic inlining. Built-in serialization. The magic lazy keyword.

But first of all, Tolk is built for readability. These contracts aren't "just cleaner" than their FunC equivalents — they're elegant. No magic. No low-level intrigues. Just clean, consistent logic — whether it's a Jetton or a Wallet.

And gas savings? They are a consequence. I didn't micro-optimize. Each contract was rewritten in about a day — just focusing on clarity. The core principle is simple: once you make it elegant, it automatically becomes efficient.


Notable changes in Tolk v1.0:

1. Lazy loading — only read what you actually use
2. AST-based inlining — zero-cost getters and methods
3. Peephole optimizations, TVM-11 — smaller code, lower gas

PR on GitHub with detailed info.

Just take a look:

val st = lazy Storage.load();
// the compiler skips everything and loads only what you access
return st.publicKey;


That's it. Lazy loading and partial updates (calculating immutable slices for writing) — one magic lazy keyword to rule them all.

⚙️ The Release. What's inside

- JetBrains IDE plugin — with completion, resolving, etc... ✓ done
- VS Code Extension — with suggestions, stdlib integration, etc... ✓ done
- Language server — for any LSP-compatible editor... ✓ done
- FunC-to-Tolk converter — migrate a project in 10 seconds... ✓ done
- Documentation — for smooth transition from FunC... ✓ done

And the TOLK language itself — exactly as I've visioned it... 7 months ago


📅 The essence of Numbers

At this moment, abstract yourself from the text. Take a look at the calendar.

Today is 07/07/25 (I mean 2+5, of course).

After the TON Gateway, throughout all November, I was laying out the roadmap towards Tolk v1.0. On December 1, I started working. It means, that the journey till now took exactly 7 months 7 days. If you scroll this channel to the top, you'll see an announcement... Of Tolk v0.7

🪐 What's next?

I could have written about my plans — for the language, TVM, ABI, TypeScript wrappers... but I won't.

Because today, instead of rushing into the future, maybe we can allow ourselves to look back.
To pause the endless race — just for a moment.
To notice how many beautiful coincidences pass us by — unnoticed in our constant hurry.

Just step outside.
Close your eyes.
Count to seven.
Feel it.

When you're ready —
Let's Tolk
🔥65👏2522👍72🤯2😱2
🫧 Tolk v1.1: built-in map<K,V>, enums, private and readonly fields, method overloads

Two months have passed — maybe you even started to worry about the silence. The reason is simple: I worked on features that are "nice to have" but complex and time-consuming to implement — and they've only just been finished.

Notable changes in Tolk v1.1:

1. map<K, V> — a convenient zero-overhead wrapper over TVM dictionaries
2. enum — group numeric constants into a distinct type
3. private and readonly fields in structures
4. Overload resolution and partial specialization

PR on GitHub with detailed info.

Built-in maps

Forget about uDictSetBuilder, sDictGetFirstAsRef, and the endless boilerplate of low-level dict helpers. A universal map<K, V> now fully replaces them.


var m: map<int8, int32> = createEmptyMap();
m.set(1, 10);
m.addIfNotExists(9, -90);
m.delete(9); // now: [ 1 => 10 ]
m.exists(1); // true
m.isEmpty(); // false


Just m.get() — no need to care about cells and slices under the hood:

val r = m.get(1);
if (r.isFound) { // true
val v = r.loadValue(); // 10
}

// or if the key 100% exists
val v = m.mustGet(1); // 10


Easily iterate forward and backward:

var r = m.findFirst();
while (r.isFound) {
// use r.getKey() and r.loadValue()
r = m.iterateNext(r);
}


Any serializable keys and values — it just works:

map<address, Point>
map<Point, Cell<Extra>>
map<int32, map<int64, bool>>
...


All in all:
- self-explanatory methods, nicely suggested by IDEs
- DICTISETREF, DICTREPLACE, DICTUREPLACEGET, ... — 100+ asm instructions covered by the type system
- all deserialization to/from cells perfectly hidden by high-level API
- absolutely zero overhead compared to low-level TVM dictionaries

Enums

A long-awaited syntax feature for grouping constants.


// will be 0 1 2
enum Color {
Red
Green
Blue
}


Being integers at runtime, enums have their own place in the type system. They resemble TypeScript/C++ enums. (Unlike Rust, where each variant may have its own shape. In Tolk we have union types — a more powerful solution)


struct Gradient {
from: Color
to: Color? = null
}

var g: Gradient = { from: Color.Blue };
g.from == Color.Red; // false


Compatible with all language features: auto-serialization, exhaustive pattern matching, generics, etc.

Private and readonly fields

Fields can now have modifiers:
* private — accessible only within methods
* readonly — immutable after object creation


struct PosInTuple {
private readonly t: tuple
curIndex: int
}

fun PosInTuple.last(mutate self) {
// `t` is visible only in methods
// and cannot be modified
self.curIndex = self.t.size() - 1;
}


Partial specialization

Now it's possible to overload methods for "more specific" implementations:


// general implementation
fun Iterator<T>.next(self) { ... }

// a more specific one
fun Iterator<Cell<T>>.next(self) { ... }


In complex scenarios, this feature lets you adjust the behavior of specific types while keeping a common interface. It "just works", but internally the compiler was enhanced with shape of types, structural depth, type dominators, and several heuristics.

🌳 After Tolk v1.0 release, many people and companies started migrating from FunC to Tolk. I have received a lot of feedback and requests (and almost zero bug reports, huh). Meanwhile, a bigger roadmap is already in motion. In the near future I'll also try to close long-standing questions around TypeScript wrappers, and deliver proper from-scratch documentation.
🔥2210👏5❤‍🔥3
🫧 Tolk v1.2: rich bounced messages, cheap deployment, and a breaking change that you'll love

Tolk v1.2 is here, aligned with TVM 12 — bringing new assembler instructions that make contracts cheaper and cleaner.

This update introduces one breaking change, several powerful new capabilities, and a few quality-of-life improvements across the compiler.

Notable changes in Tolk v1.2:

1. Breaking change: address is now "internal only"
2. Rich bounces: not 256 bits, but the full body on bounce
3. Cheap builder-to-slice, StateInit, and address composition
4. Improved compilation errors
5. Anonymous functions (lambdas)
6. Borrow checker to catch undefined behavior

PR on GitHub with detailed info.

`address` is now "internal only"

Before:
* address meant internal/external/none

Now:
* address — internal only
* address? (nullable) — internal/none, exactly like "maybe address" in @ton/core
* any_address — internal/external/none

In 99% of contracts only internal addresses are used. External ones are rare, and "none" can be expressed as nullable.


struct Storage {
// internal, checked automatically
owner: address
}


With new TVM 12 instructions, addresses are validated automatically during (de)serialization without extra gas — no more manual isInternal() checks.

So yes, it's technically a breaking change, but it removes a ton of noise.

A short migration guide, as well as technical details, available here.

Rich bounced messages

Historically, a bounced message only returned the first 256 bits of the original body.

Now TVM 12 supports rich bounces — which lets you obtain the entire body instead.


createMessage({
    bounce: BounceMode.RichBounce,
    ...
})


In onBouncedMessage, you get access to the original body, exit code, gas used, and more.

Old true/false bounce flags still work for backward compatibility.

Rich bounces simplify complex message flows and inter-contract communication — one of the most painful aspects of TON until now.

Cheap builder-to-slice and address composition

Previously, converting a builder to a slice (endCell + beginParse) consumed a lot of gas because cells are expensive. Now there's a new instruction — BTOS (builder-to-slice) — without intermediate cell creation.

- b.endCell().beginParse() is now cheap: auto-optimized to BTOS
- "builder-to-address" is the same BTOS; hacks around "return a builder with a valid address" can be removed
- cheaper StateInit hashing and address calculations

Just update to Tolk v1.2 + TVM 12, and you'll immediately save gas.

Anonymous functions (lambdas)

Can be used in general-purpose frameworks, perfectly integrated with the type system:


fun customRead(reader: (slice) -> int) { ... }

customRead(fun(s) {
return s.loadUint(32)
})


Low-level compiler enhancements

Also included: better diagnostics with precise ranges, new peephole optimizations, tuple object conversions, and multiple small fixes. A lightweight borrow checker prevents undefined behavior on concurrent mutations.

As always, all additions are carefully described in a PR.

🌳 We've also started improving TVM itself — new assembler instructions are designed specifically to fit the Tolk type system and optimizer. I have always said: the language is just the beginning. Perfect developer experience requires improving every layer of TON's stack. The road may be sharp and curvy — but we're definitely heading in the right direction.
🔥2318👍12
🫧 Tolk documentation — now complete and available for learning from scratch

From now on, Tolk has full, structured, from-scratch documentation — not just "Tolk vs FunC", but a complete language guide that lets developers approach Tolk directly, without any FunC background.

This is a major milestone. Tolk is the recommended language for TON, and now it finally has documentation that matches this role.

Read →

↓ What's inside

The documentation covers the entire language:

Type system.
Every type on its own page: numbers, addresses, structures, generics, etc. — plus overall explanations for TVM layout and serialization.

Syntax details.
Functions, conditions, loops, exceptions, and more — with minimal descriptions and clear examples.

Language features.
Everything needed for smart-contract development: message sending, contract storage, automatic serialization, etc.

Migration from FunC.
A complete guide for FunC builders — including the mindset shift Tolk encourages.

... And several articles for experienced divers. They reveal compiler internals and the language philosophy. My favourite — Stop thinking in TL-B.

㉈ Hundreds of examples

Every distinct aspect is covered with usage examples — properly highlighted in both light and dark mode.

No matter whether you are new to TON or have been here for years — you'll definitely find a few tricks you've never seen before.

∞ This documentation in numbers

All together, the new Tolk documentation contains:
• 46 pages
• 480 snippets
• 40000 words
• 280000 characters

I invested ~200 hours developing the text and picking every word.
I hope that, cumulatively, this documentation will save noticeably more time for all TON developers.

→ Start reading

https://docs.ton.org/languages/tolk

🌳 Thanks to everyone who reviewed the pull request and pointed out occasional mistakes or misprints. Just imagine the amount of content they had to deal with.
🔥32🏆128👏5🥰2
🫧 Tolk v1.3... not released yet: what's happening inside

I know it has been quiet.

The last public Tolk update was three months ago, so the question is fair:

Where did I go, and why is there still no v1.3?

I did not disappear. I have been working on something much bigger than the next language release.

A huge part of my focus has shifted toward building a TON toolchain together with the team.

Not another small utility. Not another isolated CLI. A full development environment built as one coherent system — around Tolk.

⚙️ So what do I mean by a toolchain?

The core of TON development. A single tool that:

Scaffolds real projects. Contracts, unit tests, integration tests, TypeScript wrappers, and ready-to-use templates.

Runs tests. 50x faster than the current JS sandbox. And even more: tests are native, no Node.js required.

Gives you a real debugger. Test failed with exit code 9? Stop exactly at the exception, inspect the call stack, local variables, lazy fields, and more. One thing I am especially proud of: there is no "compile without optimizations" mode. It works with fully-optimized production contracts.

Visualizes transaction flows. Run a test and immediately see the full chain of generated transactions in a clean local UI.

Covers the rest of the workflow too: code coverage and gas profiling; formatter and language server; verifier and ABI generation; all of it AI-friendly, with agent skills and MCP built-in.

The goal is to reboot development on TON

When Tolk appeared, it made FunC feel like the old world. Tolk is not just "a bit better" — it feels like a different era.

Now we are doing the same for the entire on-chain development stack. Not just to improve existing tools a little — but the current approach will feel fundamentally outdated.

🌳 I originally planned to announce all of this at TON Gateway. But it was cancelled, so I am lifting the curtain a little earlier.

The timeline has not changed: early May.

And early May is very close.
32🔥25👍18🆒3
🫧 Tolk v1.3: moving toward a general-purpose language

After the previous post, this release may feel less surprising — but still a bit unusual.

The reason is simple: Tolk is no longer evolving only as a contract language. It is becoming a foundation for the toolchain I described earlier.

This release focuses on features beyond contracts — introducing general-purpose capabilities needed for libraries and frameworks.

Notable changes in Tolk v1.3:

1. Type array<T> — dynamically sized arrays backed by TVM tuples.
2. Type unknown — a TVM primitive with unknown contents.
3. Type lisp_list<T> — nested two-element tuples (FunC-style).
4. Type string — text chunks backed by snaked cells, with StringBuilder for concatenation.
5. Compile-time string methods: "str".crc32(), "str".sha256(), etc.
6. Null coalescing operator — ?? like in TypeScript.
7. Import path mappings — import "@third_party/utils".
8. Compile-time reflection via @stdlib/reflection.
9. Custom serializers now support structures and generics.
10. The compiler now reports multiple errors at once.
11. Focused on stability — fixed dozens of minor issues found by LLM fuzzing.
12. Extensive internal refactoring towards being stateless and multi-threaded.

PR on GitHub with detailed info.

Arrays: redesigned tuples

Working with TVM tuples has been fully redesigned. There is now array<T> — a dynamically sized container:


// array<int>
var numbers = [1, 2, 3];

// array<Point?>
var optPoints = [
Point { x: 10, y: 20 },
Point { x: 30, y: 40 },
null,
];


- methods push, get(idx), etc.
- any T, including sub-arrays like array<array<int>>
- automatically serialized into snake cells
- max size: 255 (TVM limitation)

The `unknown` type

Raw TVM tuple exists, but it's no longer built-in. It's just an array... of something unknown:


type tuple = array<unknown>


The unknown gives access to the untyped TVM stack, fully integrated into the type system.

The `string` type

TVM has no strings — only binary slices. Strings were always just a convention over binary data.

Now Tolk has strings built-in.


// string
val str = "hello";


- strings are cells (not slices)
- long strings are snake cells under the hood
- methods calculateLength, equalTo, etc.
- on-chain/off-chain encoding for jettons and NFTs to comply with TEPs

StringBuilder encapsulates cell manipulation:


StringBuilder.create()
.append(content.commonContent)
.append(individualNftContent)
.build()


By the way, compile-time functions now look cleaner: "str".crc32() and so on.

Import path mappings

The import statement now accepts @aliases:


import "@common/jettons"
import "@third_party/math-lib"


This is similar to widely used path mappings in TypeScript.

Compile-time reflection

Many additions in v1.3 make sense not for contracts, but for frameworks. For example, take a look at one of reflect features:


fun log(msg: string, loc: SourceLocation = reflect.sourceLocation()) {
debug.print(loc.lineNo);
}

fun demo() {
log("a"); // prints K — current line no
log("b"); // prints K+1
}


Why is this useful? It allows errors to point to the original call site — for example, expect(...) in tests — by carrying source location at compile time.

⚙️ A huge portion of internal refactoring

A lot of work has been done inside the compiler core, peephole optimizations, and memory management.

Final result: tolk compiler is now thread-safe and re-invokable within a single process. It will be embedded into an external toolchain written in Rust, communicating via FFI.

... And more

Dozens of independent improvements. Combined, they cover the requirements not only for contracts, but for abstract libraries and the upcoming toolchain.

Feel free to check the description on GitHub.

🌳 And one more thing! Wallet-v5, compiled with Tolk v1.3, reduces gas usage by 30% compared to FunC. As of Tolk v1.0, the savings were "only" 20%. Take a look at new benchmarks.
🔥2312🤩6👍2🙏2🦄2👏1🍾1
🫧 TON enters a new era of smart-contract development

I promised this would happen in May.

Today we release:

• Tolk v1.4
• Acton — a unified toolchain for TON smart contracts

For more than half a year, we have been building this almost entirely in secret.

And honestly, this is the biggest release we have ever shipped at once.

⚙️ What is Acton?

Acton is an all-in-one CLI built around Tolk — a modern replacement for the fragmented tooling stack TON has today.

✓ Write tests directly in Tolk — including transaction flows and cross-contract interaction. 50x faster than current Sandbox + TypeScript approach.

✓ Automatic TypeScript wrappers. But not for tests! For end-to-end dApp/frontend integration.

✓ A native debugger. Step in, step over, step out, watch variables, and inspect the call stack. A smooth Web2-style developer experience — now inside a Web3 ecosystem.

✓ Entire contract lifecycle. Develop, deploy, verify, configure, and interact with contracts on-chain. Acton manages wallets and faucet top‑ups on testnet.

✓ Security built in. Coverage, fuzzing, mutation testing, gas profiling, and CI help catch regressions early.

🤖 Friendly for AI agents

One interesting thing we discovered while building Acton: good tooling for humans naturally becomes good tooling for AI.

We designed Acton for ourselves first. Every command, every flag, every workflow — polished to feel coherent and predictable.

And it turned out that agents thrive in exactly this kind of environment.

CLI-first by design, Acton becomes an agent's runtime — with structured commands, built-in skills and manuals.

🛠️ Tolk v1.4 as the foundation

None of this would be possible without the kernel behind it all: the compiler itself.

Tolk v1.4 allows external tools to finally understand the contract.

1. It emits ABI — describing how the contract is "seen" by the outside world. Explorers, frontend UI and TypeScript wrappers naturally build on top of ABI.
2. It emits source maps — allowing TVM execution to map back to Tolk source code, local variables, stack layout, and call frames.
3. It enables debugging WITHOUT bytecode modifications — for fully optimized production contracts.

This is an extremely non-trivial problem technically. But it works.

And here is the crazy part:

you can take a failed transaction from the real mainnet — and debug it step-by-step locally in your IDE.

As always — PR on GitHub with detailed info.
As always — very long and caring.

🪐 Where does it all lead?

We have prepared detailed documentation for dozens of Acton features — both for experts and newcomers.

But I am not inviting you to read it.

Instead, I am inviting you to the landing page.

Carefully designed. Written from scratch. A distilled form of the last 7 months of work.

It contains 7 videos.

Open it on desktop.
Turn on the sound.
Watch the videos.

Feel the technical depth hiding behind all this simplicity.

https://ton-blockchain.github.io/acton/

When you're ready —
Act on
🔥47💯16🤩136🤯3🙈2❤‍🔥1🙏1