> ## Documentation Index
> Fetch the complete documentation index at: https://companyname-a7d5b98e-v2-pagination.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# How to swap tokens using AppKit

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

<Aside>
  [Initialize the AppKit](/ecosystem/appkit/init) before using examples on this page. Swap functionality requires a configured [swap provider](#available-providers).
</Aside>

AppKit supports on-chain token swaps through [pluggable swap providers](#create-a-custom-swap-provider). Supported swap kinds are Toncoin to jetton and jetton to jetton.

The swap flow has two steps: [get a quote](#get-a-swap-quote) for the desired trade, then [build and send the swap transaction](#build-and-send-the-swap-transaction).

<Aside type="caution" title="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](https://github.com/tonkeeper/ton-assets). Test with smaller amounts. Confirm the obtained quote before building the transaction.
</Aside>

## Available providers

AppKit supports two swap providers:

* `OmnistonSwapProvider` integrates the [STON.fi](https://ston.fi) DEX aggregator through the Omniston SDK: `@ston-fi/omniston-sdk`. Requires the `@ston-fi/omniston-sdk` package.
* `DeDustSwapProvider` integrates the [DeDust](https://dedust.io) Router v2 aggregator. Has no additional dependencies.

Register one or more providers during [AppKit initialization](/ecosystem/appkit/init#providers). 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.

<Aside type="caution">
  Do not store the obtained swap quote anywhere in the application state, as it becomes outdated quickly.
</Aside>

<CodeGroup>
  ```tsx title="React" icon="react" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  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>
    );
  };
  ```

  ```ts title="TypeScript" icon="globe" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import { Network, type AppKit, getSwapQuote } from '@ton/appkit';

  async function fetchSwapQuote(
    /** Initialized AppKit instance */
    kit: AppKit,
    /**
     * Source jetton master (minter) contract address
     * or Toncoin 'ton' identifier
     */
    fromAddress: string,
    /**
     * Decimal precision of the source token to calculate raw amounts
     * For example, Toncoin = 9, USDT (jetton) = 6
     */
    fromDecimals: number,
    /**
     * Target jetton master (minter) contract address
     * or Toncoin 'ton' identifier
     */
    toAddress: string,
    /**
     * Decimal precision of the target token to calculate raw amounts
     * For example, Toncoin = 9, USDT (jetton) = 6
     */
    toDecimals: number,
    /**
     * Amount to swap in fractional units
     * For example, '0.1' or '1'
     */
    amount: string,
    /**
     * 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?: boolean,
  ) {
    const quote = await getSwapQuote(kit, {
      from: { address: fromAddress, decimals: fromDecimals },
      to: { address: toAddress, decimals: toDecimals },
      amount,
      // Set the target network explicitly.
      // Jettons such as USDT are available on mainnet only.
      network: Network.mainnet(),
      slippageBps: 100, // 1%
      ...(isReverseSwap && { isReverseSwap }),
    });
    console.log('Swap quote:', quote);
    return quote;
  }
  ```
</CodeGroup>

## Build and send the swap transaction

Building a swap transaction requires a quote and the sender wallet address. Some parameters are optional:

* `slippageBps` overrides the provider default slippage for a single swap.
* `destinationAddress` sets a different recipient for the output tokens.
* `deadline` sets a UNIX timestamp after which the transaction becomes invalid.

<CodeGroup>
  ```tsx title="React" icon="react" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  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>
    );
  };
  ```

  ```ts title="TypeScript" icon="globe" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import {
    Network,
    type AppKit,
    getSwapQuote,
    buildSwapTransaction,
    sendTransaction,
    getSelectedWallet,
  } from '@ton/appkit';

  async function executeSwap(
    /** Initialized AppKit instance */
    kit: AppKit,
    /** Source token address */
    fromAddress: string,
    /** Source token decimals */
    fromDecimals: number,
    /** Destination token address */
    toAddress: string,
    /** Destination token decimals */
    toDecimals: number,
    /** Amount to swap in fractional units */
    amount: string,
  ) {
    // Step 1: Get a quote
    const quote = await getSwapQuote(kit, {
      from: { address: fromAddress, decimals: fromDecimals },
      to: { address: toAddress, decimals: toDecimals },
      amount,
      network: Network.mainnet(),
    });

    // Step 2: Build the swap transaction
    const address = getSelectedWallet(kit)?.getAddress();
    if (!address) { return; }
    const transaction = await buildSwapTransaction(kit, {
      quote,
      userAddress: address,
      slippageBps: 100, // 1%
      deadline: Math.floor(Date.now() / 1000) + 600, // 10 minutes
    });

    // Step 3: Sign and send via TON Connect
    const result = await sendTransaction(kit, transaction);
    console.log('Swap transaction sent:', result);
  }
  ```
</CodeGroup>

## 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.

```ts title="TypeScript" icon="globe" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
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](/ecosystem/appkit/init#providers) or [dynamically](/ecosystem/appkit/init#add-new-providers) at runtime:

<CodeGroup>
  ```ts title="At initialization" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  import { AppKit } from '@ton/appkit';

  const kit = new AppKit({
    providers: [new CustomSwapProvider()],
  });
  ```

  ```ts title="At runtime" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  kit.swapManager.registerProvider(new CustomSwapProvider());
  ```
</CodeGroup>

### 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.

## See also

* [AppKit overview](/ecosystem/appkit/overview)
* [TON Connect overview](/ecosystem/ton-connect)
