Test the Contracts

## Test the Contracts

The project includes a comprehensive test suite with 28 tests across three test files covering ERC-20 functionality, factory operations, and pair contract interactions including liquidity management and swaps.

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:

      npx hardhat test --network localNode
      Compiling 12 Solidity files
      Successfully compiled 12 Solidity files

      UniswapV2ERC20
       ✔ name, symbol, decimals, totalSupply, balanceOf, DOMAIN_SEPARATOR, PERMIT_TYPEHASH (41ms)
       ✔ approve (4983ms)
       ✔ transfer (5047ms)
       ✔ transfer:fail
       ✔ transferFrom (6104ms)
       ✔ transferFrom:max (6138ms)

      UniswapV2Factory
       ✔ feeTo, feeToSetter, allPairsLength
       ✔ createPair (168ms)
       ✔ createPair:reverse (1198ms)
       ✔ setFeeTo (1112ms)
       ✔ setFeeToSetter (1099ms)

      UniswapV2Pair
       ✔ mint (11208ms)
       ✔ getInputPrice:0 (12374ms)
       ✔ getInputPrice:1 (17382ms)
       ✔ getInputPrice:2 (17401ms)
       ✔ getInputPrice:3 (17488ms)
       ✔ getInputPrice:4 (17433ms)
       ✔ getInputPrice:5 (17378ms)
       ✔ getInputPrice:6 (13427ms)
       ✔ optimistic:0 (17431ms)
       ✔ optimistic:1 (17730ms)
       ✔ optimistic:2 (17441ms)
       ✔ optimistic:3 (21409ms)
       ✔ swap:token0 (12449ms)
       ✔ swap:token1 (17415ms)
       ✔ burn (17474ms)
       ✔ feeTo:off (23684ms)
       ✔ feeTo:on (24775ms)

      28 passing (12m)

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


Deploy the Contracts

## 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/UniswapV2Factory.ts` deploys the UniswapV2Factory 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/UniswapV2Factory.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/UniswapV2Factory.ts --network polkadotTestnet
  ✔ Confirm deploy to network polkadotTestnet (420420417)? … yes

  Hardhat Ignition 🚀

  Deploying [ UniswapV2FactoryModule ]

  Batch #1
   Executed UniswapV2FactoryModule#UniswapV2Factory

  [ UniswapV2FactoryModule ] successfully deployed 🚀

  Deployed Addresses

  UniswapV2FactoryModule#UniswapV2Factory - 0x85b108660f47caDfAB9e0503104C08C1c96e0DA9


Where to Go Next

## Where to Go Next

-   Tutorial __Deploy Uniswap V2 Periphery__

    ---

    Deploy Router contracts for user-facing swaps, liquidity management, and WETH wrapping on top of V2 Core.

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

-   Guide __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/)

-   Guide __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/)


Tutorials

## Tutorials

-   Tutorial __V2 Core__

    ---

    Deploy unmodified Uniswap V2 Factory and Pair contracts on Polkadot Hub using Hardhat. Supports both EVM and PVM execution paths.

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


Uniswap V2 Periphery with EVM on Polkadot

Introduction

## Introduction

The [Uniswap V2 Periphery](https://developers.uniswap.org/docs/protocols/v2/overview) contracts provide the Router layer that sits on top of the [Uniswap V2 Core](/smart-contracts/cookbook/eth-dapps/uniswap-v2/core-v2/) Factory and Pair contracts. While V2 Core handles the low-level AMM logic, the Periphery Router contracts expose the user-facing functions for adding liquidity, removing liquidity, and executing token swaps safely with built-in deadline and slippage protection.

This tutorial follows the EVM execution path. With EVM (powered by [REVM](https://github.com/bluealloy/revm), a Rust implementation of the Ethereum Virtual Machine), 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 the Uniswap V2 Periphery contracts on Polkadot Hub using Hardhat and TypeScript. By the end, you will have a fully functioning WETH contract, Factory, Router02, two ERC-20 test tokens, and a trading pair deployed to the Polkadot Hub TestNet.


Prerequisites

## 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
- Completion of the [Uniswap V2 Core tutorial](/smart-contracts/cookbook/eth-dapps/uniswap-v2/core-v2/), as the Periphery contracts depend on V2 Core


Set Up the Project

## Set Up the Project

Start by cloning the Hardhat examples repository, which contains the Uniswap V2 Periphery project with a standard Hardhat and TypeScript configuration:

1. Clone the repository and navigate to the Uniswap V2 Periphery project:

    ```bash
    git clone https://github.com/polkadot-developers/revm-hardhat-examples.git
    cd revm-hardhat-examples
    git checkout a871364c8f4da052855b5c8ee4ed6b89fd182cb1
    cd uniswap-v2-periphery-hardhat/
    ```

2. Install the required dependencies:

    ```bash
    npm install
    ```

    !!! note
        The Periphery project depends on the V2 Core contracts through a local file reference (`"@uniswap/v2-core": "file:../uniswap-v2-core-hardhat"`). The `npm install` command resolves this automatically from the sibling directory in the repository.

3. Compile the contracts:

    ```bash
    npx hardhat compile
    ```

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

      npx hardhat compile
      Downloading solc 0.5.16
      Downloading solc 0.6.6
      Compiling 24 Solidity files
      Successfully compiled 24 Solidity files

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


Configure Secure Key Management

## 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"
networks: {
    hardhat: {
      allowUnlimitedContractSize: true,
    },
    localNode: {
      url: "http://127.0.0.1:8545",
    },
    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.


Uniswap V2 Periphery Architecture

## Uniswap V2 Periphery Architecture

Before interacting with the contracts, it is essential to understand how the Periphery layer extends the V2 Core system. While the [V2 Core](/smart-contracts/cookbook/eth-dapps/uniswap-v2/core-v2/) contracts handle low-level pool operations, the Periphery contracts provide a safe and convenient interface for end users and integrating applications.

The Periphery layer introduces three major components:

- **WETH9 contract**: A standard Wrapped Ether contract that allows native ETH to be used as an ERC-20 token. The Router uses WETH to support functions that accept native ETH directly (such as `addLiquidityETH` and `swapExactETHForTokens`), wrapping and unwrapping it transparently.
- **Router01 contract**: The original Router implementation providing core functions for adding and removing liquidity, and executing multi-hop token swaps through a path of pairs. It validates deadlines and minimum output amounts to protect users from slippage and front-running.
- **Router02 contract**: The production Router that extends Router01 with additional support for fee-on-transfer tokens. Functions like `swapExactTokensForTokensSupportingFeeOnTransferTokens` handle tokens that deduct fees on every transfer, ensuring swaps complete correctly even when the received amount is less than the sent amount.

This architecture separates user-facing logic from the core AMM primitives, keeping the Core contracts simple and immutable while allowing the Periphery to evolve independently.

The project scaffolding is as follows:

```text
uniswap-v2-periphery-hardhat/
├── contracts/
│   ├── interfaces/
│   │   ├── IERC20.sol
│   │   ├── IUniswapV2Router01.sol
│   │   ├── IUniswapV2Router02.sol
│   │   └── IWETH.sol
│   ├── libraries/
│   │   ├── SafeMath.sol
│   │   └── UniswapV2Library.sol
│   ├── test/
│   │   ├── CompileHelper.sol
│   │   ├── DeflatingERC20.sol
│   │   ├── ERC20.sol
│   │   ├── RouterEventEmitter.sol
│   │   └── WETH9.sol
│   ├── UniswapV2Router01.sol
│   └── UniswapV2Router02.sol
├── ignition/
│   └── modules/
│       └── UniswapV2Router02.ts
├── scripts/
│   └── deploy.ts
├── test/
│   ├── shared/
│   │   ├── fixtures.ts
│   │   └── utilities.ts
│   ├── UniswapV2Router01.test.ts
│   └── UniswapV2Router02.test.ts
├── hardhat.config.ts
├── package.json
└── tsconfig.json
```

Key differences from a typical Ethereum Hardhat project are minimal. The Solidity contracts are the original Uniswap V2 Periphery source, using both Solidity 0.5.16 (for the V2 Core dependency) and 0.6.6 (for the Router contracts) with no modifications. The Hardhat configuration includes a multi-compiler setup to handle both versions. The test files use TypeScript (`.test.ts`) and avoid `loadFixture` for compatibility with the Polkadot execution environment.


Test the Contracts

## Test the Contracts

The project includes a comprehensive test suite with 50 tests across two test files. The Router01 test file covers 38 tests for liquidity operations, token swaps, ETH swaps, and permit-based liquidity removal. The Router02 test file covers 12 tests for fee-on-transfer token support and additional swap scenarios.

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:

      npx hardhat test --network localNode
      Compiling 24 Solidity files
      Successfully compiled 24 Solidity files

      UniswapV2Router{01,02}
        UniswapV2Router01
          ✔ factory, WETH
          ✔ addLiquidity (12045ms)
          ✔ addLiquidityETH (11987ms)
          ✔ removeLiquidity (17234ms)
          ✔ removeLiquidityETH (17456ms)
          ✔ removeLiquidityWithPermit (12134ms)
          ✔ removeLiquidityETHWithPermit (17523ms)
          swapExactTokensForTokens
            ✔ happy path (12378ms)
            ✔ amounts (6145ms)
          swapTokensForExactTokens
            ✔ happy path (12401ms)
            ✔ amounts (6178ms)
          swapExactETHForTokens
            ✔ happy path (12367ms)
            ✔ amounts (6134ms)
          swapTokensForExactETH
            ✔ happy path (12489ms)
            ✔ amounts (6201ms)
          swapExactTokensForETH
            ✔ happy path (12356ms)
            ✔ amounts (6167ms)
          swapETHForExactTokens
            ✔ happy path (12412ms)
            ✔ amounts (6189ms)
        UniswapV2Router02
          ✔ factory, WETH
          ✔ addLiquidity (12067ms)
          ✔ addLiquidityETH (11998ms)
          ✔ removeLiquidity (17289ms)
          ✔ removeLiquidityETH (17512ms)
          ✔ removeLiquidityWithPermit (12156ms)
          ✔ removeLiquidityETHWithPermit (17534ms)
          swapExactTokensForTokens
            ✔ happy path (12389ms)
            ✔ amounts (6156ms)
          swapTokensForExactTokens
            ✔ happy path (12423ms)
            ✔ amounts (6187ms)
          swapExactETHForTokens
            ✔ happy path (12378ms)
            ✔ amounts (6145ms)
          swapTokensForExactETH
            ✔ happy path (12501ms)
            ✔ amounts (6212ms)
          swapExactTokensForETH
            ✔ happy path (12367ms)
            ✔ amounts (6178ms)
          swapETHForExactTokens
            ✔ happy path (12423ms)
            ✔ amounts (6198ms)

      UniswapV2Router02
        ✔ quote
        ✔ getAmountOut
        ✔ getAmountIn
        ✔ getAmountsOut (6123ms)
        ✔ getAmountsIn (6145ms)

      fee-on-transfer tokens
        ✔ removeLiquidityETHSupportingFeeOnTransferTokens (23456ms)
        ✔ removeLiquidityETHWithPermitSupportingFeeOnTransferTokens (23567ms)
        swapExactTokensForTokensSupportingFeeOnTransferTokens
          ✔ DTT -> WETH (12345ms)
          ✔ WETH -> DTT (12378ms)
        ✔ swapExactETHForTokensSupportingFeeOnTransferTokens (12367ms)
        ✔ swapExactTokensForETHSupportingFeeOnTransferTokens (12389ms)

      fee-on-transfer tokens: reloaded
        swapExactTokensForTokensSupportingFeeOnTransferTokens
          ✔ DTT -> DTT2 (12401ms)

      50 passing (25m)

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


Deploy the Contracts

## 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/UniswapV2Router02.ts` deploys WETH9, the UniswapV2Factory, and the UniswapV2Router02 contracts.

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/UniswapV2Router02.ts --network polkadotTestnet
```

When prompted, confirm the target network name and chain ID. Ignition deploys the contracts in two batches (Factory and WETH9 in parallel, then Router02) and prints all deployed addresses. The output should look similar to the following:

  npx hardhat ignition deploy ./ignition/modules/UniswapV2Router02.ts --network polkadotTestnet
  ✔ Confirm deploy to network polkadotTestnet (420420417)? … yes

  Hardhat Ignition 🚀

  Deploying [ UniswapV2Router02Module ]

  Batch #1
   Executed UniswapV2Router02Module#UniswapV2Factory
   Executed UniswapV2Router02Module#WETH9

  Batch #2
   Executed UniswapV2Router02Module#UniswapV2Router02

  [ UniswapV2Router02Module ] successfully deployed 🚀

  Deployed Addresses

  UniswapV2Router02Module#UniswapV2Factory - 0x3Ca8f8C6De9d51B4e14e9Ab7D6d2e38A39C93B85
  UniswapV2Router02Module#WETH9 - 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
  UniswapV2Router02Module#UniswapV2Router02 - 0xC36442b4a4522E871399CD717aBDD847Ab11FE88


Where to Go Next

## Where to Go Next

-   Tutorial __Uniswap V3 Core__

    ---

    Deploy unmodified Uniswap V3 Core contracts on Polkadot Hub using Hardhat and the EVM execution path.

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

-   Guide __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/)

-   Guide __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/)


Uniswap V3 Core with EVM on Polkadot Hub

Introduction

## 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

## 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

## 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:

      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

## 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

### 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

## 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

### 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

### 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

### 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

### 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

### 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

## 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:

      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

## 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:

  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

## Where to Go Next

-   Guide __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/)

-   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.

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

-   Guide __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/)


Tutorials

## Tutorials

-   Tutorial __V3 Core__

    ---

    Deploy unmodified Uniswap V3 Factory and Pool contracts on Polkadot Hub using Hardhat. Includes concentrated liquidity, fee tiers, and the full math library suite.

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


Uniswap V3 Periphery with EVM on Polkadot Hub

Introduction

## Introduction

The [Uniswap V3 Periphery](https://developers.uniswap.org/docs/protocols/v3/overview) contracts provide the user-facing layer that sits on top of the [Uniswap V3 Core](/smart-contracts/cookbook/eth-dapps/uniswap-v3/core-v3/) Factory and Pool contracts. While V3 Core handles the low-level concentrated liquidity engine, the Periphery contracts expose the functions that users and applications interact with directly: executing token swaps through one or more pools and managing concentrated liquidity positions as ERC-721 NFTs.

This tutorial follows the EVM execution path. With EVM (powered by [REVM](https://github.com/bluealloy/revm), a Rust implementation of the Ethereum Virtual Machine), 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 the Uniswap V3 Periphery contracts on Polkadot Hub using Hardhat and TypeScript. By the end, you will have a fully functioning SwapRouter and NonfungiblePositionManager deployed to the Polkadot Hub TestNet.


Prerequisites

## 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
- Completion of the [Uniswap V3 Core tutorial](/smart-contracts/cookbook/eth-dapps/uniswap-v3/core-v3/), as the Periphery contracts depend on the V3 Core contracts through a local package reference


Set Up the Project

## Set Up the Project

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

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

    ```bash
    git clone https://github.com/polkadot-developers/revm-hardhat-examples.git
    cd revm-hardhat-examples
    git checkout 96696ad15c3cf01b9168a71ad5114f27c34a8726
    cd uniswap-v3-periphery-hardhat/
    ```

2. Install the required dependencies:

    ```bash
    npm install
    ```

    !!! note
        The Periphery project depends on the V3 Core contracts through a local file reference (`"@uniswap/v3-core": "file:../uniswap-v3-core-hardhat"`). The `npm install` command resolves this automatically from the sibling directory in the repository, which is why you cloned the full monorepo rather than just the Periphery subdirectory.

3. Compile the contracts:

    ```bash
    npx hardhat compile
    ```

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

      npx hardhat compile
      Generating typings for: 111 artifacts in dir: typechain-types for target: ethers-v6
      Successfully generated 172 typings!
      Compiled 112 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


V3-Specific Configuration

### V3-Specific Configuration

The Periphery project uses the same critical compiler setting as V3 Core: `bytecodeHash` is set to `none`, which excludes the metadata hash from the compiled bytecode. This is required so that the compiled `UniswapV3Pool` bytecode matches the hardcoded `POOL_INIT_CODE_HASH` constant in `PoolAddress.sol`. The Periphery contracts use this constant to compute pool addresses via `CREATE2`. If there is a mismatch, every swap and LP operation silently calls the wrong address:

```typescript title="hardhat.config.ts"
const config: HardhatUserConfig = {
  solidity: {
    version: "0.7.6",
    settings: {
      optimizer: {
        enabled: true,
        runs: 800,
      },
      metadata: {
        // Exclude metadata hash to keep bytecode deterministic.
        // This matches the v3-core compilation settings so the compiled
        // UniswapV3Pool bytecode hash aligns with the hardcoded
        // POOL_INIT_CODE_HASH in PoolAddress.sol.
        bytecodeHash: "none",
      },
    },
  },
```

The configuration also sets `allowUnlimitedContractSize: true` for the local Hardhat network, which is required because several Periphery contracts exceed the standard 24KB EIP-170 size limit. For the `localNode` network, a fixed gas price of 50 gwei matches the gas price reported by the Polkadot local development node:

```typescript title="hardhat.config.ts"
networks: {
    hardhat: {
      allowUnlimitedContractSize: true,
    },
    localNode: {
      url: "http://127.0.0.1:8545",
      gasPrice: 50_000_000_000, // 50 gwei — matches Polkadot local node reported gas price
    },
```


Uniswap V3 Periphery Architecture

## Uniswap V3 Periphery Architecture

Before interacting with the contracts, it is essential to understand how the Periphery layer extends the V3 Core system. While the [V3 Core](/smart-contracts/cookbook/eth-dapps/uniswap-v3/core-v3/) contracts implement the concentrated liquidity engine, the Periphery contracts translate that engine into safe and ergonomic interfaces for end users and integrating applications.


Concentrated Liquidity and LP Positions

### Concentrated Liquidity and LP Positions

Uniswap V3 allows liquidity providers to concentrate capital within a chosen price range defined by a lower tick and an upper tick. A position earns trading fees only when the current pool price falls within its range.

The token composition of an out-of-range position is determined by its relationship to the current price. When the current price is below the position's range, the position holds 100% `token0` because all liquidity has been converted to the cheaper asset as the price moved down through the range. When the current price is above the range, the position holds 100% token1 because all liquidity has been converted to the more expensive asset as the price moved up. Only an in-range position holds both tokens simultaneously.

Accumulated fees are tracked separately from principal liquidity and are staged in `tokensOwed` fields on the position. Retrieving fees requires two explicit steps: calling `decreaseLiquidity` to move accrued principal and fees into `tokensOwed`, then calling `collect` to transfer those amounts to the owner's wallet.


SwapRouter

### SwapRouter

The `SwapRouter` contract routes token swaps through one or more V3 pools. It supports four swap modes:

- **`exactInputSingle`**: Spend a fixed amount of one token to receive as much of another token as possible through a single pool. The `amountOutMinimum` parameter enforces a slippage floor, and `sqrtPriceLimitX96` optionally caps how far the price can move (enabling partial fills).
- **`exactInput`**: Execute a multi-hop swap along an ABI-encoded path of pools. Each hop specifies a `tokenIn`, `fee`, and `tokenOut`. The full input amount is consumed and the final output must meet `amountOutMinimum`.
- **`exactOutputSingle`**: Buy a precise amount of one token using as little of another token as possible through a single pool. The `amountInMaximum` parameter caps the input, and the router refunds any unused allowance.
- **`exactOutput`**: Execute a multi-hop exact-output swap. The path is encoded in reverse (from the output token back to the input token), and the caller specifies the exact amount to receive.


NonfungiblePositionManager

### NonfungiblePositionManager

The `NonfungiblePositionManager` (NFPM) represents each concentrated liquidity position as an ERC-721 NFT. A single contract manages the full LP lifecycle:

- **`createAndInitializePoolIfNecessary`**: Creates a new V3 pool for a token pair and fee tier if none exists, and sets its initial price as a `sqrtPriceX96` value. Calling this on an already-initialized pool is a safe no-op.
- **`mint`**: Opens a new position within a specified tick range and mints an NFT to the recipient. The `tokenId` returned uniquely identifies the position. The `amount0Min` and `amount1Min` parameters guard against slippage during the initial deposit.
- **`increaseLiquidity`**: Adds more capital to an existing position identified by its `tokenId`. The position's tick range and fee tier remain unchanged.
- **`decreaseLiquidity`**: Removes a specified amount of liquidity from a position. Tokens are not transferred to the owner immediately; they are staged in the position's `tokensOwed0` and `tokensOwed1` fields. This two-step design allows the owner to decide when to withdraw.
- **`collect`**: Transfers the amounts staged in `tokensOwed` (from both `decreaseLiquidity` and accumulated fees) to a specified recipient. The `amount0Max` and `amount1Max` parameters allow partial collection.
- **`burn`**: Destroys the NFT for a position that has been fully exited. The position must have `liquidity == 0` and `tokensOwed0 == tokensOwed1 == 0`; attempting to burn a live or uncollected position reverts.


Project Structure

### Project Structure

The project scaffolding is as follows:

```text
uniswap-v3-periphery-hardhat/
├── contracts/
│   ├── SwapRouter.sol                      # Swap router (single-hop + multi-hop)
│   ├── NonfungiblePositionManager.sol      # LP position NFT manager
│   ├── NonfungibleTokenPositionDescriptor.sol
│   ├── base/                               # Abstract base contracts
│   │   ├── PeripheryImmutableState.sol
│   │   ├── PeripheryPayments.sol
│   │   ├── LiquidityManagement.sol
│   │   ├── ERC721Permit.sol
│   │   ├── PoolInitializer.sol
│   │   └── ...
│   ├── libraries/                          # Pure utility libraries
│   │   ├── PoolAddress.sol
│   │   ├── Path.sol
│   │   ├── LiquidityAmounts.sol
│   │   └── ...
│   ├── interfaces/                         # Contract interfaces
│   │   ├── ISwapRouter.sol
│   │   ├── INonfungiblePositionManager.sol
│   │   └── ...
│   └── test/                              # Test helper contracts
│       ├── WETH9.sol
│       ├── TestERC20.sol
│       ├── MockTimeSwapRouter.sol
│       ├── MockTimeNonfungiblePositionManager.sol
│       └── CoreContracts.sol
├── ignition/
│   └── modules/
│       └── UniswapV3Periphery.ts          # Hardhat Ignition deployment module
├── scripts/
│   └── deploy.ts
├── test/
│   ├── SwapRouter.test.ts                 # Router tests (14 tests)
│   ├── NonfungiblePositionManager.test.ts # NFPM tests (25 tests)
│   └── shared/
│       ├── fixtures.ts
│       └── utilities.ts
├── hardhat.config.ts
├── package.json
├── tsconfig.json
└── README.md
```

Key differences from the V3 Core project are minimal. The Solidity contracts use the same version 0.7.6. The `contracts/libraries/` directory contains periphery-specific math and path-encoding utilities such as `PoolAddress.sol` (deterministic CREATE2 address computation), `Path.sol` (multi-hop path encoding), and `LiquidityAmounts.sol` (token amount to liquidity unit conversion). The `contracts/test/` directory includes `CoreContracts.sol`, an import shim that forces Hardhat to compile `UniswapV3Factory` and `UniswapV3Pool` from `@uniswap/v3-core` so their artifacts are available during tests. The test suite avoids `loadFixture` for compatibility with the Polkadot execution environment, using `beforeEach` with direct fixture calls instead.


Test the Contracts

## Test the Contracts

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

- **`SwapRouter.test.ts`**: 14 tests covering all four swap modes (`exactInputSingle`, `exactInput`, `exactOutputSingle`, `exactOutput`), slippage protection enforcement, recipient routing, `sqrtPriceLimitX96` partial fills, and on-chain pool state changes after swaps.
- **`NonfungiblePositionManager.test.ts`**: 25 tests covering the full LP lifecycle, including pool creation and initialization, minting in-range and out-of-range positions, increasing and decreasing liquidity, fee collection through real swaps, and the complete burn cleanup sequence.

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:

      npx hardhat test --network localNode
      Compiled 112 Solidity files successfully (evm target: istanbul).

      NonfungiblePositionManager
       ✔ constructor — returns correct factory and WETH9 addresses (298ms)

       #createAndInitializePoolIfNecessary
         ✔ creates a new pool and sets the initial sqrtPriceX96 (1234ms)
         ✔ is a no-op when the pool already exists and is initialized (987ms)

       #mint
         ✔ fails if pool does not exist (312ms)
         in-range (full-range) position
           success cases
             ✔ mints exactly one NFT to the recipient (2156ms)
             ✔ decreases token0 and token1 balances of the caller (2087ms)
             ✔ records correct position data in positions() (2243ms)
             ✔ emits IncreaseLiquidity event with positive liquidity (2178ms)
           failure cases
             ✔ reverts when amount0Min slippage is not satisfied (1123ms)
         out-of-range position (above current price)
           ✔ mints a single-sided token0 position — zero token1 consumed (2089ms)
         out-of-range position (below current price)
           ✔ mints a single-sided token1 position — zero token0 consumed (2134ms)

       #increaseLiquidity
         ✔ increases the position liquidity (2345ms)
         ✔ emits IncreaseLiquidity event (2276ms)

       #decreaseLiquidity
         success cases
           ✔ stages removed tokens in tokensOwed (not immediately transferred) (3234ms)
           ✔ can remove all liquidity from a position (3189ms)
           ✔ emits DecreaseLiquidity event (3156ms)
         failure cases
           ✔ reverts when called by a non-owner (2087ms)
           ✔ reverts when requested liquidity exceeds the position (2134ms)

       #collect
         tokensOwed from decreaseLiquidity
           ✔ transfers tokensOwed back to the owner (3567ms)
           ✔ emits Collect event (3489ms)
           ✔ collecting less than the maximum leaves remainder in tokensOwed (3623ms)
         swap fee accumulation
           ✔ LP collects accrued trading fees after swaps through the pool (7891ms)

       #burn
         ✔ burns the NFT after full removal and collection (4234ms)
         ✔ reverts when liquidity has not been fully removed (3123ms)
         ✔ reverts when tokensOwed have not been collected (3087ms)

      SwapRouter
       ✔ constructor — returns correct factory and WETH9 addresses (289ms)

       #exactInputSingle
         success cases
           ✔ swaps token0 for token1 via a single pool (2134ms)
           ✔ respects sqrtPriceLimitX96 and executes a partial fill (2087ms)
           ✔ sends output tokens to the specified recipient, not the caller (2156ms)
         failure cases
           ✔ reverts when output is below amountOutMinimum (1234ms)

       #exactInput
         success cases
           ✔ swaps token0 → token1 → token2 via two pools (3456ms)
         failure cases
           ✔ reverts when output is below amountOutMinimum (2345ms)

       #exactOutputSingle
         success cases
           ✔ buys an exact amount of token1 using token0 (2178ms)
           ✔ spends strictly less than amountInMaximum when pool has sufficient liquidity (2234ms)
         failure cases
           ✔ reverts when amountInMaximum is exceeded (1198ms)

       #exactOutput
         success cases
           ✔ buys an exact amount of token2 using token0 through two pools (3589ms)
         failure cases
           ✔ reverts when amountInMaximum is exceeded (multi-hop) (2456ms)

       pool price impact
         ✔ exactInputSingle moves the pool sqrtPriceX96 (2089ms)
         ✔ exactOutputSingle moves the pool sqrtPriceX96 (2134ms)

      39 passing (2m)

!!! tip
    If the tests fail due to a timeout, ensure your local development node is running and accessible at `http://127.0.0.1:8545`.


Deploy the Contracts

## 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/UniswapV3Periphery.ts` deploys all four contracts: `UniswapV3Factory`, `WETH9`, `SwapRouter`, and `NonfungiblePositionManager`.

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/UniswapV3Periphery.ts --network polkadotTestnet
```

When prompted, confirm the target network name and chain ID. Ignition deploys the contracts in two batches. It first deploys the Factory and WETH9 in parallel, then deploys the `SwapRouter` and `NonfungiblePositionManager` once their dependencies are available. It prints all deployed addresses. The output should look similar to the following:

  npx hardhat ignition deploy ./ignition/modules/UniswapV3Periphery.ts --network polkadotTestnet
  ✔ Confirm deploy to network polkadotTestnet (420420417)? … yes

  Hardhat Ignition 🚀

  Deploying [ UniswapV3PeripheryModule ]

  Batch #1
   Executed UniswapV3PeripheryModule#UniswapV3Factory
   Executed UniswapV3PeripheryModule#WETH9

  Batch #2
   Executed UniswapV3PeripheryModule#SwapRouter
   Executed UniswapV3PeripheryModule#NonfungiblePositionManager

  [ UniswapV3PeripheryModule ] successfully deployed 🚀

  Deployed Addresses

  UniswapV3PeripheryModule#UniswapV3Factory - 0x5FbDB2315678afecb367f032d93F642f64180aa3
  UniswapV3PeripheryModule#WETH9 - 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  UniswapV3PeripheryModule#SwapRouter - 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
  UniswapV3PeripheryModule#NonfungiblePositionManager - 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9


Where to Go Next

## Where to Go Next

-   Tutorial __Uniswap V3 Core__

    ---

    Deploy the Uniswap V3 Factory and Pool contracts on Polkadot Hub to understand the concentrated liquidity engine that the Periphery builds on.

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

-   Guide __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/)

-   Guide __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/)


Introduction

## Introduction

This guide demonstrates how to deploy a basic Solidity smart contract to Polkadot Hub TestNet using [Hardhat](https://hardhat.org/), which provides a comprehensive development environment with built-in testing, debugging, and deployment capabilities. It's ideal for professional development workflows and team projects.


Prerequisites

## Prerequisites

Before you begin, ensure you have the following:

- A basic understanding of [Solidity](https://www.soliditylang.org/) programming.
- [Node.js](https://nodejs.org/en/download) v22.13.1 or later installed.
- Test tokens for gas fees, available 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.


Set Up Your Project

## Set Up Your Project

Use the following terminal commands to create a directory and initialize your Hardhat project inside of it:

```bash
mkdir hardhat-deployment
cd hardhat-deployment
npx hardhat@^2.27.0 init
```


Configure Hardhat

## Configure Hardhat

Open `hardhat.config.ts` and update to add `polkadotTestnet` to the `networks` configuration as highlighted in the following example code:

```typescript title='hardhat.config.ts' hl_lines='19-23'
import type { HardhatUserConfig } from 'hardhat/config';

import hardhatToolboxViemPlugin from '@nomicfoundation/hardhat-toolbox-viem';
import { vars } from 'hardhat/config';

const config: HardhatUserConfig = {
  plugins: [hardhatToolboxViemPlugin],
  solidity: {
    version: '0.8.28',
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  networks: {
    polkadotTestnet: {
      url: 'https://services.polkadothub-rpc.com/testnet',
      chainId: 420420417,
      accounts: [vars.get('PRIVATE_KEY')],
    },
  },
};

export default config;
```

!!! tip
    Visit the Hardhat [Configuration variables](https://v2.hardhat.org/hardhat-runner/docs/guides/configuration-variables) documentation to learn how to use Hardhat to handle your private keys securely.


Create the Contract

## Create the Contract

Follow these steps to create your smart contract:

1. Delete the default contract file(s) in the `contracts` directory.

2. Create a new file named `Storage.sol` inside the `contracts` directory.

3. Add the following code to create the `Storage.sol` smart contract:

    ```solidity title="Storage.sol"
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.9;

    contract Storage {
        uint256 private storedNumber;

        function store(uint256 num) public {
            storedNumber = num;
        }

        function retrieve() public view returns (uint256)
    }
    ```


Compile the Contract

## Compile the Contract

Compile your `Storage.sol` contract using the following command:

```bash
npx hardhat compile
```

You will see a message in the terminal confirming the contract was successfully compiled, similar to the following:

  npx hardhat compile
  Downloading solc 0.8.28
  Downloading solc 0.8.28 (WASM build)
  Compiled 1 Solidity file with solc 0.8.28 (evm target: cancun)


Deploy the Contract

## Deploy the Contract

You are now ready to deploy the contract to your chosen network. This example demonstrates deployment to the Polkadot TestNet. Deploy the contract as follows:

1. Delete the default file(s) inside the `ignition/modules` directory.

2. Create a new file named `Storage.ts` inside the `ignition/modules` directory.

3. Open `ignition/modules/Storage.ts` and add the following code to create your deployment module:

    ```typescript title="ignition/modules/Storage.ts"
    import { buildModule } from '@nomicfoundation/hardhat-ignition/modules';

    export default buildModule('StorageModule', (m) => {
      const storage = m.contract('Storage');
      return { storage };
    });
    ```

4. Deploy your contract to Polkadot Hub TestNet using the following command:

    ```bash
    npx hardhat ignition deploy ignition/modules/Storage.ts --network polkadotTestnet
    ```

5. Confirm the target deployment network name and chain ID when prompted:

      npx hardhat ignition deploy ignition/modules/Storage.ts --network polkadotTestnet
      ✔ Confirm deploy to network polkadotTestnet (420420417)? … yes

      Hardhat Ignition 🚀

      Deploying [ StorageModule ]

      [ StorageModule ] successfully deployed 🚀

      Deployed Addresses

      Storage - 0x12345.....

Congratulations! You've now deployed a basic smart contract to Polkadot Hub TestNet using Hardhat. Consider the following resources to build upon your progress.


Where to Go Next

## Where to Go Next

-   Guide __Deploy an ERC-20__

    ---

    Walk through deploying a fully-functional ERC-20 to the Polkadot Hub using Hardhat.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-hardhat/)

-   Guide __Deploy an NFT__

    ---

    Walk through deploying an NFT to the Polkadot Hub using Hardhat.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-hardhat/)


Introduction

## Introduction

This guide demonstrates how to deploy a basic Solidity smart contract to Polkadot Hub using [Remix IDE](https://remix.ethereum.org/), which offers a visual, browser-based environment perfect for rapid prototyping and learning. It requires no local installation and provides an intuitive interface for contract development.


Prerequisites

## Prerequisites

Before you begin, ensure you have the following:

- A basic understanding of [Solidity](https://www.soliditylang.org/) programming.
- An EVM-compatible [wallet](/smart-contracts/connect/) connected to Polkadot Hub. This example utilizes [MetaMask](https://metamask.io/).
- Test tokens for gas fees, available 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.


Locate Your Contract

## Locate Your Contract

This guide uses a default contract contract provided by Remix IDE. Follow these steps to locate the contract in Remix:

1. Navigate to [Remix IDE](https://remix.ethereum.org/) in your web browser.

2. Once the default workspace loads, locate the `contracts` folder. Inside `contracts`, locate the `Storage.sol` file which you will use as your sample contract throughout this guide.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-basic/basic-remix/remix-01.webp)


Compile the Contract

## Compile the Contract

Solidity source code compiles into bytecode that can be deployed on the blockchain. During this process, the compiler checks the contract for syntax errors, ensures type safety, and generates the machine-readable instructions needed for blockchain execution.

Ensure your `Storage.sol` contract is open in the Remix IDE editor, and use the following steps to compile:

1. Select the **Solidity Compiler** plugin from the left panel.
2. Select the **Compile Storage.sol** button.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-basic/basic-remix/remix-02.webp)

The **Solidity Compiler** icon will display a green checkmark once the contract compiles successfully. If any issues arise during contract compilation, errors and warnings will appear in the terminal panel at the bottom of the screen.


Deploy the Contract

## Deploy the Contract

Follow these steps to deploy the contract using Remix:

1. Select **Deploy & Run Transactions** from the left panel.
2. Ensure your MetaMask wallet is connected to Polkadot Hub TestNet, then select the **Environment** dropdown and select **Injected Provider - MetaMask**.
3. Select the **Deploy** button to initiate the deployment.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-basic/basic-remix/remix-03.webp)

When prompted, approve the transaction in your MetaMask wallet. After the deployment succeeds, the terminal will display the transaction details, including the contract address and transaction hash, and your contract will appear in the **Deployed Contracts** section.

Congratulations! You've successfully deployed a basic smart contract to Polkadot Hub TestNet using Remix IDE. Consider the following resources to build upon your progress.


Where to Go Next

## Where to Go Next

-   Guide __Deploy an ERC-20__

    ---

    Walk through deploying a fully-functional ERC-20 to Polkadot Hub using Remix.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-remix/)

-   Guide __Deploy an NFT__

    ---

    Walk through deploying an NFT to Polkadot Hub using Remix.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-remix/)


Introduction

## Introduction

[ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens are fungible tokens commonly used for creating cryptocurrencies, governance tokens, and staking mechanisms. Polkadot Hub enables easy deployment of ERC-20 tokens via Ethereum-compatible smart contracts and tools.

This guide demonstrates how to deploy an ERC-20 contract on Polkadot Hub TestNet using [Hardhat](https://hardhat.org/), an Ethereum development environment. The ERC-20 contract can be retrieved from OpenZeppelin's [GitHub repository](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v5.6.1/contracts/token/ERC20) or generated with the [OpenZeppelin Contracts Wizard for Polkadot](https://wizard.openzeppelin.com/polkadot).


Prerequisites

## Prerequisites

Before you begin, ensure you have the following:

- A basic understanding of [Solidity](https://www.soliditylang.org/) programming and [ERC-20](https://ethereum.org/developers/docs/standards/tokens/erc-20/) fungible tokens.
- [Node.js](https://nodejs.org/en/download) v22.13.1 or later installed.
- Test tokens for gas fees, available 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.


Set Up Your Project

## Set Up Your Project

This tutorial uses a [Hardhat ERC-20 template](https://github.com/polkadot-developers/revm-hardhat-examples/tree/master/erc20-hardhat) that contains all the necessary files.

To get started, take the following steps:

1. Clone the GitHub repository locally:

    ```bash
    git clone https://github.com/polkadot-developers/revm-hardhat-examples/
    cd revm-hardhat-examples/erc20-hardhat
    ```

2. Install the dependencies using the following command:

    ```bash
    npm i
    ```

    This command will fetch all the necessary packages to help you use Hardhat to deploy an ERC-20 to Polkadot.


Configure Hardhat

## Configure Hardhat

If you started with the cloned Hardhat ERC-20 template, `hardhat.config.ts` is already configured to deploy to the Polkadot TestNet as shown in the example below:

```ts title="hardhat.config.ts" hl_lines="14-19"
import { HardhatUserConfig, vars } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.28",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  networks: {
    polkadotTestnet: {
      url: "https://services.polkadothub-rpc.com/testnet",
      accounts: vars.has("TESTNET_PRIVATE_KEY") ? [vars.get("TESTNET_PRIVATE_KEY")] : [],
    },
  },
  mocha: {
    timeout: 40000,
  },
};

export default config;
```

!!! tip
    Visit the Hardhat [Configuration variables](https://v2.hardhat.org/hardhat-runner/docs/guides/configuration-variables) documentation to learn how to use Hardhat to handle your private keys securely.


Compile the Contract

## Compile the Contract

Next, compile the contract included with the template by running the following command:

```bash
npx hardhat compile
```

If everything compiles successfully, you will see output similar to the following:

  npx hardhat compile
  Generating typings for: 23 artifacts in dir: typechain-types for target: ethers-v6
  Successfully generated 62 typings!
  Compiled 21 Solidity files successfully (evm target: paris).


Test the Contract

## Test the Contract

You can view the predefined test file at [`test/MyToken.test.ts`](https://github.com/polkadot-developers/revm-hardhat-examples/blob/master/erc20-hardhat/test/MyToken.test.ts). This example test includes verification of the following:

- The token name and symbol exist (confirms deployment) and are correct.
- The token owner is correctly configured.
- The initial token supply is zero.
- The owner can mint tokens.
- The total supply increases after a mint.
- Successful mints to different test addresses with expected account balance and total supply changes.

Run the tests using the following command:

```bash
npx hardhat test --network polkadotTestnet
```

If tests are successful, you will see outputs similar to the following:

  npx hardhat test --network polkadotTestnet

    MyToken
      Deployment
        ✔ Should have correct name and symbol
        ✔ Should set the right owner
        ✔ Should have zero initial supply
      Minting
        ✔ Should allow owner to mint tokens
        ✔ Should increase total supply on mint
      Multiple mints
        ✔ Should correctly track balance after multiple mints

    6 passing (369ms)


Deploy the Contract

## Deploy the Contract

You are now ready to deploy the contract to your chosen network. This example demonstrates deployment to the Polkadot TestNet. Deploy the contract as follows:

1. Run the following command in your terminal:

    ```bash
    npx hardhat ignition deploy ./ignition/modules/MyToken.ts --network polkadotTestnet
    ```

2. Confirm the target deployment network name and chain ID when prompted:

      npx hardhat ignition deploy ./ignition/modules/MyToken.ts --network polkadotTestnet
      ✔ Confirm deploy to network polkadotTestnet (420420417)? … yes

      Hardhat Ignition 🚀

      Deploying [ TokenModule ]

      Batch #1
       Executed TokenModule#MyToken

      Batch #2
       Executed TokenModule#MyToken.mint

      [ TokenModule ] successfully deployed 🚀

      Deployed Addresses

      TokenModule#MyToken - 0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3

Congratulations! You've successfully deployed an ERC-20 token contract to Polkadot Hub TestNet using Hardhat. Consider the following resources to build upon your progress.


Where to Go Next

## Where to Go Next

-   Guide __Deploy an NFT__

    ---

    Walk through deploying an NFT to the Polkadot Hub using Hardhat.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-hardhat/)

-   Guide __Create a DApp__

    ---

    Learn step-by-step how to build a fully functional dApp that interacts with a smart contract deployed via Hardhat.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/dapps/zero-to-hero/)


Introduction

## Introduction

[ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens are fungible tokens commonly used for creating cryptocurrencies, governance tokens, and staking mechanisms. Polkadot Hub enables easy token deployment with Ethereum-compatible smart contracts and tools via the EVM backend.

This tutorial covers deploying an ERC-20 contract on Polkadot Hub TestNet using [Remix IDE](https://remix.ethereum.org/), a web-based development tool. The ERC-20 contract can be retrieved from OpenZeppelin's [GitHub repository](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v5.6.1/contracts/token/ERC20) or generated with the [OpenZeppelin Contracts Wizard for Polkadot](https://wizard.openzeppelin.com/polkadot).


Prerequisites

## Prerequisites

Before you begin, ensure you have:

- A basic understanding of [Solidity](https://www.soliditylang.org/) programming and [ERC-20](https://ethereum.org/developers/docs/standards/tokens/erc-20/) fungible tokens.
- An EVM-compatible [wallet](/smart-contracts/connect/) connected to Polkadot Hub. This example utilizes [MetaMask](https://metamask.io/).
- Test tokens for gas fees, available 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.


Create Your Contract

## Create Your Contract

Follow the steps below to create the ERC-20 contract:

1. Navigate to [Remix IDE](https://remix.ethereum.org/) in your web browser.
2. Select the **Create new file** button under the **contracts** folder, and name your contract `MyToken.sol`.

    ![](/images/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-remix/remix-01.webp)

3. Now, paste the following ERC-20 contract code into `MyToken.sol`:

    ```solidity title="MyToken.sol"
    // SPDX-License-Identifier: MIT
    // Compatible with OpenZeppelin Contracts ^5.4.0
    pragma solidity ^0.8.27;

    import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
    import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

    contract MyToken is ERC20, Ownable, ERC20Permit {
        constructor(address initialOwner)
            ERC20("MyToken", "MTK")
            Ownable(initialOwner)
            ERC20Permit("MyToken")
        {}

        function mint(address to, uint256 amount) public onlyOwner {
            _mint(to, amount);
        }
    }
    ```

    !!! tip
        The [OpenZeppelin Contracts Wizard for Polkadot](https://wizard.openzeppelin.com/polkadot) was used to generate this example ERC-20 contract. Use it to customize and generate your own ERC-20, ERC-721, or other OpenZeppelin-standard contracts for Polkadot Hub.


Compile the Contract

## Compile the Contract

Solidity source code compiles into bytecode that can be deployed on the blockchain. During this process, the compiler checks the contract for syntax errors, ensures type safety, and generates the machine-readable instructions needed for blockchain execution.

Ensure your `MyToken.sol` contract is open in the Remix IDE Editor, and use the following steps to compile:

1. Select the **Solidity Compiler** plugin from the left panel.
2. Select the **Compile MyToken.sol** button.

The **Solidity Compiler** icon will display a green checkmark once the contract compiles successfully. If any issues arise during contract compilation, errors and warnings will appear in the terminal panel at the bottom of the screen.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-remix/remix-03.gif)


Deploy the Contract

## Deploy the Contract

Follow these steps to deploy the contract using Remix:

1. Select **Deploy & Run Transactions** from the left panel.
2. Ensure your MetaMask wallet is connected to Polkadot Hub TestNet, then select the **Environment** dropdown and select **Injected Provider - MetaMask**.
3. Configure the contract parameters by entering the address that will own the deployed token contract.
4. Select the **Deploy** button to initiate the deployment.
4. Approve the transaction in your MetaMask wallet when prompted.
6. You will see the transaction details in the terminal when the deployment succeeds, including the contract address and deployment transaction hash.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-remix/remix-04.gif)

Once successfully deployed, your contract will appear in the **Deployed Contracts** section, ready for interaction.


Interact with the Contract

## Interact with the Contract

Once deployed, you can interact with your contract through Remix. Find your contract under **Deployed/Unpinned Contracts**, and select it to expand the available methods. In this example, you'll mint some tokens to a given address using the following steps:

1. Expand the **mint** function, then enter the recipient address and the amount (remember to add 18 zeros for one whole token).
2. Select **transact**.
3. Approve the transaction in your MetaMask wallet when prompted.
4. You will see a green check mark in the terminal when the transaction is successful.
5. You can also call the **balanceOf** function by passing the address of the **mint** call to confirm the new balance.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-remix/remix-05.gif)

Feel free to explore and interact with the contract's other functions by selecting the method, providing any required parameters, and confirming the transaction in MetaMask when prompted.

Congratulations! You've successfully deployed an ERC-20 token contract to Polkadot Hub TestNet using Remix IDE. Consider the following resources to build upon your progress.


Where to Go Next

## Where to Go Next

-   Guide __Deploy an NFT with Remix__

    ---

    Walk through deploying an ERC-721 Non-Fungible Token (NFT) using OpenZeppelin's battle-tested NFT implementation and Remix.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-remix/)


Introduction

## Introduction

Non-Fungible Tokens (NFTs) represent unique digital assets commonly used for digital art, collectibles, gaming, and identity verification.

This guide demonstrates how to deploy an [ERC-721](https://eips.ethereum.org/EIPS/eip-721) NFT contract to [Polkadot Hub](/smart-contracts/overview/#smart-contract-development) TestNet. You'll use OpenZeppelin's battle-tested [NFT implementation](https://github.com/OpenZeppelin/openzeppelin-contracts) and [Hardhat](https://hardhat.org/docs/getting-started), a comprehensive development environment with built-in testing, debugging, and deployment capabilities. You can generate a custom NFT contract with the [OpenZeppelin Contracts Wizard for Polkadot](https://wizard.openzeppelin.com/polkadot), then use it in this Hardhat workflow. Hardhat uses standard Solidity compilation to generate EVM bytecode, making it fully compatible with Polkadot Hub's EVM environment.


Prerequisites

## Prerequisites

Before you begin, ensure you have the following:

- A basic understanding of [Solidity](https://www.soliditylang.org/) programming and [ERC-721](https://ethereum.org/developers/docs/standards/tokens/erc-721/) non-fungible tokens.
- [Node.js](https://nodejs.org/en/download) v22.13.1 or later installed.
- Test tokens for gas fees, available 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.


Set Up Your Project

## Set Up Your Project

1. Use the following terminal commands to create a directory and initialize your Hardhat project inside of it:

    ```bash
    mkdir hardhat-nft-deployment
    cd hardhat-nft-deployment
    npx hardhat@^2.27.0 init
    ```

2. Install the OpenZeppelin contract dependencies using the command:

    ```bash
    npm install @openzeppelin/contracts
    ```


Configure Hardhat

## Configure Hardhat

Open `hardhat.config.ts` and update to add `polkadotTestnet` to the `networks` configuration as highlighted in the following example code:

```typescript title="hardhat.config.ts" hl_lines='18-23'
import type { HardhatUserConfig } from 'hardhat/config';

import hardhatToolboxViemPlugin from '@nomicfoundation/hardhat-toolbox-viem';
import { vars } from 'hardhat/config';

const config: HardhatUserConfig = {
  plugins: [hardhatToolboxViemPlugin],
  solidity: {
    version: '0.8.28',
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  networks: {
    polkadotTestnet: {
      url: 'https://services.polkadothub-rpc.com/testnet',
      chainId: 420420417,
      accounts: [vars.get('PRIVATE_KEY')],
    },
  },
};

export default config;
```

!!! tip
    Visit the Hardhat [Configuration variables](https://v2.hardhat.org/hardhat-runner/docs/guides/configuration-variables) documentation to learn how to use Hardhat to handle your private keys securely.


Create the Contract

## Create the Contract

Follow these steps to create your smart contract:

1. Delete the default contract file(s) in the `contracts` directory.

2. Create a new file named `MyNFT.sol` inside the `contracts` directory.

3. Add the following code to create the `MyNFT.sol` smart contract:
    ```solidity title="contracts/MyNFT.sol"
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;

    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";

    contract MyNFT is ERC721, Ownable {
        uint256 private _nextTokenId;

        constructor(
            address initialOwner
        ) ERC721("MyToken", "MTK") Ownable(initialOwner) {}

        function safeMint(address to) public onlyOwner {
            uint256 tokenId = _nextTokenId++;
            _safeMint(to, tokenId);
        }
    }
    ```


Compile the Contract

## Compile the Contract

Compile your `MyNFT.sol` contract using the following command:

```bash
npx hardhat compile
```

You will see a message in the terminal confirming the contract was successfully compiled, similar to the following:

  npx hardhat compile
  Downloading solc 0.8.28
  Downloading solc 0.8.28 (WASM build)
  Compiled 1 Solidity file with solc 0.8.28 (evm target: cancun)


Deploy the Contract

## Deploy the Contract

You are now ready to deploy the contract to your chosen network. This example demonstrates deployment to the Polkadot TestNet. Deploy the contract as follows:

1. Delete the default file(s) inside the `ignition/modules` directory.

2. Create a new file named `MyNFT.ts` inside the `ignition/modules` directory.

3. Open `ignition/modules/MyNFT.ts` and add the following code to create your deployment module:
    ```typescript title="ignition/modules/MyNFT.ts"
    import { buildModule } from '@nomicfoundation/hardhat-ignition/modules';

    export default buildModule('MyNFTModule', (m) => {
      const initialOwner = m.getParameter('initialOwner', 'INSERT_OWNER_ADDRESS');
      const myNFT = m.contract('MyNFT', [initialOwner]);
      return { myNFT };
    });
    ```

    Replace `INSERT_OWNER_ADDRESS` with your desired owner address.

4. Deploy your contract to Polkadot Hub TestNet using the following command:

    ```bash
    npx hardhat ignition deploy ignition/modules/MyNFT.ts --network polkadotTestnet
    ```

5. Confirm the target deployment network name and chain ID when prompted:

      npx hardhat ignition deploy ignition/modules/MyNFT.ts --network polkadotHubTestnet
      ✔ Confirm deploy to network polkadotTestnet (420420417)? … yes

      Hardhat Ignition 🚀

      Deploying [ MyNFTModule ]

      Batch #1
       Executed MyNFTModule#MyNFT

      Batch #2
       Executed MyNFTModule#MyNFT.safeMint

      [ TokenModule ] successfully deployed 🚀

      Deployed Addresses

      MyNFTModule#MyNFT - 0x1234.......

Congratulations! You've successfully deployed an ERC-721 NFT contract to Polkadot Hub TestNet using Hardhat. Consider the following resources to build upon your progress.


Where to Go Next

## Where to Go Next

-   Guide __Deploy an ERC-20__

    ---

    Walk through deploying a fully-functional ERC-20 to Polkadot Hub using Hardhat.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-hardhat/)

-   Guide __Create a DApp__

    ---

    Learn step-by-step how to build a fully functional dApp that interacts with a smart contract deployed via Hardhat.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/dapps/zero-to-hero/)


Introduction

## Introduction

Non-Fungible Tokens (NFTs) represent unique digital assets commonly used for digital art, collectibles, gaming, and identity verification.

This guide demonstrates how to deploy an [ERC-721](https://eips.ethereum.org/EIPS/eip-721) NFT contract to [Polkadot Hub](/smart-contracts/overview/#smart-contract-development). You'll use [OpenZeppelin's battle-tested NFT implementation](https://github.com/OpenZeppelin/openzeppelin-contracts) and [Remix](https://remix.ethereum.org/), a visual, browser-based environment perfect for rapid prototyping and learning. It requires no local installation and provides an intuitive interface for contract development.


Prerequisites

## Prerequisites

- A basic understanding of [Solidity](https://www.soliditylang.org/) programming and [ERC-721 NFT](https://ethereum.org/developers/docs/standards/tokens/erc-721/) standards.
- An EVM-compatible [wallet](/smart-contracts/connect/) connected to Polkadot Hub. This example utilizes [MetaMask](https://metamask.io/).
- Test tokens for gas fees (available 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.


Create Your Contract

## Create Your Contract

Follow the steps below to create the ERC-721 contract:

1. Navigate to [Remix IDE](https://remix.ethereum.org/) in your web browser.
2. Select the **Create new file** button under the **contracts** folder, and name your contract `MyNFT.sol`.

    ![](/images/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-remix/remix-01.webp)

3. Now, paste the following ERC-721 contract code into `MyNFT.sol`:

    ```solidity title="contracts/MyNFT.sol"
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;

    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";

    contract MyNFT is ERC721, Ownable {
        uint256 private _nextTokenId;

        constructor(
            address initialOwner
        ) ERC721("MyToken", "MTK") Ownable(initialOwner) {}

        function safeMint(address to) public onlyOwner {
            uint256 tokenId = _nextTokenId++;
            _safeMint(to, tokenId);
        }
    }
    ```

    !!! tip
        The [OpenZeppelin Contracts Wizard for Polkadot](https://wizard.openzeppelin.com/polkadot) was used to generate this example ERC-721 contract. Use it to customize and generate your own ERC-20, ERC-721, or other OpenZeppelin-standard contracts for Polkadot Hub.


Compile the Contract

## Compile the Contract

Solidity source code compiles into bytecode that can be deployed on the blockchain. During this process, the compiler checks the contract for syntax errors, ensures type safety, and generates the machine-readable instructions needed for blockchain execution.

Ensure your `MyNFT.sol` contract is open in the Remix IDE Editor, and use the following steps to compile:

1. Select the **Solidity Compiler** plugin from the left panel.
2. Select the **Compile MyToken.sol** button.

The **Solidity Compiler** icon will display a green checkmark once the contract compiles successfully. If any issues arise during contract compilation, errors and warnings will appear in the terminal panel at the bottom of the screen.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-remix/remix-02.webp)


Deploy the Contract

## Deploy the Contract

Follow these steps to deploy the contract using Remix:

1. Select **Deploy & Run Transactions** from the left panel.
2. Ensure your MetaMask wallet is connected to Polkadot Hub TestNet, then select the **Environment** dropdown and select **Injected Provider - MetaMask**.

    ![](/images/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-remix/remix-03.webp)

3. Configure the contract parameters by entering the address that will own the deployed NFT contract.
4. Select the **Deploy** button to initiate the deployment.
5. Approve the transaction in your MetaMask wallet when prompted.
6. You will see the transaction details in the terminal when the deployment succeeds, including the contract address and deployment transaction hash.

![](/images/smart-contracts/cookbook/smart-contracts/deploy-nft/nft-remix/remix-04.webp)

Once successfully deployed, your contract will appear in the **Deployed Contracts** section, ready for interaction.

Congratulations! You've successfully deployed an ERC-721 NFT contract to Polkadot Hub TestNet using Remix IDE. Consider the following resources to build upon your progress.


Where to Go Next

## Where to Go Next

-   Guide __Deploy an ERC-20__

    ---

    Walk through deploying a fully-functional ERC-20 to Polkadot Hub using Remix.

    [:octicons-arrow-right-24: Get Started](/smart-contracts/cookbook/smart-contracts/deploy-erc20/erc20-remix/)


Introduction

## Introduction

[Foundry](https://www.getfoundry.sh/) is a blazing fast, portable, and modular toolkit for Ethereum application development written in Rust. It consists of:

- **Forge**: Command-line tool to test, build, and deploy smart contracts
- **Cast**: Swiss army knife for interacting with contracts, sending transactions, and getting chain data
- **Anvil**: Local Ethereum node for development and testing
- **Chisel**: Solidity REPL for testing code snippets

This page demonstrates how to set up Foundry to work with Polkadot Hub, including installation, compilation, deployment, and verification.

!!! info "Limitations when using the built-in test framework"
    **`forge test`** runs against standard **Anvil** (Ethereum node), not a Polkadot node. Anvil does not enforce [Existential Deposit](/smart-contracts/for-eth-devs/evm-vs-pvm/#account-management-existential-deposit), and balances, timestamps, block numbers, and runtime behavior (gas, precompiles) follow Ethereum semantics—so tests that pass locally may behave differently on Polkadot. Use `forge test` for unit tests; for Polkadot-specific behavior, run against a [local dev node](/smart-contracts/dev-environments/local-dev-node/) or TestNet. See [Differences between Ethereum-native tools and Polkadot EVM networks](/smart-contracts/get-started/#differences-between-ethereum-native-tools-and-polkadot-evm-networks) for more.

!!! tip "Native Polkadot Support"
    Foundry's nightly build includes native support for Polkadot chains, allowing you to use `--chain polkadot-testnet`, `--chain polkadot`, or `--chain kusama` without manually specifying RPC URLs. This makes development more streamlined and reduces configuration.


Prerequisites

## Prerequisites

Before setting up Foundry, make sure you have:

- A Unix-based operating system (Linux or macOS) or Windows with [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
- [Git](https://git-scm.com/) installed
- A funded account on Polkadot Hub (see the [Connect to Polkadot](/smart-contracts/connect/) guide to get TestNet tokens)


Install Foundry

## Install Foundry

!!! warning "Use Nightly Build"
    To use Foundry with Polkadot Hub, you must install the **nightly build** instead of the stable release. The nightly build includes features required for contract verification on Polkadot Hub.

To install the Foundry nightly build, run:

```bash
curl -L https://foundry.paradigm.xyz | bash
```

This installs `foundryup`, the Foundry toolchain installer. Then, run:

```bash
foundryup --version nightly
```

This installs the latest nightly versions of `forge`, `cast`, `anvil`, and `chisel`.

To verify the installation:

    forge --version

    forge Version: 1.6.0-nightly
    Commit SHA: 8b4f318e6a3e83a06dc4e989b9aba87894dca88e
    Build Timestamp: 2026-01-28T06:06:35.086737000Z (1769580395)
    Build Profile: dist


Initialize a Foundry Project

## Initialize a Foundry Project

Create a new Foundry project:

```bash
forge init my-foundry-project
cd my-foundry-project
```

This creates a new directory with the following structure:

- **`src/`**: Contains your Solidity smart contracts
- **`script/`**: Holds deployment scripts
- **`test/`**: Contains test files written in Solidity
- **`lib/`**: Stores project dependencies
- **`foundry.toml`**: Configuration file for Foundry settings

A sample `Counter.sol` contract will be created in the `src/` directory along with a corresponding test file.


Compile Contracts

## Compile Contracts

To compile your contracts, run:

```bash
forge build
```

You should see output similar to:

    forge build

    [⠊] Compiling...
    [⠒] Compiling 3 files with Solc 0.8.28
    [⠢] Solc 0.8.28 finished in 1.23s
    Compiler run successful!

Forge compiles all contracts in the `src/` directory and outputs the artifacts to the `out/` directory.


Configure Foundry for Polkadot Hub

## Configure Foundry for Polkadot Hub

Foundry's nightly build includes native support for Polkadot chains with `polkadot-testnet`, `polkadot`, and `kusama` as recognized chains with default RPC endpoints.

Create or modify your `foundry.toml` file:

!!! note
    The `[etherscan]` section is used for contract verification. Routescan requires an API key; Blockscout does not.

=== "Blockscout"

    ```toml title='foundry.toml'
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]
    solc_version = "0.8.28"

    [etherscan] # Contract verification configuration
    polkadot-testnet = { key = "", url = "https://blockscout-testnet.polkadot.io/api?" }
    ```

=== "Routescan"

    ```toml title='foundry.toml'
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]
    solc_version = "0.8.28"

    [etherscan] # Contract verification configuration
    polkadot-testnet = { key = "verifyContract", url = "https://api.routescan.io/v2/network/testnet/evm/420420417/etherscan" }
    ```

With this configuration, you can use `--chain polkadot-testnet` in your commands without specifying the RPC URL explicitly.


Available Networks and RPC Endpoints

### Available Networks and RPC Endpoints

Foundry's nightly build supports three Polkadot chains:

|       Network        |         Chain Flag         |             Built-in RPC Endpoint              |  Chain ID   |
| :------------------: | :------------------------: | :--------------------------------------------: | :---------: |
| **Polkadot TestNet** | `--chain polkadot-testnet` | `https://services.polkadothub-rpc.com/testnet` | `420420417` |
|     **Polkadot**     |     `--chain polkadot`     | `https://services.polkadothub-rpc.com/mainnet` | `420420419` |
|      **Kusama**      |      `--chain kusama`      | `https://kusama-asset-hub-eth-rpc.polkadot.io` | `420420418` |


Deploy a Contract

## Deploy a Contract


Set Up Environment Variables

### Set Up Environment Variables

Create a `.env` file in your project root (make sure to add it to `.gitignore`):

```text title='.env'
PRIVATE_KEY=INSERT_PRIVATE_KEY_HERE
```

!!! warning "Never commit your private key"
    Replace `INSERT_PRIVATE_KEY_HERE` with your actual private key. Always keep your `.env` file in `.gitignore` to prevent accidentally committing sensitive information to version control.

Load the environment variables:

```bash
source .env
```


Deploy Using Forge

### Deploy Using Forge

To deploy a contract to Polkadot Hub TestNet:

```bash
forge create src/Counter.sol:Counter \
    --chain polkadot-testnet \
    --rpc-url https://services.polkadothub-rpc.com/testnet \
    --private-key $PRIVATE_KEY \
    --broadcast
```

You'll see output with the deployed contract address:

    forge create src/Counter.sol:Counter \

    --chain polkadot-testnet
    --rpc-url https://services.polkadothub-rpc.com/testnet
    --private-key $PRIVATE_KEY
    --broadcast
    [⠊] Compiling...
    No files changed, compilation skipped
    Deployer: 0x3427D90f1Ee5c5D3627c2EBb37f90393526066fd
    Deployed to: 0xF1fbAf96A16458A512A33b31c4414C4a81f50EF4
    Transaction hash: 0x1cba7b61c771192b297024766bed8b6e607f218e12899739fe61a3eed2690779


Deploy Using Scripts

### Deploy Using Scripts

For more complex deployments, create a deployment script in the `script/` directory:

```solidity title='script/Counter.s.sol'
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Script} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";

contract CounterScript is Script {
    function run() external {
        // Get the deployer's private key from environment
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

        // Start broadcasting transactions
        vm.startBroadcast(deployerPrivateKey);

        // Deploy the contract
        Counter counter = new Counter();

        // Stop broadcasting
        vm.stopBroadcast();
    }
}
```

Run the deployment script:

```bash
forge script script/Counter.s.sol:CounterScript \
    --chain polkadot-testnet \
    --broadcast
```

The `--broadcast` flag tells Forge to submit the transactions to the network.


Verify a Contract

## Verify a Contract

To verify your deployed contract on Polkadot Hub, use the verification feature with the Polkadot Hub explorer verifier.


Basic Verification

### Basic Verification

=== "Blockscout"

    ```bash
    forge verify-contract INSERT_CONTRACT_ADDRESS \
        src/Counter.sol:Counter \
        --chain polkadot-testnet
    ```

    Blockscout does not require an API key. With Foundry's native Polkadot support, `--verifier` and `--verifier-url` are not needed.

    You should see output similar to:

        forge verify-contract 0xc29C4ebCdd1c49bc77362A13Ab921f22005828A0 \
        src/Counter.sol:Counter \

        --chain polkadot-testnet
        Start verifying contract `0xc29C4ebCdd1c49bc77362A13Ab921f22005828A0` deployed on polkadot-testnet
        Submitting verification for [src/Counter.sol:Counter] 0xc29C4ebCdd1c49bc77362A13Ab921f22005828A0.
        Submitted contract for verification:
        	Response: `OK`
        	GUID: `c29c4ebcdd1c49bc77362a13ab921f22005828a0698f2622`
        	URL: https://blockscout-testnet.polkadot.io/address/0xc29c4ebcdd1c49bc77362a13ab921f22005828a0

=== "Routescan"

    ```bash
    forge verify-contract INSERT_CONTRACT_ADDRESS \
        src/Counter.sol:Counter \
        --verifier-url 'https://api.routescan.io/v2/network/testnet/evm/420420417/etherscan' \
        --etherscan-api-key "verifyContract" \
        --chain polkadot-testnet
    ```

    The Routescan API v2 structure is `https://api.routescan.io/v2/network/{testnet|mainnet}/evm/{CHAIN_ID}/etherscan`. The `--verifier-url` is the URL of the Polkadot Hub explorer verifier.

    You should see output similar to:

        forge verify-contract 0xF1fbAf96A16458A512A33b31c4414C4a81f50EF4 \
        src/Counter.sol:Counter \

        --verifier-url 'https://api.routescan.io/v2/network/testnet/evm/420420417/etherscan'
        --etherscan-api-key "verifyContract"
        --chain polkadot-testnet
        Start verifying contract `0xF1fbAf96A16458A512A33b31c4414C4a81f50EF4` deployed on polkadot-testnet
        Submitting verification for [src/Counter.sol:Counter] 0xF1fbAf96A16458A512A33b31c4414C4a81f50EF4.
        Submitted contract for verification:
        	Response: `OK`
        	GUID: `71d14e5b-eda1-5e85-98c5-2faf93306526`
        	URL: https://polkadot.testnet.routescan.io/address/0xf1fbaf96a16458a512a33b31c4414c4a81f50ef4

Replace `INSERT_CONTRACT_ADDRESS` with your deployed contract's address.


Verification with Constructor Arguments

### Verification with Constructor Arguments

For contracts with constructor arguments:

=== "Blockscout"

    ```bash
    forge verify-contract INSERT_CONTRACT_ADDRESS \
        src/Counter.sol:Counter \
        --chain polkadot-testnet \
        --constructor-args $(cast abi-encode "constructor(uint256,address)" 42 INSERT_DEPLOYER_ADDRESS)
    ```

=== "Routescan"

    ```bash
    forge verify-contract INSERT_CONTRACT_ADDRESS \
        src/Counter.sol:Counter \
        --verifier-url 'https://api.routescan.io/v2/network/testnet/evm/420420417/etherscan' \
        --etherscan-api-key "verifyContract" \
        --chain polkadot-testnet \
        --constructor-args $(cast abi-encode "constructor(uint256,address)" 42 INSERT_DEPLOYER_ADDRESS)
    ```

Replace `INSERT_CONTRACT_ADDRESS` with your deployed contract's address.


Interact with Contracts

## Interact with Contracts


Using Cast

### Using Cast

Cast is a powerful command-line tool for interacting with deployed contracts.

#### Read from a Contract

```bash
cast call INSERT_CONTRACT_ADDRESS "number()(uint256)" \
    --chain polkadot-testnet
```

#### Write to a Contract

```bash
cast send INSERT_CONTRACT_ADDRESS "setNumber(uint256)" 42 \
    --chain polkadot-testnet \
    --private-key $PRIVATE_KEY
```

#### Get Account Balance

```bash
cast balance INSERT_ACCOUNT_ADDRESS --chain polkadot-testnet
```


Using Forge Scripts

### Using Forge Scripts

For more complex interactions, create scripts in the `script/` directory that can read and write contract state programmatically.