Crypto News

Solidity Is to Ethereum What Tact Is to TON — How to Build a Voting Smart Contract on TON Using Tact

Most times, when people start learning how to write smart contracts, the first thing they hear about is Solidity and Ethereum. That was the first thing I heard about too. It’s what most tutorials focus on, and for good reason. Solidity made it possible to write programs that live on a blockchain, and Ethereum became the place where many people got started.

But Solidity isn’t the only smart contract language out there. And Ethereum isn’t the only blockchain that supports decentralized applications.

There’s also TON, short for The Open Network. It was created by Telegram, but it’s now a public, community-driven chain. It’s fast, lightweight, and handles things a bit differently from what you might be used to on Ethereum. That includes how smart contracts are written. When I started exploring the TON documentation, I came across four different languages for writing smart contracts: Tact, Tolk, FunC, and Fift. I won’t go deep into all four here.

This guide focuses on the Tact language, and we’ll see how to use it to build a basic voting contract that lets users cast votes and check results on-chain.

Why I Decided to Learn Tact First

The TON ecosystem actually supports multiple languages, each serving different use cases, levels of abstraction, and developer experience. Here’s a quick overview of the of each of them:

  • FunC is the traditional language for writing TON smart contracts. It’s low-level and gives you precise control over how your contract works under the hood. It’s powerful, but it also means you’ll need to understand how the TON Virtual Machine (TVM) works, including concepts like stack manipulation, memory layout, and deterministic execution. The syntax is somewhat similar to C, which can feel unfamiliar if you haven’t worked with that style of language before.
  • Fift is typically used alongside FunC. It’s a stack-based language that’s mostly used for interacting with the TVM directly and used for deploying, debugging, and performing on-chain calculations. It’s not usually the language you start with for writing full smart contracts, but it’s important in the overall development workflow on TON.
  • Tolk is a newer addition that’s still evolving. From what I’ve gathered, it aims to improve tooling and compatibility with higher-level languages. It’s promising, but not yet as widely adopted or documented.
  • Tact is a high-level language that’s designed specifically to make TON smart contract development more accessible and developer friendly. Tact simplifies a lot of the lower-level complexity and lets you focus on writing your logic in a clean, readable way. The syntax is closer to what you’d see in TypeScript or Solidity, which makes it much easier to get started without needing to dive deep into TVM internals.

Tact provides and a quicker path for building and deploying contracts on the TON blockchain.

Understanding How Tact Works

Before we start writing code, it’s important to understand how Tact smart contracts are structured. A typical Tact contract includes a few core components:

  • contract block – This is where you define the name of your contract and declare any state variables.

  • init block – It initializes your contract’s state variables and sets the contract’s starting conditions. This block runs once at the time of deployment.

  • receive blocks – These are like event listeners. They handle incoming messages and define how your contract reacts to them.

  • Getter functions (get fun) – These are optional read-only functions that allow users or other contracts to query the contract’s state without changing it.

Tact uses message-based communication, which is how all interactions on TON work. Each contract receives a message and processes it in its own receive block. This message-based structure helps organize your contract logic in a modular, maintainable way.

Let’s now apply this in a real example by building a simple voting contract.

Building Your First Voting Contract with Tact (Using the TON Web IDE)

In this section, we’ll walk through how to implement a basic voting system using Tact. This voting contract will allows users to vote for predefined candidates and tracks the total number of votes each candidate receives.

We’ll be doing everything inside the TON Web IDE, which is an in-browser tool where you can write, build, and test your contracts without installing anything locally.

Step 1 – Open the TON Web IDE

  • Go to https://ide.ton.org.
  • Click Create a new project. In the popup:
    • Make sure the language is on Tact.
    • Choose Blank Contract as your template.
    • Name your project something like VotingContract.
    • Click + Create.

Step 2 – Writing the Voting Contract Code

After creating your project, open the main.tact file. You’ll see a boilerplate setup:

// Import the Deployable trait so the contract can be deployed easily
import "@stdlib/deploy";

contract BlankContract with Deployable {
    init() {
        
    }
}
  • import "@stdlib/deploy"; is required for deployment to work and should not be removed from the code.
  • BlankContract is the placeholder name.
  • The init() block runs only once when the contract is deployed and is used to initialize state variables.

Now, let’s map out our own code.

First, we’ll define the message structure for voting:

// Import the Deployable trait so the contract can be deployed easily
import "@stdlib/deploy";

