Token Swaps Guide

Overview

Token swaps are the core functionality of JAMM DEX, allowing users to trade between different ERC-20 tokens without permission. This guide details how to use JAMM DEX for various types of token swaps, including exact input, exact output, and swaps involving JU tokens.

Swap Types

Exact Input Swaps

Users specify the exact input amount, and the system calculates the output amount.

// swapExactTokensForTokens
const amountIn = ethers.utils.parseEther("1.0"); // Exact input 1 TokenA
const amountOutMin = ethers.utils.parseEther("0.95"); // Minimum 0.95 TokenB
const path = [tokenA_address, tokenB_address];
const fees = [100]; // 1% fee rate (100 basis points)
const to = userAddress;
const referrer = ethers.constants.AddressZero;
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // Expires in 20 minutes

const tx = await router.swapExactTokensForTokens(
    amountIn,
    amountOutMin,
    path,
    fees,
    to,
    referrer,
    deadline
);

Exact Output Swaps

Users specify the exact output amount, and the system calculates the required input amount.

// swapTokensForExactTokens
const amountOut = ethers.utils.parseEther("1.0"); // Exact output 1 TokenB
const amountInMax = ethers.utils.parseEther("1.1"); // Maximum pay 1.1 TokenA
const path = [tokenA_address, tokenB_address];
const fees = [100]; // 1% fee rate

const tx = await router.swapTokensForExactTokens(
    amountOut,
    amountInMax,
    path,
    fees,
    to,
    referrer,
    deadline
);

JU Token Swaps

Buy Tokens with JU

// swapExactETHForTokens - Use exact amount of JU to buy tokens
const amountOutMin = ethers.utils.parseEther("95"); // Minimum 95 tokens
const path = [WJU_address, token_address]; // Path must start with WJU
const fees = [100]; // 1% fee rate

const tx = await router.swapExactETHForTokens(
    amountOutMin,
    path,
    fees,
    to,
    referrer,
    deadline,
    { value: ethers.utils.parseEther("1.0") } // Send 1 JU
);

Buy Exact Amount of Tokens with JU

// swapETHForExactTokens - Use JU to buy exact amount of tokens
const amountOut = ethers.utils.parseEther("100"); // Exact output 100 tokens
const path = [WJU_address, token_address];
const fees = [100];

// First calculate how much JU is needed
const amounts = await router.getAmountsIn(amountOut, path, fees);
const juNeeded = amounts[0];

const tx = await router.swapETHForExactTokens(
    amountOut,
    path,
    fees,
    to,
    referrer,
    deadline,
    { value: juNeeded.mul(110).div(100) } // Send 10% extra to prevent price changes
);

Sell Tokens for JU

// swapExactTokensForETH - Sell exact amount of tokens for JU
const amountIn = ethers.utils.parseEther("100"); // Sell 100 tokens
const amountOutMin = ethers.utils.parseEther("0.9"); // Minimum 0.9 JU
const path = [token_address, WJU_address]; // Path must end with WJU
const fees = [100];

const tx = await router.swapExactTokensForETH(
    amountIn,
    amountOutMin,
    path,
    fees,
    to,
    referrer,
    deadline
);

Sell Tokens for Exact Amount of JU

// swapTokensForExactETH - Sell tokens for exact amount of JU
const amountOut = ethers.utils.parseEther("1.0"); // Exact output 1 JU
const amountInMax = ethers.utils.parseEther("110"); // Maximum sell 110 tokens
const path = [token_address, WJU_address];
const fees = [100];

const tx = await router.swapTokensForExactETH(
    amountOut,
    amountInMax,
    path,
    fees,
    to,
    referrer,
    deadline
);

Pre-Swap Preparation

1. Token Approval

Before swapping ERC-20 tokens, you need to approve the Router contract to spend your tokens:

const tokenContract = new ethers.Contract(tokenAddress, erc20ABI, signer);

// Check current allowance
const currentAllowance = await tokenContract.allowance(userAddress, routerAddress);
const requiredAmount = ethers.utils.parseEther("100");

if (currentAllowance.lt(requiredAmount)) {
    // Approve Router to use tokens
    const approveTx = await tokenContract.approve(routerAddress, requiredAmount);
    await approveTx.wait();
    console.log("Approval successful");
}

2. Price Query

Query expected output amount before swapping:

// Query output amount for exact input
const amountIn = ethers.utils.parseEther("1");
const path = [tokenA, tokenB];
const fees = [100];

const amounts = await router.getAmountsOut(amountIn, path, fees);
const expectedOutput = amounts[amounts.length - 1];
console.log("Expected output:", ethers.utils.formatEther(expectedOutput));

// Query input amount needed for exact output
const amountOut = ethers.utils.parseEther("1");
const amountsIn = await router.getAmountsIn(amountOut, path, fees);
const requiredInput = amountsIn[0];
console.log("Required input:", ethers.utils.formatEther(requiredInput));

3. Slippage Settings

Set reasonable slippage protection based on market volatility:

