Uniswap V3 SDK Python: Build DeFi Trading Bots Like a Pro
Learn how to use the Uniswap V3 SDK in Python to query pool data, automate swaps, and build DeFi bots. Covers fees, API setup, and real trading examples.
Learn how to use the Uniswap V3 SDK in Python to query pool data, automate swaps, and build DeFi bots. Covers fees, API setup, and real trading examples.
Uniswap V3 changed the game for decentralized trading — and Python gives you the keys to interact with it programmatically. Whether you want to monitor pool prices, automate swaps, or build a full liquidity management system, the Uniswap V3 SDK in Python is your entry point. Centralized exchanges like Binance and Coinbase offer clean REST APIs, but DeFi operates differently — you talk directly to smart contracts on-chain. That shift in mental model is what this guide is here to bridge.
If you've used Uniswap V2 or traded on platforms like Bybit or OKX, you're used to straightforward price execution. Uniswap V3 introduced a concept called concentrated liquidity — and it fundamentally changes how the protocol works under the hood.
In V2, liquidity was spread uniformly across all possible prices from zero to infinity. In V3, liquidity providers pick a price range — say ETH between $2,800 and $3,200 — and their capital only works within that range. This means that when the price is inside your range, you earn fees much more efficiently than V2. When the price moves outside, you stop earning and hold a single asset (the cheaper one).
Think of it like a market maker on Binance who only quotes prices between two specific levels instead of maintaining a ladder across the entire order book. More capital efficiency, more precision, more complexity.
Key Takeaway: Uniswap V3's concentrated liquidity makes it more capital-efficient, but also more technically complex. If you want to automate interactions, Python is one of the best tools available — but you need to understand ticks, fee tiers, and pool math before writing your first script.
There are two main libraries Python developers use to interact with Uniswap V3: the community-built `uniswap-python` package, and a lower-level approach using `web3.py` directly with Uniswap's ABI. For most use cases — reading prices, executing swaps, querying pool state — `uniswap-python` is the faster path.
# Install dependencies
pip install uniswap-python web3 python-dotenv requests
You'll also need access to an Ethereum node. You have a few options: Infura, Alchemy, or your own node. Alchemy's free tier gives you 300 million compute units per month, which is plenty for monitoring and light automation. Once you have your node URL, set it as an environment variable — never hardcode API keys.
import os
from web3 import Web3
from uniswap import Uniswap
from dotenv import load_dotenv
load_dotenv()
WEB3_PROVIDER = os.getenv("WEB3_PROVIDER_URL") # e.g. Alchemy or Infura
PRIVATE_KEY = os.getenv("PRIVATE_KEY")
WALLET_ADDRESS = os.getenv("WALLET_ADDRESS")
w3 = Web3(Web3.HTTPProvider(WEB3_PROVIDER))
# Connect to Uniswap V3
uniswap = Uniswap(
address=WALLET_ADDRESS,
private_key=PRIVATE_KEY,
web3=w3,
version=3
)
print("Connected:", w3.is_connected())
The `version=3` parameter is critical — without it, the library defaults to V2 logic and your calculations will be wrong. Once connected, you can start reading pool data, checking prices, and eventually executing swaps programmatically.
Key Takeaway: Always use environment variables for your private key and RPC URL. Never commit credentials to GitHub. Use a dedicated hot wallet with limited funds for automation — not your main portfolio wallet.
The Uniswap V3 API in Python can mean two things: querying on-chain contract data directly via web3.py, or hitting the Uniswap subgraph on The Graph protocol. The subgraph approach is much faster for historical data and analytics. Direct contract calls are better for real-time execution.
For price monitoring, the subgraph is ideal. You send a GraphQL query and get back pool data including current price, volume, TVL, and fee income — no need to decode raw contract state.
import requests
SUBGRAPH_URL = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
def get_pool_data(pool_address: str) -> dict:
query = """
{
pool(id: "%s") {
token0 { symbol }
token1 { symbol }
feeTier
liquidity
token0Price
token1Price
volumeUSD
totalValueLockedUSD
}
}
""" % pool_address.lower()
response = requests.post(
SUBGRAPH_URL,
json={"query": query},
timeout=10
)
return response.json()["data"]["pool"]
# ETH/USDC 0.05% pool
pool = get_pool_data("0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640")
print(f"{pool['token0']['symbol']}/{pool['token1']['symbol']}")
print(f"Price: {pool['token0Price']}")
print(f"TVL: ${float(pool['totalValueLockedUSD']):,.0f}")
For real-time swap price quotes without executing a transaction, use the `quoter` contract. This simulates the swap and tells you exactly how many tokens you'd receive, accounting for current liquidity distribution across ticks.
from web3 import Web3
# Uniswap V3 Quoter contract address (Ethereum mainnet)
QUOTER_ADDRESS = "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6"
QUOTER_ABI = [
{
"inputs": [
{"name": "tokenIn", "type": "address"},
{"name": "tokenOut", "type": "address"},
{"name": "fee", "type": "uint24"},
{"name": "amountIn", "type": "uint256"},
{"name": "sqrtPriceLimitX96", "type": "uint160"}
],
"name": "quoteExactInputSingle",
"outputs": [{"name": "amountOut", "type": "uint256"}],
"type": "function",
"stateMutability": "nonpayable"
}
]
WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
quoter = w3.eth.contract(address=QUOTER_ADDRESS, abi=QUOTER_ABI)
# Quote 1 ETH -> USDC at 0.05% fee tier
amount_in = w3.to_wei(1, "ether")
fee = 500 # 0.05% = 500 basis points in Uniswap fee units
amount_out = quoter.functions.quoteExactInputSingle(
WETH, USDC, fee, amount_in, 0
).call()
print(f"1 ETH = {amount_out / 1e6:.2f} USDC")
This is far more accurate than using a simple price feed because it accounts for actual liquidity depth at each tick. If you're building arbitrage detection between Uniswap V3 and centralized books on OKX or Coinbase, this is the correct price to compare against.
Uniswap V3 fees are one of the most important variables to get right in any trading bot. Get them wrong and your profitability calculations will be off — sometimes drastically.
Uniswap V3 has four fee tiers, and each maps to a different use case. Unlike Binance's flat maker/taker model or Bybit's VIP tiers, Uniswap fees go entirely to liquidity providers in the specific pool — Uniswap Labs takes a small protocol cut on some pairs through a fee switch.
| Fee Tier | Basis Points | Best For |
|---|---|---|
| 0.01% | 100 | Stablecoin pairs (USDC/USDT, DAI/USDC) |
| 0.05% | 500 | Correlated pairs (ETH/WBTC, stETH/ETH) |
| 0.3% | 3000 | Standard pairs (ETH/USDC, ETH/DAI) |
| 1% | 10000 | Exotic or low-liquidity tokens |
In Python, you need to pick the right fee tier when querying or routing swaps. If you try to interact with the ETH/USDC 0.3% pool when most liquidity has migrated to the 0.05% pool, you'll get worse prices. Always check TVL across fee tiers before hardcoding one.
FEE_TIERS = [100, 500, 3000, 10000]
def find_best_fee_tier(token0: str, token1: str, amount_in: int) -> tuple:
"""Find the fee tier with best output for a given swap."""
best_out = 0
best_fee = None
for fee in FEE_TIERS:
try:
amount_out = quoter.functions.quoteExactInputSingle(
token0, token1, fee, amount_in, 0
).call()
if amount_out > best_out:
best_out = amount_out
best_fee = fee
except Exception:
continue # Pool may not exist at this tier
return best_fee, best_out
best_fee, best_out = find_best_fee_tier(WETH, USDC, w3.to_wei(1, "ether"))
print(f"Best fee tier: {best_fee / 10000:.2f}% → {best_out / 1e6:.2f} USDC")
Key Takeaway: Never assume which fee tier has the best liquidity. Always iterate over all fee tiers and compare output amounts before executing a swap. The difference between the wrong and right fee tier can be 0.5% or more on a single trade.
Once you can fetch prices and pool data, the next step is turning that into something actionable. A simple price monitor polls Uniswap V3 every few seconds and compares the on-chain price to a reference — either another DEX, a CEX like Coinbase or Gate.io, or a signal from a platform like VoiceOfChain.
VoiceOfChain provides real-time trading signals across major crypto assets. By cross-referencing a VoiceOfChain signal with the live Uniswap V3 pool price, you can build a system that triggers an on-chain swap only when both conditions align — the signal says buy AND the DEX price is within your acceptable range.
import time
from datetime import datetime
def get_eth_usdc_price() -> float:
amount_in = w3.to_wei(1, "ether")
amount_out = quoter.functions.quoteExactInputSingle(
WETH, USDC, 500, amount_in, 0
).call()
return amount_out / 1e6
def monitor_price(interval_seconds: int = 10, threshold_pct: float = 0.5):
print("Starting Uniswap V3 price monitor...")
last_price = get_eth_usdc_price()
while True:
time.sleep(interval_seconds)
current_price = get_eth_usdc_price()
change_pct = abs(current_price - last_price) / last_price * 100
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] ETH/USDC: ${current_price:,.2f} | Δ {change_pct:.3f}%")
if change_pct >= threshold_pct:
print(f" ⚡ Price moved {change_pct:.2f}% — alerting system")
# Hook into VoiceOfChain signal or your alert logic here
last_price = current_price
monitor_price(interval_seconds=15, threshold_pct=0.3)
This loop runs indefinitely and flags any price move above your threshold. You can extend it to push alerts to Telegram, compare against Binance spot price for arbitrage detection, or feed data into a trading strategy. Keep in mind Ethereum mainnet is slow compared to querying Binance's REST API — plan your polling intervals accordingly and consider caching block data.
For more advanced setups, subscribe to pending transactions via a WebSocket node connection. This lets you detect large swaps before they're confirmed — useful for front-run protection or MEV awareness, though that's a whole other territory.
Reading data is safe. Executing swaps moves real money on-chain. Before automating swaps, understand slippage tolerance, gas estimation, and transaction confirmation. Unlike clicking 'Swap' on the Uniswap UI where the interface handles this, in Python you're responsible for every parameter.
from uniswap import Uniswap
# Re-initialize with version=3
uniswap = Uniswap(
address=WALLET_ADDRESS,
private_key=PRIVATE_KEY,
web3=w3,
version=3
)
def swap_eth_for_usdc(eth_amount_eth: float, slippage: float = 0.005):
"""
Swap ETH for USDC on Uniswap V3.
slippage: acceptable slippage as a decimal (0.005 = 0.5%)
"""
amount_in_wei = w3.to_wei(eth_amount_eth, "ether")
# Get expected output
expected_out = quoter.functions.quoteExactInputSingle(
WETH, USDC, 500, amount_in_wei, 0
).call()
# Apply slippage tolerance
min_amount_out = int(expected_out * (1 - slippage))
print(f"Swapping {eth_amount_eth} ETH")
print(f"Expected: {expected_out / 1e6:.2f} USDC")
print(f"Min acceptable: {min_amount_out / 1e6:.2f} USDC")
# Execute swap (use small amounts for testing)
tx = uniswap.make_trade(
input_token=WETH,
output_token=USDC,
qty=amount_in_wei,
fee=500
)
print(f"TX hash: {tx.hex()}")
receipt = w3.eth.wait_for_transaction_receipt(tx)
print(f"Confirmed in block: {receipt['blockNumber']}")
return receipt
# Test with 0.001 ETH first — always start small
# swap_eth_for_usdc(0.001)
Key Takeaway: Always test swaps with the smallest possible amount before scaling up automation. On mainnet, failed transactions still cost gas. Use a fork of mainnet (via Hardhat or Anvil) to simulate full swap flows before going live with real funds.
Gas management is the hidden cost that eats into DeFi automation profitability. Unlike paying trading fees on KuCoin or Bitget where costs are predictable, Ethereum gas fluctuates with network congestion. Fetch the current base fee and add your priority fee (tip) dynamically — don't hardcode gas prices.
def get_gas_params() -> dict:
"""Get current EIP-1559 gas parameters."""
latest = w3.eth.get_block("latest")
base_fee = latest["baseFeePerGas"]
priority_fee = w3.to_wei(1.5, "gwei") # tip to validator
max_fee = base_fee * 2 + priority_fee
return {
"maxFeePerGas": max_fee,
"maxPriorityFeePerGas": priority_fee,
"type": 2 # EIP-1559
}
gas = get_gas_params()
base_gwei = gas["maxFeePerGas"] / 1e9
print(f"Max gas: {base_gwei:.1f} gwei")
The Uniswap V3 SDK in Python opens up a range of possibilities: arbitrage detection between Uniswap and centralized books on Binance or Coinbase, automated LP rebalancing, price alert systems, and full execution bots. The building blocks covered here — connecting via web3.py, querying the subgraph, using the Quoter contract, and executing swaps with proper slippage and gas handling — are the foundation for all of them.
The DeFi space moves fast. Pool compositions shift, fee tier preferences migrate, and new V3 deployments appear on new chains. Pair your on-chain Python tooling with a real-time signal layer like VoiceOfChain to stay ahead of market moves — knowing when to interact with a pool matters just as much as knowing how. Start small, test on testnets or with tiny amounts on mainnet, and build up from there. The infrastructure is already there waiting — Python just gives you the wheel.