Skip to main content

Execute a Swap

This guide walks through the complete 5-step process to execute a token swap on Kroko DEX.

Overview

Step 1: Approve token → Permit2          (one-time per token)
Step 2: Permit2 → approve Universal Router (one-time per token)
Step 3: Get quote from API
Step 4: Get swap calldata from API
Step 5: Send transaction

Prerequisites

import { ethers } from 'ethers';

const PERMIT2 = '0x2E1987F680FD7Bc8B33d3Bf94f12B988A0B50034';
const UNIVERSAL_ROUTER = '0xefeCc1c2dE3BfE4C6D43030F2AcDD5C3cE279024';
const API_BASE = 'https://dex.kasplex.org/swap-api';

const ERC20_ABI = [
  'function allowance(address,address) view returns (uint256)',
  'function approve(address,uint256) returns (bool)'
];

const PERMIT2_ABI = [
  'function approve(address token, address spender, uint160 amount, uint48 expiration)',
  'function allowance(address owner, address token, address spender) view returns (uint160, uint48, uint48)'
];

Step 1: Approve Token to Permit2

Each token only needs to be approved to Permit2 once. We recommend approving the maximum amount.
const tokenContract = new ethers.Contract(tokenIn, ERC20_ABI, signer);
const userAddress = await signer.getAddress();

// Check existing approval
const allowance = await tokenContract.allowance(userAddress, PERMIT2);

if (allowance < amountIn) {
  console.log('Approving token to Permit2...');
  const tx = await tokenContract.approve(PERMIT2, ethers.MaxUint256);
  await tx.wait();
}

Step 2: Permit2 Approve Universal Router

Grant the Universal Router permission to spend your token through Permit2.
const permit2 = new ethers.Contract(PERMIT2, PERMIT2_ABI, signer);

// Check existing Permit2 approval
const [amount, expiration] = await permit2.allowance(
  userAddress,
  tokenIn,
  UNIVERSAL_ROUTER
);
const now = Math.floor(Date.now() / 1000);

if (amount < amountIn || expiration < now) {
  console.log('Approving Universal Router via Permit2...');
  const tx = await permit2.approve(
    tokenIn,
    UNIVERSAL_ROUTER,
    ethers.MaxUint160,            // Max uint160
    now + 365 * 24 * 60 * 60      // 1 year
  );
  await tx.wait();
}

Step 3: Get Quote

Fetch the optimal swap route and expected output.
// "I want to sell 1 token — how much do I get?"
const params = new URLSearchParams({
  tokenIn,
  tokenOut,
  amountIn: '1000000000000000000',
  tradeType: '0'
});

const res = await fetch(`${API_BASE}/api/v1/quote?${params}`);
const quote = await res.json();

console.log(`Expected output: ${quote.amountOut}`);
console.log(`Price impact: ${quote.priceImpact}%`);
console.log(`Route: ${quote.route.protocol} (${quote.route.hops} hop)`);

Step 4: Get Swap Calldata

Request the encoded transaction data from the Swap API.
const swapRes = await fetch(`${API_BASE}/api/v1/swap`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tokenIn,
    tokenOut,
    amountIn: '1000000000000000000',
    tradeType: 0,
    slippage: 0.5,        // 0.5% slippage tolerance
    recipient: userAddress,
    deadline: 1200         // 20 minutes
  })
});

const swapData = await swapRes.json();

// Verify slippage protection
console.log(`Min output: ${swapData.quote.minAmountOut}`);

Step 5: Execute the Swap

Send the transaction using the calldata from the API.
const tx = await signer.sendTransaction({
  to: swapData.to,        // Universal Router
  data: swapData.data,    // Encoded calldata
  value: swapData.value   // 0 for ERC-20, or KAS amount for native token
});

const receipt = await tx.wait();
console.log(`Swap executed: ${receipt.hash}`);

Native KAS Swaps

When selling native KAS, the flow is the same except:
  • Use the WKAS address as tokenIn in the API calls
  • Skip Steps 1 and 2 (no approval needed for native KAS)
  • The API returns a non-zero value — include it in the transaction
// Selling KAS for a token
const swapData = await getSwapData({
  tokenIn: WKAS_ADDRESS,   // Use WKAS address, not zero address
  tokenOut: targetToken,
  amountIn: '1000000000000000000',
  // ...
});

// value will be non-zero
const tx = await signer.sendTransaction({
  to: swapData.to,
  data: swapData.data,
  value: swapData.value  // "1000000000000000000" (1 KAS)
});

Error Handling

ErrorCauseSolution
TRANSFER_FROM_FAILEDMissing approval or insufficient balanceCheck Steps 1 & 2
EXPIREDDeadline passedUse a longer deadline or resubmit
V3_INVALID_AMOUNT_OUTSlippage exceededIncrease slippage tolerance
No route foundNo liquidity path existsCheck token addresses and pool existence
ACTION_REJECTED (code 4001)User rejected in walletNo action needed
See Error Handling for a comprehensive reference.