---
title: Uniswap V3 Core with EVM on Polkadot Hub
description: Deploy and test unmodified Uniswap V3 Core contracts on Polkadot Hub using standard Hardhat and TypeScript with the EVM execution path.
categories:
- Smart Contracts
- Tooling
url: https://docs.polkadot.com/smart-contracts/cookbook/eth-dapps/uniswap-v3/core-v3/
word_count: 2575
token_estimate: 4756
version_hash: sha256:b31ee67d04e8769ec7d106984949c527a544f39b6ea71badf9613b51c8a6b6da
last_updated: '2026-05-15T14:43:50+00:00'
---

# 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](https://github.com/bluealloy/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](https://developers.uniswap.org/docs/protocols/v3/overview) 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](https://nodejs.org/) v22.0.0 or later and npm installed
- Basic understanding of [Solidity](https://www.soliditylang.org/) and TypeScript
- Familiarity with the [Hardhat](/smart-contracts/dev-environments/hardhat/) development environment
- Some test tokens to cover transaction fees, obtained from the [Polkadot faucet](https://faucet.polkadot.io/). See [Get Test Tokens](/smart-contracts/faucet/#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:

    ```bash
    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:

    ```bash
    npm install
    ```

3. Compile the contracts:

    ```bash
    npx hardhat compile
    ```

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

    <div id="termynal" data-termynal>
      <span data-ty="input"><span class="file-path"></span>npx hardhat compile</span>
      <span data-ty>Generating typings for: 50 artifacts in dir: typechain-types for target: ethers-v6</span>
      <span data-ty>Successfully generated 78 typings!</span>
      <span data-ty>Compiled 50 Solidity files successfully (evm target: istanbul).</span>
    </div>
    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](https://v2.hardhat.org/hardhat-runner/docs/guides/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:

```bash
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:

```typescript title="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](https://eips.ethereum.org/EIPS/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:

```typescript title="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:

```typescript title="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:

```text
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](/smart-contracts/dev-environments/local-dev-node/) guide to set it up.

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

    ```bash
    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:

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

Make sure you have [configured your private key](#configure-secure-key-management) and that your account has test tokens. Then run:

```bash
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:

<div id="termynal" data-termynal markdown>
  <span data-ty="input">npx hardhat ignition deploy ./ignition/modules/UniswapV3Factory.ts --network polkadotTestnet</span>
  <span data-ty>✔ Confirm deploy to network polkadotTestnet (420420417)? … yes</span>
  <span data-ty>&nbsp;</span>
  <span data-ty>Hardhat Ignition 🚀</span>
  <span data-ty>&nbsp;</span>
  <span data-ty>Deploying [ UniswapV3FactoryModule ]</span>
  <span data-ty>&nbsp;</span>
  <span data-ty>Batch #1</span>
  <span data-ty> Executed UniswapV3FactoryModule#UniswapV3Factory</span>
  <span data-ty>&nbsp;</span>
  <span data-ty>[ UniswapV3FactoryModule ] successfully deployed 🚀</span>
  <span data-ty>&nbsp;</span>
  <span data-ty>Deployed Addresses</span>
  <span data-ty>&nbsp;</span>
  <span data-ty>UniswapV3FactoryModule#UniswapV3Factory - 0x2e234DAe75C793f67A35089C9d99245E1C58470b</span>
  <span data-ty="input"><span class="file-path"></span></span>
</div>
## Where to Go Next

<div class="grid cards" markdown>

-   <span class="badge guide">Guide</span> __Hardhat on Polkadot__

    ---

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

    [:octicons-arrow-right-24: Reference](/smart-contracts/dev-environments/hardhat/)

-   <span class="badge tutorial">Tutorial</span> __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.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/eth-dapps/uniswap-v3/periphery-v3/)

-   <span class="badge guide">Guide</span> __Local Development Node__

    ---

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

    [:octicons-arrow-right-24: Set Up](/smart-contracts/dev-environments/local-dev-node/)

</div>
