How to swap tokens using AppKit
Initialize the AppKit before using examples on this page. Swap functionality requires a configured swap provider.
AppKit supports on-chain token swaps through pluggable swap providers. Supported swap kinds are Toncoin to jetton and jetton to jetton.
The swap flow has two steps: get a quote for the desired trade, then build and send the swap transaction.
Funds at risk
Token swaps are irreversible on-chain operations. Verify the quote details before confirming: review jetton master (minter) contract addresses, amounts, and slippage.
Mitigation: Double-check the addresses against official sources or public allowlists. Test with smaller amounts. Confirm the obtained quote before building the transaction.
Available providers
AppKit supports two swap providers:
OmnistonSwapProviderintegrates the STON.fi DEX aggregator through the Omniston SDK:@ston-fi/omniston-sdk. Requires the@ston-fi/omniston-sdkpackage.DeDustSwapProviderintegrates the DeDust Router v2 aggregator. Has no additional dependencies.
Register one or more providers during AppKit initialization. AppKit uses the first registered swap provider by default. Pass providerId when requesting a quote to target a specific provider.
Get a swap quote
A swap quote estimates how many tokens are received for a given input amount. The quote requires the source token, destination token, and the amount to swap.
Do not store the obtained swap quote anywhere in the application state, as it becomes outdated quickly.
import { Network } from '@ton/appkit';
import { useSwapQuote } from '@ton/appkit-react';
export const SwapQuoteCard = ({
fromAddress = 'ton',
fromDecimals = 9,
toAddress = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs',
toDecimals = 6,
amount = '1',
}) => {
const {
data: quote,
isLoading,
error,
} = useSwapQuote({
// Source token: what to swap
from: {
// Either a source jetton master (minter) contract address
// or a Toncoin identifier in its stead
address: fromAddress ?? 'ton',
// Decimal precision of the token to calculate raw unit amounts
// For example, Toncoin = 9, USDT (jetton) = 6
decimals: fromDecimals,
},
// Target token: what to receive
to: {
address: toAddress,
decimals: toDecimals,
},
// Amount to swap in fractional units
// For example, '0.1' or '1'
amount,
// Set the target network explicitly.
// Jettons such as USDT are available on mainnet only.
network: Network.mainnet(),
// Optional slippage tolerance in basis points
slippageBps: 100, // 1%
// (optional) Direction of the swap
// If true, `amount` sets the target amount of tokens to receive (buy)
// If false, `amount` sets the source amount of tokens to spend (sell)
// Defaults to false
isReverseSwap: false,
});
if (isLoading) {
return <div>Loading quote...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return (
<div>
<p><em>Swap quote</em></p>
{quote && (
<div>
<p>From: {quote.fromToken.address}</p>
<p>To: {quote.toToken.address}</p>
<p>Input amount: {quote.fromAmount}</p>
<p>Expected output: {quote.toAmount}</p>
<p>Minimum received: {quote.minReceived}</p>
<p>Price impact: {quote.priceImpact ? `${quote.priceImpact / 100}%` : 'n/a'}</p>
</div>
)}
</div>
);
};Build and send the swap transaction
Building a swap transaction requires a quote and the sender wallet address. Some parameters are optional:
slippageBpsoverrides the provider default slippage for a single swap.destinationAddresssets a different recipient for the output tokens.deadlinesets a UNIX timestamp after which the transaction becomes invalid.
import {
Network,
useSwapQuote,
useBuildSwapTransaction,
useSendTransaction,
useAddress,
} from '@ton/appkit-react';
export const SwapForm = ({ fromToken, toToken, amount }) => {
const address = useAddress();
const { data: quote } = useSwapQuote({
from: fromToken,
to: toToken,
amount,
network: Network.mainnet(),
});
const { mutateAsync: buildTransaction, isPending: isBuilding } =
useBuildSwapTransaction();
const { mutateAsync: sendTransaction, isPending: isSending } =
useSendTransaction();
const handleSwap = async () => {
if (!quote || !address) return;
// Build the swap transaction from the quote
const transaction = await buildTransaction({
quote,
userAddress: address,
slippageBps: 100, // 1%
deadline: Math.floor(Date.now() / 1000) + 600, // 10 minutes
});
// Sign and send via TON Connect
const result = await sendTransaction(transaction);
console.log('Swap transaction sent:', result);
};
const isPending = isBuilding || isSending;
return (
<div>
{quote && (
<div>
<p>Output amount: {quote.toAmount}</p>
<button onClick={handleSwap} disabled={isPending}>
{isPending ? 'Processing...' : 'Swap'}
</button>
</div>
)}
</div>
);
};Create a custom swap provider
Custom providers can be added to integrate other DEXes or aggregators. A swap provider implements the SwapProvider interface — two methods for quoting and transaction building: getQuote() and buildSwapTransaction().
The following example assumes DEX APIs that return SwapQuote and TransactionRequest in the exact shapes AppKit expects. In practice, the contents of response.json() require additional mapping and processing before the results can be safely produced from the getQuote() and buildSwapTransaction() functions, respectively.
import type {
SwapProvider,
SwapQuote,
SwapQuoteParams,
SwapParams,
TransactionRequest,
} from '@ton/appkit';
class CustomSwapProvider implements SwapProvider {
readonly type = 'swap';
// Unique identifier for this provider.
// It must not collide with existing identifiers,
// such as 'omniston' and 'dedust'.
readonly providerId = 'custom-dex';
async getQuote<T = unknown>(
params: SwapQuoteParams<T>,
): Promise<SwapQuote> {
// Fetch a quote from the DEX API
const qs = new URLSearchParams({
from: params.from,
to: params.to,
amount: params.amount,
});
const response = await fetch(`https://api.example-dex.com/quote?${qs.toString()}`);
const data = await response.json();
return {
fromToken: params.from,
toToken: params.to,
rawFromAmount: data.rawFromAmount,
rawToAmount: data.rawToAmount,
fromAmount: params.amount,
toAmount: data.outputAmount,
rawMinReceived: data.rawMinReceived,
minReceived: data.minReceived,
network: params.network,
providerId: this.providerId,
metadata: data,
};
}
async buildSwapTransaction<T = unknown>(
params: SwapParams<T>,
): Promise<TransactionRequest> {
// Build the transaction payload using the quote data
const response = await fetch('https://api.example-dex.com/build', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
quote: params.quote,
userAddress: params.userAddress,
destinationAddress: params.destinationAddress,
slippageBps: params.slippageBps,
deadline: params.deadline,
}),
});
return response.json();
}
}Register the provider
Register the custom provider during AppKit initialization or dynamically at runtime:
import { AppKit } from '@ton/appkit';
const kit = new AppKit({
providers: [new CustomSwapProvider()],
});Specify the provider
Once registered, the provider is available through getSwapQuote and buildSwapTransaction. Target it by passing providerId: 'custom-dex' when fetching quotes.
AppKit uses the first registered swap provider by default. To change the default later, call kit.swapManager.setDefaultProvider('custom-dex').
Inspect registered provider IDs with kit.swapManager.getRegisteredProviders(). Check whether an ID is already in use with kit.swapManager.hasProvider('custom-dex') before registering a new provider.