Docs
Your first smart contract

1. Create an empty project

Scaffold a new Acton project from the empty template, explore its structure, and run the first build.

Scaffold the project

acton new poll --template empty

Expected output:

terminal
$ acton new poll --template empty
 Created new Acton project
  Project name: poll
  Description: A TON blockchain project
  Template: empty
  License: MIT

Created Acton.toml with project configuration

Next steps:
...
cd poll

Explore the project structure

Empty.tolkContract entry point
types.tolkStorage, messages, and errors
deploy.tolkDeployment script
contract.test.tolkContract tests
Empty.gen.tolkGenerated contract wrapper
Acton.tomlProject manifest

Acton.toml declares which .tolk files are contract entry points, where tests live, and any script aliases to run with acton run:

Acton.toml
[package]
name = "poll"
description = "A TON blockchain project"
version = "0.1.0"
license = "MIT"

[contracts.Empty]
src = "contracts/Empty.tolk"

[test]
include = ["tests/**"]

contracts/Empty.tolk is the contract entry point. Every message handler and getter lives here or in files it imports:

contracts/Empty.tolk
import "types"

contract Empty {
    author: "<author>"
    version: "1.0.0"
    description: "Minimal starter contract with owner management"
    storage: Storage
    incomingMessages: AllowedMessage
}

type AllowedMessage = ChangeOwner

fun onInternalMessage(in: InMessage) {
    val msg = lazy AllowedMessage.fromSlice(in.body);

    match (msg) {
        ChangeOwner => {
            var storage = lazy Storage.load();
            assert (in.senderAddress == storage.owner) throw Errors.NotOwner;
            storage.owner = msg.newOwner;
            storage.save();
        }
        else => {
            assert (in.body.isEmpty()) throw Errors.InvalidMessage;
        }
    }
}

fun onBouncedMessage(_in: InMessageBounced) {}

get fun owner(): address {
    val storage = lazy Storage.load();
    return storage.owner;
}

The shape to remember:

  • contract declaration names the contract and declares its storage type and message union
  • onInternalMessage handles every inbound message
  • match (msg) dispatches on the message type
  • get fun functions are read-only getters callable off-chain

contracts/types.tolk holds storage structs, error codes, and message structs:

contracts/types.tolk
enum Errors {
    NotOwner = 100
    InvalidMessage = 0xFFFF
}

struct Storage {
    owner: address
}

fun Storage.load(): Storage {
    return Storage.fromCell(contract.getData());
}

fun Storage.save(self) {
    contract.setData(self.toCell());
}

struct (0x2ce05111) ChangeOwner {
    newOwner: address
}

The hex literal in struct (0x2ce05111) ChangeOwner is the opcode — a 32-bit identifier automatically prepended to the message body. When onInternalMessage dispatches with match, the runtime reads those 4 bytes to find the right branch.

wrappers/Empty.gen.tolk is auto-generated by acton wrapper. It wraps the contract in a typed API for tests and scripts. Regenerate it whenever the contract changes.

.acton/ is the Acton standard library. Do not edit this directory.

Build the project

acton build
terminal
$ acton build
   Compiling contracts
   Compiling Empty
    Finished in 13.567916ms
    Finished in 15.653666ms

The compiled bytecode lands in build/Empty.json:

build/Empty.json
{
  "code_boc64": "te6ccgEBBAEAbwABFP8A9KQT9Lzy...",
  "hash": "A7F3..."
}

Run with --info for the artifacts overview in the output:

acton build --info
terminal
$ acton build --info
  Compiling contracts
   Finished in 298.17µs

  Artifacts of Empty
       Code te6ccgEBBAEATwABFP8A9KQT9Lzy...
       Hash 0x469C66F1F6F75827610074DD67EBF392486301F438BDCF02FFFEF95E17761DA8

Lint and format

acton check
terminal
$ acton check
    Checking Empty
    Checking scripts/deploy.tolk
    Checking tests/contract.test.tolk
acton fmt --check
terminal
$ acton fmt --check
All files are properly formatted

Run acton fmt without --check to auto-format files in place.

Checkpoint

The project compiles and passes lint. The next page sketches the first version of the poll contract — and runs into a real limitation of the TON blockchain.

Commands introduced: acton new, acton build, acton build --info, acton check, acton fmt --check

Last updated on

On this page