Skip to content

RevxChain/uniswap-v3-utils

Repository files navigation

UniswapV3 Utils

A Solidity library with various utility functions for UniswapV3 interactions — spot price quotes, TWAP quotes, calculation of amounts for providing liquidity and existing position, sqrtPriceX96 calculations, valid tick and accumulated fee getters.

Ethereum Solidity Uniswap V3 Hardhat License: MIT GitHub Actions Workflow Status NPM Version


Table of Contents


Overview

UniswapV3Utils is a Solidity library that contains the common math operations and getters needed when integrating with the UniswapV3 protocol. It consolidates spot price quotes, TWAP quotes, calculation of amounts for providing liquidity and existing position, sqrtPriceX96 calculations, valid tick and accumulated fee getters into a single module.

It also includes a Hardhat Fixture for full local deployment of the UniswapV3 protocol and a setup for testing integration using a mainnet fork.


Main Features

Feature Function
TWAP quote (default 15 min window) getTimeWeightedAmountOut(pool, tokenIn, amountIn)
TWAP quote with spot-price fallback getForceTimeWeightedAmountOut(pool, tokenIn, amountIn)
Full TWAP quote with custom window and force flag getTimeWeightedAmountOut(pool, tokenIn, amountIn, secondsAgo, force)
Spot price quote getAmountOut(pool, tokenIn, amountIn)
Upper sqrtPriceX96 boundary for a position getUpperSqrtPriceX96(lowerSqrtPriceX96, currentSqrtPriceX96, amount0, amount1)
Lower sqrtPriceX96 boundary for a position getLowerSqrtPriceX96(currentSqrtPriceX96, upperSqrtPriceX96, amount0, amount1)
Proportional token amounts for a position getProportionalAmounts(pool, amount0, amount1, tickLower, tickUpper)
Nearest valid tick from sqrtPriceX96 getValidTick(sqrtPriceX96, tickSpacing)
Nearest valid tick from tick getValidTick(tick, tickSpacing)
sqrtPriceX96 from token amount ratio getSqrtPriceX96(amount0, amount1)
Accumulated position fees getAccumulatedFees(positionManager, pool, tokenId)
Principal token amounts in the existed position getPositionLiquidity(positionManager, pool, tokenId)

Project Structure

uniswap-v3-utils/
├── src/
│   ├── UniswapV3Utils.sol                       # Main Solidity library
│   │
│   ├── interfaces/
│   │   ├── IUniswapV3PoolTyped.sol              # Struct-typed pool interface
│   │   └── INonfungiblePositionManagerTyped.sol # Struct-typed position manager interface
│   │
│   ├── libraries/
│   │   ├── uniswap-v3-periphery-0.8/            # LiquidityAmounts, OracleLibrary
│   │   └── sir-trading-core/                    # FullMath
│   │
│   └── mocks/
│       ├── ERC20Token.sol                       # Test ERC20 token
│       ├── Multicall2.sol                       # Mock Multicall2
│       └── Permit2.sol                          # Mock Permit2
│
├── test/
│   ├── UniswapV3DeploymentFixture.js            # Full UniswapV3 local deployment + mainnet fork setup
│   ├── UniswapV3UtilsFixture.js                 # Fixture for library test
│   ├── UniswapV3Utils.js                        # Main test suite
│   ├── FixturesTest.js                          # Fixture tests
│   └── Utils.js                                 # Test helpers
│
├── artifacts/                                   # Compiled contract artifacts
├── coverage/                                    # Code coverage reports
├── build/                                       # Pre-compiled artifacts (uniswap, weth, multicall2)
├── lib/                                         # Submodule dev dependencies
├── env.example                                  # Example of .env file
├── uniswap.addresses.json                       # Uniswap deployments per chain
├── hardhat.config.js                            # Hardhat configuration
├── solc.compilers.json                          # Per-contract compiler overrides
├── slither.config.json                          # Slither analyzer config for CI
├── package.json                                 # Dependencies and package info
└── README.md                                    # This file

Key Components

The primary Solidity library. Relies on the following math libraries:

  • LiquidityAmounts — converts liquidity ↔ token amounts using sqrtPriceX96 boundaries from @uniswap/v3-periphery.
  • OracleLibrary — computes getQuoteAtTick for TWAP-based price quotes from @uniswap/v3-periphery.
  • FullMath — overflow-safe mulDiv supporting 512-bit intermediate products from SIR-trading/core.
  • FixedPointMathLibsqrt from solmate.
  • TickMath — UniswapV3 tick math from @uniswap/v3-core.

Hardhat fixture that deploys the complete UniswapV3 protocol from pre-compiled artifacts stored in build/:

Contract Source
WETH9 build/gnosis/canonical-weth/
UniswapV3Factory build/@uniswap/v3-core-0.7/
NFTDescriptor build/@uniswap/v3-periphery-0.7/
NonfungibleTokenPositionDescriptor build/@uniswap/v3-periphery-0.7/
NonfungiblePositionManager build/@uniswap/v3-periphery-0.7/
SwapRouter01 build/@uniswap/v3-periphery-0.7/
SwapRouter02 build/@uniswap/swap-router-contracts/
QuoterV1 build/@uniswap/v3-periphery-0.7/
QuoterV2 build/@uniswap/v3-periphery-0.7/
TickLens build/@uniswap/v3-periphery-0.7/
UniswapInterfaceMulticall build/@uniswap/v3-periphery-0.7/
Multicall2 build/sky-ecosystem/multicall/
Permit2 build/@uniswap/permit2/
UniversalRouter build/@uniswap/universal-router/

Hardhat fixture creation function that deploys the complete UniswapV3 protocol from pre-compiled artifacts stored in build/ with custom WETH contract.

The function uses uniswap.addresses.json to attach to live deployments for mainnet fork testing.

Network Chain ID
Ethereum 1
Unichain 130
Arbitrum 42161
Optimism 10
Polygon 137
Base 8453
BSC 56
Avalanche 43114
Celo 42220
Blast 81457
Zora 7777777
Monad 143
MegaETH 4326
WorldChain 480

Warning

For networks where Ether is not the native asset, the WETH contract is used regardless. In reality, depends on the network; this is the wrapped version of the native asset.

Warning

Each network has a unique set of deployed contracts. For example, some networks might lack SwapRouter01 or one of the Multicall contracts. Must familiarize with the deployments of the selected network before using it. For unsupported networks or/and contracts, zero-address placeholders will be used.


Dependencies

Required dependencies

Note

Required to import UniswapV3Utils library.

Package Version Description
@uniswap/v3-core 1.0.2-solc-0.8-simulate UniswapV3 core contracts
solmate ^6.5.0 FixedPointMathLib for sqrt function

Optional dev dependencies

Note

Required to import pre-made Uniswap fixtures and setups.

Package Tag Version Description
hardhat hh2 ^2.28.6 Hardhat development environment
@nomicfoundation/hardhat-toolbox hh2 ^6.1.0 Hardhat development toolkit (helpers, ethers, chai, coverage etc.)

Installation

Repository

Step 1: Clone the repository

git clone https://github.com/RevxChain/uniswap-v3-utils.git
cd uniswap-v3-utils

Step 2: Install dependencies

npm install
git submodule update --init --recursive

Step 3: Create environment file (optional)

cp .env.example .env

Edit .env with your configuration:

# PRIVATE KEYS
PRIVATE_KEY = your_private_key_here

# MAINNET FORK TEST
FORK_RPC_URL = https://eth.llamarpc.com
FORK_BLOCK_NUMBER = 24540000

# RPC URLS
ETH_RPC_URL = https://eth.llamarpc.com

Step 4: Compile contracts

npx hardhat compile

Compiles all Solidity contracts and generates artifacts.

Step 5: Run tests

npx hardhat test

Step 6: Run coverage

npx hardhat coverage

Step 7: Run tests with custom fuzzing runs

Set fuzzing runs value in the hardhat.config.js:

fuzzing: {
    enabled: true,
    runs: 100
},

Run tests:

npx hardhat test

Run coverage:

npx hardhat coverage

Package

npm install @revxchain/uniswap-v3-utils

Quick Start

Solidity library:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {UniswapV3Utils} from "@revxchain/uniswap-v3-utils/src/UniswapV3Utils.sol";

