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 emptyExpected output:
$ 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
Created .acton/ directory with standard library
Created .acton/tolk-stdlib directory with Tolk standard librarycd pollExplore the project structure
Acton.toml declares which .tolk files are contract entry points, where tests live, and any script aliases to run with acton run:
[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:
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:
contractdeclaration names the contract and declares its storage type and message uniononInternalMessagehandles every inbound messagematch (msg)dispatches on the message typeget funfunctions are read-only getters callable off-chain
contracts/types.tolk holds storage structs, error codes, and message structs:
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$ acton build
Compiling contracts
Finished in 312.41µsThe compiled bytecode lands in build/Empty.json:
{
"code_boc64": "te6ccgEBBAEAbwABFP8A9KQT9Lzy...",
"hash": "A7F3..."
}Run with --info for a size summary:
acton build --info$ acton build --info
Compiling contracts
Finished in 298.17µs
Artifacts of Empty
Code te6ccgEBBAEATwABFP8A9KQT9Lzy...
Hash 0x469C66F1F6F75827610074DD67EBF392486301F438BDCF02FFFEF95E17761DA8Lint and format
acton check$ acton check
Checking Empty
Checking scripts/deploy.tolk
Checking tests/contract.test.tolkacton fmt --check$ acton fmt --check
All files are properly formattedRun 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