Jetton Processing
Best Practices on Jettons Processing
Jettons are tokens on TON Blockchain - one can consider them similarly to ERC-20 tokens on Ethereum.
TON transactions are irreversible after just one confirmation. For the best UX/UI avoid additional waiting.
Withdrawal
Highload Wallet v3 - this is TON Blockchain latest solution which is the gold standard for jetton withdrawals. It allows you to take advantage of batched withdrawals.
Batched withdrawals - Meaning that multiple withdrawals are sent in batches, allowing for quick and cheap withdrawals.
Deposits
It is suggested to set several MEMO deposit wallets for better performance.
Memo Deposits - This allows you to keep one deposit wallet, and users add a memo in order to be identified by your system. This means that you don’t need to scan the entire blockchain, but is slightly less easy for users.
Memo-less deposits - This solution also exists, but is more difficult to integrate. However, we can assist with this, if you would prefer to take this route. Please notify us before deciding to implement this approach.
Additional Info
It is expected that every service in the Ecosystem will set the forward_ton_amount
to 0.000000001 TON (1 nanoton) when a jetton withdrawal is made in order to send a Jetton Notify upon successful transfer, otherwise the transfer will not be standard compliant and will not be able to be processed by other CEXes and services.
-
Please find the JS lib example - tonweb - which is the official JS library from the TON Foundation.
-
If you want to use Java, you can look into ton4j.
-
For Go, one should consider tonutils-go. At the moment, we recommend the JS lib.
Content List
In following docs offers details about Jettons architecture generally, as well as core concepts of TON which may be different from EVM-like and other blockchains. This is crucial reading in order for one to grasp a good understanding of TON, and will greatly help you.
This document describes the following in order:
- Overview
- Architecture
- Jetton Master Contract (Token Minter)
- Jetton Wallet Contract (User Wallet)
- Message Layouts
- Jetton Processing (off-chain)
- Jetton Processing (on-chain)
- Wallet processing
- Best Practices
Overview
TON transactions are irreversible after just one confirmation. For clear understanding, the reader should be familiar with the basic principles of asset processing described in this section of our documentation. In particular, it is important to be familiar with contracts, wallets, messages and deployment process.
For the best user experience, it is suggested to avoid waiting on additional blocks once transactions are finalized on the TON Blockchain. Read more in the Catchain.pdf.
Quick jump to the core description of jetton processing:
Centralized ProcessingOn-Chain Processing
TON Blockchain and its underlying ecosystem classifies fungible tokens (FTs) as jettons. Because sharding is applied on TON Blockchain, our implementation of fungible tokens is unique when compared to similar blockchain models.
In this analysis, we take a deeper dive into the formal standards detailing jetton behavior and metadata. A less formal sharding-focused overview of jetton architecture can be found in our anatomy of jettons blog post.
We have also provided specific details discussing our third-party open-source TON Payment Processor (bicycle) which allows users to deposit and withdraw both Toncoin and jettons using a separate deposit address without using a text memo.
Jetton Architecture
Standardized tokens on TON are implemented using a set of smart contracts, including:
- Jetton master smart contract
- Jetton wallet smart contracts
Jetton master smart contract
The jetton master smart contract stores general information about the jetton (including the total supply, a metadata link, or the metadata itself).
It is possible for any user to create a counterfeit clone of a valuable jetton (using an arbitrary name, ticker, image, etc.) that is nearly identical to the original. Thankfully, counterfeit jettons are distinguishable by their addresses and can be identified quite easily.
Jettons with the symbol
==TON
or those that contain system notification messages, such as:
ERROR
, SYSTEM
, and others. Be sure to check that jettons are displayed in your interface in such a way that they cannot
be mixed with TON transfers, system notifications, etc.. At times, even the symbol
,name
and image
will be created to look nearly identical to the original with the hopes of misleading users.
To eliminate the possibility of fraud for TON users, please look up the original jetton address (Jetton master contract) for specific jetton types or follow the project’s official social media channel or website to find the correct information. Check assets to eliminate the possibility of fraud with Tonkeeper ton-assets list.
Retrieving Jetton data
To retrieve more specific Jetton data use contract's get method get_jetton_data()
.
This method returns the following data:
Name | Type | Description |
---|---|---|
total_supply | int | the total number of issued jettons measured in indivisible units. |
mintable | int | details whether new jettons can be minted or not. This value is either -1 (can be minted) or 0 (cannot be minted). |
admin_address | slice | |
jetton_content | cell | data in accordance with TEP-64, check jetton metadata parsing page for more. |
jetton_wallet_code | cell |
It is also possible to use the method /jetton/masters
from the Toncenter API to retrieve the already decoded Jetton data and metadata. We have also developed methods for (js) tonweb and (js) ton-core/ton, (go) tongo and (go) tonutils-go, (python) pytonlib and many other SDKs.
Example of using Tonweb to run a get method and get url for off-chain metadata:
import TonWeb from "tonweb";
const tonweb = new TonWeb();
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: "<JETTON_MASTER_ADDRESS>"});
const data = await jettonMinter.getJettonData();
console.log('Total supply:', data.totalSupply.toString());
console.log('URI to off-chain metadata:', data.jettonContentUri);
Jetton minter
As mentioned before, jettons can be either mintable
or non-mintable
.
If they are non-mintable, the logic becomes simple—there is no way to mint additional tokens. To mint jettons for the first time, refer to the Mint your first jetton page.
If the jettons are mintable, there is a special function in the minter contract to mint additional jettons. This function can be called by sending an internal message
with a specified opcode from the admin address.
If the jetton admin wants to restrict jetton creation, there are three ways to do it:
- If you can't or do not want to update the contract's code, the admin needs to transfer ownership from the current admin to the zero address. This will leave the contract without a valid admin, thus preventing anyone from minting jettons. However, it will also prevent any changes to the jetton metadata.
- If you have access to source code and can change it, you can create a method in the contract that sets a flag to abort any minting process after it is called, and add a statement to check this flag in the mint function.
- If you can update contract's code, you can add restrictions by updating the code of the already deployed contract.
Jetton wallet smart contract
Jetton wallet
contracts are used to send, receive, and burn jettons. Each jetton wallet contract stores wallet balance information for specific users.
In specific instances, jetton wallets are used for individual jetton holders for each jetton type.
Jetton wallets
should not be confused with wallet’s meant for blockchain interaction and storing
only the Toncoin asset (e.g., v3R2 wallets, highload wallets, and others),
which is responsible for supporting and managing only a specific jetton type.
Jetton Wallet Deployment
When transferring jettons
between wallets, transactions (messages) require a certain amount of TON
as payment for network gas fees and the execution of actions according to the Jetton wallet contract's code.
This means that the recipient does not need to deploy a jetton wallet prior to receiving jettons.
The recipient's jetton wallet will be deployed automatically as long as the sender holds enough TON
in the wallet to pay the required gas fees.
Retrieving Jetton wallet addresses for a given user
To retrieve a jetton wallet
address
using an owner address
(a TON Wallet address),
the Jetton master contract
provides the get method get_wallet_address(slice owner_address)
.
- API
- js
Run
get_wallet_address(slice owner_address)
through/runGetMethod
method from the Toncenter API. In real cases (not test ones) it is important to always check that wallet indeed is attributed to desired Jetton Master. Check code example for more.
import TonWeb from 'tonweb';
const tonweb = new TonWeb();
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, { address: '<JETTON_MASTER_ADDRESS>' });
const jettonWalletAddress = await jettonMinter.getJettonWalletAddress(new TonWeb.utils.Address('<OWNER_WALLET_ADDRESS>'));
// It is important to always check that wallet indeed is attributed to desired Jetton Master:
const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider, {
address: jettonWalletAddress
});
const jettonData = await jettonWallet.getData();
if (jettonData.jettonMinterAddress.toString(false) !== jettonMinter.address.toString(false)) {
throw new Error('jetton minter address from jetton wallet doesnt match config');
}
console.log('Jetton wallet address:', jettonWalletAddress.toString(true, true, true));
For more examples read the TON Cookbook.
Retrieving data for a specific Jetton wallet
To retrieve the wallet’s account balance, owner identification information, and other info related to a specific jetton wallet contract use the get_wallet_data()
get method within the jetton wallet contract.
This method returns the following data:
Name | Type |
---|---|
balance | int |
owner | slice |
jetton | slice |
jetton_wallet_code | cell |
- API
- js
Use the
/jetton/wallets
get method from the Toncenter API to retrieve previously decoded jetton wallet data.
import TonWeb from "tonweb";
const tonweb = new TonWeb();
const walletAddress = "EQBYc3DSi36qur7-DLDYd-AmRRb4-zk6VkzX0etv5Pa-Bq4Y";
const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider,{address: walletAddress});
const data = await jettonWallet.getData();
console.log('Jetton balance:', data.balance.toString());
console.log('Jetton owner address:', data.ownerAddress.toString(true, true, true));
// It is important to always check that Jetton Master indeed recognize wallet
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: data.jettonMinterAddress.toString(false)});
const expectedJettonWalletAddress = await jettonMinter.getJettonWalletAddress(data.ownerAddress.toString(false));
if (expectedJettonWalletAddress.toString(false) !== new TonWeb.utils.Address(walletAddress).toString(false)) {
throw new Error('jetton minter does not recognize the wallet');
}
console.log('Jetton master address:', data.jettonMinterAddress.toString(true, true, true));
Message Layouts
Read more about Messages here.
Communication between Jetton wallets and TON wallets occurs through the following communication sequence:
Message 0
Sender -> sender's jetton wallet
. Transfer message contains the following data:
Name | Type | Description |
---|---|---|
query_id | uint64 | Allows applications to link three messaging types Transfer , Transfer notification and Excesses to each other. For this process to be carried out correctly it is recommended to always use a unique query id. |
amount | coins | Total ton coin amount, that will be send with message. |
destination | address | Address of the new owner of the jettons |
response_destination | address | Wallet address used to return remained ton coins with excesses message. |
custom_payload | maybe cell | Size always is >= 1 bit. Custom data (which is used by either sender or receiver jetton wallet for inner logic). |
forward_ton_amount | coins | Must be > 0 if you want to send transfer notification message with forward payload . It's a part of amount value and must be lesser than amount |
forward_payload | maybe cell | Size always is >= 1 bit. If first 32 bits = 0x0 it's just a simple message. |
Message 2'
payee's jetton wallet -> payee
. Transfer notification message. Only sent if forward_ton_amount
not zero. Contains the following data:
Name | Type |
---|---|
query_id | uint64 |
amount | coins |
sender | address |
forward_payload | cell |
Here sender
address is an address of Alice's Jetton wallet
.
Message 2''
payee's jetton wallet -> Sender
. Excess message body. Only sent if any ton coins are left after paying the fees. Contains the following data:
Name | Type |
---|---|
query_id | uint64 |
A detailed description of the jetton wallet contract fields can be found in the TEP-74 Jetton standard
interface description.