contract MyContract {

    /// @notice Calculates the output amount for a token swap using the default TWAP observation 
    /// window (15 minutes).
    function getTimeWeightedAmountOut(
        address pool, 
        address tokenIn, 
        uint256 amountIn
    ) external view returns(uint256 amountOut) {
        return UniswapV3Utils.getTimeWeightedAmountOut(pool, tokenIn, amountIn);
    }

    /// @notice Calculates the output amount using TWAP with fallback to spot price if observation 
    /// data unavailable.
    function getForceTimeWeightedAmountOut(
        address pool, 
        address tokenIn, 
        uint256 amountIn
    ) external view returns(uint256 amountOut) {
        return UniswapV3Utils.getForceTimeWeightedAmountOut(pool, tokenIn, amountIn);
    }

    /// @notice Calculates the output amount for a token swap using a custom TWAP observation window 
    /// with optional force fallback.
    function getTimeWeightedAmountOut(
        address pool, 
        address tokenIn, 
        uint256 amountIn, 
        uint32 secondsAgo,
        bool force
    ) external view returns(uint256 amountOut) {
        return UniswapV3Utils.getTimeWeightedAmountOut(pool, tokenIn, amountIn, secondsAgo, force);
    }

    /// @notice Calculates the output amount for a token swap at the current spot price.
    function getAmountOut(
        address pool, 
        address tokenIn, 
        uint256 amountIn
    ) external view returns(uint256 amountOut) {
        return UniswapV3Utils.getAmountOut(pool, tokenIn, amountIn);
    }

    /// @notice Calculates the upper price boundary (sqrtPriceX96) for a liquidity position.
    function getUpperSqrtPriceX96(
        uint160 lowerSqrtPriceX96, 
        uint160 currentSqrtPriceX96,
        uint256 amount0,
        uint256 amount1
    ) external pure returns(uint160 upperSqrtPriceX96) {
        return UniswapV3Utils.getUpperSqrtPriceX96(
            lowerSqrtPriceX96, 
            currentSqrtPriceX96,
            amount0,
            amount1
        );
    }

    /// @notice Calculates the lower price boundary (sqrtPriceX96) for a liquidity position.
    function getLowerSqrtPriceX96(
        uint160 currentSqrtPriceX96,
        uint160 upperSqrtPriceX96,
        uint256 amount0,
        uint256 amount1
    ) external pure returns(uint256 lowerSqrtPriceX96) {
        return UniswapV3Utils.getLowerSqrtPriceX96(
            currentSqrtPriceX96,
            upperSqrtPriceX96,
            amount0,
            amount1
        );
    }

    /// @notice Calculates the proportional amounts of both tokens required to provide liquidity
    /// within a specified price range.
    function getProportionalAmounts(
        address pool,
        uint256 amount0,
        uint256 amount1,
        int24 tickLower,
        int24 tickUpper
    ) external view returns(uint256 amount0Required, uint256 amount1Required) {
        return UniswapV3Utils.getProportionalAmounts(
            pool,
            amount0,
            amount1,
            tickLower,
            tickUpper
        );
    }

    /// @notice Finds the nearest valid tick for a specified square root price, aligned 
    /// to the specified tick spacing.
    function getValidTick(
        uint160 sqrtPriceX96, 
        int24 tickSpacing
    ) external pure returns(int24 validTick) {
        return UniswapV3Utils.getValidTick(sqrtPriceX96, tickSpacing);
    }

    /// @notice Finds the nearest valid tick for a specified tick, aligned to the 
    /// specified tick spacing.
    function getValidTick(
        int24 tick, 
        int24 tickSpacing
    ) external pure returns(int24 validTick) {
        return UniswapV3Utils.getValidTick(tick, tickSpacing);
    }

    /// @notice Calculates the effective square root of price (in Q64.96 format) from the ratio 
    /// of two token amounts.
    function getSqrtPriceX96(
        uint256 amount0, 
        uint256 amount1
    ) external pure returns(uint160 sqrtPriceX96) {
        return UniswapV3Utils.getSqrtPriceX96(amount0, amount1);
    }

    /// @notice Calculates the accumulated trading fees for a UniswapV3 liquidity position.
    function getAccumulatedFees(
        address positionManager, 
        address pool, 
        uint256 tokenId
    ) external view returns(uint256 amount0, uint256 amount1) {
        return UniswapV3Utils.getAccumulatedFees(positionManager, pool, tokenId);
    }

    /// @notice Calculates the principal token amounts (amount0 and amount1) represented 
    /// by a position's liquidity.
    function getPositionLiquidity(
        address positionManager, 
        address pool, 
        uint256 tokenId
    ) external view returns(uint256 amount0, uint256 amount1) {
        return UniswapV3Utils.getPositionLiquidity(positionManager, pool, tokenId);
    }
}

