In TON, a contract’s address depends on its initial storage when it is created on-chain. Assign default values to fields that must be defined at deployment:
Copy
Ask AI
struct WalletStorage { // these fields must have these values when deploying // to make the contact's address predictable jettonBalance: coins = 0 isFrozen: bool = false // these fields must be manually assigned for deployment ownerAddress: address minterAddress: address}
To calculate the contract’s initial address, these two fields are required.
When developing multiple contracts in one project simultaneously, for example, a jetton minter and a jetton wallet, each contract has its own storage shape described by a struct.Name these structures descriptively, for example, MinterStorage and WalletStorage. Place these structures in a single file storage.tolk along with their methods.Contracts may deploy other contracts, requiring initial storage to be provided at deployment. For example, a minter deploys a wallet, so WalletStorage becomes accessible through an import:
Copy
Ask AI
// all symbols from imported files become visibleimport "storage"fun deploy(ownerAddress: address, minterAddress: address) { val emptyWalletStorage: WalletStorage = { ownerAddress, minterAddress, // the other two use their defaults }; // ...}
Contracts may start with a storage layout and extend it after deployment. For example:
At deployment, storage contains only a, b, c.
Followed by a message supplying d and e, storage becomes a, b, c, d, e.
This behavior is not related to nullable types. Nullable values such as int8? or cell? serialize with an explicit null marker 0 bit. In this case, fields are absent entirely, and no extra bits appear in serialization.This pattern is common in NFT contracts. Initially, an NFT contains only itemIndex and collectionAddress. After initialization, ownerAddress and content are added to the storage.Since arbitrary imperative code is allowed, the approach is:
Define two structures: initialized and uninitialized storages.
Start loading using contract.getData().
Determine whether the storage is initialized based on its bits and refs counts.
Parse into the corresponding struct.
Example:
Copy
Ask AI
// two structures representing different storage statesstruct NftItemStorage { itemIndex: uint64 collectionAddress: address ownerAddress: address content: cell}struct NftItemStorageNotInitialized { itemIndex: uint64 collectionAddress: address}// instead of the usual `load()` method — `startLoading()`fun NftItemStorage.startLoading() { return NftItemStorageLoader.fromCell(contract.getData())}fun NftItemStorage.save(self) { contract.setData(self.toCell())}// this helper detects shape of a storagestruct NftItemStorageLoader { itemIndex: uint64 collectionAddress: address private rest: RemainingBitsAndRefs}// when `rest` is empty, `collectionAddress` is the last fieldfun NftItemStorageLoader.isNotInitialized(self) { return self.rest.isEmpty()}// `endLoading` continues loading when `rest` is not emptyfun NftItemStorageLoader.endLoading(mutate self): NftItemStorage { return { itemIndex: self.itemIndex, collectionAddress: self.collectionAddress, ownerAddress: self.rest.loadAny(), content: self.rest.loadAny(), }}
Usage in onInternalMessage:
Copy
Ask AI
var loadingStorage = NftItemStorage.startLoading();if (loadingStorage.isNotInitialized()) { // ... probably, initialize and save return;}var storage = loadingStorage.endLoading();// and the remaining logic: lazy match, etc.
Different shapes with missing fields can also be expressed using generics and the void type.