> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ton.org/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/tolk/examples",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Tolk contract examples

export const FileTree = ({items = [], defaultOpen = true}) => {
  const renderItem = (item, index) => {
    if (item === "..." || item === "…") {
      return <Tree.File key={index} name="…" />;
    }
    if (typeof item === "string" || item.kind === "file") {
      const fileName = typeof item === "string" ? item : item.name;
      const note = typeof item === "string" ? null : item.note;
      const displayName = note ? `${fileName} — ${note}` : fileName;
      return <Tree.File key={index} name={displayName} />;
    }
    if (item.kind === "folder") {
      const isOpen = item.open ?? defaultOpen;
      const displayName = item.note ? `${item.name} — ${item.note}` : item.name;
      return <Tree.Folder key={index} name={displayName} defaultOpen={isOpen}>
          {item?.items?.map((nestedItem, nestedIndex) => renderItem(nestedItem, nestedIndex))}
          {}
        </Tree.Folder>;
    }
    throw new Error([`In the FileTree component, found: ${item}.`, `Expected either of: ..., …, string, { kind: "file", ... }, or { kind: "folder", ... }`].join(' '));
  };
  return <Tree>{items.map((item, index) => renderItem(item, index))}</Tree>;
};

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

Basic jetton and NFT contract examples from [`ton-blockchain/tolk-bench`](https://github.com/ton-blockchain/tolk-bench), extracted from commit [`cb9648b`](https://github.com/ton-blockchain/tolk-bench/tree/cb9648bdf936f88eb9d773d9058405f74a1e24d9), appear here as accordions by source file.

<Aside type="caution">
  All examples are given for educational purposes only. Never apply them directly in production without prior testing.
</Aside>

## Jetton

Source directory: [`contracts_Tolk/01_jetton`](https://github.com/ton-blockchain/tolk-bench/tree/cb9648bdf936f88eb9d773d9058405f74a1e24d9/contracts_Tolk/01_jetton).

<FileTree
  items={[{
kind: "folder",
name: "01_jetton/",
items: ["errors.tolk", "fees-management.tolk", "storage.tolk", "messages.tolk", "jetton-utils.tolk", "jetton-minter-contract.tolk", "jetton-wallet-contract.tolk"]
}]}
/>

Some files in the source directory are not runnable on their own and depend on others. Keep all the listed files together.

<AccordionGroup>
  <Accordion title="errors.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    const ERR_INVALID_OP = 709
    const ERR_NOT_FROM_ADMIN = 73
    const ERR_UNAUTHORIZED_BURN = 74
    const ERR_NOT_ENOUGH_AMOUNT_TO_RESPOND = 75
    const ERR_NOT_FROM_OWNER = 705
    const ERR_NOT_ENOUGH_TON = 709
    const ERR_NOT_ENOUGH_GAS = 707
    const ERR_INVALID_WALLET = 707
    const ERR_WRONG_WORKCHAIN = 333
    const ERR_NOT_ENOUGH_BALANCE = 706
    const ERR_INVALID_PAYLOAD = 708
    ```
  </Accordion>

  <Accordion title="fees-management.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    // 6905(computational_gas_price) * 1000(cur_gas_price) = 6905000 ~= 0.01 TON
    const MINIMAL_MESSAGE_VALUE_BOUND = ton("0.01")
    const MIN_TONS_FOR_STORAGE = ton("0.01")
    const JETTON_WALLET_GAS_CONSUMPTION = ton("0.015")
    ```
  </Accordion>

  <Accordion title="storage.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    struct WalletStorage {
        jettonBalance: coins
        ownerAddress: address
        minterAddress: address
    }

    struct MinterStorage {
        totalSupply: coins
        adminAddress: address
        content: cell
        jettonWalletCode: cell
    }


    fun MinterStorage.load() {
        return MinterStorage.fromCell(contract.getData())
    }

    fun MinterStorage.save(self) {
        contract.setData(self.toCell())
    }


    fun WalletStorage.load() {
        return WalletStorage.fromCell(contract.getData())
    }

    fun WalletStorage.save(self) {
        contract.setData(self.toCell())
    }
    ```
  </Accordion>

  <Accordion title="messages.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    type ForwardPayloadRemainder = RemainingBitsAndRefs

    struct (0x0f8a7ea5) AskToTransfer {
        queryId: uint64
        jettonAmount: coins
        transferRecipient: address
        sendExcessesTo: address?
        customPayload: cell?
        forwardTonAmount: coins
        forwardPayload: ForwardPayloadRemainder
    }

    struct (0x7362d09c) TransferNotificationForRecipient {
        queryId: uint64
        jettonAmount: coins
        transferInitiator: address?
        forwardPayload: ForwardPayloadRemainder
    }

    struct (0x178d4519) InternalTransferStep {
        queryId: uint64
        jettonAmount: coins
        // is null when minting (not initiated by another wallet)
        transferInitiator: address?
        sendExcessesTo: address?
        forwardTonAmount: coins
        forwardPayload: ForwardPayloadRemainder
    }

    struct (0xd53276db) ReturnExcessesBack {
        queryId: uint64
    }

    struct (0x595f07bc) AskToBurn {
        queryId: uint64
        jettonAmount: coins
        sendExcessesTo: address?
        customPayload: cell?
    }

    struct (0x7bdd97de) BurnNotificationForMinter {
        queryId: uint64
        jettonAmount: coins
        burnInitiator: address
        sendExcessesTo: address?
    }

    struct (0x2c76b973) RequestWalletAddress {
        queryId: uint64
        ownerAddress: address
        includeOwnerAddress: bool
    }

    struct (0xd1735400) ResponseWalletAddress {
        queryId: uint64
        jettonWalletAddress: address?
        ownerAddress: Cell<address>?
    }

    struct (0x00000015) MintNewJettons {
        queryId: uint64
        mintRecipient: address
        tonAmount: coins
        internalTransferMsg: Cell<InternalTransferStep>
    }

    struct (0x00000003) ChangeMinterAdmin {
        queryId: uint64
        newAdminAddress: address
    }

    struct (0x00000004) ChangeMinterContent {
        queryId: uint64
        newContent: cell
    }
    ```
  </Accordion>

  <Accordion title="jetton-utils.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import "storage"

    fun calcDeployedJettonWallet(
        ownerAddress: address,
        minterAddress: address,
        jettonWalletCode: cell,
    ): AutoDeployAddress {
        val emptyWalletStorage: WalletStorage = {
            jettonBalance: 0,
            ownerAddress,
            minterAddress,
        };

        return {
            stateInit: {
                code: jettonWalletCode,
                data: emptyWalletStorage.toCell(),
            }
        }
    }

    fun calcAddressOfJettonWallet(
        ownerAddress: address,
        minterAddress: address,
        jettonWalletCode: cell,
    ) {
        val jwDeployed = calcDeployedJettonWallet(
            ownerAddress,
            minterAddress,
            jettonWalletCode,
        );
        return jwDeployed.calculateAddress()
    }
    ```
  </Accordion>

  <Accordion title="jetton-minter-contract.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import "@stdlib/gas-payments"
    import "errors"
    import "jetton-utils"
    import "messages"
    import "storage"
    import "fees-management"

    type AllowedMessageToMinter =
        | MintNewJettons
        | BurnNotificationForMinter
        | RequestWalletAddress
        | ChangeMinterAdmin
        | ChangeMinterContent

    fun onInternalMessage(in: InMessage) {
        val msg = lazy AllowedMessageToMinter.fromSlice(in.body);

        match (msg) {
            BurnNotificationForMinter => {
                var storage = lazy MinterStorage.load();
                assert (in.senderAddress ==
                    calcAddressOfJettonWallet(
                        msg.burnInitiator,
                        contract.getAddress(),
                        storage.jettonWalletCode,
                    )) throw ERR_UNAUTHORIZED_BURN;

                storage.totalSupply -= msg.jettonAmount;
                storage.save();

                if (msg.sendExcessesTo == null) {
                    return;
                }

                val excessesMsg = createMessage({
                    bounce: BounceMode.NoBounce,
                    dest: msg.sendExcessesTo,
                    value: 0,
                    body: ReturnExcessesBack {
                        queryId: msg.queryId
                    }
                });
                excessesMsg.send(
                    SEND_MODE_IGNORE_ERRORS +
                    SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE
                );
            }

            RequestWalletAddress => {
                assert (in.valueCoins >
                    in.originalForwardFee + MINIMAL_MESSAGE_VALUE_BOUND)
                    throw ERR_NOT_ENOUGH_AMOUNT_TO_RESPOND;

                var respondOwnerAddress: Cell<address>? = msg.includeOwnerAddress
                    ? msg.ownerAddress.toCell()
                    : null;

                var walletAddress: address? = null;
                if (msg.ownerAddress.getWorkchain() == BASECHAIN) {
                    var storage = lazy MinterStorage.load();
                    walletAddress = calcAddressOfJettonWallet(
                        msg.ownerAddress,
                        contract.getAddress(),
                        storage.jettonWalletCode,
                    );
                }

                val respondMsg = createMessage({
                    bounce: BounceMode.Only256BitsOfBody,
                    dest: in.senderAddress,
                    value: 0,
                    body: ResponseWalletAddress {
                        queryId: msg.queryId,
                        jettonWalletAddress: walletAddress,
                        ownerAddress: respondOwnerAddress,
                    }
                });
                respondMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
            }

            MintNewJettons => {
                var storage = lazy MinterStorage.load();
                assert (in.senderAddress == storage.adminAddress)
                    throw ERR_NOT_FROM_ADMIN;

                var internalTransferMsg = lazy msg.internalTransferMsg.load();
                storage.totalSupply += internalTransferMsg.jettonAmount;
                storage.save();

                val deployMsg = createMessage({
                    bounce: BounceMode.Only256BitsOfBody,
                    dest: calcDeployedJettonWallet(
                        msg.mintRecipient,
                        contract.getAddress(),
                        storage.jettonWalletCode,
                    ),
                    value: msg.tonAmount,
                    // a newly-deployed wallet contract will immediately handle it
                    body: msg.internalTransferMsg,
                });
                deployMsg.send(SEND_MODE_PAY_FEES_SEPARATELY);
            }

            ChangeMinterAdmin => {
                var storage = lazy MinterStorage.load();
                assert (in.senderAddress == storage.adminAddress)
                    throw ERR_NOT_FROM_ADMIN;
                storage.adminAddress = msg.newAdminAddress;
                storage.save();
            }

            ChangeMinterContent => {
                var storage = lazy MinterStorage.load();
                assert (in.senderAddress == storage.adminAddress)
                    throw ERR_NOT_FROM_ADMIN;
                storage.content = msg.newContent;
                storage.save();
            }

            else => {
                // ignore empty messages, "wrong opcode" for others
                assert (in.body.isEmpty()) throw 0xFFFF
            }
        }
    }

    struct JettonDataReply {
        totalSupply: int
        mintable: bool
        adminAddress: address
        jettonContent: cell
        jettonWalletCode: cell
    }

    get fun get_jetton_data(): JettonDataReply {
        val storage = lazy MinterStorage.load();

        return {
            totalSupply: storage.totalSupply,
            mintable: true,
            adminAddress: storage.adminAddress,
            jettonContent: storage.content,
            jettonWalletCode: storage.jettonWalletCode,
        }
    }

    get fun get_wallet_address(ownerAddress: address): address {
        val storage = lazy MinterStorage.load();
        return calcAddressOfJettonWallet(
            ownerAddress,
            contract.getAddress(),
            storage.jettonWalletCode,
        );
    }
    ```
  </Accordion>

  <Accordion title="jetton-wallet-contract.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import "@stdlib/gas-payments"
    import "errors"
    import "jetton-utils"
    import "messages"
    import "fees-management"
    import "storage"

    type AllowedMessageToWallet =
        | AskToTransfer
        | AskToBurn
        | InternalTransferStep

    type BounceOpToHandle = InternalTransferStep | BurnNotificationForMinter

    fun onBouncedMessage(in: InMessageBounced) {
        in.bouncedBody.skipBouncedPrefix();

        val msg = lazy BounceOpToHandle.fromSlice(in.bouncedBody);
        val restoreAmount = match (msg) {
            // fetching jettonAmount is safe because
            // it is at the beginning of the message body
            InternalTransferStep => msg.jettonAmount,
            BurnNotificationForMinter => msg.jettonAmount,
        };

        var storage = lazy WalletStorage.load();
        storage.jettonBalance += restoreAmount;
        storage.save();
    }

    fun onInternalMessage(in: InMessage) {
        val msg = lazy AllowedMessageToWallet.fromSlice(in.body);

        match (msg) {
            InternalTransferStep => {
                var storage = lazy WalletStorage.load();
                if (in.senderAddress != storage.minterAddress) {
                    assert (in.senderAddress ==
                        calcAddressOfJettonWallet(
                            msg.transferInitiator!,
                            storage.minterAddress,
                            contract.getCode(),
                        )) throw ERR_INVALID_WALLET;
                }
                storage.jettonBalance += msg.jettonAmount;
                storage.save();

                var msgValue = in.valueCoins;
                var tonBalanceBeforeMsg = contract.getOriginalBalance() - msgValue;
                var storageFee = MIN_TONS_FOR_STORAGE - min(
                    tonBalanceBeforeMsg,
                    MIN_TONS_FOR_STORAGE,
                );
                msgValue -= (storageFee + JETTON_WALLET_GAS_CONSUMPTION);

                if (msg.forwardTonAmount) {
                    msgValue -= (msg.forwardTonAmount + in.originalForwardFee);

                    val notifyOwnerMsg = createMessage({
                        // cause receiver can have uninitialized contract
                        bounce: BounceMode.NoBounce,
                        dest: storage.ownerAddress,
                        value: msg.forwardTonAmount,
                        body: TransferNotificationForRecipient {
                            queryId: msg.queryId,
                            jettonAmount: msg.jettonAmount,
                            transferInitiator: msg.transferInitiator,
                            forwardPayload: msg.forwardPayload
                        }
                    });
                    notifyOwnerMsg.send(SEND_MODE_PAY_FEES_SEPARATELY);
                }

                if (msg.sendExcessesTo != null & (msgValue > 0)) {
                    val excessesMsg = createMessage({
                        bounce: BounceMode.NoBounce,
                        dest: msg.sendExcessesTo!,
                        value: msgValue,
                        body: ReturnExcessesBack {
                            queryId: msg.queryId
                        }
                    });
                    excessesMsg.send(SEND_MODE_IGNORE_ERRORS);
                }
            }

            AskToTransfer => {
                assert (msg.forwardPayload.remainingBitsCount())
                    throw ERR_INVALID_PAYLOAD;
                assert (msg.transferRecipient.getWorkchain() == BASECHAIN)
                    throw ERR_WRONG_WORKCHAIN;

                var storage = lazy WalletStorage.load();
                assert (in.senderAddress == storage.ownerAddress)
                    throw ERR_NOT_FROM_OWNER;
                assert (storage.jettonBalance >= msg.jettonAmount)
                    throw ERR_NOT_ENOUGH_BALANCE;
                storage.jettonBalance -= msg.jettonAmount;
                storage.save();

                var forwardedMessagesCount = msg.forwardTonAmount ? 2 : 1;
                assert (in.valueCoins >
                    msg.forwardTonAmount +
                    // 3 messages: wal1->wal2,  wal2->owner, wal2->response
                    // but last one is optional (it is ok if it fails)
                    forwardedMessagesCount * in.originalForwardFee +
                    (2 * JETTON_WALLET_GAS_CONSUMPTION + MIN_TONS_FOR_STORAGE)
                ) throw ERR_NOT_ENOUGH_TON;

                val deployMsg = createMessage({
                    bounce: BounceMode.Only256BitsOfBody,
                    dest: calcDeployedJettonWallet(
                        msg.transferRecipient,
                        storage.minterAddress,
                        contract.getCode(),
                    ),
                    value: 0,
                    body: InternalTransferStep {
                        queryId: msg.queryId,
                        jettonAmount: msg.jettonAmount,
                        transferInitiator: storage.ownerAddress,
                        sendExcessesTo: msg.sendExcessesTo,
                        forwardTonAmount: msg.forwardTonAmount,
                        forwardPayload: msg.forwardPayload,
                    }
                });
                deployMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
            }

            AskToBurn => {
                var storage = lazy WalletStorage.load();
                assert (in.senderAddress == storage.ownerAddress)
                    throw ERR_NOT_FROM_OWNER;
                assert (storage.jettonBalance >= msg.jettonAmount)
                    throw ERR_NOT_ENOUGH_BALANCE;
                storage.jettonBalance -= msg.jettonAmount;
                storage.save();

                val notifyMinterMsg = createMessage({
                    bounce: BounceMode.Only256BitsOfBody,
                    dest: storage.minterAddress,
                    value: 0,
                    body: BurnNotificationForMinter {
                        queryId: msg.queryId,
                        jettonAmount: msg.jettonAmount,
                        burnInitiator: storage.ownerAddress,
                        sendExcessesTo: msg.sendExcessesTo,
                    }
                });
                notifyMinterMsg.send(
                    SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE
                    | SEND_MODE_BOUNCE_ON_ACTION_FAIL
                );
            }

            else => {
                // ignore empty messages, "wrong opcode" for others
                assert (in.body.isEmpty()) throw 0xFFFF
            }
        }
    }

    struct JettonWalletDataReply {
        jettonBalance: coins
        ownerAddress: address
        minterAddress: address
        jettonWalletCode: cell
    }

    get fun get_wallet_data(): JettonWalletDataReply {
        val storage = lazy WalletStorage.load();

        return {
            jettonBalance: storage.jettonBalance,
            ownerAddress: storage.ownerAddress,
            minterAddress: storage.minterAddress,
            jettonWalletCode: contract.getCode(),
        }
    }
    ```
  </Accordion>
</AccordionGroup>

## NFT

Source directory: [`contracts_Tolk/02_nft`](https://github.com/ton-blockchain/tolk-bench/tree/cb9648bdf936f88eb9d773d9058405f74a1e24d9/contracts_Tolk/02_nft).

<FileTree
  items={[{
kind: "folder",
name: "02_nft/",
items: ["errors.tolk", "fees-management.tolk", "storage.tolk", "messages.tolk", "nft-collection-contract.tolk", "nft-item-contract.tolk"]
}]}
/>

Some files in the source directory are not runnable on their own and depend on others. Keep all the listed files together.

<AccordionGroup>
  <Accordion title="errors.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    const ERROR_NOT_FROM_ADMIN = 401
    const ERROR_NOT_FROM_OWNER = 401
    const ERROR_NOT_FROM_COLLECTION = 405
    const ERROR_BATCH_LIMIT_EXCEEDED = 399
    const ERROR_INVALID_ITEM_INDEX = 402
    const ERROR_INCORRECT_FORWARD_PAYLOAD = 708
    const ERROR_INVALID_WORKCHAIN = 333
    const ERROR_TOO_SMALL_REST_AMOUNT = 402
    ```
  </Accordion>

  <Accordion title="fees-management.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    const MIN_TONS_FOR_STORAGE = ton("0.05")
    ```
  </Accordion>

  <Accordion title="storage.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    // SnakeString describes a (potentially long) string inside a cell;
    // short strings are stored as-is, like "my-picture.png";
    // long strings are nested refs, like "xxxx".ref("yyyy".ref("zzzz"))
    type SnakeString = slice

    fun SnakeString.unpackFromSlice(mutate s: slice) {
        // SnakeString can be only the last — it's just the remainder;
        // For correctness, it's better to validate it has no more refs:
        //   assert (s.remainingRefsCount() <= 1) throw 5;
        // Since it is matching the original FunC implementation,
        // checks are not kept
        val snakeRemainder = s;
        s = createEmptySlice();     // no more left to read
        return snakeRemainder
    }

    fun SnakeString.packToBuilder(self, mutate b: builder) {
        b.storeSlice(self)
    }

    struct RoyaltyParams {
        numerator: uint16
        denominator: uint16
        royaltyAddress: address
    }

    struct NftCollectionStorage {
        adminAddress: address
        nextItemIndex: uint64
        content: Cell<CollectionContent>
        nftItemCode: cell
        royaltyParams: Cell<RoyaltyParams>
    }

    struct CollectionContent {
        collectionMetadata: cell
        commonContent: Cell<SnakeString>
    }

    struct NftItemStorage {
        itemIndex: uint64
        collectionAddress: address
        ownerAddress: address
        content: Cell<SnakeString>
    }

    struct NftItemStorageNotInitialized {
        itemIndex: uint64
        collectionAddress: address
    }

    fun NftCollectionStorage.load() {
        return NftCollectionStorage.fromCell(contract.getData())
    }

    fun NftCollectionStorage.save(self) {
        contract.setData(self.toCell())
    }

    // Actual storage of an NFT item is tricky: it's either initialized or not;
    // After NFT has been inited, it's represented as `NftItemStorage`;
    // Before initialization, it has only itemIndex and collectionAddress;
    // Hence, detect whether it's inited or not during parsing.
    struct NftItemStorageMaybeNotInitialized {
        contractData: slice
    }

    // how do we detect whether it's initialized or not?
    // the answer: when "inited", we store `content` (cell),
    // so, we have a ref, and for uninited, we don't have a ref
    fun NftItemStorageMaybeNotInitialized.isInitialized(self) {
        val hasContent = self.contractData.remainingRefsCount();
        return hasContent
    }

    fun NftItemStorageMaybeNotInitialized.parseNotInitialized(self) {
        return NftItemStorageNotInitialized.fromSlice(self.contractData)
    }

    fun NftItemStorageMaybeNotInitialized.parseInitialized(self) {
        return NftItemStorage.fromSlice(self.contractData)
    }

    fun startLoadingNftItemStorage(): NftItemStorageMaybeNotInitialized {
        return {
            contractData: contract.getData().beginParse()
        }
    }

    fun NftItemStorage.save(self) {
        contract.setData(self.toCell())
    }

    fun calcDeployedNftItem(
        itemIndex: uint64,
        collectionAddress: address,
        nftItemCode: cell,
    ): AutoDeployAddress {
        val emptyNftItemStorage: NftItemStorageNotInitialized = {
            itemIndex,
            collectionAddress,
        };

        return {
            stateInit: {
                code: nftItemCode,
                data: emptyNftItemStorage.toCell()
            }
        }
    }
    ```
  </Accordion>

  <Accordion title="messages.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import "storage"

    struct NftItemInitAtDeployment {
        ownerAddress: address
        content: Cell<SnakeString>
    }

    struct (0x693d3950) RequestRoyaltyParams {
        queryId: uint64
    }

    struct (0xa8cb00ad) ResponseRoyaltyParams {
        queryId: uint64
        royaltyParams: RoyaltyParams
    }

    struct (0x00000001) DeployNft {
        queryId: uint64
        itemIndex: uint64
        attachTonAmount: coins
        initParams: Cell<NftItemInitAtDeployment>
    }

    struct (0x00000002) BatchDeployNfts {
        queryId: uint64
        deployList: map<uint64, BatchDeployDictItem>
    }

    struct BatchDeployDictItem {
        attachTonAmount: coins
        initParams: Cell<NftItemInitAtDeployment>
    }

    struct (0x00000003) ChangeCollectionAdmin {
        queryId: uint64
        newAdminAddress: address
    }

    struct (0x2fcb26a2) RequestStaticData {
        queryId: uint64
    }

    struct (0x8b771735) ResponseStaticData {
        queryId: uint64
        itemIndex: uint256
        collectionAddress: address
    }

    struct (0x05138d91) NotificationForNewOwner {
        queryId: uint64
        oldOwnerAddress: address
        payload: RemainingBitsAndRefs
    }

    struct (0xd53276db) ReturnExcessesBack {
        queryId: uint64
    }

    struct (0x5fcc3d14) AskToChangeOwnership {
        queryId: uint64
        newOwnerAddress: address
        sendExcessesTo: address?
        customPayload: dict
        forwardTonAmount: coins
        forwardPayload: RemainingBitsAndRefs
    }
    ```
  </Accordion>

  <Accordion title="nft-collection-contract.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import "errors"
    import "storage"
    import "messages"

    fun deployNftItem(
        itemIndex: int,
        nftItemCode: cell,
        attachTonAmount: coins,
        initParams: Cell<NftItemInitAtDeployment>,
    ) {
        val deployMsg = createMessage({
            bounce: BounceMode.Only256BitsOfBody,
            dest: calcDeployedNftItem(
                itemIndex,
                contract.getAddress(),
                nftItemCode,
            ),
            value: attachTonAmount,
            body: initParams,
        });
        deployMsg.send(SEND_MODE_PAY_FEES_SEPARATELY);
    }

    type AllowedMessageToNftCollection =
        | RequestRoyaltyParams
        | DeployNft
        | BatchDeployNfts
        | ChangeCollectionAdmin

    fun onInternalMessage(in: InMessage) {
        val msg = lazy AllowedMessageToNftCollection.fromSlice(in.body);

        match (msg) {
            DeployNft => {
                var storage = lazy NftCollectionStorage.load();
                assert (in.senderAddress == storage.adminAddress)
                    throw ERROR_NOT_FROM_ADMIN;
                assert (msg.itemIndex <= storage.nextItemIndex)
                    throw ERROR_INVALID_ITEM_INDEX;

                var isLast = msg.itemIndex == storage.nextItemIndex;
                deployNftItem(
                    msg.itemIndex,
                    storage.nftItemCode,
                    msg.attachTonAmount,
                    msg.initParams,
                );
                if (isLast) {
                    storage.nextItemIndex += 1;
                    storage.save();
                }
            }

            RequestRoyaltyParams => {
                val storage = lazy NftCollectionStorage.load();
                val respondMsg = createMessage({
                    bounce: BounceMode.NoBounce,
                    dest: in.senderAddress,
                    value: 0,
                    body: ResponseRoyaltyParams {
                        queryId: msg.queryId,
                        royaltyParams: storage.royaltyParams.load(),
                    }
                });
                respondMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
            }

            BatchDeployNfts => {
                var storage = lazy NftCollectionStorage.load();
                assert (in.senderAddress == storage.adminAddress)
                    throw ERROR_NOT_FROM_ADMIN;

                var counter = 0;
                var r = msg.deployList.findFirst();
                while (r.isFound) {
                    counter += 1;
                    // due to limits of action list size
                    assert (counter < 250) throw ERROR_BATCH_LIMIT_EXCEEDED;

                    val itemIndex = r.getKey();
                    assert (itemIndex <= storage.nextItemIndex)
                        throw ERROR_NOT_FROM_ADMIN + counter;

                    val dictItem = r.loadValue();
                    deployNftItem(
                        itemIndex,
                        storage.nftItemCode,
                        dictItem.attachTonAmount,
                        dictItem.initParams,
                    );
                    if (itemIndex == storage.nextItemIndex) {
                        storage.nextItemIndex += 1;
                    }

                    r = msg.deployList.iterateNext(r);
                }
                storage.save();
            }

            ChangeCollectionAdmin => {
                var storage = lazy NftCollectionStorage.load();
                assert (in.senderAddress == storage.adminAddress)
                    throw ERROR_NOT_FROM_ADMIN;
                storage.adminAddress = msg.newAdminAddress;
                storage.save();
            }

            else => {
                // ignore empty messages, "wrong opcode" for others
                assert (in.body.isEmpty()) throw 0xFFFF
            }
        }
    }

    struct CollectionDataReply {
        nextItemIndex: int
        collectionMetadata: cell
        adminAddress: address
    }

    struct (0x01) OffchainMetadataReply {
        string: SnakeString
    }

    get fun get_collection_data(): CollectionDataReply {
        val storage = lazy NftCollectionStorage.load();
        val content = lazy storage.content.load();

        return {
            nextItemIndex: storage.nextItemIndex,
            collectionMetadata: content.collectionMetadata,
            adminAddress: storage.adminAddress,
        }
    }

    get fun get_nft_address_by_index(itemIndex: int): address {
        val storage = lazy NftCollectionStorage.load();
        val nftDeployed = calcDeployedNftItem(
            itemIndex,
            contract.getAddress(),
            storage.nftItemCode,
        );
        return nftDeployed.calculateAddress();
    }

    get fun royalty_params(): RoyaltyParams {
        val storage = lazy NftCollectionStorage.load();
        return storage.royaltyParams.load();
    }

    get fun get_nft_content(
        itemIndex: int,
        individualNftContent: Cell<SnakeString>,
    ): Cell<OffchainMetadataReply> {
        val storage = lazy NftCollectionStorage.load();
        val content = lazy storage.content.load();

        // construct a responce from "common content" and "individual content";
        // for example:
        // common content = "https://site.org/my-collection/"
        // individual nft = "my-picture-123.png" (a long, snake-encoded string)
        return OffchainMetadataReply {
            string: beginCell()
                // assume it's short (no refs)
                .storeSlice(content.commonContent.load())
                // so, it's the first ref (snake encoding)
                .storeRef(individualNftContent)
                .endCell().beginParse()
        }.toCell()
    }
    ```
  </Accordion>

  <Accordion title="nft-item-contract.tolk">
    ```tolk theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
    import "@stdlib/gas-payments"
    import "errors"
    import "storage"
    import "messages"
    import "fees-management"

    type AllowedMessageToNftItem =
        | AskToChangeOwnership
        | RequestStaticData

    fun onInternalMessage(in: InMessage) {
        var loadingStorage = startLoadingNftItemStorage();
        if (!loadingStorage.isInitialized()) {
            val uninitedSt = loadingStorage.parseNotInitialized();
            assert (in.senderAddress == uninitedSt.collectionAddress)
                throw ERROR_NOT_FROM_COLLECTION;

            // using a message from collection,
            // convert "uninitialized" to "initialized" state
            val initParams = NftItemInitAtDeployment.fromSlice(in.body);
            val storage: NftItemStorage = {
                itemIndex: uninitedSt.itemIndex,
                collectionAddress: uninitedSt.collectionAddress,
                ownerAddress: initParams.ownerAddress,
                content: initParams.content,
            };
            storage.save();
            return;
        }

        var storage = loadingStorage.parseInitialized();

        val msg = lazy AllowedMessageToNftItem.fromSlice(in.body);

        match (msg) {
            AskToChangeOwnership => {
                assert (in.senderAddress == storage.ownerAddress)
                    throw ERROR_NOT_FROM_OWNER;
                assert (msg.forwardPayload.remainingBitsCount())
                    throw ERROR_INCORRECT_FORWARD_PAYLOAD;
                assert (msg.newOwnerAddress.getWorkchain() == BASECHAIN)
                    throw ERROR_INVALID_WORKCHAIN;

                val fwdFee = in.originalForwardFee;
                var restAmount = contract.getOriginalBalance() - MIN_TONS_FOR_STORAGE;
                if (msg.forwardTonAmount) {
                    restAmount -= (msg.forwardTonAmount + fwdFee);
                }
                if (msg.sendExcessesTo != null) {
                    assert (msg.sendExcessesTo.getWorkchain() == BASECHAIN)
                        throw ERROR_INVALID_WORKCHAIN;
                    restAmount -= fwdFee;
                }

                // base nft spends fixed amount of gas, will not check for response
                assert (restAmount >= 0) throw ERROR_TOO_SMALL_REST_AMOUNT;

                if (msg.forwardTonAmount) {
                    val ownershipMsg = createMessage({
                        bounce: BounceMode.NoBounce,
                        dest: msg.newOwnerAddress,
                        value: msg.forwardTonAmount,
                        body: NotificationForNewOwner {
                            queryId: msg.queryId,
                            oldOwnerAddress: storage.ownerAddress,
                            payload: msg.forwardPayload,
                        }
                    });
                    ownershipMsg.send(SEND_MODE_PAY_FEES_SEPARATELY);
                }
                if (msg.sendExcessesTo != null) {
                    val excessesMsg = createMessage({
                        bounce: BounceMode.NoBounce,
                        dest: msg.sendExcessesTo,
                        value: restAmount,
                        body: ReturnExcessesBack {
                            queryId: msg.queryId,
                        }
                    });
                    excessesMsg.send(SEND_MODE_PAY_FEES_SEPARATELY);
                }

                storage.ownerAddress = msg.newOwnerAddress;
                storage.save();
            }

            RequestStaticData => {
                val respondMsg = createMessage({
                    bounce: BounceMode.NoBounce,
                    dest: in.senderAddress,
                    value: 0,
                    // The `itemIndex` was encoded as 256-bit in FunC implementation,
                    // we do the same here to pass FunC tests;
                    // As such, response becomes too long (64 + 256 + address),
                    // and the compiler will create a ref;
                    // To circumvent that, let's force the compiler to inline the body,
                    // since it is guaranteed that with value (coins) = 0,
                    // it will always fit into a message cell directly.
                    body: UnsafeBodyNoRef {
                        forceInline: ResponseStaticData {
                            queryId: msg.queryId,
                            itemIndex: storage.itemIndex as uint256,
                            collectionAddress: storage.collectionAddress,
                        }
                    }
                });
                respondMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
            }

            else => {
                // ignore empty messages, "wrong opcode" for others
                assert (in.body.isEmpty()) throw 0xFFFF
            }
        }
    }

    struct NftDataReply {
        isInitialized: bool
        itemIndex: int
        collectionAddress: address
        ownerAddress: address? = null
        content: Cell<SnakeString>? = null
    }

    get fun get_nft_data(): NftDataReply {
        var loadingStorage = startLoadingNftItemStorage();
        if (!loadingStorage.isInitialized()) {
            val uninitedSt = loadingStorage.parseNotInitialized();
            return {
                isInitialized: false,
                itemIndex: uninitedSt.itemIndex,
                collectionAddress: uninitedSt.collectionAddress,
            }
        }

        val storage = loadingStorage.parseInitialized();
        return {
            isInitialized: true,
            itemIndex: storage.itemIndex,
            collectionAddress: storage.collectionAddress,
            ownerAddress: storage.ownerAddress,
            content: storage.content,
        }
    }
    ```
  </Accordion>
</AccordionGroup>

## See also

* [Tolk language overview](/languages/tolk/overview)
* [Tolk vs FunC](/languages/tolk/from-func/tolk-vs-func)
