# Swapping Tokens
Trade tokens on JuiceSwap with automatic JUSD/svJUSD conversion and concentrated liquidity.
# How Swapping Works
JuiceSwap uses Uniswap V3's concentrated liquidity AMM (Automated Market Maker) to enable trustless token swaps. The JuiceSwapGateway adds a layer of convenience by automatically handling token conversions.
# Automatic Conversions
When you swap through JuiceSwap, the Gateway handles these conversions automatically:
| You Provide | Gateway Uses | You Receive |
|---|---|---|
| JUSD | svJUSD (in pool) | Your desired token |
| cBTC (native) | WcBTC | Your desired token |
| Any token | As-is | JUSD (converted from svJUSD) |
Why svJUSD? All pools use svJUSD (the interest-bearing savings vault token) instead of plain JUSD. This means liquidity providers earn savings interest in addition to swap fees. The conversion is invisible to users.
# Executing a Swap
# Via JuiceSwapGateway
function swapExactTokensForTokens(
address tokenIn, // Token you're selling
address tokenOut, // Token you're buying
uint24 fee, // Fee tier (500, 3000, or 10000)
uint256 amountIn, // Amount to swap
uint256 minAmountOut, // Minimum output (slippage protection)
address to, // Recipient address
uint256 deadline // Transaction deadline
) external payable returns (uint256 amountOut)
# Fee Tiers
JuiceSwap supports multiple fee tiers for different pair volatilities:
| Fee Tier | Fee | Best For |
|---|---|---|
| 500 | 0.05% | Stable pairs (JUSD/USDT) |
| 3000 | 0.30% | Standard pairs (default) |
| 10000 | 1.00% | Exotic/volatile pairs |
Note: If you pass fee = 0, the Gateway uses the default fee tier (0.30%).
# Example: Swapping JUSD for WcBTC
const gateway = new ethers.Contract(GATEWAY_ADDRESS, gatewayAbi, signer);
// Approve Gateway to spend JUSD
await jusd.approve(GATEWAY_ADDRESS, amountIn);
// Execute swap
const amountOut = await gateway.swapExactTokensForTokens(
JUSD_ADDRESS, // tokenIn
WCBTC_ADDRESS, // tokenOut
3000, // 0.3% fee tier
ethers.parseEther("100"), // 100 JUSD
ethers.parseEther("0.001"), // min 0.001 WcBTC
userAddress, // recipient
Math.floor(Date.now() / 1000) + 600 // 10 min deadline
);
# Swapping Native cBTC
For native Bitcoin (cBTC) swaps, send the amount as msg.value:
// Swap native cBTC for JUSD
const amountOut = await gateway.swapExactTokensForTokens(
ethers.ZeroAddress, // tokenIn = native cBTC
JUSD_ADDRESS, // tokenOut
3000, // fee tier
0, // amountIn (ignored, uses msg.value)
minJusdOut, // slippage protection
userAddress, // recipient
deadline,
{ value: ethers.parseEther("0.01") } // 0.01 cBTC
);
# Slippage Protection
Always set minAmountOut to protect against price movements:
// Calculate minimum output with 1% slippage tolerance
const expectedOutput = await getQuote(tokenIn, tokenOut, amountIn);
const minAmountOut = expectedOutput * 99n / 100n; // 1% slippage
If the actual output is less than minAmountOut, the transaction reverts with InsufficientOutput().
# Price Impact
Large trades relative to pool liquidity will experience price impact. The concentrated liquidity model means:
- Within range: Excellent pricing from concentrated liquidity
- Outside range: May need to use multiple pools or experience higher slippage
# Checking Liquidity
Before large trades, check pool liquidity:
// Get pool address
const poolAddress = await factory.getPool(tokenA, tokenB, fee);
// Check pool liquidity
const pool = new ethers.Contract(poolAddress, poolAbi, provider);
const liquidity = await pool.liquidity();
# Multi-Hop Swaps
For tokens without a direct pool, use multi-hop routing through the SwapRouter:
// Path encoding: token0 + fee + token1 + fee + token2
const path = ethers.solidityPacked(
['address', 'uint24', 'address', 'uint24', 'address'],
[TOKEN_A, 3000, JUSD_ADDRESS, 3000, TOKEN_B]
);
// Execute multi-hop swap via SwapRouter
const router = new ethers.Contract(SWAP_ROUTER_ADDRESS, routerAbi, signer);
await router.exactInput({
path: path,
recipient: userAddress,
amountIn: amountIn,
amountOutMinimum: minAmountOut
});
# Transaction Deadlines
Always include a deadline to prevent stale transactions:
// 10 minute deadline
const deadline = Math.floor(Date.now() / 1000) + 600;
If block.timestamp > deadline, the transaction reverts with DeadlineExpired().
# Error Handling
| Error | Cause | Solution |
|---|---|---|
InsufficientOutput | Slippage exceeded | Increase minAmountOut tolerance |
DeadlineExpired | Transaction too old | Use fresh deadline |
InvalidAmount | Zero input amount | Provide non-zero amount |
InvalidFee | Fee tier doesn't exist | Use 500, 3000, or 10000 |
InvalidToken | Unsupported token | Check token addresses |
# Events
Monitor swaps through the SwapExecuted event:
event SwapExecuted(
address indexed sender,
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut
);
# Gas Optimization
- Approve once: Use
approve(MAX_UINT256)to avoid repeated approvals - Native cBTC: Swapping native cBTC saves the wrap/unwrap gas cost
- Direct pools: Single-hop swaps are cheaper than multi-hop
# Security Considerations
- Always use slippage protection - Set realistic
minAmountOut - Check pool liquidity - Large trades in small pools have high impact
- Verify addresses - Double-check token and recipient addresses
- Use deadlines - Prevent transaction front-running
# Contract Addresses
# Mainnet (Chain ID: 4114)
| Contract | Address |
|---|---|
| JuiceSwapGateway | 0xAFcfD58Fe17BEb0c9D15C51D19519682dFcdaab9 (opens new window) |
| SwapRouter | 0x565eD3D57fe40f78A46f348C220121AE093c3cF8 (opens new window) |
| UniswapV3Factory | 0xd809b1285aDd8eeaF1B1566Bf31B2B4C4Bba8e82 (opens new window) |
# Testnet (Chain ID: 5115)
| Contract | Address |
|---|---|
| JuiceSwapGateway | 0x8eE3Dd585752805A258ad3a963949a7c3fec44eB (opens new window) |
| SwapRouter | 0x26C106BC45E0dd599cbDD871605497B2Fc87c185 (opens new window) |
| UniswapV3Factory | 0xdd6Db52dB41CE2C03002bB1adFdCC8E91C594238 (opens new window) |