Skip to content

Deploy Uniswap V3 Core with EVM

Introduction

Polkadot Hub supports two execution paths for running smart contracts: PVM (which compiles Solidity to the Polkadot Virtual Machine via the revive compiler) and EVM (powered by REVM, a Rust implementation of the Ethereum Virtual Machine, which runs standard EVM bytecode with zero modifications). This tutorial follows the EVM path.

With EVM, you deploy the same unmodified Solidity contracts using the same standard Hardhat toolchain you already know. No special compiler plugins, no contract rewrites, and no porting effort. If your project compiles with vanilla Hardhat, it runs on Polkadot Hub through EVM.

This tutorial walks you through cloning, compiling, testing, and deploying Uniswap V3 Core on Polkadot Hub using Hardhat and TypeScript. By the end, you will have a fully functioning UniswapV3Factory contract deployed to the Polkadot Hub TestNet.

Prerequisites

Before starting, make sure you have:

  • Node.js v22.0.0 or later and npm installed
  • Basic understanding of Solidity and TypeScript
  • Familiarity with the Hardhat development environment
  • Some test tokens to cover transaction fees, obtained from the Polkadot faucet. See Get Test Tokens for a guide to using the faucet
  • A wallet with a private key for signing transactions
  • Basic understanding of how AMMs and liquidity pools work

Set Up the Project

Start by cloning the EVM Hardhat examples repository, which contains the Uniswap V3 Core project with a standard Hardhat and TypeScript configuration:

  1. Clone the repository, check out the pinned commit, and navigate to the Uniswap V3 project:

    git clone https://github.com/polkadot-developers/revm-hardhat-examples.git
    cd revm-hardhat-examples
    git checkout 3ff28ae44c4ab041a96953f49d0e2dae0408f28f
    cd uniswap-v3-core-hardhat/
    
  2. Install the required dependencies:

    npm install
    
  3. Compile the contracts:

    npx hardhat compile
    

    If the compilation is successful, you should see output similar to the following:

    npx hardhat compile Generating typings for: 50 artifacts in dir: typechain-types for target: ethers-v6 Successfully generated 78 typings! Compiled 50 Solidity files successfully (evm target: istanbul).

    After running this command, the compiled artifacts (ABI and bytecode) appear in the artifacts directory.

Configure Secure Key Management

This project uses Hardhat Configuration Variables to manage private keys securely. Unlike .env files, configuration variables are stored outside your project directory and are never at risk of being committed to version control.

To set your private key for TestNet deployment, run:

npx hardhat vars set TESTNET_PRIVATE_KEY

When prompted, paste your private key. Hardhat stores it securely and makes it available through vars.get("TESTNET_PRIVATE_KEY") in the configuration file.

Warning

Keep your private key safe and never share it with anyone. If it is compromised, your funds can be stolen.

The hardhat.config.ts file references the variable conditionally, so the project works without it for local development:

hardhat.config.ts
polkadotTestnet: {
  url: "https://services.polkadothub-rpc.com/testnet",
  accounts: vars.has("TESTNET_PRIVATE_KEY")
    ? [vars.get("TESTNET_PRIVATE_KEY")]
    : [],
},

Note

You only need the TESTNET_PRIVATE_KEY variable when deploying to the Polkadot Hub TestNet. Local development against the development node does not require any key configuration.

V3-Specific Configuration

Uniswap V3 Core requires specific Solidity compiler settings to keep the UniswapV3Factory contract under the EIP-170 24KB contract size limit. The hardhat.config.ts file sets bytecodeHash to "none", which excludes the metadata hash from the compiled bytecode. This matches the original Uniswap V3 Core deployment configuration:

hardhat.config.ts
solidity: {
  version: "0.7.6",
  settings: {
    optimizer: {
      enabled: true,
      runs: 800,
    },
    metadata: {
      // Exclude metadata hash to keep contract size deterministic and under EIP-170 limit
      // This matches the original Uniswap V3 Core deployment configuration
      bytecodeHash: "none",
    },
  },
},

