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:
Swap Types: Exact input, exact output, JU token swaps
Preparation: Approval, price queries, slippage settings
Complete Examples: Practical swap code templates
Advanced Features: Referrer system, batch swaps
Error Handling: Common error identification and resolution
Best Practices: Safe and efficient swap strategies
By following this guide, developers can safely and efficiently integrate JAMM DEX token swap functionality.