Generating wrappers
Learn how to automatically generate wrappers for contracts
Integration tests often need utility wrapper files to interact with smart contracts: send messages and call get-methods. The acton wrapper command generates those wrappers from contract ABI.
For precompiled .boc contracts, add a types interface file first. See Precompiled BoC contracts.
Generate a wrapper
Run acton wrapper with the contract name from Acton.toml:
acton wrapper CounterThis will generate a Tolk wrapper file, e.g., wrappers/Counter.gen.tolk.
Bootstrap tests with --test
New contract tests repeat the same boilerplate: imports, deploy, and basic checks. Pass --test to emit a runnable test stub file in addition to the wrapper:
acton wrapper Counter --testThis generates tests/Counter.test.tolk with a setupTest() helper and an example test case:
import "wrappers/Counter.gen"
import "@acton/emulation/testing"
// ... other imports
/// Initializes the test environment, creating a fresh instance of the contract.
/// Returns the contract wrapper and two treasury accounts (`deployer` and `notDeployer`).
fun setupTest(): (Counter, Treasury, Treasury) {
// Create a treasury account for deployment (typically the owner)
val deployer = testing.treasury("deployer");
// Create another treasury account for testing interactions from other users
val notDeployer = testing.treasury("notDeployer");
// Initialize and deploy the contract with default values
val contract = Counter.fromStorage({
id: 0,
owner: deployer.address,
counter: 0,
});
val res = contract.deploy(deployer.address, { value: grams("1") });
expect(res).toHaveSuccessfulDeploy({ to: contract.address });
return (contract, deployer, notDeployer);
}
/// Example test case demonstrating the basic flow
get fun `test basic flow`() {
val (contract, deployer, notDeployer) = setupTest();
// TODO: Implement your test logic here
// Example:
// val res = contract.sendMsg(deployer.address, ...);
// expect(res).toHaveTransaction({ ... });
}Requirements and caveats
Contract header is required
Wrapper generation uses the ABI emitted by the Tolk compiler. The contract header is the source of truth for:
- storage, via
storage: ...; - incoming message wrappers, via
incomingMessages: ....
If incomingMessages is not declared in the contract header, send{MessageName} methods will not be generated. If storage is missing, the wrapper falls back to an empty fromStorage() initializer instead of a typed storage-based one.
contract Counter {
storage: Storage
incomingMessages: AllowedMessage
}
struct Storage {
counter: uint32
}
struct (0x7e8764ef) Increase {
amount: int32
}
type AllowedMessage = Increase
fun onInternalMessage(in: InMessage) {
val msg = lazy AllowedMessage.fromSlice(in.body);
match (msg) {
Increase => { ... }
}
}Types in the contract file or imports
Storage and message types may live either in the contract file itself or in imported files. Both layouts are supported.
struct (0x7e8764ef) Increase {
amount: int32
}
type AllowedMessage = Increaseimport "types"
contract Counter {
storage: Storage
incomingMessages: AllowedMessage
}
struct Storage {
counter: uint32
}
fun onInternalMessage(in: InMessage) {
val msg = lazy AllowedMessage.fromSlice(in.body);
match (msg) {
Increase => { ... }
}
}Register the contract in Acton.toml
The command takes a contract name, not a file path. The contract must appear under [contracts.<Name>] in Acton.toml:
[contracts.Counter]
display-name = "Counter"
src = "contracts/Counter.tolk"Use the generated wrapper
The generated wrapper provides a struct representing the contract and helper methods to deploy and interact with it.
Initialize and deploy
The wrapper provides static fromStorage() method to create the initial state and a deploy method to put it on the chain.
import "@wrappers/Counter.gen"
// NOTE: Always import types used by the storage struct!
import "@contracts/types"
import "@acton/emulation/testing"
get fun `test example`() {
// 1. Create contract instance from storage data
val contract = Counter.fromStorage({
counter: 0,
});
// 2. Deploy it
val deployer = testing.treasury("deployer");
contract.deploy(deployer.address, { value: grams("1") });
}Send messages
For every message type listed in contract { incomingMessages: ... }, the wrapper generates a corresponding send{MessageName} method.
For instance, if the counter contract handles the Increase message, the method signature would look like this:
// Generated method signature:
fun Counter.sendIncrease(self, from: address, amount: int, config: ...): SendResultListUsage examples:
// Send the "Increase" message
contract.sendIncrease(deployer.address, 10);
// With custom value and bounce flag
contract.sendIncrease(deployer.address, 10, {
value: grams("0.5"),
bounce: true
});Call get methods
Get-method helpers are also generated. They return typed values directly:
val current = contract.currentCounter();
expect(current).toEqual(10);Command-line options
Customize output paths:
# Generate wrapper in a specific file
acton wrapper Counter --output tests/my_wrapper.tolk
# Generate wrapper into a specific directory
acton wrapper Counter --output-dir tests/generated
# Generate a test file alongside the wrapper
acton wrapper Counter --test --test-output tests/my.test.tolk
# Generate a test file into a specific directory
acton wrapper Counter --test --test-output-dir tests/generatedCustomize project defaults in Acton.toml:
[wrappers.tolk]
output-dir = "tests/generated-wrappers"
generate-test = true
test-output-dir = "tests/generated-tests"
[wrappers.typescript]
output-dir = "./wrappers-ts"
[contracts.Counter.wrappers.tolk]
output-dir = "tests/counter-wrappers"
generate-test = false
[contracts.Counter.wrappers.typescript]
output-dir = "app/src/wrappers/counter"With this configuration:
acton wrapper Counterwritestests/counter-wrappers/Counter.gen.tolkwithout a test stub because the contract overrides those two Tolk fieldsacton wrapper Counter --tswritesapp/src/wrappers/counter/Counter.gen.ts- contracts without nested wrapper settings continue to use
tests/generated-wrappers,tests/generated-tests, andwrappers-ts
Per-contract settings use the same fields as the project defaults and inherit any fields they omit. CLI path and test flags override both levels.
See the acton wrapper command reference for more details on CLI options.
Last updated on