The configuration also sets a fixed gas price of 50 gwei for the localNode network to match the gas price reported by the Polkadot local development node:

hardhat.config.ts
localNode: {
  url: "http://127.0.0.1:8545",
  gasPrice: 50_000_000_000, // 50 gwei — matches Polkadot local node reported gas price
},

Uniswap V3 Core Architecture

Before interacting with the contracts, it is essential to understand the core architecture that powers Uniswap V3. This version introduces concentrated liquidity, a fundamentally different model from V2's uniform distribution that allows liquidity providers to allocate capital within specific price ranges for dramatically improved capital efficiency.

Concentrated Liquidity and Fee Tiers

In Uniswap V2, liquidity is spread uniformly across the entire price curve from zero to infinity. Uniswap V3 replaces this with concentrated liquidity, where providers choose a specific price range in which their capital is active. This means a position only earns fees when the current price falls within its selected range, but the capital within that range is far more effective.

Uniswap V3 also introduces multiple fee tiers so that pools can match the risk profile of different token pairs:

Fee Tier Fee Percentage Tick Spacing Typical Use Case
500 0.05% 10 Stable pairs (e.g., USDC/DAI)
3000 0.30% 60 Standard pairs (e.g., ETH/USDC)
10000 1.00% 200 Exotic or volatile pairs

Tick System and Price Math

Prices in Uniswap V3 are represented as the square root of the price ratio, stored as a fixed-point Q64.96 value (sqrtPriceX96). The continuous price space is divided into discrete ticks, where each tick represents a 0.01% (1 basis point) price change. Liquidity positions are bounded by a lower tick and an upper tick, and the protocol tracks which ticks have active liquidity through a bitmap data structure.

Core Contracts

At the heart of Uniswap V3 are four core smart contracts:

  • UniswapV3Factory: Creates and registers new pools. Each unique combination of two tokens and a fee tier produces a single pool. The Factory also controls protocol fee settings and ownership.
  • UniswapV3Pool: The main contract for each trading pair and fee tier. It manages concentrated liquidity positions, executes swaps using the tick-based price system, and maintains a built-in TWAP (time-weighted average price) oracle.
  • UniswapV3PoolDeployer: A helper contract used by the Factory to deploy new pools via CREATE2, ensuring deterministic pool addresses.
  • NoDelegateCall: A security base contract that prevents delegate call exploits by verifying the execution context matches the original deployment address.

Math Libraries

The V3 protocol relies on an extensive suite of 16 math library contracts for precise fixed-point arithmetic, tick calculations, and price computations. These include TickMath for converting between ticks and sqrt prices, SqrtPriceMath for computing swap amounts, FullMath for 512-bit multiplication, and Oracle for TWAP accumulator management, among others.

Project Structure

The project scaffolding is as follows:

