DeFi

How to Earn from Uniswap V3 Liquidity Pools: Complete Strategy Guide

Uniswap V3 liquidity provision can earn 15–60% APY — but only if you manage positions correctly. This guide covers concentrated liquidity, impermanent loss, auto-rebalancing bots, and the strategies that actually make money.

A
AI Agents Hub·2025-05-09·5 min read·933 words

Builder of AI agents, crypto trading bots, and open-source automation tools. Sharing practical guides on how to build, deploy, and profit from AI and DeFi technology.

Why Uniswap V3 Is Different

In Uniswap V2, you provided liquidity across the entire price range (0 to infinity). This was simple but capital-inefficient.

In Uniswap V3, you choose a specific price range. All your capital concentrates in that range, earning a much larger fraction of fees when trades happen there.

Same capital. 10–100x more fee income. But you need to actively manage your position.

How Fee Earnings Work

Uniswap V3 pools have three fee tiers:

  • 0.01% — Stablecoin/stablecoin pairs (USDC/USDT)
  • 0.05% — Stable-like pairs (USDC/ETH)
  • 0.3% — Standard pairs (ETH/BTC)
  • 1% — Exotic/volatile pairs

Every trade that passes through your price range earns you a proportional share of those fees.

Example: The USDC/ETH 0.05% pool trades $500M/day. If you own 0.1% of the liquidity in range, you earn $250/day.

The Core Challenge: Impermanent Loss

When the price moves outside your range, you stop earning fees AND your position shifts entirely to the falling asset.

Example with ETH/USDC:

  • You provide liquidity from $3,000 to $3,500
  • ETH rises to $3,600 → Your entire position converts to USDC
  • ETH then rises to $4,000
  • You missed $400 of ETH appreciation (impermanent loss)

This is why Uniswap V3 requires active management. Set-and-forget loses to simply holding the assets.

Strategy 1: Stablecoin Pairs (Lowest Risk)

The easiest entry point: provide USDC/USDT liquidity.

  • No impermanent loss (both assets stay at ~$1)
  • Pure fee income: 0.01–0.05% on every trade
  • Range: Set tight range like $0.997–$1.003
const { ethers } = require('ethers')
const { Pool, Position, NonfungiblePositionManager } = require('@uniswap/v3-sdk')
const { Token } = require('@uniswap/sdk-core')

const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC')
const USDT = new Token(1, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT')

// Mint a new position in the USDC/USDT 0.01% pool
async function addStablecoinLiquidity(amount_usdc, amount_usdt) {
  const pool = await getPool(USDC, USDT, FeeAmount.LOWEST) // 0.01%
  
  // Use a very tight range around $1
  const position = Position.fromAmounts({
    pool,
    tickLower: nearestUsableTick(-100, pool.tickSpacing), // ~$0.990
    tickUpper: nearestUsableTick(100, pool.tickSpacing),  // ~$1.010
    amount0: ethers.parseUnits(amount_usdc.toString(), 6),
    amount1: ethers.parseUnits(amount_usdt.toString(), 6),
    useFullPrecision: true,
  })
  
  // Submit mint transaction
  const { calldata, value } = NonfungiblePositionManager.addCallParameters(position, {
    slippageTolerance: new Percent(50, 10000), // 0.5% slippage
    recipient: wallet.address,
    deadline: Math.floor(Date.now() / 1000) + 60 * 20, // 20 minutes
  })
  
  return wallet.sendTransaction({ to: POSITION_MANAGER_ADDRESS, data: calldata, value })
}

Expected APY: 10–20% on stablecoin pairs during high-volume periods.

Strategy 2: ETH/USDC with Auto-Rebalancing Bot

For non-stable pairs, you need a bot to rebalance your position as price moves.

from web3 import Web3
from uniswap import Uniswap
import time

w3 = Web3(Web3.HTTPProvider(os.getenv('RPC_URL')))
uniswap = Uniswap(address=MY_ADDRESS, private_key=MY_KEY, web3=w3, version=3)

ETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"  # WETH
USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"

RANGE_WIDTH_PCT = 0.20  # Provide liquidity ±10% around current price

def get_current_price():
    """Get ETH/USDC price from Uniswap pool."""
    return uniswap.get_price_input(ETH, USDC, 10**18) / 10**6

def is_in_range(lower, upper, current):
    """Check if current price is within our range."""
    # Add 5% buffer before rebalancing
    buffer = (upper - lower) * 0.05
    return lower + buffer < current < upper - buffer

def calculate_new_range(current_price):
    """Calculate new range centered on current price."""
    half_width = current_price * (RANGE_WIDTH_PCT / 2)
    return current_price - half_width, current_price + half_width

class LiquidityManager:
    def __init__(self):
        self.position_id = None
        self.lower_price = None
        self.upper_price = None
    
    def rebalance(self):
        """Remove existing position and create new one centered at current price."""
        current = get_current_price()
        
        if self.position_id and is_in_range(self.lower_price, self.upper_price, current):
            print(f"Price ${current:.2f} — still in range ${self.lower_price:.2f}-${self.upper_price:.2f}")
            return
        
        # Remove old position (collect fees + remove liquidity)
        if self.position_id:
            print(f"Price ${current:.2f} left range. Rebalancing...")
            self.collect_and_remove()
        
        # Create new position
        new_lower, new_upper = calculate_new_range(current)
        self.position_id = self.add_liquidity(new_lower, new_upper)
        self.lower_price = new_lower
        self.upper_price = new_upper
        print(f"New position: ${new_lower:.2f} - ${new_upper:.2f}")
    
    def collect_and_remove(self):
        """Collect accumulated fees and remove liquidity."""
        # Collect fees first
        uniswap.collect_fees(self.position_id)
        # Remove liquidity
        uniswap.remove_liquidity(self.position_id, remove_percent=100)
        self.position_id = None
    
    def add_liquidity(self, lower_price, upper_price):
        """Add new liquidity position."""
        balance_eth = w3.eth.get_balance(MY_ADDRESS) / 10**18 * 0.9
        balance_usdc = uniswap.get_token_balance(USDC) / 10**6 * 0.9
        
        return uniswap.add_liquidity_new_position(
            token0=ETH,
            token1=USDC,
            fee=500,  # 0.05% fee tier
            lower_price=lower_price,
            upper_price=upper_price,
            amount0=balance_eth,
            amount1=balance_usdc,
        )

manager = LiquidityManager()

while True:
    try:
        manager.rebalance()
        time.sleep(300)  # Check every 5 minutes
    except Exception as e:
        print(f"Error: {e}")
        time.sleep(60)

Understanding Your Returns

Two components of return:

  1. Fee income: The trading fees you collect (good)
  2. Impermanent loss: The opportunity cost vs. just holding (bad)

Net return = Fee income − Impermanent loss

For ETH/USDC with 20% range width:

  • Fee income: 15–40% APY in active markets
  • Impermanent loss: 2–15% per rebalance
  • Net: 5–30% APY depending on market conditions

When this strategy works best:

  • High trading volume (more fees)
  • Low price volatility (less IL)
  • You rebalance before price leaves range by too much

Tools for Monitoring Positions

  • Revert Finance — Best dashboard for Uniswap V3 analytics
  • DeFi Lab — Shows fee APY for your specific position
  • Gamma Strategies — Automated rebalancing vaults

FAQ

Q: How much do I need to start? A: Minimum $500, but $2,000+ makes the gas costs worthwhile.

Q: Is impermanent loss permanent? A: Only if you remove liquidity while the price has moved. If price returns to your range, IL reverses.

Q: Can I automate this on a small budget? A: Yes — our DeFi bot on the Tools page handles position monitoring and rebalancing for ETH/USDC automatically.

Q: Which fee tier should I use? A: ETH/USDC → 0.05% (most volume). New or volatile pairs → 0.3% or 1%.

Get the Automated Bot

Our DeFi Yield Optimizer handles Uniswap V3 position management automatically — monitoring, rebalancing, and compounding fees. Available on the Tools page.

Related Articles