---
title: Fungible Tokens (Jettons)
description: "Common examples of working with Fungible Tokens (Jettons) in Tact"
---

This page lists common examples of working with [Fungible Tokens (Jettons)](https://docs.ton.org/develop/dapps/asset-processing/jettons).

Jettons are token standards on the TON Blockchain, designed to create fungible tokens (similar to ERC-20 on Ethereum) using a decentralized approach. They are implemented as a pair of smart contracts, typically consisting of two core components:

* Jetton Master Contract (Jetton master)
* Jetton Wallet Contract (Jetton wallet)

These contracts interact with each other to manage token supply, distribution, transfers, and other operations related to the Jetton.

## Jetton Master Contract

The Jetton Master Contract serves as the central entity for a given Jetton. It maintains critical information about the Jetton itself. Key responsibilities and data stored in the Jetton Master Contract include:

* Jetton metadata: Information such as the token's name, symbol, total supply, and decimals.

* Minting and burning: When new Jettons are minted (created), the Jetton Master manages the creation process and distributes them to the appropriate wallets. It also manages the burning (destruction) of tokens as needed.

* Supply management: The Jetton Master keeps track of the total supply of Jettons and ensures proper accounting of all issued Jettons.

## Jetton Wallet Contract

The Jetton Wallet Contract represents an individual holder's token wallet and is responsible for managing the balance and token-related operations for a specific user. Each user or entity holding Jettons will have its own unique Jetton Wallet Contract. Key features of the Jetton Wallet Contract include:

* Balance tracking: The wallet contract stores the user's token balance.

* Token transfers: The wallet is responsible for handling token transfers between users. When a user sends Jettons, the Jetton Wallet Contract ensures proper transfer and communication with the recipient's wallet. The Jetton Master is not involved in this activity and does not create a bottleneck. Wallets can effectively utilize TON's sharding capability.

* Token burning: The Jetton Wallet interacts with the Jetton Master to burn tokens.

* Owner control: The wallet contract is owned and controlled by a specific user, meaning that only the owner of the wallet can initiate transfers or other token operations.

## Examples

The following are common examples of working with Jettons.

For larger Jetton recipes that include smart contracts, auxiliary scripts, and testing suites, see the [Tact's DeFi Cookbook on GitHub](https://github.com/tact-lang/defi-cookbook).

### Accepting Jetton transfer

The transfer notification message has the following structure:

```tact
message(0x7362d09c) JettonTransferNotification {
    // Unique identifier used to trace transactions across multiple contracts
    // Defaults to 0, which means we don't mark messages to trace their chains
    queryId: Int as uint64 = 0;

    // Amount of Jettons transferred
    amount: Int as coins;

    // Address of the sender of the Jettons
    sender: Address;

    // Optional custom payload
    forwardPayload: Slice as remaining;
}
```

Use the [receiver](/book/receive) function to accept token notification messages.

:::caution

  The sender of transfer notifications must be validated!

:::

The sender of a transfer notification must be validated because malicious actors could attempt to spoof notifications from an unauthorized account. If this validation is not performed, the contract may accept unauthorized transactions, leading to potential security vulnerabilities.

Validation is performed using the Jetton address from the contract:

1. Sender sends a message with `0xf8a7ea5` as its 32-bit header (opcode) to their Jetton wallet.
2. The Jetton wallet transfers funds to the contract's Jetton wallet.
3. After successfully accepting the transfer, the contract's Jetton wallet sends a transfer notification to its owner contract.
4. The contract validates the Jetton message.

You may obtain the contract's Jetton wallet address using the [`contractAddress(){:tact}`](/ref/core-addresses#contractaddress) function or calculating this address offchain.

To obtain the Jetton wallet's state init, you need the wallet's data and code. While there is a common structure for the initial data layout, it may differ in some cases, such as with [USDT](#usdt-jetton-operations).

Since notifications originate from your contract's Jetton wallet, the function [`myAddress(){:tact}`](/ref/core-contextstate#myaddress) should be used in the `ownerAddress` field.

:::caution

  Notifications are not always guaranteed to be sent. By default, the implementation does not send a notification if the `forwardAmount` is set to zero. Therefore, in such cases, you cannot rely on notifications being sent.

:::

```tact
struct JettonWalletData {
    balance: Int as coins;
    ownerAddress: Address;
    jettonMasterAddress: Address;
    jettonWalletCode: Cell;
}

fun calculateJettonWalletAddress(
    ownerAddress: Address,
    jettonMasterAddress: Address,
    jettonWalletCode: Cell,
): Address {
    let initData = JettonWalletData {
        balance: 0,
        ownerAddress,
        jettonMasterAddress,
        jettonWalletCode,
    };

    return contractAddress(StateInit {
        code: jettonWalletCode,
        data: initData.toCell(),
    });
}

message(0x7362d09c) JettonTransferNotification {
    queryId: Int as uint64;
    amount: Int as coins;
    sender: Address;
    forwardPayload: Slice as remaining;
}

contract Example {
    myJettonWalletAddress: Address;
    myJettonAmount: Int as coins = 0;

    init(jettonWalletCode: Cell, jettonMasterAddress: Address) {
        self.myJettonWalletAddress = calculateJettonWalletAddress(
            myAddress(),
            jettonMasterAddress,
            jettonWalletCode,
        );
    }

    // Empty receiver for the deployment,
    // which forwards the remaining value back to the sender
    receive() { cashback(sender()) }

    receive(msg: JettonTransferNotification) {
        require(
            sender() == self.myJettonWalletAddress,
            "Notification not from your jetton wallet!",
        );

        self.myJettonAmount += msg.amount;

        // Forward excesses
        self.forward(msg.sender, null, false, null);
    }
}
```

### Sending Jetton transfer

A Jetton transfer is the process of sending a specified amount of Jettons from one wallet (contract) to another.

To send a Jetton transfer, use the [`send(){:tact}`](/book/send) function.

```tact
message(0xf8a7ea5) JettonTransfer {
    queryId: Int as uint64;
    amount: Int as coins;
    destination: Address;
    responseDestination: Address?;
    customPayload: Cell? = null;
    forwardTonAmount: Int as coins;
    forwardPayload: Slice as remaining;
}

const JettonTransferGas: Int = ton("0.05");

struct JettonWalletData {
    balance: Int as coins;
    ownerAddress: Address;
    jettonMasterAddress: Address;
    jettonWalletCode: Cell;
}

fun calculateJettonWalletAddress(
    ownerAddress: Address,
    jettonMasterAddress: Address,
    jettonWalletCode: Cell,
): Address {
    let initData = JettonWalletData {
        balance: 0,
        ownerAddress,
        jettonMasterAddress,
        jettonWalletCode,
    };

    return contractAddress(StateInit {
        code: jettonWalletCode,
        data: initData.toCell(),
    });
}

message Withdraw {
    amount: Int as coins;
}

contract Example {
    myJettonWalletAddress: Address;
    myJettonAmount: Int as coins = 0;

    init(jettonWalletCode: Cell, jettonMasterAddress: Address) {
        self.myJettonWalletAddress = calculateJettonWalletAddress(
            myAddress(),
            jettonMasterAddress,
            jettonWalletCode,
        );
    }

    // Empty receiver for the deployment,
    // which forwards the remaining value back to the sender
    receive() { cashback(sender()) }

    receive(msg: Withdraw) {
        require(
            msg.amount <= self.myJettonAmount,
            "Not enough funds to withdraw",
        );

        send(SendParameters {
            to: self.myJettonWalletAddress,
            value: JettonTransferGas,
            body: JettonTransfer {
                // Unique identifier used to trace transactions across multiple contracts
                queryId: 42,
                // Jetton amount to transfer
                amount: msg.amount,
                // Where to transfer Jettons:
                // this is the address of the Jetton wallet
                // owner and not the Jetton wallet itself
                destination: sender(),
                // Where to send a confirmation notice of a successful transfer
                // and the remainder of the incoming message value
                responseDestination: sender(),
                // Can be used for custom logic of the Jettons themselves,
                // or set to null otherwise
                customPayload: null,
                // Amount to transfer with JettonTransferNotification,
                // which is needed for the execution of custom logic
                forwardTonAmount: 1, // if it's 0, the notification won't be sent!
                // Compile-time way of expressing:
                //     beginCell().storeUint(0xF, 4).endCell().beginParse()
                // For more complicated transfers, adjust accordingly
                forwardPayload: rawSlice("F"),
            }.toCell(),
        });
    }
}
```

### Burning Jetton

Jetton burning is the process of permanently removing a specified amount of Jettons from circulation, with no possibility of recovery.

```tact
message(0x595f07bc) JettonBurn {
    queryId: Int as uint64;
    amount: Int as coins;
    responseDestination: Address?;
    customPayload: Cell? = null;
}

const JettonBurnGas: Int = ton("0.05");

struct JettonWalletData {
    balance: Int as coins;
    ownerAddress: Address;
    jettonMasterAddress: Address;
    jettonWalletCode: Cell;
}

fun calculateJettonWalletAddress(
    ownerAddress: Address,
    jettonMasterAddress: Address,
    jettonWalletCode: Cell,
): Address {
    let initData = JettonWalletData {
        balance: 0,
        ownerAddress,
        jettonMasterAddress,
        jettonWalletCode,
    };

    return contractAddress(StateInit {
        code: jettonWalletCode,
        data: initData.toCell(),
    });
}

message ThrowAway {
    amount: Int as coins;
}

contract Example {
    myJettonWalletAddress: Address;
    myJettonAmount: Int as coins = 0;

    init(jettonWalletCode: Cell, jettonMasterAddress: Address) {
        self.myJettonWalletAddress = calculateJettonWalletAddress(
            myAddress(),
            jettonMasterAddress,
            jettonWalletCode,
        );
    }

    // Empty receiver for the deployment,
    // which forwards the remaining value back to the sender
    receive() { cashback(sender()) }

    receive(msg: ThrowAway) {
        require(
            msg.amount <= self.myJettonAmount,
            "Not enough funds to throw away",
        );

        send(SendParameters {
            to: self.myJettonWalletAddress,
            value: JettonBurnGas,
            body: JettonBurn {
                // Unique identifier used to trace transactions across multiple contracts
                queryId: 42,
                // Jetton amount you want to burn
                amount: msg.amount,
                // Destination address for a confirmation notice of a successful burn
                // and the remainder of the incoming message value
                responseDestination: sender(),
                // Can be used for custom logic of Jettons;
                // if unused, it can be set to null
                customPayload: null,
            }.toCell(),
        });
    }
}
```

### USDT Jetton operations

Operations with USDT (on TON) remain the same, except that `JettonWalletData` will have the following structure:

```tact
struct JettonWalletData {
    status: Int as uint4;
    balance: Int as coins;
    ownerAddress: Address;
    jettonMasterAddress: Address;
}

// The function to calculate the wallet address may look like this:
fun calculateJettonWalletAddress(
    ownerAddress: Address,
    jettonMasterAddress: Address,
    jettonWalletCode: Cell,
): Address {
    let initData = JettonWalletData {
        status: 0,
        balance: 0,
        ownerAddress,
        jettonMasterAddress,
    };

    return contractAddress(StateInit {
        code: jettonWalletCode,
        data: initData.toCell(),
    });
}
```

### Onchain metadata creation

```tact
/// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#jetton-metadata-example-offchain
fun composeJettonMetadata(
    // Full name
    name: String,

    // Text description of the Jetton
    description: String,

    // "Stock ticker" symbol without the $ prefix, like USDT or SCALE
    symbol: String,

    // Link to the image
    image: String,

    // There could be other data, see:
    // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#jetton-metadata-attributes
): Cell {
    let dict: map<Int as uint256, Cell> = emptyMap();
    dict.set(sha256("name"), name.asMetadataCell());
    dict.set(sha256("description"), description.asMetadataCell());
    dict.set(sha256("symbol"), symbol.asMetadataCell());
    dict.set(sha256("image"), image.asMetadataCell());

    return beginCell()
        .storeUint(0, 8) //                a null byte prefix
        .storeMaybeRef(dict.asCell()!!) // 1 as a single bit, then a reference
        .endCell();
}

// Taking flight!
fun poorMansLaunchPad() {
    let jettonMetadata = composeJettonMetadata(
        "Best Jetton",
        "A very descriptive description describing the Jetton descriptively",
        "JETTON",
        "...link to ipfs or somewhere trusted...",
    );
}

// Prefixes the String with a single null byte and converts it to a Cell
// The null byte prefix is used to express metadata in various standards, like NFT or Jetton
inline extends fun asMetadataCell(self: String): Cell {
    return beginTailString().concat(self).toCell();
}
```

:::note[Useful links:]

  [Token Data Standard in TEPs](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#jetton-metadata-attributes)

:::

:::tip[Hey there!]

  Didn't find your favorite example of Jetton usage? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues)

  For complete and exhaustive Jetton recipes that include smart contracts, auxiliary scripts, and testing suites, see the [Tact's DeFi Cookbook on GitHub](https://github.com/tact-lang/defi-cookbook).

:::
