Docs
Testing

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 Counter

This 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 --test

This generates tests/Counter.test.tolk with a setupTest() helper and an example test case:

tests/Counter.test.tolk
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.

contracts/Counter.tolk
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.

contracts/types.tolk
struct (0x7e8764ef) Increase {
    amount: int32
}

type AllowedMessage = Increase
contracts/Counter.tolk
import "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:

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.

tests/Counter.test.tolk
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: ...): SendResultList

Usage 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/generated

Customize project defaults in Acton.toml:

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 Counter writes tests/counter-wrappers/Counter.gen.tolk without a test stub because the contract overrides those two Tolk fields
  • acton wrapper Counter --ts writes app/src/wrappers/counter/Counter.gen.ts
  • contracts without nested wrapper settings continue to use tests/generated-wrappers, tests/generated-tests, and wrappers-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

On this page