Skip to main content
Some protocols need to store a lot of information in contracts, for example tokens that have many users. In TON there is a limit on how much can be stored in a single contract. The solution in TON is to split the data across many different contracts, where you can quickly find the right contract by a key and retrieve the required information from it. In such protocols there is a child contract that initially contains the information identified by a key. In some protocols it is important to know the Parent contract, which acts as the information manager. To avoid having to know the key upfront, in StateInit we do not populate that field; we only populate the key field. This makes it easy to locate the required contract later.
Child contracts should store information about the Parent so that only it can authorize important state changes.
Shard pattern pic Consider NFTs: the collection serves as the Parent contract, and the NFT items are the child contracts. The key in this case is the index, and only a message from the collection can set the initial owner. For Jettons, the Parent is the minter and the Children are user wallets. The key is the user’s smart contract address, and the value is the user’s token balance. In general, jettons and NFTs share this principle, but broadly speaking, jetton protocols have a unique contract per user, while NFTs have a single contract per item (by index) that is shared across all users.

Unbounded data structures

An interesting property of this pattern is that the number of potential children is unbounded! We can have an infinite number of children. In general, infinite data structures that can actually scale to billions are very difficult to implement on blockchain efficiently. This pattern showcases the power of TON.
Tact
import "@stdlib/deploy";

// we have multiple instances of the children
contract TodoChild {

    seqno: Int as uint64;
 
    // when deploying an instance, we must specify its index (sequence number)
    init(seqno: Int) {
        self.seqno = seqno;
    }

    // this message handler will just debug print the seqno so we can see when it's called
    receive("identify") {
        dump(self.seqno);
    }
}

// we have one instance of the parent
contract TodoParent with Deployable {

    numChildren: Int as uint64;
 
    init() {
        self.numChildren = 0;
    }

    // this message handler will cause the contract to deploy another child
    receive("deploy another") {
        self.numChildren = self.numChildren + 1;
        let init: StateInit = initOf TodoChild(self.numChildren);
        send(SendParameters{
            to: contractAddress(init),
            value: ton("0.1"),              // pay for message, the deployment and give some TON for storage
            mode: SendIgnoreErrors,
            code: init.code,                // attaching the state init will cause the message to deploy
            data: init.data,
            body: "identify".asComment()    // we must piggyback the deployment on another message
        });
    }

    get fun numChildren(): Int {
        return self.numChildren;
    }
}
I