function calculateMinOutput(expectedOutput, slippagePercent) {
    const slippage = ethers.BigNumber.from(slippagePercent * 100); // Convert to basis points
    const minOutput = expectedOutput.mul(10000 - slippage).div(10000);
    return minOutput;
}

// Set 1% slippage
const expectedOutput = ethers.utils.parseEther("99");
const minOutput = calculateMinOutput(expectedOutput, 1); // 1% slippage
console.log("Minimum output:", ethers.utils.formatEther(minOutput));

Complete Swap Examples

Basic Token Swap

async function swapTokens(
    tokenIn,
    tokenOut,
    amountIn,
    slippagePercent,
    userAddress,
    signer
) {
    const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, signer);
    
    try {
        // 1. Check and approve
        const tokenInContract = new ethers.Contract(tokenIn, erc20ABI, signer);
        const allowance = await tokenInContract.allowance(userAddress, ROUTER_ADDRESS);
        
        if (allowance.lt(amountIn)) {
            console.log("Approving tokens...");
            const approveTx = await tokenInContract.approve(ROUTER_ADDRESS, amountIn);
            await approveTx.wait();
        }
        
        // 2. Query price
        const path = [tokenIn, tokenOut];
        const fees = [100]; // Assume 1% fee rate
        const amounts = await router.getAmountsOut(amountIn, path, fees);
        const expectedOutput = amounts[1];
        
        // 3. Calculate minimum output (slippage protection)
        const minOutput = expectedOutput.mul(10000 - slippagePercent * 100).div(10000);
        
        // 4. Execute swap
        const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
        const tx = await router.swapExactTokensForTokens(
            amountIn,
            minOutput,
            path,
            fees,
            userAddress,
            ethers.constants.AddressZero, // No referrer
            deadline
        );
        
        console.log("Swap transaction sent:", tx.hash);
        const receipt = await tx.wait();
        console.log("Swap successful, block:", receipt.blockNumber);
        
        return receipt;
    } catch (error) {
        console.error("Swap failed:", error.message);
        throw error;
    }
}

// Usage example
await swapTokens(
    "0xTokenA_Address",
    "0xTokenB_Address", 
    ethers.utils.parseEther("10"), // Swap 10 TokenA
    1, // 1% slippage
    await signer.getAddress(),
    signer
);

JU Token Swap Example

async function swapJUForTokens(
    tokenOut,
    juAmount,
    slippagePercent,
    userAddress,
    signer
) {
    const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, signer);
    
    try {
        // 1. Query price
        const path = [WJU_ADDRESS, tokenOut];
        const fees = [100];
        const amounts = await router.getAmountsOut(juAmount, path, fees);
        const expectedOutput = amounts[1];
        
        // 2. Calculate minimum output
        const minOutput = expectedOutput.mul(10000 - slippagePercent * 100).div(10000);
        
        // 3. Execute swap
        const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
        const tx = await router.swapExactETHForTokens(
            minOutput,
            path,
            fees,
            userAddress,
            ethers.constants.AddressZero,
            deadline,
            { value: juAmount }
        );
        
        console.log("JU swap transaction sent:", tx.hash);
        const receipt = await tx.wait();
        console.log("Swap successful");
        
        return receipt;
    } catch (error) {
        console.error("JU swap failed:", error.message);
        throw error;
    }
}

Advanced Features

Referrer System

Using a referrer can provide fee discounts:

// Set referrer
const factory = new ethers.Contract(FACTORY_ADDRESS, factoryABI, signer);
const referrerAddress = "0xReferrer_Address";

// Check if referrer is already set
const currentReferrer = await factory.referrer(userAddress);
if (currentReferrer === ethers.constants.AddressZero) {
    const setReferrerTx = await factory.setReferrer(referrerAddress);
    await setReferrerTx.wait();
}

// Use referrer in swap
const tx = await router.swapExactTokensForTokens(
    amountIn,
    amountOutMin,
    path,
    fees,
    to,
    referrerAddress, // Use referrer
    deadline
);

Batch Swaps

async function batchSwap(swapParams, signer) {
    const results = [];
    
    for (const params of swapParams) {
        try {
            const result = await swapTokens(
                params.tokenIn,
                params.tokenOut,
                params.amountIn,
                params.slippage,
                params.userAddress,
                signer
            );
            results.push({ success: true, result });
        } catch (error) {
            results.push({ success: false, error: error.message });
        }
    }
    
    return results;
}

Error Handling

Common Errors and Solutions

function handleSwapError(error) {
    const errorMessage = error.message || error.toString();
    
    if (errorMessage.includes("INSUFFICIENT_OUTPUT_AMOUNT")) {
        return {
            type: "SLIPPAGE_TOO_HIGH",
            message: "Slippage too high, please increase slippage tolerance or try again later",
            solution: "Increase slippage settings or wait for market stability"
        };
    }
    
    if (errorMessage.includes("EXPIRED")) {
        return {
            type: "TRANSACTION_EXPIRED", 
            message: "Transaction expired",
            solution: "Resubmit transaction"
        };
    }
    
    if (errorMessage.includes("INSUFFICIENT_ALLOWANCE")) {
        return {
            type: "INSUFFICIENT_ALLOWANCE",
            message: "Insufficient token allowance",
            solution: "Increase token approval amount"
        };
    }
    
    if (errorMessage.includes("INSUFFICIENT_BALANCE")) {
        return {
            type: "INSUFFICIENT_BALANCE",
            message: "Insufficient balance",
            solution: "Check account balance"
        };
    }
    
    return {
        type: "UNKNOWN_ERROR",
        message: "Unknown error: " + errorMessage,
        solution: "Please contact technical support"
    };
}

