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
Next steps:
...cd 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
Compiling Empty
Finished in 13.567916ms
Finished in 15.653666msThe compiled bytecode lands in build/Empty.json:
{
"code_boc64": "te6ccgEBBAEAbwABFP8A9KQT9Lzy...",
"hash": "A7F3..."
}Run with --info for the artifacts overview in the output:
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