TON: How to interact with Jettons

Introduction

Jettons are the standard token implementation on the TON blockchain, analogous to ERC-20 tokens on Ethereum. They enable developers to create and manage custom tokens that can represent a variety of assets or utilities within decentralized applications. The standard for Jettons on TON is described in TEP64.

👍

Get you own node endpoint today

Start for free and get your app to production levels immediately. No credit card required.

You can sign up with your GitHub, X, Google, or Microsoft account.

TON APIs

To interact with the TON blockchain, developers can use either HTTP APIs or the ADNL protocol. HTTP APIs are simpler and suitable for most applications, while ADNL offers advanced low-level network communication.

The TON HTTP API has two versions: V2, which provides real-time data, and V3, which offers indexed blockchain data. In this tutorial, we will use TonWeb, which operates on the V2 JSON-RPC endpoint.

Jettons on TON

Jettons are smart contracts on TON that follow a standard interface, allowing wallets and applications to interact with them uniformly. Each Jetton consists of:

  1. Master (or minter) contract: the contract that manages the Jetton metadata and logic.
  2. Wallet contract: a contract for each user holding the Jetton, managing the user's balance and transfers.

Prerequisites

  1. Node.js and npm installed.
  2. TonWeb installed.
  3. RPC endpoint from Chainstack.

TonWeb is a JavaScript SDK that allows developers to interact with the TON blockchain via HTTP APIs. Install it using npm:

npm install tonweb

Interactions

Fetching Jetton metadata

Fetching Jetton metadata allows you to retrieve essential information about a specific Jetton token. To achieve the same using HTTP requests, you can use the /getTokenData endpoint of TON API v2.

const TonWeb = require('tonweb');

// Initialize TonWeb with a provider
const tonweb = new TonWeb(
    new TonWeb.HttpProvider('https://ton-mainnet.core.chainstack.com/.../api/v2/jsonRPC')
);

// Replace with the actual Jetton master contract address
const jettonMasterAddress = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'; // e.g., mainnet USDT

async function fetchJettonMetadata(jettonMasterAddress) {
    try {
        const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: jettonMasterAddress});
        const data = await jettonMinter.getJettonData();

        console.log('Jetton Metadata:');
        console.log('Total supply:', data.totalSupply.toString());
        console.log('URI to off-chain metadata:', data.jettonContentUri);
    } catch (error) {
        console.error('Error fetching jetton metadata:', error);
    }
}

fetchJettonMetadata(jettonMasterAddress);
const fetch = require('node-fetch');

const apiUrl = 'https://ton-mainnet.core.chainstack.com/.../api/v2/getTokenData';
const jettonMasterAddress = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs';

async function fetchJettonMetadata(jettonMasterAddress) {
    try {
        const params = new URLSearchParams({
            address: jettonMasterAddress
        });
        const url = `${apiUrl}?${params.toString()}`;
        const response = await fetch(url);
        const data = (await response.json()).result;
        console.log('Jetton Metadata:');
        console.log('Total supply:', data.total_supply);
        console.log('Mintable:', data.mintable);
        console.log('Admin address:', data.admin_address);
        console.log('URI:', data.jetton_content.data.uri);
        console.log('Decimals:', data.jetton_content.data.decimals);
    } catch (error) {
        console.error('Error fetching jetton metadata:', error);
    }
}

fetchJettonMetadata(jettonMasterAddress);

Fetching user Jetton balance

To display a user's Jetton balance, you need to query the blockchain for the amount of a specific Jetton that the user holds. The first example shows how to obtain a Jetton wallet for a specific user.

const TonWeb = require('tonweb');

const tonweb = new TonWeb(
    new TonWeb.HttpProvider('https://ton-mainnet.core.chainstack.com/.../api/v2/jsonRPC')
);

// Replace with the actual Jetton master contract address and owner wallet address
const jettonMasterAddress = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'; // e.g., mainnet USDT
const ownerWalletAddress = 'UQ...';

async function fetchJettonWalletAddress(jettonMasterAddress, ownerWalletAddress) {
    try {
        const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {
            address: jettonMasterAddress
        });

        const jettonWalletAddress = await jettonMinter.getJettonWalletAddress(
            new TonWeb.utils.Address(ownerWalletAddress)
        );

        const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider, {
            address: jettonWalletAddress
        });

        const jettonData = await jettonWallet.getData();

        // Verify that the Jetton Minter address matches
        if (jettonData.jettonMinterAddress.toString(false) !== jettonMinter.address.toString(false)) {
            throw new Error('Jetton minter address from jetton wallet does not match the expected minter address');
        }

        console.log('Jetton wallet address:', jettonWalletAddress.toString(true, true, true));
    } catch (error) {
        console.error('Error fetching jetton wallet address:', error);
    }
}

fetchJettonWalletAddress(jettonMasterAddress, ownerWalletAddress);

When the Jetton wallet is known, we can fetch its balance:

const TonWeb = require('tonweb');

const tonweb = new TonWeb(
    new TonWeb.HttpProvider('https://ton-mainnet.core.chainstack.com/.../api/v2/jsonRPC')
);

// Replace with the actual Jetton wallet address
const walletAddress = 'EQ...';

async function fetchJettonBalance(walletAddress) {
    try {
        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));
    } catch (error) {
        console.error('Error fetching jetton balance:', error);
    }
}

fetchJettonBalance(walletAddress);

Fetching user Jetton transfers

Fetching a user's Jetton transfer history allows you to display past transactions involving the Jetton token for that user.

const TonWeb = require('tonweb');

// Initialize TonWeb with a provider
const tonweb = new TonWeb(
    new TonWeb.HttpProvider('https://ton-mainnet.core.chainstack.com/.../api/v2/jsonRPC')
);

// Replace with the actual Jetton wallet address (user's Jetton wallet address)
const jettonWalletAddress = 'EQ...';

async function fetchJettonTransferHistory(jettonWalletAddress) {
    try {
        const limit = 10;
        const transactions = await tonweb.provider.getTransactions(jettonWalletAddress, limit);
        console.log(`Last ${limit} transactions for Jetton wallet ${jettonWalletAddress}:`);

        for (const tx of transactions) {
            // Basic transaction info
            console.log('Transaction ID:', tx.transaction_id.hash);
            console.log('LT:', tx.transaction_id.lt);
            console.log('Timestamp:', new Date(tx.utime * 1000).toISOString());

            // Check if the transaction is an inbound or outbound message
            if (tx.in_msg && tx.in_msg.msg_data) {
                console.log('Incoming Message Data:', tx.in_msg.msg_data);
            }

            if (tx.out_msgs && tx.out_msgs.length > 0) {
                tx.out_msgs.forEach((outMsg, index) => {
                    console.log(`Outgoing Message ${index + 1} Data:`, outMsg.msg_data);
                });
            }

            console.log('----------------------------------------');
        }
    } catch (error) {
        console.error('Error fetching jetton transfer history:', error);
    }
}

fetchJettonTransferHistory(jettonWalletAddress);

Conlcusion

TonWeb is a JavaScript SDK that simplifies the process of interacting with Jetton contracts on the TON blockchain. In this tutorial, we explored how to peform basic fetching operations with Jettons.

About author

Anton Sauchyk

🥑 Developer Advocate @ Chainstack
💻 Helping people understand Web3 and blockchain development
Anton Sauchyk | GitHub Anton Sauchyk | LinkedIn