How to work with Jettons using AppKit
Initialize the AppKit before using examples on this page.
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.
To deploy and mint a new jetton on the mainnet without writing code, use the dedicated official tool: TON MINTER.
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>
);
};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>;
};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
Do not store the balance check results anywhere in the application state, as they become outdated quickly. For UI purposes, do continuous balance monitoring.
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>;
};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>
);
};Continuous balance monitoring
Obtain real-time jetton balance updates with a streaming API provider 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
Modify the following example according to the application logic:
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>;
};Only polling
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>;
};Transfers
Funds at risk
Each jetton stores the decimals parameter in its 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.
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}
/>
);
};Next steps
See also
Jettons:
General: