# Providing Liquidity
Earn swap fees and savings interest by providing liquidity to JuiceSwap pools.
# Understanding Liquidity Provision
JuiceSwap uses Uniswap V3's concentrated liquidity model, where liquidity providers (LPs) can concentrate their capital within specific price ranges for greater capital efficiency.
# Double Yield Advantage
JuiceSwap offers a unique benefit: LPs earn both swap fees AND savings interest:
| Yield Source | Description |
|---|---|
| Swap Fees | 0.05% - 1% per trade (based on fee tier) |
| Savings Interest | Automatic via svJUSD (when JUSD is in the pool) |
This is possible because the Gateway automatically converts JUSD to svJUSD (the interest-bearing savings vault token) when you provide liquidity.
# Concentrated Liquidity Basics
Unlike traditional AMMs with infinite price ranges, Uniswap V3 lets you choose where to concentrate your liquidity:
Price
│
│ ┌─────────────────┐
│ │ Your Liquidity │
│ │ (Concentrated)│
│ └─────────────────┘
│ ────────────────────────── Traditional AMM (Spread Thin)
│
└────────────────────────────► Range
Lower Upper
# Benefits of Concentration
| Concentration | Capital Efficiency | Risk |
|---|---|---|
| Narrow range | Very high (up to 4000x) | Higher (out-of-range risk) |
| Medium range | Moderate (10-100x) | Balanced |
| Full range | Same as V2 (1x) | Lowest |
# Adding Liquidity
# Via JuiceSwapGateway
The Gateway simplifies liquidity provision with automatic token conversions:
function addLiquidity(
address tokenA, // First token (e.g., JUSD)
address tokenB, // Second token (e.g., WcBTC)
uint24 fee, // Fee tier (500, 3000, 10000)
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin, // Slippage protection
uint256 amountBMin,
address to, // NFT recipient
uint256 deadline
) external payable returns (uint256 amountA, uint256 amountB, uint256 liquidity)
Note: This creates a full-range position. For custom price ranges, use the NonfungiblePositionManager directly.
# Example: Adding JUSD/WcBTC Liquidity
const gateway = new ethers.Contract(GATEWAY_ADDRESS, gatewayAbi, signer);
// Approve tokens
await jusd.approve(GATEWAY_ADDRESS, amountJusd);
await wcbtc.approve(GATEWAY_ADDRESS, amountWcbtc);
// Add liquidity (creates full-range position)
const [amountA, amountB, tokenId] = await gateway.addLiquidity(
JUSD_ADDRESS,
WCBTC_ADDRESS,
3000, // 0.3% fee tier
ethers.parseEther("1000"), // 1000 JUSD
ethers.parseEther("0.01"), // 0.01 WcBTC
ethers.parseEther("990"), // min 990 JUSD (1% slippage)
ethers.parseEther("0.0099"), // min 0.0099 WcBTC
userAddress,
deadline
);
console.log(`Position NFT ID: ${tokenId}`);
# Adding Liquidity with Native cBTC
const [amountA, amountB, tokenId] = await gateway.addLiquidity(
JUSD_ADDRESS,
ethers.ZeroAddress, // Native cBTC
3000,
ethers.parseEther("1000"),
0, // Ignored, uses msg.value
ethers.parseEther("990"),
ethers.parseEther("0.0099"),
userAddress,
deadline,
{ value: ethers.parseEther("0.01") } // Send cBTC
);
# LP Position NFTs
Unlike traditional LP tokens, Uniswap V3 positions are represented as NFTs (ERC-721):
| Property | Description |
|---|---|
| Unique | Each position has a unique token ID |
| Transferable | Can be sold or transferred |
| Composable | Can be used in DeFi protocols |
| Range-specific | Contains tick range information |
# Viewing Your Position
const positionManager = new ethers.Contract(
POSITION_MANAGER_ADDRESS,
positionManagerAbi,
provider
);
const position = await positionManager.positions(tokenId);
// Returns: nonce, operator, token0, token1, fee,
// tickLower, tickUpper, liquidity,
// feeGrowthInside0LastX128, feeGrowthInside1LastX128,
// tokensOwed0, tokensOwed1
# Increasing Liquidity
Add more tokens to an existing position:
function increaseLiquidity(
uint256 tokenId,
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline
) external payable returns (uint256 amountA, uint256 amountB, uint128 liquidity)
Important: You must approve the Gateway to transfer your position NFT first.
// Approve NFT transfer
await positionManager.approve(GATEWAY_ADDRESS, tokenId);
// Increase liquidity
const [amountA, amountB, addedLiquidity] = await gateway.increaseLiquidity(
tokenId,
JUSD_ADDRESS,
WCBTC_ADDRESS,
ethers.parseEther("500"),
ethers.parseEther("0.005"),
ethers.parseEther("495"),
ethers.parseEther("0.00495"),
deadline
);
# Removing Liquidity
Withdraw tokens from your position:
function removeLiquidity(
uint256 tokenId,
uint128 liquidityToRemove, // 0 = remove all
address tokenA,
address tokenB,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB)
# Example: Full Withdrawal
// Approve NFT transfer
await positionManager.approve(GATEWAY_ADDRESS, tokenId);
// Remove all liquidity
const [amountA, amountB] = await gateway.removeLiquidity(
tokenId,
0, // 0 = remove all liquidity
JUSD_ADDRESS,
WCBTC_ADDRESS,
0, // No minimum (be careful in production!)
0,
userAddress,
deadline
);
# Partial Withdrawal
// Remove 50% of liquidity
const position = await positionManager.positions(tokenId);
const halfLiquidity = position.liquidity / 2n;
const [amountA, amountB] = await gateway.removeLiquidity(
tokenId,
halfLiquidity,
JUSD_ADDRESS,
WCBTC_ADDRESS,
amountAMin,
amountBMin,
userAddress,
deadline
);
# Collecting Fees
Accumulated swap fees can be collected separately from removing liquidity:
// Collect fees via Position Manager
const [amount0, amount1] = await positionManager.collect({
tokenId: tokenId,
recipient: userAddress,
amount0Max: ethers.MaxUint128,
amount1Max: ethers.MaxUint128
});
Note: Fees are denominated in the pool's actual tokens (svJUSD, not JUSD).
# svJUSD Interest Mechanism
When you provide JUSD liquidity:
- Gateway converts your JUSD to svJUSD
- svJUSD is deposited into the pool
- svJUSD appreciates in value over time (savings interest)
- When you withdraw, svJUSD is converted back to more JUSD
Deposit: 1000 JUSD → 1000 svJUSD (shares)
↓
Pool earns swap fees
svJUSD earns savings interest
↓
Withdraw: 1000 svJUSD → 1020 JUSD (2% interest earned)
+ swap fees earned
# Price Ranges and Ticks
Uniswap V3 uses "ticks" to define price ranges:
| Concept | Description |
|---|---|
| Tick | Discrete price point (each tick = 0.01% price change) |
| Tick Spacing | Minimum tick interval for a fee tier |
| Full Range | Ticks from MIN_TICK to MAX_TICK |
# Fee Tier to Tick Spacing
| Fee Tier | Tick Spacing |
|---|---|
| 500 (0.05%) | 10 |
| 3000 (0.30%) | 60 |
| 10000 (1.00%) | 200 |
# Impermanent Loss
Concentrated liquidity amplifies both gains and impermanent loss:
| Price Movement | Concentrated LP | Traditional LP |
|---|---|---|
| Within range | Higher fees, some IL | Lower fees, less IL |
| Outside range | No fees, max IL for range | Still earning, less IL |
Mitigation strategies:
- Wider ranges for volatile pairs
- Active management (rebalancing)
- Focus on stable pairs
# Advanced: Custom Range Positions
For custom price ranges, interact directly with NonfungiblePositionManager:
const mintParams = {
token0: svJUSD_ADDRESS, // Must use actual pool tokens
token1: WCBTC_ADDRESS,
fee: 3000,
tickLower: -887220, // Custom lower tick
tickUpper: 887220, // Custom upper tick
amount0Desired: amount0,
amount1Desired: amount1,
amount0Min: 0,
amount1Min: 0,
recipient: userAddress,
deadline: deadline
};
const [tokenId, liquidity, amount0, amount1] =
await positionManager.mint(mintParams);
# Events
// Liquidity added
event LiquidityAdded(
address indexed sender,
address tokenA,
address tokenB,
uint256 amountA,
uint256 amountB,
uint256 indexed tokenId
);
// Liquidity increased
event LiquidityIncreased(
address indexed sender,
uint256 indexed tokenId,
uint256 amountA,
uint256 amountB,
uint128 liquidity
);
// Liquidity removed
event LiquidityRemoved(
address indexed sender,
uint256 indexed tokenId,
uint256 amountA,
uint256 amountB,
uint128 liquidity
);
# Error Handling
| Error | Cause | Solution |
|---|---|---|
NotNFTOwner | You don't own the position NFT | Use your own position |
InsufficientLiquidity | Trying to remove more than available | Check position liquidity |
TokenMismatch | Wrong tokens for position | Verify token addresses |
InvalidTokenPair | Both tokens convert to same actual token | Use different tokens |
SlippageExceeded | Price moved too much | Increase slippage tolerance |
# Contract Addresses
# Mainnet (Chain ID: 4114)
| Contract | Address |
|---|---|
| JuiceSwapGateway | 0xAFcfD58Fe17BEb0c9D15C51D19519682dFcdaab9 (opens new window) |
| NonfungiblePositionManager | 0x3D3821D358f56395d4053954f98aec0E1F0fa568 (opens new window) |
| UniswapV3Factory | 0xd809b1285aDd8eeaF1B1566Bf31B2B4C4Bba8e82 (opens new window) |
# Testnet (Chain ID: 5115)
| Contract | Address |
|---|---|
| JuiceSwapGateway | 0x8eE3Dd585752805A258ad3a963949a7c3fec44eB (opens new window) |
| NonfungiblePositionManager | 0x86e7A161cb9696E6d438c0c77dd18244efa2B8b1 (opens new window) |
| UniswapV3Factory | 0xdd6Db52dB41CE2C03002bB1adFdCC8E91C594238 (opens new window) |