Skip to main content
Jettons are fungible tokens on TON, similar to ERC-20 tokens on Ethereum. Unlike Toncoin, which is the native TON currency used in all transfers, each jetton has a separate master (minter) contract and an individual wallet contract for each holder. For example, USDT on TON is implemented as a jetton, and its minter contract address is EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs. By providing this address and the recipient’s TON wallet contract address, AppKit knows which tokens to send and to whom.

Metadata

Retrieve metadata about a specific jetton, such as its name, symbol, and decimals:
import { useJettonInfo } from '@ton/appkit-react';

export const JettonCard = ({ jettonAddress }) => {
  const {
    data: info,
    isLoading,
    error,
  } = useJettonInfo({
    // Jetton master (minter) contract address
    address: jettonAddress,
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      <p><em>Jetton info</em></p>
      <p>Name: {info?.name}</p>
      <p>Symbol: {info?.symbol}</p>
      <p>Decimals: {info?.decimals}</p>
    </div>
  );
};
import { type AppKit, getJettonInfo } from '@ton/appkit';

async function fetchJettonInfo(
  /** Initialized AppKit instance */
  kit: AppKit,
  /** Jetton master (minter) contract address */
  jettonAddress: string,
) {
  const info = await getJettonInfo(kit, {
    address: jettonAddress,
  });
  console.log('Jetton info:', info);
}

Jetton wallet address

Each jetton holder has a dedicated jetton wallet contract. To resolve its address for a given owner:
import {
  useJettonWalletAddress,
  useAddress,
} from '@ton/appkit-react';

export const JettonWalletAddressCard = ({ jettonAddress }) => {
  const ownerAddress = useAddress();
  const {
    data: walletAddress,
    isLoading,
    error,
  } = useJettonWalletAddress({
    // TON wallet address of the jetton holder
    ownerAddress: ownerAddress ?? '<TON_WALLET_ADDRESS>',

    // Jetton master (minter) contract address
    jettonAddress,
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return <div>Jetton wallet address: {walletAddress?.toString()}</div>;
};
import {
  type AppKit,
  getJettonWalletAddress,
  getSelectedWallet,
} from '@ton/appkit';

async function fetchJettonWalletAddress(
  /** Initialized AppKit instance */
  kit: AppKit,
  /** Jetton master (minter) contract address */
  jettonAddress: string,
) {
  const selectedWallet = getSelectedWallet(kit);
  const address = await getJettonWalletAddress(kit, {
    jettonAddress,
    // TON wallet address of the jetton holder
    ownerAddress: selectedWallet?.getAddress() ?? '<TON_WALLET_ADDRESS>',
  });
  console.log('Jetton wallet address:', address);
}

Balance

Similar to Toncoin balance checks, discrete one-off checks have limited value on their own and continuous monitoring should be used for UI display. Unlike Toncoin, the balance units and decimal places vary between jettons — use the decimals field from the jetton’s metadata to interpret raw amounts correctly. USDT has a decimal precision of 6, meaning that the fractional balance string '0.1' represents a balance of 0.1 USDT, or 100000 micro USDT (raw units).

On-demand balance check

Single jetton

Check the balance of a specific jetton for the connected TON wallet or an arbitrary address:
import {
  useJettonBalanceByAddress,
  useAddress,
} from '@ton/appkit-react';

export const JettonBalanceCard = ({ jettonAddress }) => {
  const ownerAddress = useAddress();
  const {
    data: balance,
    isLoading,
    error,
  } = useJettonBalanceByAddress({
    // TON wallet address of the jetton holder
    ownerAddress: ownerAddress ?? '<TON_WALLET_ADDRESS>',

    // Jetton master (minter) contract address
    jettonAddress,

    // Jetton decimals to calculate raw unit amounts
    jettonDecimals: 6,
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return <div>Jetton balance: {balance ?? '0'}</div>;
};
import {
  type AppKit,
  getJettonBalance,
  getSelectedWallet,
} from '@ton/appkit';

async function fetchJettonBalance(
  /** Initialized AppKit instance */
  kit: AppKit,
  /** Jetton master (minter) contract address */
  jettonAddress: string,
  /** Jetton decimals to calculate raw unit amounts */
  jettonDecimals: number = 6,
) {
  const selectedWallet = getSelectedWallet(kit);
  const balance = await getJettonBalance(kit, {
    jettonAddress,
    // TON wallet address of the jetton holder
    ownerAddress: selectedWallet?.getAddress() ?? '<TON_WALLET_ADDRESS>',
  });
  console.log('Jetton balance:', balance ?? '0');
}

All jettons

Retrieve every jetton held by the connected TON wallet or an arbitrary address:
import {
  useJettonsByAddress,
  useAddress,
  // Helper function targeting the connected wallet
  useJettons,
} from '@ton/appkit-react';

export const JettonListByAddress = () => {
  const address = useAddress();
  const {
    data: jettons,
    isLoading,
    error,
  } = useJettonsByAddress({
    // TON wallet address of the jetton holder
    address: address ?? '<TON_WALLET_ADDRESS>',
  });

  // Alternatively, query the connected wallet directly
  // const { data: jettons, isLoading, error } = useJettons();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      <p>Jettons</p>
      <ul>
        {jettons?.jettons.map((jetton) => (
          <li key={jetton.walletAddress}>
            {jetton.info.name}: {jetton.balance ?? '0'}
          </li>
        ))}
      </ul>
    </div>
  );
};
import {
  type AppKit,
  getJettonsByAddress,
  getSelectedWallet,
  // Helper function targeting the connected wallet
  getJettons,
} from '@ton/appkit';

async function fetchJettonsByAddress(
  /** AppKit instance */
  kit: AppKit,
) {
  const selectedWallet = getSelectedWallet(kit);
  const response = await getJettonsByAddress(kit, {
    address: selectedWallet?.getAddress() ?? '<TON_WALLET_ADDRESS>',
  });

  // Alternatively, query the connected wallet directly
  // const response = await getJettons();

  console.log('Jettons by address:', response.jettons.length);
  response.jettons.forEach((j) =>
    console.log(`- ${j.info.name}: ${j.balance ?? '0'}`),
  );
}

Continuous balance monitoring

Poll the balance at regular intervals to keep the displayed value up to date. Use an appropriate interval based on UX requirements — shorter intervals provide fresher data but increase API usage. Modify the following example according to the application logic:
import {
  useJettonBalanceByAddress,
  useAddress,
} from '@ton/appkit-react';

export const JettonBalanceCard = ({ jettonAddress }) => {
  const ownerAddress = useAddress();
  const {
    data: balance,
    isLoading,
    error,
    refetch,
  } = useJettonBalanceByAddress({
    // TON wallet address of the jetton holder
    ownerAddress: ownerAddress ?? '<TON_WALLET_ADDRESS>',

    // Jetton master (minter) contract address
    jettonAddress,
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return (
      <div>
        <p>Error: {error.message}</p>
        <button onClick={() => refetch()}>Try again</button>
      </div>
    );
  }

  return <div>Jetton balance: {balance ?? '0'}</div>;
};
// Not runnable: implement the updateUI()
import {
  type AppKit,
  getJettonBalance,
  getSelectedWallet,
} from '@ton/appkit';

/**
 * Starts the monitoring of a specific jetton balance for the connected wallet,
 * calling `onBalanceUpdate()` every `intervalMs` milliseconds
 *
 * @returns a function to stop monitoring
 */
export function startJettonBalanceMonitoring(
  kit: AppKit,
  jettonAddress: string,
  onBalanceUpdate: (balance: string) => void,
  intervalMs: number = 10_000,
): () => void {
  let isRunning = true;

  const poll = async () => {
    while (isRunning) {
      const selectedWallet = getSelectedWallet(kit);
      if (selectedWallet) {
        const balance = await getJettonBalance(kit, {
          jettonAddress,
          // TON wallet address of the jetton holder
          ownerAddress: selectedWallet?.getAddress() ?? '<TON_WALLET_ADDRESS>',
        });
        onBalanceUpdate(balance);
      }
      await new Promise((resolve) => setTimeout(resolve, intervalMs));
    }
  };

  // Start monitoring
  poll();

  // Return a cleanup function to stop monitoring
  return () => {
    isRunning = false;
  };
}

// Usage
const stopMonitoring = startJettonBalanceMonitoring(
  kit,
  // Jetton master (minter) contract address
  // E.g., USDT on TON mainnet:
  'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs',
  // The updateUI() function is exemplary and should be replaced by
  // an app function that refreshes the state of the balance displayed
  // in the interface
  (balance) => updateUI(balance),
);

// Stop monitoring once it is no longer needed
stopMonitoring();

Transfers

Before making a transfer, make sure there is enough Toncoin in the balance to cover the fees. Modify the following examples according to the application logic:
// Pre-built UI component for sending jetton transactions by clicking a button.
// It handles success and error states while being customizable.
import { SendJettonButton } from '@ton/appkit-react';

export const SendJetton = () => {
  // For example: 'UQ...'
  const recipientAddress = '<TON_WALLET_ADDRESS>';
  // For example, '0.1' or '1' jetton
  const jettonAmount = '<FRACTIONAL_JETTON_AMOUNT>';
  // For example, 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'
  const jettonAddress = '<JETTON_MINTER_ADDRESS>';

  return (
    <SendJettonButton
      // New owner of the sent jettons
      recipientAddress={recipientAddress}

      // Jetton amount in fractional units
      amount={jettonAmount}

      // What kind of jettons to send
      jetton={{
        // Jetton master (minter) contract address
        address: jettonAddress,

        // Short ticker name
        symbol: 'USDT',

        // Jetton decimals to calculate raw unit amounts
        // For example, USDT defaults to 6, while Toncoin to 9:
        decimals: 6,
      }}

      // (optional) Comment
      comment="Hello from AppKit!"

      // (optional) Add custom button title
      text="Send some jetton"

      // (optional) Handle successes
      onSuccess={(result) => console.log('Transaction sent:', result)}

      // (optional) Handle errors
      onError={(error) => console.error('Transaction failed:', error)}

      // (optional) Add custom CSS classes
      className=''

      // (optional) When set to `true`, the button is disabled
      disabled={false}
    />
  );
};
import { useTransferJetton } from '@ton/appkit-react';

export const SendJetton = ({ recipientAddress, jettonAddress }) => {
  const { mutate: transfer, isPending, error, data } = useTransferJetton();

  const handleTransfer = () => {
    transfer({
      // New owner of the sent jettons.
      // For example: 'UQ...'
      recipientAddress,

      // Jetton amount string in fractional units.
      // For example, '0.1' or '1' jetton
      amount: '<FRACTIONAL_JETTON_AMOUNT>',

      // Jetton master (minter) contract address.
      jettonAddress,

      // Jetton decimals to calculate raw unit amounts.
      // For example, USDT defaults to 6, while Toncoin to 9:
      jettonDecimals: 6,

      // (optional) Comment string. Defaults to none if not provided.
      comment: 'Hello from AppKit!',
    });
  };

  return (
    <div>
      <button onClick={handleTransfer} disabled={isPending}>
        {isPending ? 'Transferring...' : 'Transfer jetton'}
      </button>
      {error && <div>Error: {error.message}</div>}
      {data && (
        <div>
          <p>Transfer successful: {data.boc}</p>
        </div>
      )}
    </div>
  );
};
import {
  type AppKit,
  type Base64String,
  // Single-call transfer
  transferJetton,
  // Two-step transfer: create a transaction object separately from sending it
  createTransferJettonTransaction,
  sendTransaction,
} from '@ton/appkit';

async function sendJetton(
  /** Initialized AppKit instance */
  kit: AppKit,
  /** Recipient's TON wallet address as a string */
  recipientAddress: string,
  /**
   * Jetton amount string in fractional units
   * E.g., '0.1' or '1' jetton
   */
  amount: string,
  /** Jetton decimals to calculate raw unit amounts */
  jettonDecimals: number = 6,
  /** Jetton master (minter) contract address */
  jettonAddress: string,
  /** Optional comment string */
  comment?: string,
) {
  // Sign and send via TON Connect
  const result = await transferJetton(kit, {
    recipientAddress,
    amount,
    jettonDecimals,
    jettonAddress,
    ...(comment && { comment }),
  });
  console.log('Transaction sent:', result.boc);

  // Alternatively, build the transaction first with createTransferJettonTransaction,
  // then pass the resulting object to the sendTransaction function.
}

Next steps

Work with NFTs

See also

Jettons: General: