Swap Example
A minimal but complete example of executing a token swap on Kroko DEX using ethers.js v6.Full Code
Copy
Ask AI
import { ethers } from 'ethers';
// === Configuration ===
const RPC_URL = 'https://evmrpc.kasplex.org';
const API_BASE = 'https://dex.kasplex.org/swap-api';
const PERMIT2 = '0x2E1987F680FD7Bc8B33d3Bf94f12B988A0B50034';
const UNIVERSAL_ROUTER = '0xefeCc1c2dE3BfE4C6D43030F2AcDD5C3cE279024';
// === ABIs ===
const ERC20_ABI = [
'function allowance(address,address) view returns (uint256)',
'function approve(address,uint256) returns (bool)',
'function balanceOf(address) view returns (uint256)',
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
];
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)',
];
// === Trade Types ===
enum TradeType {
EXACT_INPUT = 0,
EXACT_OUTPUT = 1,
}
/**
* Execute a complete swap on Kroko DEX.
*
* @param signer - Connected ethers Signer
* @param tokenIn - Input token address (use WKAS address for native KAS)
* @param tokenOut - Output token address
* @param amount - Amount in raw units (amountIn for EXACT_INPUT, amountOut for EXACT_OUTPUT)
* @param tradeType - 0 for Exact Input, 1 for Exact Output
* @param slippage - Slippage tolerance as percentage (e.g., 0.5 for 0.5%)
*/
async function executeSwap(
signer: ethers.Signer,
tokenIn: string,
tokenOut: string,
amount: string,
tradeType: TradeType = TradeType.EXACT_INPUT,
slippage: number = 0.5
): Promise<ethers.TransactionReceipt> {
const userAddress = await signer.getAddress();
const token = new ethers.Contract(tokenIn, ERC20_ABI, signer);
const permit2 = new ethers.Contract(PERMIT2, PERMIT2_ABI, signer);
// --- Step 1: Approve token → Permit2 ---
const tokenAllowance = await token.allowance(userAddress, PERMIT2);
if (tokenAllowance < BigInt(amount)) {
console.log('Step 1: Approving token to Permit2...');
const tx = await token.approve(PERMIT2, ethers.MaxUint256);
await tx.wait();
console.log(' Done.');
} else {
console.log('Step 1: Token already approved to Permit2.');
}
// --- Step 2: Permit2 → approve Universal Router ---
const [p2Amount, p2Expiration] = await permit2.allowance(
userAddress,
tokenIn,
UNIVERSAL_ROUTER
);
const now = Math.floor(Date.now() / 1000);
if (p2Amount < BigInt(amount) || Number(p2Expiration) < now) {
console.log('Step 2: Approving Universal Router via Permit2...');
const tx = await permit2.approve(
tokenIn,
UNIVERSAL_ROUTER,
ethers.MaxUint160,
now + 365 * 24 * 60 * 60
);
await tx.wait();
console.log(' Done.');
} else {
console.log('Step 2: Universal Router already approved via Permit2.');
}
// --- Step 3: Get quote ---
console.log('Step 3: Fetching quote...');
const quoteParams = new URLSearchParams({
tokenIn,
tokenOut,
tradeType: tradeType.toString(),
});
if (tradeType === TradeType.EXACT_INPUT) {
quoteParams.set('amountIn', amount);
} else {
quoteParams.set('amountOut', amount);
}
const quoteRes = await fetch(`${API_BASE}/api/v1/quote?${quoteParams}`);
const quote = await quoteRes.json();
if (quote.error) throw new Error(`Quote failed: ${quote.error}`);
console.log(` Route: ${quote.route.protocol} (${quote.route.hops} hop)`);
console.log(` Amount in: ${quote.amountIn}`);
console.log(` Amount out: ${quote.amountOut}`);
console.log(` Price impact: ${quote.priceImpact}%`);
// --- Step 4: Get swap calldata ---
console.log('Step 4: Generating calldata...');
const swapBody: Record<string, unknown> = {
tokenIn,
tokenOut,
tradeType,
slippage,
recipient: userAddress,
deadline: 1200,
};
if (tradeType === TradeType.EXACT_INPUT) {
swapBody.amountIn = amount;
} else {
swapBody.amountOut = amount;
}
const swapRes = await fetch(`${API_BASE}/api/v1/swap`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(swapBody),
});
const swapData = await swapRes.json();
if (swapData.error) throw new Error(`Swap API failed: ${swapData.error}`);
if (tradeType === TradeType.EXACT_INPUT) {
console.log(` Min output: ${swapData.quote.minAmountOut}`);
} else {
console.log(` Max input: ${swapData.quote.maxAmountIn}`);
}
// --- Step 5: Execute ---
console.log('Step 5: Sending transaction...');
const tx = await signer.sendTransaction({
to: swapData.to,
data: swapData.data,
value: swapData.value,
});
console.log(` Tx hash: ${tx.hash}`);
const receipt = await tx.wait();
console.log(` Confirmed in block ${receipt!.blockNumber}`);
return receipt!;
}
// === Usage ===
async function main() {
// Connect wallet (example using private key — use a secure method in production)
const provider = new ethers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);
const TOKEN_A = '0xB190a6A7fC2873f1Abf145279eD664348d5Ef630';
const TOKEN_B = '0x3Ac3B30b7f18AEFD4590D7FE4d9C5944aaeB7220';
// Exact Input: sell 1 TOKEN_A for TOKEN_B
await executeSwap(
signer,
TOKEN_A,
TOKEN_B,
ethers.parseEther('1').toString(),
TradeType.EXACT_INPUT,
0.5
);
}
main().catch(console.error);
Key Points
- Steps 1 & 2 are one-time per token — you can skip them for subsequent swaps
- Native KAS: Use the WKAS address as
tokenIn, and skip Steps 1 & 2. The API setsvalueautomatically. - Error handling: Always check for
quote.errorandswapData.errorbefore proceeding - Slippage: The on-chain
minAmountOut/maxAmountInprotection is encoded in the calldata