Behind the scenes at TON: lessons learned about implementing smart contracts, part 2

11 min read

This is the second article in our series about integrating payment channels on Telegram Open Network. In the first part we introduced the network, detailed our experience with the competition and explained how synchronous and asynchronous smart contracts work. As the next addition to the series, this article describes how we built a synchronous payment channel on the network during the TON competition in September. Here we will only talk about Fift (the general programming language of TON) and FunC (the programming language of TON for writing smart contracts).

The TON white paper offers more in-depth information about payment channels, but we will briefly explain them again.

Related: Behind the scenes at TON: lessons learned about implementing smart contracts, part 1

A synchronous payment channel makes it possible to send transactions between two users off-chain using on-chain assets. In our case – GRAM & # 39; s. It is impossible for one party to cheat the other off-chain and transactions are executed much faster than performing low-one blockchain transactions, because only user devices are used to complete them without having to write to the blockchain. There are two basic operations: depositing and withdrawing. The withdrawal is the most challenging to implement.

To make a correct recording, users must provide the latest information about the status of their channel. The status consists of the steps and digital signatures of each participant, which means that it is not possible to provide a correct status with data that has not been approved by both parties.

To implement a smart contract, you must write an implementation script in Fift and compile this to one .boc (bag of cells) file. If you do this, multiple cells will be linked together. GRAM & # 39; s must then be sent to the address received during the implementation script execution. Once GRAM & # 39; s arrive at the address, send the .boc file to the network and the contract is implemented.

To make a function call, write a script that sends an external message to the implemented smart contract.

In short, everything on TON is a cell with a few references. A bag of cells is a data structure designed by the Telegram team. It is an actor model. More details can be found on TON whitepaper: "everything is a bag of cells." You build a cell that will communicate with another cell when it is implemented.

Every peer-to-peer payment channel is a single smart contract. Let us look at the segments of a smart contract.

Related: What to expect from the Telegram Open Network: the perspective of a developer

Implementation part

A serialized Fift script is used to implement a contract. It is stored in a .boc file and sent to the network via TON Cli, the light client of the network.

The newest cell in the stack is the result of executing the above Fift script.

The usual segments of a Fift implementation script include (but are not limited to):

  1. Code of the smart contract as a single cell (usually written in FunC, then compiled into Fift ASM code and included in the main text .fif use file path to compiled asm.fif).
  2. First storage of the smart contract (see below).
  3. New smart contract address (the hash from the initial state of the smart contract that also includes the smart contract code cell and the initial storage cell).
  4. Arguments of the first call of the recv_external function (the number of arguments and type depends on the contract).
  5. An external message cell for initialization, which is serialized in bytes and packaged in the .boc file, which consists of all data from points 1-4 and some additional information that still do not contain any documentation.

When the .boc has been compiled, a specific number of GRAMs must be sent to the smart contract address. The .boc file must be sent to the network to initialize the smart contract. The number of GRAM & # 39; s depends on the size and volume of the calculations of the external message cell of the implemented smart contract (not just its code). Gas × gas price is withdrawn from the smart contract balance used. This amount is the minimum required to pay for gas during the bet.

A view of the storage:

  1. SEQNO 32 bits
  2. contract status 4 bits
  3. first_user_pubkey. The public key of the first party 256 bits
  4. second_user_pubkey. The public key of the second party 256 bits
  5. time_to_send. Time to send after the first current status has been sent 32 bits (valid until 2038)
  6. depositSum. The deposited sum of two participants up to 121 bits
  7. state_num 64 bits. The current number of states that occurred

A cell contains a maximum of 1023 bits and four references to other cells. We could place the entire storage in one cell without a single reference. Our storage can take up to 765 bits.

All smart contract states

0x0 – Implementation status

0x1 – Channel opened and ready for deposit

0x2 – Deposit by user 1

0x3 – Deposit by user 2

0x4 – The deposit has been blocked. It is possible to give the smart contract a status

0x5 – User 1 has specified the state

0x6 – User 2 has provided the status

0x7 – The channel is closed


The deposit function receives a message from a simple wallet (transfer) with extra load capacity.