// Define a message structure for voting
message Vote {
    candidate: Int as uint32; // 1 = Alice, 2 = Bob
}

This is the Vote message. When someone wants to vote, they’ll send a message to the contract that includes a number:

Tact uses this structure to process the incoming vote and decide which candidate gets the point.

Next, we’ll set up our contract and add two state variables to keep track of each candidate’s votes:

...
contract VotingContract with Deployable {

    // State variables to track votes
    votesAlice: Int as uint32;
    votesBob: Int as uint32;

Inside the contract, we defined two variables:

  • votesAlice: stores the number of votes Alice receives.
  • votesBob: stores the number of votes Bob receives.

We’ll now initialize those vote counts to zero inside the init block to set the starting state of the contract when it’s first deployed.

    init() {
        self.votesAlice = 0;
        self.votesBob = 0;
    }

The init block runs only once, right when the contract is deployed and it sets both vote counts to zero.

Now comes the logic. When a vote is sent, we want the contract to check who the vote is for and increase the correct vote count.

    // Handle vote messages
    receive(msg: Vote) {
        if (msg.candidate == 1) {
            self.votesAlice += 1;
        } else if (msg.candidate == 2) {
            self.votesBob += 1;
        }
    }

So when a vote is received:

  • If msg.candidate is 1, we add +1 to votesAlice
  • If msg.candidate is 2, we add +1 to votesBob

Finally, we’ll create getter functions to let anyone query the vote count for each candidate without changing the contract state.

    // Getter for Alice's votes
    get fun getVotesForAlice(): Int {
        return self.votesAlice;
    }

    // Getter for Bob's votes
    get fun getVotesForBob(): Int {
        return self.votesBob;
    }
}

These two getter functions let us check the number of votes each candidate has received without modifying anything in the contract. It’s a read-only operation.

Below is the full voting contract code:

import "@stdlib/deploy";

// Define a message structure for voting
message Vote {
    candidate: Int as uint32; // 1 = Alice, 2 = Bob
}

contract VotingContract with Deployable {

    // State variables to track votes
    votesAlice: Int as uint32;
    votesBob: Int as uint32;

    init() {
        self.votesAlice = 0;
        self.votesBob = 0;
    }

    // Handle vote messages
    receive(msg: Vote) {
        if (msg.candidate == 1) {
            self.votesAlice += 1;
        } else if (msg.candidate == 2) {
            self.votesBob += 1;
        }
    }

    // Getter for Alice's votes
    get fun getVotesForAlice(): Int {
        return self.votesAlice;
    }

    // Getter for Bob's votes
    get fun getVotesForBob(): Int {
        return self.votesBob;
    }
}

Step 4 – Build and Deploy the Contract

  • On the left sidebar, click on Build & Deploy

  • Under Environment, make sure Sandbox is selected.
  • Make sure main.tact is selected and click Build. This will compile your contract and check for any syntax errors or issues in your code.
  • Next, make sure VotingContract is selected in the dropdown as that’s your actual contract, not the default placeholder. If you don’t see it, press Ctrl + S to save your file so the IDE can detect the updated contract.
  • Then click ReDeploy. If everything works correctly, you’ll see a confirmation message in the logs showing that your contract was successfully deployed on Sandbox.

Step 5 – Interact With the Contract

Once deployed, scroll down and you’ll see two sections:

  • Getters: getVotesForAlice, getVotesForBob
  • Receivers: Vote

To cast a vote: In the Vote section, enter 1 in the candidate input field and click Send. You’ve just voted for Alice! You can repeat this to cast more votes.

To check the vote count: Click Call under getVotesForAlice and check the logs panel to see the vote count

  • Do the same for Bob by sending 2 in the candidate field, then check getVotesForBob

In my test run, I voted for Alice 9 times and Bob 6 times, and the getter functions showed exactly that.

💭 Final Thoughts: Keep Building, Keep Exploring

🙌 Congrats if you read all the way through!

Now that you’ve seen how a simple voting contract works in Tact, you’ve taken your first step into smart contract development on TON. This contract might be basic, but the structure and concepts apply to more complex logic too.

If you want to keep experimenting, try extending this contract or exploring other prebuilt templates from https://tact-by-example.org/all. The TON Web IDE also makes it easy to try out different use cases and it comes with templates as well to help you build and learn faster.

So go ahead, tweak, test, build something better.

Related Articles

Leave a Reply

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

Back to top button