For the complete documentation index, see llms.txt. This page is also available as Markdown.

Executing swaps

Once you have a quote, you submit a transaction to one of two routers. This page covers when to use which and shows end-to-end calldata flows.

Router
When to use

SwapRouter02

Standard V3 swaps. The simplest path.

UniversalRouter

Bundling Permit2 + swap, multi-protocol routes, atomic operations

For Kumbaya mainnet addresses see Contract Addresses.

Path 1: SwapRouter02 - exact input single

For a single-pool exact-input swap, this is the minimum viable flow:

import { createWalletClient, createPublicClient, http, parseUnits, parseAbi } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'

const SWAP_ROUTER_02 = '0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e' // mainnet
const RPC = 'https://mainnet.megaeth.com/rpc'

const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY')
const wallet = createWalletClient({ account, transport: http(RPC) })

const swapAbi = parseAbi([
  'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountOut)',
])

// Step 1 - approve once per (token, router) if not already approved.
// (Skip for ETH; required for any ERC-20.)
// await tokenContract.write.approve([SWAP_ROUTER_02, MAX_UINT256])

// Step 2 - submit the swap.
const txHash = await wallet.writeContract({
  address: SWAP_ROUTER_02,
  abi: swapAbi,
  functionName: 'exactInputSingle',
  args: [{
    tokenIn:  '0xWETH_ADDRESS',
    tokenOut: '0xTOKEN_OUT',
    fee:      3000,
    recipient: account.address,
    amountIn: parseUnits('1', 18),
    amountOutMinimum: minimumOut,    // from your quote × (1 - slippageBps/10000)
    sqrtPriceLimitX96: 0n,
  }],
})

Paying with native ETH: pass tokenIn = WETH, set value: parseEther('amount') on the call (pulled from msg.value); SwapRouter02 wraps it for you.

Receiving native ETH out: set recipient = address(2) (the router's ADDRESS_THIS constant), then multicall([exactInputSingle, unwrapWETH9(amountMinimum, userAddress)]) so the router holds the WETH and unwraps it to the user. Sending recipient = msg.sender directly gives you WETH (no unwrap).

Path 2: SwapRouter02 - multi-hop exact input

For routes through more than one pool, use exactInput with a packed path:

The path is tokenAddress, fee, tokenAddress, fee, ... packed - fees go between adjacent token pairs.

Path 3: Using the Exchange API's calldata

If you got your quote from the Exchange API, the response already contains the calldata. Just submit it.

For a GET /api/v1/quote response (methodParameters.calldata):

For a POST /api/v1/quote partner response (transaction.callData):

The two endpoints return different field names (calldata vs callData). Mind the casing.

This is the lowest-effort production path. Just do the approval first if needed.

Path 4: UniversalRouter with Permit2

UniversalRouter lets you bundle a Permit2 signature + swap into one tx, so you don't need an upfront ERC-20 approve. Use @kumbaya_xyz/universal-router-sdk to encode the commands:

Use this when:

  • You want to skip ERC-20 approvals via Permit2 signatures.

  • You're combining swap + transfer + wrap/unwrap atomically.

  • Your route spans multiple Uniswap protocols (V2 + V3).

Recipient sentinels

SwapRouter02 accepts two special recipient values that resolve at execution time:

recipient

Resolves to

Use case

address(1) (MSG_SENDER)

The original msg.sender

Direct send to the user (skip if you set recipient to the user explicitly)

address(2) (ADDRESS_THIS)

The router itself

Hold tokens in the router for a follow-up call (unwrapWETH9, sweepToken, etc.) via multicall

Source: swap-router-contracts/contracts/libraries/Constants.sol. Use these in calldata when chaining operations.

Approvals

Before any non-ETH swap with SwapRouter02, the router needs allowance for tokenIn. Common patterns:

  • One-shot max approve - approve(SWAP_ROUTER_02, MAX_UINT256). Simple, but trusts the router with your full balance.

  • Per-trade approve - approve exactly the amount you're swapping. Higher gas overhead, lower trust footprint.

  • Permit2 - sign instead of approve. Use UniversalRouter for this path.

Slippage handling

amountOutMinimum is your slippage protection. Compute it from your quote:

If on-chain conditions move price more than this between your quote and execution, the swap reverts and you lose only the gas.

Common reverts

Revert reason
Likely cause

Too little received

Slippage exceeded - bump tolerance or reduce size

STF (SafeTransferFrom)

Token allowance is too low or zero

LOK

Pool re-entered (extremely rare)

Plain revert with no reason

Path packed wrong or recipient mismatch

Where to next

Last updated