---
title: 同质化代币（Jettons）
description: 在Tact中处理同质化代币（Jettons）的常见示例
---

本页列出了使用 [jettons](https://docs.ton.org/develop/dapps/asset-processing/jettons) 的常见示例。

Jettons 是TON区块链上的代币标准，旨在创建同质化代币（类似于以太坊上的ERC-20），并采用去中心化的方式。 它们以一对智能合约的形式实现，通常由两个核心组件组成：

- Jetton Master Contracting (Jetton master)
- Jetton Wallet Contract (Jetton wallet)

这些合同相互影响，以管理代币供应、分配、转让以及与捷通相关的其他操作。

## Jetton Master Contract

Jetton 主合同是特定 Jetton 的中心实体。 它保留了有关Jetton本身的重要信息。 Jetton 主合同中存储的主要责任和数据包括：

- Jetton元数据：诸如代币名称、符号、总供应量和小数点等信息。

- 铸造和销毁：当新的Jettons被铸造（创建）时，Jetton主合约管理创建过程并将其分发到相应的钱包。 它还能根据需要管理代币的燃烧（销毁）。

- 供应管理：Jetton Master 跟踪 Jettons 的总供应量，并确保对所有已发放的 Jettons 进行适当核算。

## Jetton Wallet 合约

Jetton Wallet 合约是个人持有者的代币钱包，负责管理特定用户的余额和与代币相关的操作。 每个持有Jettons的用户或实体将拥有自己独特的Jetton钱包合约。 Jetton Wallet 合约的主要特点包括：

- 余额跟踪：钱包合约存储用户的代币余额。

- 代币转账：钱包负责处理用户之间的代币转账。 当用户发送Jetton时，Jetton Wallet 合约确保与收件人的钱包进行适当的转移和沟通。 Jetton Master 不参与这项活动，也不会造成瓶颈。 钱包可充分利用 TON 的分片功能

- 代币销毁：Jetton钱包与Jetton Master交互以销毁代币。

- 所有者控制：钱包合约由特定用户拥有和控制 表示只有钱包的所有者可以启动转账或其他代币操作。

## 示例：

处理Jettons的常见示例。

### 接受 jetton 转移 {#accepting-jetton-transfer}

转账通知信息的结构如下

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

使用 [receiver](/zh-cn/book/receive) 功能接受代币通知信息。

:::caution

  必须验证转账通知的发送方！

:::

必须验证传输通知的发件人，因为恶意行为者可能试图从未经授权的账户中伪造通知。
如果不进行这种确认，合约可接受未经授权的交易，从而造成潜在的安全脆弱性。

使用合约中的 Jetton 地址进行验证：

1. 发送者向其 Jetton 钱包发送以 `0xf8a7ea5` 作为 32 位标题（操作码）的信息。
2. Jetton钱包将资金转到合约的Jetton钱包。
3. 在成功的转账接受后，合约的Jetton钱包将转账通知给他的所有者合约。
4. 合约验证Jetton信息。

你可以使用 [`contractAddress(){:tact}`](/zh-cn/ref/core-common#contractaddress) 函数获取合约的 Jetton 钱包，或链下计算此地址。

要获取 Jetton 钱包的初始状态，您需要钱包的数据和代码。 虽然初始数据布局有一个共同的结构，但在某些情况下可能不同，如 [USDT](#usdt-jetton-operations)。

由于通知源自您的合约的 Jetton 钱包，函数[`myAddress(){:tact}`](/zh-cn/ref/core-contextstate#myaddress) 应在 \`owners' 字段中使用。

:::caution

  通知并不总能保证发送。 默认情况下，如果 `forwardAmount` 设置为零，执行不会发送通知。 因此，在这种情况下，不能依靠发送通知。

:::

```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,
        );
    }

    receive() { cashback(sender()) } // for the deployment

    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);
    }
}
```

### 发送Jetton转账

Jetton 转账是将指定数量的 Jetton 从一个钱包（合约）发送到另一个钱包的过程。

若要发送Jetton转账，请使用 [`send(){:tact}`](/zh-cn/book/send) 函数。

```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,
        );
    }

    receive() { cashback(sender()) } // for the deployment

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

        send(SendParameters {
            to: self.myJettonWalletAddress,
            value: JettonTransferGas,
            body: JettonTransfer{
                // To prevent replay attacks
                queryId: 42,
                // Jetton amount to transfer
                amount: msg.amount,
                // Where to transfer Jettons:
                // this is an 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 rest of the incoming message value
                responseDestination: sender(),
                // Can be used for custom logic of Jettons themselves,
                // and without such can be set to null
                customPayload: null,
                // Amount to transfer with JettonTransferNotification,
                // which is needed for the execution of custom logic
                forwardTonAmount: 1, // if its 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(),
        });
    }
}
```

### 销毁 jetton {#burning-jetton}

Jetton 销毁是将指定数量的 Jetton 永久移出流通的过程，无法恢复。

```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,
        );
    }

    receive() { cashback(sender()) } // for the deployment

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

        send(SendParameters {
            to: self.myJettonWalletAddress,
            value: JettonBurnGas,
            body: JettonBurn{
                // To prevent replay attacks
                queryId: 42,
                // Jetton amount you want to burn
                amount: msg.amount,
                // Where to send a confirmation notice of a successful burn
                // and the rest of the incoming message value
                responseDestination: sender(),
                // Can be used for custom logic of Jettons themselves,
                // and without such can be set to null
                customPayload: null,
            }.toCell(),
        });
    }
}
```

### USDT jetton 业务 {#usdt-jetton-operations}

除了 `JettonWalletData` 将采用以下结构外，USDT（在 TON 上）的操作保持不变：

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

// And 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(),
    });
}
```

:::tip[Hey there!]

没有找到您最喜欢的 jetton 使用范例？  您有很酷的实施方案吗？ [欢迎投稿！](https://github.com/tact-lang/tact/issues)

:::