uniswap-v3-core-hardhat/
├── contracts/
│   ├── interfaces/
│   │   ├── callback/
│   │   │   ├── IUniswapV3FlashCallback.sol
│   │   │   ├── IUniswapV3MintCallback.sol
│   │   │   └── IUniswapV3SwapCallback.sol
│   │   ├── pool/
│   │   │   ├── IUniswapV3PoolActions.sol
│   │   │   ├── IUniswapV3PoolDerivedState.sol
│   │   │   ├── IUniswapV3PoolEvents.sol
│   │   │   ├── IUniswapV3PoolImmutables.sol
│   │   │   ├── IUniswapV3PoolOwnerActions.sol
│   │   │   └── IUniswapV3PoolState.sol
│   │   ├── IERC20Minimal.sol
│   │   ├── IUniswapV3Factory.sol
│   │   ├── IUniswapV3Pool.sol
│   │   └── IUniswapV3PoolDeployer.sol
│   ├── libraries/
│   │   ├── BitMath.sol
│   │   ├── FixedPoint128.sol
│   │   ├── FixedPoint96.sol
│   │   ├── FullMath.sol
│   │   ├── LiquidityMath.sol
│   │   ├── LowGasSafeMath.sol
│   │   ├── Oracle.sol
│   │   ├── Position.sol
│   │   ├── SafeCast.sol
│   │   ├── SqrtPriceMath.sol
│   │   ├── SwapMath.sol
│   │   ├── Tick.sol
│   │   ├── TickBitmap.sol
│   │   ├── TickMath.sol
│   │   ├── TransferHelper.sol
│   │   └── UnsafeMath.sol
│   ├── test/
│   │   ├── BitMathTest.sol
│   │   ├── FullMathTest.sol
│   │   ├── LiquidityMathTest.sol
│   │   ├── MockTimeUniswapV3Pool.sol
│   │   ├── MockTimeUniswapV3PoolDeployer.sol
│   │   ├── NoDelegateCallTest.sol
│   │   ├── OracleTest.sol
│   │   ├── SqrtPriceMathTest.sol
│   │   ├── SwapMathTest.sol
│   │   ├── TestERC20.sol
│   │   ├── TestUniswapV3Callee.sol
│   │   ├── TestUniswapV3ReentrantCallee.sol
│   │   ├── TestUniswapV3Router.sol
│   │   ├── TestUniswapV3SwapPay.sol
│   │   ├── TickBitmapTest.sol
│   │   ├── TickMathTest.sol
│   │   └── TickTest.sol
│   ├── NoDelegateCall.sol
│   ├── UniswapV3Factory.sol
│   ├── UniswapV3Pool.sol
│   └── UniswapV3PoolDeployer.sol
├── ignition/
│   └── modules/
│       └── UniswapV3Factory.ts
├── scripts/
│   └── deploy.ts
├── test/
│   ├── shared/
│   │   ├── checkObservationEquals.ts
│   │   ├── fixtures.ts
│   │   ├── format.ts
│   │   └── utilities.ts
│   ├── UniswapV3Factory.test.ts
│   └── UniswapV3Pool.test.ts
├── hardhat.config.ts
├── package.json
└── tsconfig.json

Key differences from V2 are significant. The Solidity contracts use version 0.7.6 (V2 used 0.5.16). The contracts/libraries/ directory contains 16 math libraries for tick calculations, fixed-point arithmetic, and oracle management. The contracts/test/ directory includes 17 test helper contracts, including mock pools, routers, and math test harnesses. The test suite is split across two files instead of three, with a shared utilities directory.

Test the Contracts

The project includes a comprehensive test suite with 187 tests across two test files:

  • UniswapV3Factory.test.ts: 21 tests covering factory operations, pool creation, fee tier management, and ownership controls.
  • UniswapV3Pool.test.ts: 166 tests covering concentrated liquidity positions, swaps across tick boundaries, fee accumulation, flash loans, oracle observations, and edge cases.

