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

# Swap with Viem

> Execute a direct V2 swap or a smart-order-routed swap from a Viem app.

These examples use **GIWA Sepolia** (`91342`) and Viem. Replace the sample tokens and recipient with your own values. Always request a fresh quote immediately before a trade.

## Shared setup

```ts theme={null}
import {
  createPublicClient,
  createWalletClient,
  custom,
  defineChain,
  http,
  maxUint256,
  parseAbi,
  parseUnits,
  type Address,
} from 'viem'

const giwaSepolia = defineChain({
  id: 91342,
  name: 'GIWA Sepolia',
  nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
  rpcUrls: { default: { http: ['https://sepolia-rpc.giwa.io/'] } },
  blockExplorers: {
    default: { name: 'GIWA Explorer', url: 'https://sepolia-explorer.giwa.io/' },
  },
  testnet: true,
})

const publicClient = createPublicClient({ chain: giwaSepolia, transport: http() })
const walletClient = createWalletClient({
  chain: giwaSepolia,
  transport: custom(window.ethereum!),
})
const [account] = await walletClient.getAddresses()

const TTEST = '0x01a24d6f238c29d4bebc2ac40f56723f4f8e83d2' as Address
const WETH = '0x4200000000000000000000000000000000000006' as Address
```

## Direct V2 swap — without SOR

Use this when you intentionally want one known V2 path. It does not search V3 pools or alternative paths. The example reads the V2 quote from the router, applies 0.5% slippage protection, approves the V2 router, then sends the swap.

```ts theme={null}
const V2_ROUTER = '0xdaf3fd5919c7f2910832bfdba0384167946edabb' as Address
const amountIn = parseUnits('1', 18) // 1 TTEST
const deadline = BigInt(Math.floor(Date.now() / 1_000) + 20 * 60)

const erc20Abi = parseAbi([
  'function approve(address spender, uint256 amount) returns (bool)',
])
const v2RouterAbi = parseAbi([
  'function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[] amounts)',
  'function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) returns (uint256[] amounts)',
])

const amounts = await publicClient.readContract({
  address: V2_ROUTER,
  abi: v2RouterAbi,
  functionName: 'getAmountsOut',
  args: [amountIn, [TTEST, WETH]],
})
const quotedOut = amounts[amounts.length - 1]!
const amountOutMin = (quotedOut * 9_950n) / 10_000n // 0.5% slippage

const approvalHash = await walletClient.writeContract({
  account,
  address: TTEST,
  abi: erc20Abi,
  functionName: 'approve',
  args: [V2_ROUTER, maxUint256],
})
await publicClient.waitForTransactionReceipt({ hash: approvalHash })

const swapHash = await walletClient.writeContract({
  account,
  address: V2_ROUTER,
  abi: v2RouterAbi,
  functionName: 'swapExactTokensForTokens',
  args: [amountIn, amountOutMin, [TTEST, WETH], account, deadline],
})
await publicClient.waitForTransactionReceipt({ hash: swapHash })
```

<Warning>
  `getAmountsOut` is a V2-only estimate. It can change before your transaction lands. Never set `amountOutMin` to zero in a production integration.
</Warning>

## Smart-order-routed swap — with SOR

The Topokki API evaluates available V2 and V3 routes and returns ready-to-submit Universal Router calldata. For ERC-20 input tokens, authorize both Permit2 and the Universal Router in Permit2 before sending the quote's transaction. See [Get a swap quote](/developers/endpoints/quote) for the complete endpoint reference.

```ts theme={null}
const API_URL = 'https://api.testnet.topokki.exchange'
const PERMIT2 = '0x000000000022D473030F116dDEE9F6B43aC78BA3' as Address
const UNIVERSAL_ROUTER = '0xc4a0afb2436792e11d7892c107482f8e8e65c4cd' as Address
const amountIn = parseUnits('1', 18)

const query = new URLSearchParams({
  tokenIn: TTEST,
  tokenOut: WETH,
  amount: amountIn.toString(),
  tradeType: 'exactIn',
  recipient: account,
  tokenInChainId: '91342',
  tokenOutChainId: '91342',
  slippageBps: '50',
})
const response = await fetch(`${API_URL}/v1/quote?${query}`)
if (!response.ok) throw new Error(`Quote failed: ${await response.text()}`)

const quote = await response.json() as {
  methodParameters?: { to: Address; calldata: `0x${string}`; value: string }
}
if (!quote.methodParameters) throw new Error('Quote has no executable transaction')

const erc20Abi = parseAbi([
  'function approve(address spender, uint256 amount) returns (bool)',
])
const permit2Abi = parseAbi([
  'function approve(address token, address spender, uint160 amount, uint48 expiration)',
])

// 1. Let Permit2 transfer the input token.
const erc20Approval = await walletClient.writeContract({
  account,
  address: TTEST,
  abi: erc20Abi,
  functionName: 'approve',
  args: [PERMIT2, maxUint256],
})
await publicClient.waitForTransactionReceipt({ hash: erc20Approval })

// 2. Let the Universal Router spend that token through Permit2.
const permit2Approval = await walletClient.writeContract({
  account,
  address: PERMIT2,
  abi: permit2Abi,
  functionName: 'approve',
  args: [TTEST, UNIVERSAL_ROUTER, (1n << 160n) - 1n, (1n << 48n) - 1n],
})
await publicClient.waitForTransactionReceipt({ hash: permit2Approval })

// 3. Submit the exact transaction returned by the fresh quote.
const swapHash = await walletClient.sendTransaction({
  account,
  to: quote.methodParameters.to,
  data: quote.methodParameters.calldata,
  value: BigInt(quote.methodParameters.value),
})
await publicClient.waitForTransactionReceipt({ hash: swapHash })
```

The first two approvals can be skipped when the existing allowances already cover the input amount and intended expiry. For native ETH input, request a quote with `tokenInIsNative=true`; do not perform ERC-20/Permit2 approval for that input.

<Warning>
  Do not modify the transaction destination, calldata, or value returned by the quote. If the amount, recipient, slippage, or route requirements change, request a new quote.
</Warning>
