Back to Blog

How to Fork Uniswap V2: Complete Guide 2026

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:

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:

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:

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:

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:

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:

  1. Create a pair and add liquidity. Confirm LP tokens are received.
  2. Execute a swap in both directions. Check that output amounts match expected values.
  3. Remove liquidity. Verify you get both tokens back proportionally.
  4. Test with the native currency wrapper (WETH/WBNB).
  5. Test slippage protection — set a tight amountOutMin and confirm it reverts when price moves.
  6. Test the deadline parameter — set an expired deadline and confirm the transaction reverts.
  7. If you enabled protocol fees, verify that LP token balances for the feeTo address increase over time.
  8. 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)

Frontend Hosting (Monthly)

Security Audit (Recommended)

Ongoing Maintenance

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.