> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a swap UI

> Quote a token swap, show the estimate, execute it from the user's wallet, and track it to completion.

## Prerequisites

Before this page: a connected or embedded wallet (see [Connecting external wallets](https://docs.dynamic.xyz/docs/javascript/building-ui/connecting-external-wallets.md)).

## What you'll build

A swap flow has four steps:

1. **Quote** — ask for a route between two tokens.
2. **Show** the estimated amount received and gas cost.
3. **Execute** — sign and submit from the user's wallet.
4. **Track** — poll until the swap is done.

Amounts are always in the token's **smallest unit** (for example, `1000000` for 1 USDC with 6 decimals).

## Quoting a swap

`getSwapQuote` takes a `from` and `to` side. Set `from.amount` to quote by input (or `to.amount` to quote by desired output — they're mutually exclusive). The response includes the `signingPayload` you'll execute and the estimated `to.amount`.

> [tab] TypeScript
>
> ```typescript theme={"system"}
> import { getSwapQuote, type WalletAccount } from '@dynamic-labs-sdk/client';
>
> async function quoteUsdcToWeth(walletAccount: WalletAccount) {
>   const quote = await getSwapQuote({
>     from: {
>       address: walletAccount.address,
>       chain: walletAccount.chain,
>       networkId: '1',
>       tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
>       amount: '1000000',
>     },
>     to: {
>       address: walletAccount.address,
>       chain: walletAccount.chain,
>       networkId: '1',
>       tokenAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
>     },
>     slippage: 0.005,
>   });
>
>   // showEstimate is your implementation.
>   showEstimate(quote.to.amount, quote.gasCostUSD);
>   return quote;
> }
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { type WalletAccount } from '@dynamic-labs-sdk/client';
> import { useGetSwapQuote } from '@dynamic-labs-sdk/react-hooks';
>
> function useUsdcToWethQuote(walletAccount: WalletAccount, amount: string) {
>   // useGetSwapQuote is a query hook -- it fetches (and refetches) whenever
>   // the params change, so pass amount from your own input state to keep
>   // the quote live as the user types.
>   return useGetSwapQuote({
>     from: {
>       address: walletAccount.address,
>       chain: walletAccount.chain,
>       networkId: '1',
>       tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
>       amount,
>     },
>     to: {
>       address: walletAccount.address,
>       chain: walletAccount.chain,
>       networkId: '1',
>       tokenAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
>     },
>     slippage: 0.005,
>   });
> }
> ```
>
> Use the returned `{ data: quote, isLoading }` to render the estimate — `quote` is `undefined` until the first fetch resolves.

## Executing the swap

Pass the quote's `signingPayload` to `executeSwapTransaction`. ERC-20 swaps may need a separate approval first — `onStepChange` tells you which step is running so you can update the UI.

> [tab] TypeScript
>
> ```typescript theme={"system"}
> import { executeSwapTransaction } from '@dynamic-labs-sdk/client';
> import type { SwapQuoteResponse, WalletAccount } from '@dynamic-labs-sdk/client';
>
> async function executeSwap(
>   walletAccount: WalletAccount,
>   quote: SwapQuoteResponse,
> ) {
>   if (!quote.signingPayload) return;
>
>   const { transactionHash } = await executeSwapTransaction({
>     walletAccount,
>     signingPayload: quote.signingPayload,
>     onStepChange: (step) => {
>       // updateStep is your implementation — 'approval' or 'transaction'.
>       updateStep(step);
>     },
>   });
>
>   return transactionHash;
> }
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import type { SwapQuoteResponse, WalletAccount } from '@dynamic-labs-sdk/client';
> import { useExecuteSwapTransaction } from '@dynamic-labs-sdk/react-hooks';
>
> function SwapButton({
>   walletAccount,
>   quote,
> }: {
>   walletAccount: WalletAccount;
>   quote: SwapQuoteResponse;
> }) {
>   const { mutate: execute, isPending } = useExecuteSwapTransaction();
>
>   const onSwap = () => {
>     if (!quote.signingPayload) return;
>
>     execute({
>       walletAccount,
>       signingPayload: quote.signingPayload,
>       // updateStep is your implementation — step is 'approval' or 'transaction'.
>       onStepChange: (step) => updateStep(step),
>     });
>   };
>
>   return (
>     <button type="button" onClick={onSwap} disabled={isPending}>
>       Swap
>     </button>
>   );
> }
> ```

## Tracking status

A swap can take time to settle — especially across chains. Poll `getSwapStatus` with the transaction hash until the status is `DONE` or `FAILED`.

> [tab] TypeScript
>
> Re-check on an interval and resolve only once the swap reaches a terminal status.
>
> ```typescript theme={"system"}
> import { getSwapStatus } from '@dynamic-labs-sdk/client';
>
> const POLL_INTERVAL_MS = 3000;
>
> const delay = (ms: number) =>
>   new Promise((resolve) => setTimeout(resolve, ms));
>
> // Resolves only once the swap settles — DONE or FAILED.
> async function trackSwap(txHash: string): Promise<'DONE' | 'FAILED'> {
>   while (true) {
>     const { status } = await getSwapStatus({ txHash });
>
>     // status is 'PENDING' | 'DONE' | 'FAILED' | 'NOT_FOUND'.
>     if (status === 'DONE' || status === 'FAILED') {
>       return status;
>     }
>
>     // Not settled yet — wait, then check again.
>     await delay(POLL_INTERVAL_MS);
>   }
> }
> ```

> [tab] React
>
> Drive polling with `refetchInterval` — return the interval while the swap is in flight, and `false` once it settles so the hook stops refetching.
>
> ```tsx theme={"system"}
> import { useGetSwapStatus } from '@dynamic-labs-sdk/react-hooks';
>
> const POLL_INTERVAL_MS = 3000;
>
> function SwapStatus({ txHash }: { txHash: string }) {
>   const { data } = useGetSwapStatus({
>     txHash,
>     queryParams: {
>       refetchInterval: (query) => {
>         const status = query.state.data?.status;
>         const settled = status === 'DONE' || status === 'FAILED';
>         return settled ? false : POLL_INTERVAL_MS;
>       },
>     },
>   });
>
>   // data.status is 'PENDING' | 'DONE' | 'FAILED' | 'NOT_FOUND'.
>   return <span>{data?.status ?? 'PENDING'}</span>;
> }
> ```

## Handling errors

| Error / status | Cause | What to do |
| - | - | - |
| `TokenSwapError` | `getSwapStatus` couldn't check the swap's status | Show the message and let the user retry the status check — the swap itself may still be fine. |
| `getSwapQuote`/`executeSwapTransaction` rejects | No route, stale quote, or the user rejected the wallet prompt | Show the message and let the user re-quote — routes and prices change. |
| Status `FAILED` | The swap didn't settle | Surface the failure; the `substatus` gives more context. |
| Status `PENDING` | Still settling | Keep polling; show an in-progress state. |

## See also

* [Transaction confirmation & simulation](https://docs.dynamic.xyz/docs/javascript/building-ui/transaction-confirmation.md) — confirm the underlying transaction
* [Token balances & display](https://docs.dynamic.xyz/docs/javascript/building-ui/token-balances-display.md) — reflect balances after the swap
* [Funding flows](https://docs.dynamic.xyz/docs/javascript/building-ui/funding-flows.md) — get tokens to swap


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.