What Is Uniswap V2 and Why Fork It?
Uniswap V2 is an automated market maker (AMM) protocol that revolutionized decentralized trading when it launched on Ethereum in May 2020. Instead of using traditional order books, it relies on liquidity pools governed by the constant product formula x * y = k. Liquidity providers deposit equal values of two tokens into a pool, and traders swap against that pool, paying a small fee that accrues to the providers.
The protocol consists of two core contract sets: the Factory, which deploys new trading pair contracts, and the Router, which handles multi-hop swaps, slippage protection, and deadline enforcement. Each trading pair is its own contract (the Pair) that holds reserves and mints LP tokens to track liquidity ownership.
So why would you want to fork Uniswap V2? There are several compelling reasons:
- Revenue from trading fees. The default 0.3% swap fee goes to liquidity providers, but you can redirect a portion (or all) of it to a treasury address you control. Even a modest-volume DEX can generate meaningful revenue.
- Custom branding and UX. Running your own exchange lets you build a brand, design the interface exactly how you want, and curate the token list for your community.
- Chain-specific opportunities. Many newer chains (Base, Arbitrum, Polygon zkEVM, opBNB) still have room for well-executed DEX deployments. Getting in early on a growing chain gives you a first-mover advantage.
- Learning and portfolio building. Forking and deploying a full DEX is one of the best ways to deeply understand DeFi smart contract architecture.
The Uniswap V2 codebase is fully open-source under GPL-3.0, which means you can freely fork, modify, and deploy it on any EVM-compatible chain. This guide walks you through every step of the process.
Prerequisites
Before you start, make sure you have the following tools and knowledge in place:
- Solidity fundamentals. You should understand contract inheritance, interfaces, events, and how ERC-20 tokens work. You do not need to be an expert, but you need to be able to read and modify Solidity code confidently.
- Node.js (v18+) and npm/yarn. The build tooling, deployment scripts, and frontend all run on Node. Install the latest LTS version.
- Hardhat or Foundry. These are the two leading smart contract development frameworks. This guide uses Hardhat because it has a larger ecosystem of plugins, but all concepts translate directly to Foundry.
- A wallet with testnet funds. You will deploy contracts on a testnet first. Get test ETH from a faucet for Sepolia, or test BNB for BSC Testnet.
- An RPC endpoint. Use Alchemy, Infura, or a public RPC URL for the chain you are deploying to.
- A block explorer API key. You will want to verify your contracts on Etherscan, BscScan, or the relevant explorer for transparency and trust.
If you are comfortable with all of the above, you are ready to fork Uniswap V2.
Step 1 — Clone the Uniswap V2 Contracts
The Uniswap V2 protocol is split across two repositories: v2-core (Factory and Pair contracts) and v2-periphery (Router and helper libraries). You will need both.
Start by creating your project structure:
mkdir my-dex && cd my-dex
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init
# Choose "Create a JavaScript project"
Now create your contract files. The three essential contracts are:
- Factory.sol — Deploys new Pair contracts for each token pair. It stores the mapping of all pairs and emits events when new ones are created.
- Pair.sol — The core AMM logic. Holds token reserves, calculates swap amounts using the constant product formula, mints and burns LP tokens, and collects fees.
- Router.sol — The user-facing entry point. Wraps native currency (ETH/BNB), handles multi-hop routing, enforces slippage limits, and provides helper functions for adding/removing liquidity.
Your contracts directory should look like this:
contracts/
core/
Factory.sol
Pair.sol
ERC20.sol # LP token implementation
Math.sol # Safe math and sqrt
UQ112x112.sol # Fixed-point math for price oracles
periphery/
Router.sol
libraries/
Library.sol # Pair address calculation, sorting
SafeMath.sol
TransferHelper.sol
interfaces/
IFactory.sol
IPair.sol
IRouter.sol
IERC20.sol
IWETH.sol
Understanding INIT_CODE_HASH
This is one of the most critical and most commonly misunderstood parts of a Uniswap V2 fork. The Router uses the INIT_CODE_HASH to deterministically compute Pair contract addresses without making on-chain calls. This hash is the keccak256 of the Pair contract's creation bytecode.
Every time you modify the Pair contract (even adding a comment can change it in some compilers), this hash changes. If the hash in your Library contract does not match the actual hash of your Pair bytecode, every swap and liquidity operation will fail because the Router will compute wrong addresses.
To get the correct hash after compiling your contracts:
// In a Hardhat script: scripts/getInitCodeHash.js
const { ethers } = require("hardhat");
async function main() {
const Pair = await ethers.getContractFactory("Pair");
const bytecode = Pair.bytecode;
const hash = ethers.keccak256(bytecode);
console.log("INIT_CODE_HASH:", hash);
}
main();
Copy this hash into your Library.sol where pairFor() computes addresses. We will revisit this when we deploy.
Step 2 — Modify the Fee Structure
The default Uniswap V2 fee is 0.3% on every swap. Of that, 100% goes to liquidity providers by default. However, the protocol includes a built-in mechanism to divert 0.05% (one-sixth of the fee) to a designated address. This is called the protocol fee, and it is controlled by the feeTo address in the Factory contract.
The fee logic lives in the _mintFee function inside Pair.sol. Here is a simplified version showing how it works:
// Inside Pair.sol
address public factory;
function _mintFee(
uint112 _reserve0,
uint112 _reserve1
) private returns (bool feeOn) {
address feeTo = IFactory(factory).feeTo();
feeOn = feeTo != address(0);
uint _kLast = kLast;
if (feeOn) {
if (_kLast != 0) {
uint rootK = Math.sqrt(
uint(_reserve0) * uint(_reserve1)
);
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
uint numerator = totalSupply * (rootK - rootKLast);
// Change the denominator to adjust fee split
// Default: 5 gives 1/6 of 0.3% = 0.05% to protocol
// Use 3 for 1/4 = 0.075% to protocol
// Use 1 for 1/2 = 0.15% to protocol
uint denominator = rootK * 5 + rootKLast;
uint liquidity = numerator / denominator;
if (liquidity > 0) {
_mint(feeTo, liquidity);
}
}
}
} else if (_kLast != 0) {
kLast = 0;
}
}
The key line is the denominator calculation. The multiplier (default 5) determines what fraction of the fee goes to the protocol versus liquidity providers:
- Multiplier 5 — Protocol gets 1/6 of 0.3% = 0.05%. LPs get 0.25%.
- Multiplier 3 — Protocol gets 1/4 of 0.3% = 0.075%. LPs get 0.225%.
- Multiplier 1 — Protocol gets 1/2 of 0.3% = 0.15%. LPs get 0.15%.
If you want to change the total swap fee itself (not just the protocol split), you need to modify the swap function in Pair.sol. Look for the line that enforces the constant product invariant after the swap:
// Default: 0.3% fee (multiply amounts by 997 out of 1000)
uint balance0Adjusted = balance0 * 1000 - amount0In * 3;
uint balance1Adjusted = balance1 * 1000 - amount1In * 3;
require(
balance0Adjusted * balance1Adjusted >=
uint(_reserve0) * uint(_reserve1) * (1000**2),
"K"
);
To change the fee to 0.2%, replace the 3 with 2. For 0.5%, use 5. For 1%, use 10. Be careful with this — higher fees discourage trading, and lower fees may not attract liquidity providers.
Finally, set the feeTo address after deployment by calling setFeeTo() on the Factory from the feeToSetter account (which is the deployer by default). If feeTo is the zero address, no protocol fee is collected.
Step 3 — Deploy Contracts
With your contracts modified and compiled, it is time to deploy. The deployment order matters: Factory first, then Router (which needs the Factory address and the WETH/WBNB address).
First, configure Hardhat for your target chain. Here is an example hardhat.config.js for BSC Testnet:
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: { enabled: true, runs: 999999 },
},
},
networks: {
bscTestnet: {
url: "https://data-seed-prebsc-1-s1.binance.org:8545",
chainId: 97,
accounts: [process.env.DEPLOYER_PRIVATE_KEY],
},
bscMainnet: {
url: "https://bsc-dataseed.binance.org/",
chainId: 56,
accounts: [process.env.DEPLOYER_PRIVATE_KEY],
},
polygon: {
url: "https://polygon-rpc.com",
chainId: 137,
accounts: [process.env.DEPLOYER_PRIVATE_KEY],
},
},
etherscan: {
apiKey: process.env.EXPLORER_API_KEY,
},
};
Now create the deployment script:
// scripts/deploy.js
const { ethers } = require("hardhat");
// WETH/WBNB addresses per chain
const WRAPPED_NATIVE = {
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // ETH Mainnet
56: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", // BSC
97: "0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd", // BSC Testnet
137: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", // Polygon (WMATIC)
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // Arbitrum
8453: "0x4200000000000000000000000000000000000006", // Base
};
async function main() {
const [deployer] = await ethers.getSigners();
const chainId = (await ethers.provider.getNetwork()).chainId;
console.log("Deploying with:", deployer.address);
console.log("Chain ID:", chainId);
// 1. Deploy Factory
const Factory = await ethers.getContractFactory("Factory");
const factory = await Factory.deploy(deployer.address);
await factory.waitForDeployment();
const factoryAddr = await factory.getAddress();
console.log("Factory deployed to:", factoryAddr);
// 2. Get INIT_CODE_HASH (update Library.sol if needed)
const Pair = await ethers.getContractFactory("Pair");
const initCodeHash = ethers.keccak256(Pair.bytecode);
console.log("INIT_CODE_HASH:", initCodeHash);
// 3. Deploy Router
const wrappedNative = WRAPPED_NATIVE[Number(chainId)];
if (!wrappedNative) throw new Error("No WETH address for chain " + chainId);
const Router = await ethers.getContractFactory("Router");
const router = await Router.deploy(factoryAddr, wrappedNative);
await router.waitForDeployment();
const routerAddr = await router.getAddress();
console.log("Router deployed to:", routerAddr);
// 4. Enable protocol fee (optional)
const tx = await factory.setFeeTo(deployer.address);
await tx.wait();
console.log("Protocol fee enabled, feeTo:", deployer.address);
console.log("\n--- Deployment Summary ---");
console.log("Factory:", factoryAddr);
console.log("Router: ", routerAddr);
console.log("WETH: ", wrappedNative);
}
main().catch(console.error);
Run the deployment on BSC Testnet:
DEPLOYER_PRIVATE_KEY=0xYOUR_KEY npx hardhat run scripts/deploy.js --network bscTestnet
After deployment, verify your contracts on the block explorer so users can read the source code and build trust:
npx hardhat verify --network bscTestnet FACTORY_ADDRESS "DEPLOYER_ADDRESS"
npx hardhat verify --network bscTestnet ROUTER_ADDRESS "FACTORY_ADDRESS" "WBNB_ADDRESS"
Step 4 — Fork the Frontend
With your contracts live on a testnet, you need a frontend for users to interact with. The original Uniswap Interface is a React application. While you can fork it directly, be aware that recent versions are complex with many dependencies. Many builders prefer to start from a simplified fork or a purpose-built DEX frontend.
If you go the DIY route, here is what you need to change in the frontend code:
- Contract addresses. Replace the Factory and Router addresses with your deployed contracts. These are usually defined in a constants file or environment variables.
- Chain configuration. Update the supported chains list to include only the chains where you have deployed contracts. Remove references to Uniswap-specific chains if they do not apply.
- INIT_CODE_HASH. The frontend SDK uses this hash to compute pair addresses client-side. It must match the hash from your deployed Pair bytecode.
- Token list. Replace the default Uniswap token list with your own curated list. You can host a JSON file following the Token Lists standard, or hardcode a small set of tokens.
- Branding. Change the logo, colors, page title, and favicon. Update any references to "Uniswap" in the UI copy.
- Analytics and tracking. Remove Uniswap's analytics endpoints and replace with your own (or remove entirely).
A minimal approach is to use ethers.js or viem directly with a simple React or vanilla JavaScript frontend. For swapping, you only need to call two functions on the Router:
// Swap exact tokens for tokens (simplified)
await router.swapExactTokensForTokens(
amountIn, // Amount of input token
amountOutMin, // Minimum output (slippage protection)
[tokenA, tokenB], // Swap path
userAddress, // Recipient
deadline // Unix timestamp
);
// For native currency swaps (ETH/BNB), use:
await router.swapExactETHForTokens(
amountOutMin,
[WETH, tokenB],
userAddress,
deadline,
{ value: amountIn }
);
You can host your frontend anywhere: Vercel, Netlify, GitHub Pages, or your own server. For a decentralized approach, deploy to IPFS. If you are building on WordPress, you can embed the DEX interface as a plugin — which is exactly what DeFinance does.
Step 5 — Add Liquidity and Test
Your DEX is useless without liquidity. Before launching publicly, you need to create at least one trading pair and seed it with tokens so that swaps can actually execute.
Create Your First Pair
Call the Factory to create a pair, then add liquidity through the Router:
// scripts/addLiquidity.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
const routerAddr = "YOUR_ROUTER_ADDRESS";
const tokenA = "YOUR_TOKEN_A_ADDRESS";
const tokenB = "YOUR_TOKEN_B_ADDRESS";
const router = await ethers.getContractAt("IRouter", routerAddr);
const tokenAContract = await ethers.getContractAt("IERC20", tokenA);
const tokenBContract = await ethers.getContractAt("IERC20", tokenB);
// Approve Router to spend tokens
const amountA = ethers.parseEther("1000");
const amountB = ethers.parseEther("1000");
await tokenAContract.approve(routerAddr, amountA);
await tokenBContract.approve(routerAddr, amountB);
// Add liquidity (this also creates the pair if it doesn't exist)
const deadline = Math.floor(Date.now() / 1000) + 600; // 10 min
const tx = await router.addLiquidity(
tokenA,
tokenB,
amountA,
amountB,
0, // amountAMin (set to 0 for first deposit)
0, // amountBMin
deployer.address,
deadline
);
const receipt = await tx.wait();
console.log("Liquidity added. TX:", receipt.hash);
}
main().catch(console.error);
For a native currency pair (e.g., BNB/USDT), use addLiquidityETH instead and send the native amount as msg.value.
Testing Checklist
Before going to mainnet, verify all of these on testnet:
- Create a pair and add liquidity. Confirm LP tokens are received.
- Execute a swap in both directions. Check that output amounts match expected values.
- Remove liquidity. Verify you get both tokens back proportionally.
- Test with the native currency wrapper (WETH/WBNB).
- Test slippage protection — set a tight
amountOutMinand confirm it reverts when price moves. - Test the deadline parameter — set an expired deadline and confirm the transaction reverts.
- If you enabled protocol fees, verify that LP token balances for the
feeToaddress increase over time. - Test the frontend end-to-end: connect wallet, approve tokens, swap, add/remove liquidity.
Skip the Coding — Use DeFinance Plugin
DeFinance gives you a ready-made Uniswap V2 fork with a full admin panel. No coding required. Set your swap fees anywhere from 0% to 20%, customize branding and colors, manage token lists, and deploy to any EVM chain — all from a WordPress dashboard.
Common Pitfalls When Forking Uniswap V2
Having walked hundreds of developers through the process of how to fork Uniswap V2, we have seen the same mistakes come up repeatedly. Here are the ones that cost the most time:
1. INIT_CODE_HASH Mismatch
This is the single most common issue. If the hash in your Library contract does not match your compiled Pair bytecode, every call to the Router will revert with a generic error. The fix is straightforward: recompile, recalculate the hash, update Library.sol, and redeploy. But many developers spend hours debugging before they realize this is the problem.
Tip: log the hash during deployment (as shown in our deploy script above) and compare it against the value hardcoded in Library.sol before going live.
2. Wrong WETH/WBNB Address
Every chain has a different wrapped native currency contract. Using the Ethereum WETH address on BSC will cause all native-currency swaps to fail silently or revert. Always double-check the wrapped native address for your target chain. We listed the common ones in the deployment script above.
3. Forgetting to Verify Contracts
Unverified contracts are a major red flag for users and liquidity providers. Nobody wants to interact with a DEX whose contract source code they cannot read. Verify on the block explorer immediately after deploying. It takes two minutes and builds enormous trust.
4. Liquidity Bootstrapping Problems
The initial price of a pair is determined by the ratio of tokens in the first liquidity deposit. If you add 1 ETH and 1000 USDT, the price is 1000 USDT per ETH. Get this ratio wrong, and arbitrage bots will drain your pool within seconds of launch. Always match the initial deposit ratio to the current market price.
5. Compiler Version and Optimizer Settings
Changing the Solidity compiler version or optimizer settings changes the bytecode, which changes the INIT_CODE_HASH. Lock your compiler version in hardhat.config.js and use the same settings for compilation and verification.
6. Not Handling Fee-on-Transfer Tokens
Some tokens (like SafeMoon and its clones) charge a fee on every transfer. The standard Router functions do not account for this. Uniswap V2 includes separate functions (swapExactTokensForTokensSupportingFeeOnTransferTokens) for these tokens. If your DEX will support them, make sure the Router includes these functions and your frontend offers them as an option.
Uniswap V2 vs V3: Which to Fork?
If you are deciding whether to fork Uniswap V2 or V3, the choice depends on your goals, your team's capabilities, and the legal landscape.
| Feature | Uniswap V2 | Uniswap V3 |
|---|---|---|
| AMM Model | Constant product (x * y = k) | Concentrated liquidity |
| Capital Efficiency | Lower — liquidity spread across full range | Higher — LPs choose price ranges |
| Complexity | Simple — ~1,000 lines of Solidity | Complex — ~3,500 lines with tick math |
| License | GPL-3.0 (fully open) | BSL 1.1 (expired April 2023, now GPL-2.0) |
| Fork Difficulty | Beginner-friendly | Requires strong Solidity skills |
| Frontend Complexity | Standard swap UI | Needs range selection UI for LPs |
| Gas Cost (Swap) | ~150,000 gas | ~180,000-250,000 gas |
| Gas Cost (Deploy) | ~$50-100 | ~$150-400 |
| Oracle | Cumulative price (simple TWAP) | Observation array (advanced TWAP) |
| Battle-Tested Forks | SushiSwap, PancakeSwap, QuickSwap | Fewer production forks |
| Best For | New builders, quick launches, smaller chains | Advanced teams, high-volume pairs |
Our recommendation: If you are building your first DEX, start with a Uniswap V2 fork. The codebase is simpler, easier to audit, cheaper to deploy, and has a proven track record across dozens of successful forks (PancakeSwap alone does billions in monthly volume on a V2 base). You can always upgrade to concentrated liquidity later once you have traction and a development team.
The BSL license on V3 expired in April 2023, so it is now legally safe to fork. However, the added complexity of tick-based liquidity, range orders, and the frontend UI for managing positions makes it a significantly larger engineering effort. For most teams, V2 remains the pragmatic choice.
How Much Does It Cost?
One of the most common questions from developers learning how to fork Uniswap V2 is "how much will it cost?" Here is a realistic breakdown:
Contract Deployment (One-Time)
- Ethereum Mainnet: $200-800 depending on gas prices. Factory and Router together consume roughly 5-6 million gas.
- BSC: $5-15. Gas is cheap on BSC.
- Polygon: $1-5. Even cheaper.
- Arbitrum / Base: $5-30. L2 costs vary but are generally low.
Frontend Hosting (Monthly)
- Vercel / Netlify free tier: $0/month for low-to-moderate traffic.
- Custom domain: $10-15/year.
- IPFS (decentralized): Free via Pinata or Fleek.
Security Audit (Recommended)
- If you did not modify core logic: A lightweight review ($2,000-5,000) may suffice since the base code is already audited.
- If you changed fee logic or added features: Full audit from a reputable firm runs $10,000-50,000+ depending on scope.
Ongoing Maintenance
- Monitoring, subgraph indexing, token list curation, and community support. Budget 5-10 hours per week minimum.
DIY vs Ready-Made Solution
If you factor in developer time (research, coding, debugging, testing, frontend work), a DIY fork typically takes 2-4 weeks of full-time effort for an experienced developer. At market rates, that is $5,000-15,000+ in time cost alone.
A ready-made solution like DeFinance or the Uniswap Fork package gives you a tested, deployed, and customizable DEX for a fraction of that cost, often deployable in under an hour. The trade-off is less control over the contract layer, but for many use cases (branded DEX, community exchange, token launch platform) that trade-off makes sense.
Frequently Asked Questions
Is it legal to fork Uniswap V2?
Yes. Uniswap V2 is released under the GPL-3.0 open-source license, which explicitly permits copying, modifying, and redistributing the code. You must keep the same license on your derivative work (copyleft), but you are free to deploy and commercialize it. Many of the largest DEXes in crypto — PancakeSwap, SushiSwap, Trader Joe, QuickSwap — are direct forks of Uniswap V2. Uniswap V3 was initially under a Business Source License (BSL) that restricted commercial forks for two years, but that license expired in April 2023 and converted to GPL-2.0.
Can I fork Uniswap V2 to BSC, Polygon, or Arbitrum?
Absolutely. The Uniswap V2 contracts are standard EVM Solidity and deploy on any EVM-compatible chain without modification. You only need to change two things: the wrapped native currency address (WBNB on BSC, WMATIC on Polygon, WETH on Arbitrum) and the RPC endpoint in your deployment configuration. The contract logic, fee structure, and AMM math work identically on every EVM chain. Many of the most successful Uniswap V2 forks actually run on chains other than Ethereum — PancakeSwap on BSC and QuickSwap on Polygon being prime examples.
How do I change the swap fee?
The swap fee is controlled by a single line in the Pair contract's swap function. The default calculation multiplies input amounts by 997 (out of 1000), which implements a 0.3% fee. To change it to 0.2%, use 998. For 0.5%, use 995. For 1%, use 990. The formula is: multiplier = 1000 - (fee_percentage * 10). Separately, you can control how much of the fee goes to the protocol versus liquidity providers by adjusting the denominator multiplier in the _mintFee function, as described in Step 2 of this guide.
Do I need to audit my fork?
If you are deploying a DEX that will hold real user funds, a security audit is strongly recommended. That said, if you have not modified the core swap and liquidity logic (only changed fees, branding, and addresses), the risk is lower because the original Uniswap V2 contracts are among the most battle-tested in DeFi. At minimum, have an experienced Solidity developer review your changes. If you have added new features (custom fee distribution, governance, staking rewards), a professional audit becomes much more important. Many audit firms offer "fork reviews" at a lower price point ($2,000-5,000) for projects that only make minor modifications to audited codebases.
How long does it take to fork Uniswap V2?
For an experienced Solidity developer, deploying a basic fork (contracts + minimal frontend) takes 1-2 weeks. Building a polished product with custom branding, a curated token list, analytics, mobile responsiveness, and proper testing takes 3-6 weeks. If you are learning Solidity while building, expect 1-3 months. Using a pre-built solution like DeFinance can reduce this to a single day — it handles contracts, frontend, token list management, and admin panel out of the box.
Ready to Launch Your Own DEX?
Whether you want to build from scratch or get started in minutes, we have you covered. DeFinance is a production-ready Uniswap V2 fork with a WordPress admin panel, multi-chain support, customizable fees, and built-in token list management. Deployed by 50+ teams on Ethereum, BSC, Polygon, Arbitrum, and more.