To run the tests locally:

  1. Start the local development node. Follow the steps in the Local Development Node guide to set it up.

  2. In a new terminal, run the test suite against the local node:

    npx hardhat test --network localNode
    

    The tests are configured with a 120-second Mocha timeout to accommodate Polkadot network block times. The result should look similar to the following:

    npx hardhat test --network localNode Compiled 50 Solidity files successfully (evm target: istanbul). UniswapV3Factory ✔ owner is deployer ✔ initial enabled fee amounts #createPool ✔ succeeds for low fee pool (312ms) ✔ succeeds for medium fee pool (298ms) ✔ succeeds for high fee pool (301ms) ✔ succeeds if tokens are passed in reverse (287ms) ✔ fails if token a == token b ✔ fails if token a is 0 or token b is 0 ✔ fails if fee amount is not enabled #setOwner ✔ fails if caller is not owner ✔ updates owner (189ms) ✔ emits event (203ms) ✔ cannot be called by original owner #enableFeeAmount ✔ fails if caller is not owner ✔ fails if fee is too great ✔ fails if tick spacing is too small ✔ fails if tick spacing is too large ✔ fails if already initialized ✔ sets the fee amount in the mapping (211ms) ✔ emits an event (198ms) ✔ enables pool creation (267ms) UniswapV3Pool ✔ constructor initializes immutables #initialize ✔ fails if already initialized ✔ fails if starting price is too low ✔ fails if starting price is too high ✔ can be initialized at MIN_SQRT_RATIO (412ms) ✔ can be initialized at MAX_SQRT_RATIO - 1 (389ms) ✔ sets initial variables (401ms) ✔ initializes the first observations slot (445ms) ✔ emits a Initialized event with the input tick (378ms) #increaseObservationCardinalityNext ✔ fails if not initialized ✔ does not change cardinality next if less than current (356ms) ✔ increases cardinality next (498ms) ✔ emits an event (423ms) #mint after initialization ✔ fails if not initialized ✔ fails if tickLower greater than tickUpper (312ms) ✔ fails if tickLower less than min tick (298ms) ✔ fails if tickUpper greater than max tick (287ms) ✔ fails if amount is zero (401ms) ✔ mints within the range (15243ms) ✔ mints at the range lower edge (14987ms) ✔ mints at the range upper edge (15104ms) ✔ provides liquidity in both tokens when in range (16312ms) ✔ emits a Mint event (12876ms) #burn ✔ cannot burn more than position liquidity (398ms) ✔ burns partial liquidity (18934ms) ✔ burns entire liquidity (19201ms) ✔ emits a Burn event (18756ms) #observe ✔ fails if not initialized ✔ returns correct cumulative values (14523ms) ✔ interpolates correctly between observations (16891ms) #collect ✔ reverts if position has no tokens owed (312ms) ✔ collects token0 and token1 fees (21345ms) ✔ collects protocol fees (22109ms) #feeProtocol ✔ fails if caller is not factory owner ✔ sets fee protocol (19876ms) ✔ emits a SetFeeProtocol event (18543ms) 187 passing (42m)

Tip

If tests time out, ensure your local development node is running and accessible at http://127.0.0.1:8545.

Deploy the Contracts

After successfully testing the contracts, you can deploy them to the Polkadot Hub TestNet using Hardhat Ignition. The Ignition module at ignition/modules/UniswapV3Factory.ts deploys the UniswapV3Factory contract.

Make sure you have configured your private key and that your account has test tokens. Then run:

npx hardhat ignition deploy ./ignition/modules/UniswapV3Factory.ts --network polkadotTestnet

When prompted, confirm the target network name and chain ID. Ignition deploys the Factory contract and prints the deployed address. The output should look similar to the following:

npx hardhat ignition deploy ./ignition/modules/UniswapV3Factory.ts --network polkadotTestnet ✔ Confirm deploy to network polkadotTestnet (420420417)? … yes   Hardhat Ignition 🚀   Deploying [ UniswapV3FactoryModule ]   Batch #1 Executed UniswapV3FactoryModule#UniswapV3Factory   [ UniswapV3FactoryModule ] successfully deployed 🚀   Deployed Addresses   UniswapV3FactoryModule#UniswapV3Factory - 0x2e234DAe75C793f67A35089C9d99245E1C58470b

Where to Go Next

  • Guide Hardhat on Polkadot


    Learn how to create, compile, test, and deploy smart contracts on Polkadot Hub using Hardhat.

    Reference

  • Tutorial Uniswap V3 Periphery


    Deploy the SwapRouter and NonfungiblePositionManager contracts on Polkadot Hub to add token swaps and NFT-based liquidity position management on top of V3 Core.

    Get Started

  • Guide Local Development Node


    Set up and run a local development node for testing your smart contracts against Polkadot.

    Set Up

Last update: May 15, 2026
| Created: April 23, 2026