// Usage example
try {
    await swapTokens(...);
} catch (error) {
    const errorInfo = handleSwapError(error);
    console.error("Swap failed:", errorInfo.message);
    console.log("Solution:", errorInfo.solution);
}

Best Practices

1. Pre-Swap Checks

async function preSwapChecks(tokenIn, tokenOut, amountIn, userAddress) {
    const checks = {
        tokenBalance: false,
        tokenAllowance: false,
        pairExists: false,
        sufficientLiquidity: false
    };
    
    // Check token balance
    const tokenContract = new ethers.Contract(tokenIn, erc20ABI, provider);
    const balance = await tokenContract.balanceOf(userAddress);
    checks.tokenBalance = balance.gte(amountIn);
    
    // Check allowance
    const allowance = await tokenContract.allowance(userAddress, ROUTER_ADDRESS);
    checks.tokenAllowance = allowance.gte(amountIn);
    
    // Check if pair exists
    const factory = new ethers.Contract(FACTORY_ADDRESS, factoryABI, provider);
    const pairAddress = await factory.getPair(tokenIn, tokenOut, 100);
    checks.pairExists = pairAddress !== ethers.constants.AddressZero;
    
    if (checks.pairExists) {
        // Check liquidity
        const pair = new ethers.Contract(pairAddress, pairABI, provider);
        const reserves = await pair.getReserves();
        checks.sufficientLiquidity = reserves.reserve0.gt(0) && reserves.reserve1.gt(0);
    }
    
    return checks;
}

2. Price Impact Calculation

function calculatePriceImpact(amountIn, reserveIn, reserveOut) {
    // Theoretical price (no price impact)
    const theoreticalPrice = reserveOut.mul(ethers.utils.parseEther("1")).div(reserveIn);
    
    // Actual output amount
    const actualOutput = getAmountOut(amountIn, reserveIn, reserveOut, 100);
    
    // Actual price
    const actualPrice = actualOutput.mul(ethers.utils.parseEther("1")).div(amountIn);
    
    // Price impact = (theoretical price - actual price) / theoretical price
    const priceImpact = theoreticalPrice.sub(actualPrice).mul(10000).div(theoreticalPrice);
    
    return priceImpact; // Returns basis points (100 = 1%)
}

3. Dynamic Slippage Settings

function calculateDynamicSlippage(priceImpact, baseSlippage = 50) {
    // Base slippage 50 basis points (0.5%)
    // Adjust dynamically based on price impact
    if (priceImpact.lt(100)) { // Less than 1%
        return baseSlippage;
    } else if (priceImpact.lt(300)) { // 1-3%
        return baseSlippage + 50; // Add 0.5%
    } else { // Greater than 3%
        return baseSlippage + 100; // Add 1%
    }
}

Monitoring and Analytics

Swap Event Monitoring

// Monitor swap events
const pair = new ethers.Contract(pairAddress, pairABI, provider);

pair.on("Swap", (sender, amount0In, amount1In, amount0Out, amount1Out, to) => {
    console.log("Swap event:", {
        sender,
        amount0In: ethers.utils.formatEther(amount0In),
        amount1In: ethers.utils.formatEther(amount1In),
        amount0Out: ethers.utils.formatEther(amount0Out),
        amount1Out: ethers.utils.formatEther(amount1Out),
        to
    });
});

Swap History Query

async function getSwapHistory(userAddress, fromBlock = 0) {
    const router = new ethers.Contract(ROUTER_ADDRESS, routerABI, provider);
    
    // Query user's swap history
    const filter = {
        address: ROUTER_ADDRESS,
        fromBlock: fromBlock,
        toBlock: 'latest'
    };
    
    const logs = await provider.getLogs(filter);
    const swapEvents = logs.filter(log => 
        log.topics[0] === ethers.utils.id("Swap(address,uint256,uint256,uint256,uint256,address)")
    );
    
    return swapEvents.map(event => {
        const decoded = router.interface.parseLog(event);
        return {
            blockNumber: event.blockNumber,
            transactionHash: event.transactionHash,
            ...decoded.args
        };
    });
}

Summary

Token swaps are the core functionality of JAMM DEX. This guide covers:

  1. Swap Types: Exact input, exact output, JU token swaps

  2. Preparation: Approval, price queries, slippage settings

  3. Complete Examples: Practical swap code templates

  4. Advanced Features: Referrer system, batch swaps

  5. Error Handling: Common error identification and resolution

  6. Best Practices: Safe and efficient swap strategies

By following this guide, developers can safely and efficiently integrate JAMM DEX token swap functionality.