Deposit GRAM & # 39; s on the channel:

  1. The user generates an additional content load with a message (e.g. 1 bit) and its signature in a separate one .fif File.
  2. The payload of the body is compiled into one .boc File.
  3. The body payload is loaded here .boc file in a .fif file as a "transfer code" of the body cell (the .fif is responsible for transferring GRAM & # 39; s from the portfolio).
  4. The recv_external function is called with arguments (the deposit amount and the destination address of the channel) when the compiled .fif file is sent to the network.
  5. The send_raw_message function is executed. Deposited GRAMs and additional load capacity are sent to a P2P channel destination address.
  6. The recv_internal function of the P2P channel is called smart contract. GRAM & # 39; s are received through channel contracts.

The deposit function can be called up if the status of the P2P channel is smart contract 0x1 or 0x2 or 0x3.

FunC code that checks the status:

Only the owners of the public keys (written in the first store) may make a deposit. The smart contract checks the signature of every internal message that will be received via the recv_internal position. If the message is signed by one of the public key owners, the contract status changes to 0x2 or 0x3 (0x2 if the public key is 1 and 0x3 if it is a public key 2). When all users have made a deposit, the contract status changes to 0x4 on the same function call.

The FunC code that is responsible for changing the contract status:


Funds can be returned if a counterparty has not made a down payment on time.

To do that, a user must provide his address and signature via an external message. The money is refunded if the signature provided belongs to public key 1 or public key 2 (people who have made a down payment) and the contract status is 0x2 or 0x3.

FunC code responsible for verifying the reimbursement request:

the retreat

Each person must specify an exit status, the signature of this status and the signature of the main message.

State details:

  1. Smart contract address (to exclude the possibility of entering the correct status from the previous P2P channel with the same participants).
  2. The final balance of the first participant.
  3. The final balance of the second participant.
  4. State number.

The body text signature is stored in the main segment, the status is stored in a separate reference and status signatures are stored as references to a "signature" reference to prevent cell overflow.

Withdrawal steps:

  1. Check the signature of the main message and determine the participant.

  1. Check whether it is the participant's turn or that 24 hours have elapsed since the last status entered. Write the turn of the current participant (0x5 or 0x6) to the contract status.

An example of a correct signature of the main message for the owner of first_user_pubkey:

Next, we must verify that the smart contract address written to the state is the actual contract address:

Next, we must verify signatures under the status:

Then there are two statements:

  1. The amount deposited from the surcharge must be equal to the sum of the participants' total balances.
  2. The newly entered status number must be greater than or equal to the previous one.

In case of new_state_num> state_num we must save new_state_num with the new time_to_send equal to now () + 86401 (24 hours from the current time) and also write the current contract status (0x5 if the first participant has made a call, otherwise 0x6).

In another case, if new_state_num == state_num we must place two additional references to the "signatures" reference with the addresses of each participant and signatures under their addresses.

If the signatures are correct, GRAM & # 39; s will be removed from one address and placed at the owner's address.

9 "src =" "title =" 9

Every time a successful conversation takes place, we must save all storage data, even if it does not change.

Unresolved issues

The assumption is that the first user has implemented the contract and the participants' commissions have been agreed. The agreement on commissions in our case reaches off-chain.

We have not yet discovered how to calculate the total commission, taking into account that players can write an irrelevant state and then record the actual status. Please note that every time we call successfully, we have to pay costs from the P2P channel smart contract recv_internal or recv_external functions.

As mentioned earlier, we must add a number of GRAM & # 39; s to a non-bounceable future smart contract address to initialize it.

On the last day of the competition, the developers of TON committed to the stdlib.fc library with a new function that makes it possible to get the actual smart contract balance.


Suggestions for possible solutions to this problem are welcome!


FunC and Fift give every developer access to the low-level world of software engineering, opening new opportunities and functions for blockchain developers who are already used to Ethereum or another smart contract platform. It is important that TON is a shended blockchain, so implementing smart contracts is a bigger challenge. For example, the contracts of Ethereum are synchronous and do not require handling situations such as waiting for an answer from another contract.

The asynchronous way of smart contract communication is the only option to make it scalable and TON has these options. Our solution turned out to be harder to implement than Solidity, but there is always a trade-off. It is absolutely possible to build an advanced smart contract on TON, and the way the TON team dealt with it is very impressive. We look forward to more libraries and tools that help implement and build FunC contracts.

We greatly enjoyed all the tasks and wished we had more time to do them all. Nevertheless, we won two prizes at TON Contest: the first place for the best synchronous payment channel and the third place for the best asynchronous payment channel.

We will share our own personal feedback in part three.

The opinions, thoughts and opinions expressed here are for the authors only and do not necessarily reflect the views and opinions of Cointelegraph.

This article is co-written by Nick Kozlov and Kirill Kuznetsov.

Nick Kozlov is the CTO and co-founder of Button Wallet, a software developer and researcher, and one of the winners of the TON competition.

Kirill Kuznetsov is the co-founder of Button Wallet and one of the winners of the TON competition.

window.fbAsyncInit = function () { FB.init({ appId: ‘1922752334671725’, xfbml: true, version: ‘v2.9’ }); FB.AppEvents.logPageView(); }; (function (d, s, id) { var js, fjs = d.getElementsByTagName(s)(0); if (d.getElementById(id)) { return; } js = d.createElement(s); = id; js.src = “”; js.async = true; fjs.parentNode.insertBefore(js, fjs); }(document, ‘script’, ‘facebook-jssdk’)); !function (f, b, e, v, n, t, s) { if (f.fbq) return; n = f.fbq = function () { n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments) }; if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = ‘2.0’; n.queue = (); t = b.createElement(e); t.async = !0; t.src = v; s = b.getElementsByTagName(e)(0); s.parentNode.insertBefore(t, s) }(window, document, ‘script’, ‘’); fbq(‘init’, ‘1922752334671725’); fbq(‘track’, ‘PageView’);

Written by

Don Bradman

Leave a Reply

Your email address will not be published. Required fields are marked *