Tolk is a next-generation language for smart contracts in TON. It replaces FunC with modern syntax, strong types, and built-in serialization — while generating even more efficient assembler code.
Using single command, you can transform an exising FunC project to Tolk:
// all .fc files in a folder
npx @ton/convert-func-to-tolk contracts
// or a single file
npx @ton/convert-func-to-tolk jetton-minter.fc
Example input: jetton-minter.fc
Example output: jetton-minter.tolk
This is a syntax-level converter that helps migrate FunC contracts to Tolk. It rewrites your code with 1:1 semantics — so you get a Tolk version of your contract that "looks and smells" like FunC.
The converted contract won't use modern Tolk features like struct
, auto-serialization, or clean message composition. But after some manual fixes, it compiles, runs, and passes tests.
From there, you can gradually modernize the code — step by step, while keeping it functional at every stage.
Not required: just use npx
above.
npx
above uses default options, but here is what you can pass:
--warnings-as-comments
— insert/* _WARNING_ */
comments (not just print warnings to output)--no-camel-case
— don't transform snake_case to camelCase
- Converts
;; comment
→// comment
- Converts
#include
→import
- Removes deprecated
#pragma
- Changes function syntax:
int f(slice a)
→fun f(a: slice): int
- Changes globals declarations:
global slice cs
→global cs: slice
- Changes variable declarations:
int a = 0
→var a: int = 0
- Changes complex variable declarations:
var (int a, slice b) = …
→var (a: int, b: slice) = …
- Removes
impure
specifiers (inserting@pure
for asm functions) - Replaces
inline
andinline_ref
specifiers with@
attributes - Converts
method_id
to get methods:int seqno() method_id
→get seqno(): int
- Converts
forall
to generics syntax:forall X -> X f()
→fun f<X>(): X
- Removes forward declarations, since Tolk locates symbols in advance, like most languages
- Wraps invalid identifiers into backticks:
op:increase
→`op:increase`
- Replaces
ifnot (x)
→if (!x)
and similar - Replaces
throw_unless(condition, excNo)
→assert(excNo, condition)
and similar - Replaces
null()
→null
- Replaces
null?(x)
→x == null
- Does all stdlib renaming:
begin_cell
→beginCell
,end_parse
→assertEnd
, etc. - Converts
~
declarations:(slice, int) ~load_op(slice s) { return s~load_uint(32); }
→fun slice.loadOp(mutate self): int { return self.loadUint(32); }
- Replaces
~method()
with.method()
- Replaces entrypoints:
recv_internal
→onInternalMessage
and similar - Replaces string postfixes:
"str"c
→stringCrc32("str")
and others - Tries to guess when you need
address
instead ofslice
- Converts functions and locals from snake_case to camelCase (globals/constants no, since a converter works per-file, whereas they are often imported)
- ... and lots of other stuff, actually
Of course, the convertion result seems quite dirty. For instance, Tolk has logical operators, and therefore if (~ found)
could probably be if (!found)
. For instance, assert(!(a < 0))
is actually assert(a >= 0)
. And so on.
Contracts written in modern Tolk from scratch look much nicer. But that's what exactly expected from auto-conversion.
Helpful link: Tolk vs FunC: in short.
Almost all problems are related to methods. For two reasons:
- FunC has
~methods
and.methods
, Tolk has only.methods
, which may mutate, or may not, the firstself
parameter. - FunC allows any function
f(x)
to be called as a methodx.f()
, Tolk does not: functions and methods are different, like in other languages.
If a method was called strangely in FunC, it becomes an incorrect call in Tolk:
// FunC
int r = n~divmod(10);
// auto-converted, incorrect now
var r: int = n.divMod(10);
This line should be rewritten:
var (newN, r) = divMod(n, 10);
// use newN or assign n = newN
// alternatively, use `redef` keyword:
var (n redef, r) = divMod(n, 10);
Lots of stdlib functions became mutating. This way, cs~load_int(32)
replaced by cs.loadInt(32)
works equally, but cs.skip_bits(8)
(with dot! not tilda) replaced by cs.skipBits(8)
works differently, because skipBits()
mutates an object.
Hence, if you used dot-call for .methods
which become mutating, that part should be rewritten (it either won't compile or would work differently). Most common cases are skip
-functions and dict
-functions. Dict API in Tolk is mutating. If you used dict~udict_set(…)
, it was replaced by dict.uDictSet(…)
, and everything is fine. But if you used dict.udict_set(…)
to get a copy, it's a problem.
The converter prints warnings in such cases. Some are falsy, but most are related to this exact problem.
Deep dive: Tolk vs FunC: mutability.
If you had a function in FunC
builder store_msg_flags_and_address_none(builder b, int msg_flags) inline {
return b.store_uint(msg_flags, 6);
}
// and somewhere
begin_cell()
.store_msg_flags_and_address_none(BOUNCEABLE)
...
This will be auto-converted but won't compile:
@inline
fun storeMsgFlagsAndAddressNone(b: builder, msgFlags: int): builder {
return b.storeUint(msgFlags, 6);
}
// and somewhere
beginCell()
.storeMsgFlagsAndAddressNone(NON_BOUNCEABLE) // won't compile
...
It won't compile with a message:
error: method `storeMsgFlagsAndAddressNone` not found for type `builder`
In FunC, cell_hash(c)
and c.cell_hash()
were equivalent. This is a reason why cell_hash
and slice_hash
are different functions.
In Tolk, functions and methods are different. cell.hash()
and slice.hash()
are now different methods (not global functions). Stdlib contains short names: you call tupleVar.size()
instead of tupleVar.tupleSize()
, and so on.
Hence, you should transform storeMsgFlagsAndAddressNone
into a method for builder
. Since it modifies a builder, it's a mutating method. Since it's chainable, it returns self
:
@inline
fun builder.storeMsgFlagsAndAddressNone(mutate self, msgFlags: int): self {
return self.storeUint(msgFlags, 6);
}
-
Some stdlib functions were removed. For instance,
pair(a,b)
removed, as it can be replaced with[a,b]
. As well as various functions working with tuples, which are supposed to be expressed syntactically. -
Tolk is null-safe, so you can't assign
null
tocell
, only tocell?
. And since some stdlib functions return nullable values, types will most likely mismatch. -
Tolk has
bool
type (FunC had onlyint
). Comparison operators== != < >
return bool. Logical operators&& ||
return bool. In FunC, you have to writeif (~ equal)
. In Tolk, bitwise~
for bool is an error, write justif (!equal)
. You can't assignbool
toint
directly, only viaboolVar as int
(but most likely, it's a bad idea). Remember, thattrue
is -1, not 1. -
Tolk has
address
type (FunC had onlyslice
). You can't assignslice
toaddress
and vice versa withoutas
operator. The converter tries to guess whetheraddress
should be used instead. For instanceslice sender_address = ...
will be converted tovar senderAddress: address = ...
.
Deep dive: Tolk vs FunC: in detail.
This converter gives you a working starting point — a Tolk contract that's still written in a "FunC style," but ready to be modernized step by step.
- Once you fix compilation errors, you'll have a functional contract with the same logic as before — just in Tolk syntax. It won't use structs, auto-serialization, and other features yet.
- From there, begin gradually refactoring the code:
- Replace
onInternalMessage(inMsgFull: cell, inMsgBody: slice)
withonInternalMessage(in: InMessage)
and use its fields directly - To handle bounces, use
onBouncedMessage(in: InMessageBounced)
— it's called automatically - Extract a
Storage
struct, with toCell/fromCell like in examples, use it everywhere instead of manual functions - Refactor incoming messages into structs with 32-bit opcodes — incrementally, one message at a time
- Define a union of possible messages, use
val msg = lazy MyUnion.fromSlice(in.body)
andmatch (msg)
- Extract outgoing messages into structs, and send them with
createMessage(...)
- Replace
The key idea: your tests keep running at every stage. That's what makes this approach safe — you can confidently refactor and modernize the codebase without breaking anything.
Deep dive: Auto-packing to/from cells.
Deep dive: Universal createMessage.
Here: Tolk vs FunC benchmarks. This repository contains several contracts migrated from FunC — preserving original logic and passing the same tests.
If you are familiar with how jettons or NFTs are implemented in FunC, you'll feel right at home. The Tolk versions are significantly clearer and more readable. You can also explore the Git history to see how each contract was gradually rewritten, step by step.