Skip to main content
Contract storage is not “something special”. It is a regular struct, serialized into persistent blockchain data. Tolk does not impose strict rules, although several common guidelines are helpful in practice.

Common pattern: Storage, load(), and save()

A storage a regular structure. It is convenient to add load and store methods:
Then, at any point in a program, it can be easily accessed or modified:
Concepts used:
  • struct behaves similarly to a TypeScript class. See structures.
  • fun Storage.f(self) defines an instance method. See functions.
  • T.fromCell() deserializes a cell into T, and obj.toCell() packs it back into a cell. See automatic serialization.
  • lazy operator does this parsing on demand. See lazy loading.
  • contract.getData() fetches persistent data. See standard library.

Set default values to fields

In TON, the contract’s address depends on its initial storage, when a contract is created on-chain. A good practice is to assign default values to fields that must have defined values at deployment:
Therefore, to calculate an initial address, only two fields are required to be provided.

Multiple contracts in a project

When developing multiple contracts simultaneously (for example, a jetton minter and a jetton wallet), every contract has its own storage shape described by a struct. Give these structs reasonable names — for example, MinterStorage and WalletStorage. It’s better to place them in a single file (storage.tolk) together with their methods. Contracts often deploy each other, and initial storage must be provided during deployment. For example, a minter deploys a wallet, so WalletStorage becomes accessible via a simple import:
See sending messages for examples of deployment.

Storage that changes its shape

Another pattern for address calculation and for security is:
  • when a contract is deployed, it has fields a,b,c (uninitialized storage)
  • followed by a message supplying d,e — it becomes a,b,c,d,e
Such patterns are common in NFTs. Initially, an NFT has only itemIndex and collectionAddress, nothing more (an uninitialized NFT). Upon initialization, fields ownerAddress and content are appended to a storage. How can such logic be implemented? Since arbitrary imperative code is allowed, the suggested approach is:
  • describe two structures: “initialized” and “uninitialized” storage
  • start loading contract.getData()
  • detect whether storage is initialized based on its bits/refs counts
  • parse into one or another struct
A long demo with detailed comments:
Usage in onInternalMessage: