In practice, contract code is split across multiple files. Projects with multiple contracts share a single codebase, including messages, storage layouts, and related logic. Symbols defined in one file can be used in another by importing that file. After an import, all its public symbols are available to the importing file.

Importing files

Error codes are commonly placed in a separate file, for example errors.tolk:
const ERR_ZERO_BALANCE = 123
// ...
To use these constants in parse.tolk, the file must be imported explicitly:
import "errors"

fun validate(balance: coins) {
    assert (balance > 0) throw ERR_ZERO_BALANCE;
}

Name uniqueness

All top-level symbols must have unique names.
  • There is no export const ... or export fun ... declarations needed.
  • All constants, functions, and other top-level declarations are visible by default.
  • No module‑private symbols exist.
As a result, all top-level symbols across imported files must have unique names. Declaring fun log() in multiple files causes a duplicate declaration error when both files are imported. Techniques to avoid name collisions:
  1. Use long, descriptive names for top-level symbols, especially in multi‑contract projects.
    • Good: ReturnExcessesBack, WalletStorage.
    • Bad: Excesses, Storage.
  2. Group integer constants to enums.
  3. Prefer methods to global-scope functions.

Repeated symbols across contracts

When developing multiple contracts, each contract has its own file and compilation target. Place a contract declaration in every entrypoint file:
// file: ContractA.tolk
import "storage"
import "errors"

contract ContractA {
    storage: StorageA
    incomingMessages: MsgsA
}

fun onInternalMessage(in: InMessage) {
    // ...
}

get fun name() {
    return "a"
}
// file: ContractB.tolk
import "storage"
import "errors"
import "ContractA"   // brings types from ContractA, but NOT its entrypoints

contract ContractB {
    storage: StorageB
    incomingMessages: MsgsB
}

fun onInternalMessage(in: InMessage) {
    // ...
}

get fun name() {
    return "b"
}
onInternalMessage, onExternalMessage, and get fun belong to a specific contract. When a file declares contract, importing it exposes its types and functions but not these entrypoints — so ContractB can reuse ContractA’s storage and messages without any naming conflicts on onInternalMessage or get methods. In addition, when contract is present, all entrypoints must live in the same file as the contract declaration; importing a file that declares a get fun is not allowed. In a multi-contract project, each contract file typically contains only:
  • its contract declaration,
  • its entrypoints,
  • and contract-specific logic.
The remaining codebase — messages, errors, utils, shared storage structs — is split into supplementary files that every contract may import.

Import path mappings

Apart from relative paths, the import statement can accept @-aliases:
import "@common/jettons"
import "@third_party/math-lib"
This mechanism resembles path mappings in TypeScript, i.e., paths in tsconfig.json. Unlike paths, Tolk does not use file masks: each alias is specified independently. Specify mappings at compiler invocation:
tolk --path-mapping @common=/some/dir --path-mapping @third_party=/another/dir ...
Mapped aliases can point to absolute or relative directories:
"@node_modules": "../../../node_modules"