Hardhat test environment:

const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
const { UniswapV3DeploymentFixture } = require("@revxchain/uniswap-v3-utils/test/UniswapV3DeploymentFixture.js");

const {
    uniswapV3Deployer, weth, uniswapFactory, descriptorLibrary, tokenDescriptor, nonfungiblePositionManager, 
    swapRouter01, swapRouter02, quoter01, quoter02, tickLens, multicall, multicall2, permit2, 
    universalRouter, uniswapV3PoolBytecode, uniswapV3PoolAbi
} = await loadFixture(UniswapV3DeploymentFixture);
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
const { createUniswapV3DeploymentFixtureCustomWETH } = require("@revxchain/uniswap-v3-utils/test/UniswapV3DeploymentFixture.js");

// your custom WETH's address
const wethAddress = "0x..."; 

// call function with custom WETH's address
const UniswapV3DeploymentFixtureCustomWETH = createUniswapV3DeploymentFixtureCustomWETH(wethAddress);

// initialize fixture
const {
    uniswapV3Deployer, uniswapFactory, descriptorLibrary, tokenDescriptor, nonfungiblePositionManager, 
    swapRouter01, swapRouter02, quoter01, quoter02, tickLens, multicall, multicall2, permit2, 
    universalRouter, uniswapV3PoolBytecode, uniswapV3PoolAbi
} = await loadFixture(UniswapV3DeploymentFixtureCustomWETH);
const { UniswapV3MainnetForkSetup } = require("@revxchain/uniswap-v3-utils/test/UniswapV3DeploymentFixture.js");

const targetChainId = 1;

const {
    weth, uniswapFactory, descriptorLibrary, tokenDescriptor, nonfungiblePositionManager, 
    swapRouter01, swapRouter02, quoter01, quoter02, tickLens, multicall, multicall2, permit2, 
    universalRouter, uniswapV3PoolBytecode, uniswapV3PoolAbi
} = await UniswapV3MainnetForkSetup(targetChainId);

Security Considerations

  • No input validation. UniswapV3Utils does not verify the correctness of any input. Passing an address that is not a valid UniswapV3Pool, or a tokenIn that is not one of the pool's tokens, may result in silent zero returns rather than a revert. You MUST validate inputs externally.

  • TWAP manipulation resistance. The default TWAP window (DEFAULT_TWAP_AGE = 15 minutes) provides protection against single-block sandwich attacks, but longer windows provide stronger guarantees. Always assess the time window relative to your protocol's risk tolerance.

  • Spot price susceptibility. Functions that use the current tick (getAmountOut) or the force fallback path (getForceTimeWeightedAmountOut) are susceptible to price manipulation within the same block. Use with caution — never as the sole price oracle for financial decisions.

  • Fixed-point rounding and discrete tick logic. All calculations use integer fixed-point arithmetic with Q64.96 precision. In cases of very wide ranges, very narrow ranges, or extremely disproportionate token amounts, computed boundaries (getUpperSqrtPriceX96, getLowerSqrtPriceX96, getProportionalAmounts, getSqrtPriceX96) may differ significantly from a benchmark. Always verify remaining amounts (dust) after using this calculation for interaction. If your requirements allow for off-chain calculations, it's better to do it that way. This library is useful when full on-chain autonomy and minimal risk of functionality blocking are required.

  • Oracle observation history. getTimeWeightedAmountOut (without force) will revert if the pool does not have sufficient historical observations for the requested secondsAgo window. This is an expected UniswapV3 behaviour — ensure adequate observation cardinality before relying on TWAP.


License

MIT License

Permission is hereby granted to use, copy, modify, and distribute this software freely.

See LICENSE file for full terms.


Contributing

Contributions welcome! Please:

  1. Fork the repository;
  2. Create a feature branch;
  3. Add tests for new functionality;
  4. Submit a pull request.

Support

About

Solidity library with various functions for interacting with Uniswap V3.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors