# How to work with Jettons using AppKit (https://docs-i0yym09dy-ton-core-docs.vercel.app/llms/ecosystem/appkit/jettons/content.md)



<Callout>
  [Initialize the AppKit](/llms/ecosystem/appkit/init/content.md) before using examples on this page.
</Callout>

[Jettons](/llms/standard/tokens/jettons/overview/content.md) 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.

<Callout type="note">
  To deploy and mint a new jetton on the mainnet without writing code, use the dedicated official tool: [TON MINTER](https://minter.ton.org).
</Callout>

## Metadata [#metadata]

Retrieve metadata about a specific jetton, such as its name, symbol, and decimals:

<CodeGroup>
  <CodeBlockTabs defaultValue="React">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="React">
        React
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="React">
      ```tsx  icon="react"
      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>
        );
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="TypeScript">
      ```ts  icon="globe"
      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);
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

### Jetton wallet address [#jetton-wallet-address]

Each jetton holder has a dedicated jetton wallet contract. To resolve its address for a given owner:

<CodeGroup>
  <CodeBlockTabs defaultValue="React">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="React">
        React
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="React">
      ```tsx  icon="react" expandable
      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>;
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="TypeScript">
      ```ts  icon="globe" expandable
      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);
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

## Balance [#balance]

Similar to Toncoin balance checks, [discrete one-off checks](#on-demand-balance-check) have limited value on their own and [continuous monitoring](#continuous-balance-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](#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 [#on-demand-balance-check]

<Callout type="caution">
  Do not store the balance check results anywhere in the application state, as they become outdated quickly. For UI purposes, do [continuous balance monitoring](#continuous-balance-monitoring).
</Callout>

#### Single jetton [#single-jetton]

Check the balance of a specific jetton for the connected TON wallet or an arbitrary address:

<CodeGroup>
  <CodeBlockTabs defaultValue="React">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="React">
        React
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="React">
      ```tsx  icon="react"
      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>;
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="TypeScript">
      ```ts  icon="globe"
      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');
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

#### All jettons [#all-jettons]

Retrieve every jetton held by the connected TON wallet or an arbitrary address:

<CodeGroup>
  <CodeBlockTabs defaultValue="React">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="React">
        React
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="React">
      ```tsx  icon="react"
      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>
        );
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="TypeScript">
      ```ts  icon="globe"
      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'}`),
        );
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

### Continuous balance monitoring [#continuous-balance-monitoring]

Obtain real-time jetton balance updates with a [streaming API provider](/llms/ecosystem/appkit/init/content.md) and fallback to polling at regular intervals to keep the displayed value up to date. For polling, use an appropriate interval based on UX requirements — shorter intervals provide fresher data but increase API usage.

#### With streaming [#with-streaming]

Modify the following example according to the application logic:

<CodeGroup>
  <CodeBlockTabs defaultValue="React">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="React">
        React
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="React">
      ```tsx  icon="react"
      import {
        useJettonBalanceByAddress,
        useAddress,
        useWatchJettonsByAddress,
      } from '@ton/appkit-react';

      export const JettonBalanceCard = ({ jettonAddress }) => {
        const ownerAddress = useAddress();

        useWatchJettonsByAddress({
          address: ownerAddress ?? '<TON_WALLET_ADDRESS>',
        });

        const {
          data: balance,
          isLoading,
          error,
          refetch,
        } = useJettonBalanceByAddress({
          ownerAddress: ownerAddress ?? '<TON_WALLET_ADDRESS>',
          jettonAddress,
          jettonDecimals: 6,
        });

        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>;
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="TypeScript">
      ```ts  icon="globe" expandable
      // Not runnable: implement the updateUI()
      import {
        type AppKit,
        Network,
        getSelectedWallet,
        hasStreamingProvider,
        watchJettons,
      } from '@ton/appkit';

      export function startJettonBalanceMonitoring(
        kit: AppKit,
        jettonAddress: string,
        onBalanceUpdate: (balance: string) => void,
        network: Network = Network.mainnet(),
      ): () => void {
        if (!hasStreamingProvider(kit, network)) {
          throw new Error([
            `No streaming provider registered for network ${network.chainId}. `,
            'Register one via kit.streamingManager.registerProvider(...) first.',
          ].join());
        }

        return watchJettons(kit, {
          network,
          onChange: (update) => {
            const selectedWallet = getSelectedWallet(kit);
            if (!selectedWallet) return;
            if (update.status !== 'finalized') return;
            if (update.masterAddress !== jettonAddress) return;

            onBalanceUpdate(update.balance);
          },
        });
      }

      // 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();
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

#### Only polling [#only-polling]

Modify the following example according to the application logic:

<CodeGroup>
  <CodeBlockTabs defaultValue="React">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="React">
        React
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="React">
      ```tsx  icon="react"
      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>;
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="TypeScript">
      ```ts  icon="globe" expandable
      // 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();
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

## Transfers [#transfers]

<Callout type="danger" title="Funds at risk">
  Each jetton stores the `decimals` parameter in its [metadata](#metadata). Transferring without accounting for decimals can result in sending drastically more or fewer tokens than intended.

  Mitigation: Verify the correct `decimals` value before calculating transfer amounts. For USDTs, the decimals value is 6.
</Callout>

Before making a transfer, make sure there is enough Toncoin in the balance to cover the [fees](/llms/foundations/fees/content.md).

Modify the following examples according to the application logic:

<CodeGroup>
  <CodeBlockTabs defaultValue="React (component)">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="React (component)">
        React (component)
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="React (hook)">
        React (hook)
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="React (component)">
      ```tsx  icon="react"
      // 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}
          />
        );
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="React (hook)">
      ```tsx  icon="react"
      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>
        );
      };
      ```
    </CodeBlockTab>

    <CodeBlockTab value="TypeScript">
      ```ts  icon="globe"
      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.
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

## Next steps [#next-steps]

<Columns cols="2">
  <Card title="Work with NFTs" icon="image" horizontal="true" href="/ecosystem/appkit/nfts" />
</Columns>

## See also [#see-also]

Jettons:

* [Jettons overview](/llms/standard/tokens/jettons/overview/content.md)
* [Jetton transfers](/llms/standard/tokens/jettons/transfer/content.md)
* [Token metadata and decimals](/llms/standard/tokens/metadata/content.md)

General:

* [Transaction fees](/llms/foundations/fees/content.md)
* [AppKit overview](/llms/ecosystem/appkit/overview/content.md)
* [TON Connect overview](/llms/ecosystem/ton-connect/content.md)
