struct, serialized into persistent blockchain data.
Tolk does not impose strict rules, although several common guidelines are helpful in practice.
For convenience, keep the struct and its methods in a separate file, e.g.,
storage.tolk.
When developing multiple projects, consistent file structure improves navigation.Common pattern: Storage, load(), and save()
A storage a regular structure. It is convenient to addload and store methods:
structbehaves similarly to a TypeScript class. See structures.fun Storage.f(self)defines an instance method. See functions.T.fromCell()deserializes a cell intoT, andobj.toCell()packs it back into a cell. See automatic serialization.lazyoperator 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: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 astruct.
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:
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 becomesa,b,c,d,e
It’s not about nullable types — nullables like
int8? or cell?, being serialized as null,
are encoded as ‘0’ bit.
It’s about the absence of fields at all — no extra bits in serialization.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
onInternalMessage:
Different shapes with missing fields may also be expressed using generics and
the void type.
A powerful, but harder to understand solution.