npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@revxchain/uniswap-v4-utils

v1.0.0

Published

Solidity library with various utility functions for UniswapV4 interactions

Downloads

10

Readme

UniswapV4 Utils

A Solidity library providing various utility functions for UniswapV4 interactions — spot price quotes, TWAP quotes (via hook oracle), sqrtPriceX96 calculations, calculations of amounts for providing liquidity and existing position, tick aligners and accumulated fee getters.

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


Table of Contents


Overview

UniswapV4Utils is a Solidity library that contains the common math operations and getters needed when integrating with the UniswapV4 protocol.

It consolidates spot price quotes, TWAP quotes (if a suitable hook implementing the IGeomeanOracle interface is available), sqrtPriceX96 calculations (using amounts, lowerSqrtPriceX96, or upperSqrtPriceX96), calculations of amounts for providing liquidity (compute proportional amounts) and existing position (compute current position's principal), tick aligners (by sqrtPriceX96 or tick) and accumulated fee getters (unclaimed and total earned) into a single module.

It also includes Hardhat Fixtures for full local deployment of the UniswapV4 protocol and a setup for testing integration using a mainnet fork (as well as combined V3 + V4 for both).


Main Features

| Feature | Function | |---|---| | TWAP quote with custom window and force flag | getTimeWeightedAmountOut(poolManager, poolKey, tokenIn, amountIn, secondsAgo, force, observeGasLimit) | | Spot price quote | getAmountOut(poolManager, poolKey, tokenIn, amountIn) | | Upper sqrtPriceX96 boundary for a position | getUpperSqrtPriceX96(lowerSqrtPriceX96, currentSqrtPriceX96, amount0, amount1) | | Lower sqrtPriceX96 boundary for a position | getLowerSqrtPriceX96(currentSqrtPriceX96, upperSqrtPriceX96, amount0, amount1) | | sqrtPriceX96 from tokens amounts ratio | getSqrtPriceX96(amount0, amount1) | | Nearest valid tick from sqrtPriceX96 | getValidTick(sqrtPriceX96, tickSpacing) | | Nearest valid tick from tick | getValidTick(tick, tickSpacing) | | Proportional token amounts for a providing liquidity | getProportionalAmounts(poolManager, poolKey, amount0, amount1, tickLower, tickUpper) | | Liquidity units from token amounts | getLiquidityForAmounts(poolManager, poolKey, tickLower, tickUpper, amount0, amount1) | | Principal token amounts in existing position | getPositionPrincipal(poolManager, positionManager, tokenId) | | Unclaimed swap fees | getUnclaimedFees(poolManager, positionManager, tokenId) | | Total earned swap fees | getTotalEarnedFees(poolManager, positionManager, tokenId) |


Project Structure

uniswap-v4-utils/
├── src/
│   ├── UniswapV4Utils.sol              # Main Solidity library
│   │
│   ├── interfaces/
│   │   ├── IPositionManagerTyped.sol   # Struct-typed PositionManager interface
│   │   └── IGeomeanOracle.sol          # GeomeanOracle hook interface
│   │
│   └── mocks/
│       ├── ERC20Token.sol              # Test ERC20 token
│       ├── UniswapV4UtilsMock.sol      # UniswapV4Utils wrapper
│       └── GeomeanOracle/              # Test GeomeanOracle hook
│           ├── GeomeanOracle.sol
│           ├── BaseHook.sol
│           ├── ImmutableState.sol
│           ├── Oracle.sol
│           └── SafeCallback.sol
│
├── test/
│   ├── UniswapV4DeploymentFixture.js   # Full UniswapV4 local deployment + mainnet fork setup
│   ├── UniswapV3V4DeploymentFixture.js # Combined Uniswap V3 + V4 local deployment + mainnet fork setup
│   ├── UniswapV4UtilsFixture.js        # Fixture for library test
│   ├── UniswapV4UtilsTest.js           # Main test suite
│   ├── FixturesTest.js                 # Fixture tests
│   └── TestUtils.js                    # Test helpers
│
├── artifacts/                          # Compiled contract artifacts
├── coverage/                           # Code coverage reports
├── .github/                            # Github CI files
├── env.example                         # Example of .env file
├── uniswap.addresses.json              # UniswapV4 deployments per chain
├── hardhat.config.js                   # Hardhat configuration
├── .solcover.js                        # Solidity coverage configuration
├── slither.config.json                 # Slither analyzer config for CI
├── package.json                        # Dependencies and package info
└── README.md                           # This file

Key Components

UniswapV4Utils.sol

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

UniswapV4 Deployment Fixture

Hardhat fixture that deploys the complete UniswapV4 protocol from pre-compiled artifacts:

| Contract | Source | |---|---| | WETH9 | github/gnosis/canonical-weth/build/gnosis/canonical-weth/ | | PoolManager | @uniswap/v4-core/out/ | | PositionDescriptor | @uniswap/v4-periphery/foundry-out/ | | PositionManager | @uniswap/v4-periphery/foundry-out/ | | V4Quoter | @uniswap/v4-periphery/foundry-out/ | | StateView | @uniswap/v4-periphery/foundry-out/ | | Permit2 | @revxchain/uniswap-v3-utils/build/@uniswap/permit2/ | | UniversalRouter | @uniswap/universal-router/artifacts |

UniswapV4 Deployment Fixture custom WETH

Hardhat fixture creation function that deploys the complete UniswapV4 protocol from compiled artifacts with custom WETH contract.

UniswapV4 Mainnet Fork Setup

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

UniswapV3-V4 Deployment Fixture

Hardhat fixture that deploys both the complete UniswapV3 and UniswapV4 protocols simultaneously, sharing a single WETH and Permit2 instance, and deploying a unified UniversalRouter that routes through both protocol versions.

UniswapV3-V4 Deployment Fixture custom WETH

Hardhat fixture creation function that deploys both UniswapV3 and UniswapV4 protocols with custom WETH contract.

UniswapV3-V4 Mainnet Fork Setup

The function attaches to live UniswapV3 and UniswapV4 deployments for mainnet fork testing using uniswap.addresses.json for V4 contracts and uniswap.addresses.json for V3.

V4 Supported networks

| 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 | | Ink | 57073 | | Soneium | 1868 |

[!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] V3 and V4 deployments has a unique set of supported networks and contracts. Must familiarize with the V4 deployments and V3 deployments of the selected network before using it. For unsupported networks or/and contracts, zero-address placeholders will be used.


Installation

Repository

Step 1: Clone the repository

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

Step 2: Install dependencies

npm install

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-v4-utils

Quick Start

Solidity library:

UniswapV4Utils.sol

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

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";

import {UniswapV4Utils} from "@revxchain/uniswap-v4-utils/src/UniswapV4Utils.sol";

contract MyContract {

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

    /// @notice Calculates the output amount for a token swap at the current spot price.
    function getAmountOut(
        IPoolManager poolManager, 
        PoolKey memory poolKey, 
        address tokenIn, 
        uint256 amountIn
    ) external view returns(uint256 amountOut) {
        return UniswapV4Utils.getAmountOut(poolManager, poolKey, 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 UniswapV4Utils.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(uint160 lowerSqrtPriceX96) {
        return UniswapV4Utils.getLowerSqrtPriceX96(currentSqrtPriceX96, upperSqrtPriceX96, amount0, amount1);
    }

    /// @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 UniswapV4Utils.getSqrtPriceX96(amount0, amount1);
    }

    /// @notice Finds the nearest valid tick for a specified square root price, aligned to the tick spacing.
    function getValidTick(uint160 sqrtPriceX96, int24 tickSpacing) external pure returns(int24 validTick) {
        return UniswapV4Utils.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 UniswapV4Utils.getValidTick(tick, tickSpacing);
    }

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

    /// @notice Calculates liquidity units from the specified token amounts for the specified price range.
    function getLiquidityForAmounts(
        IPoolManager poolManager,
        PoolKey memory poolKey,
        int24 tickLower,
        int24 tickUpper,
        uint256 amount0,
        uint256 amount1
    ) external view returns(uint128 liquidity) {
        return UniswapV4Utils.getLiquidityForAmounts(
            poolManager,
            poolKey,
            tickLower,
            tickUpper,
            amount0,
            amount1
        );
    }

    /// @notice Calculates the actual token amounts (amount0 and amount1) represented by a position's liquidity.
    function getPositionPrincipal(
        IPoolManager poolManager,
        address positionManager,
        uint256 tokenId
    ) external view returns(uint256 amount0, uint256 amount1) {
        return UniswapV4Utils.getPositionPrincipal(poolManager, positionManager, tokenId);
    }

    /// @notice Calculates the unclaimed swap fees for the specified liquidity position.
    function getUnclaimedFees(
        IPoolManager poolManager, 
        address positionManager, 
        uint256 tokenId
    ) external view returns(uint256 amount0, uint256 amount1) {
        return UniswapV4Utils.getUnclaimedFees(poolManager, positionManager, tokenId);
    }

    /// @notice Calculates the total earned swap fees for the specified liquidity position.
    function getTotalEarnedFees(
        IPoolManager poolManager, 
        address positionManager, 
        uint256 tokenId
    ) external view returns(uint256 amount0, uint256 amount1) {
        return UniswapV4Utils.getTotalEarnedFees(poolManager, positionManager, tokenId);
    }
}

Hardhat test environment:

UniswapV4DeploymentFixture()

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

const {
    uniswapV4Deployer, weth, poolManager, positionDescriptor, positionManager, quoter, stateView, permit2, universalRouter
} = await loadFixture(UniswapV4DeploymentFixture);

createUniswapV4DeploymentFixtureCustomWETH()

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

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

// call function with custom WETH's address
const UniswapV4DeploymentFixtureCustomWETH = createUniswapV4DeploymentFixtureCustomWETH(wethAddress);

// initialize fixture
const {
    uniswapV4Deployer, weth, poolManager, positionDescriptor, positionManager, quoter, stateView,
    permit2, universalRouter
} = await loadFixture(UniswapV4DeploymentFixtureCustomWETH);

UniswapV4MainnetForkSetup()

const { UniswapV4MainnetForkSetup } = require("@revxchain/uniswap-v4-utils/test/UniswapV4DeploymentFixture.js");

const targetChainId = 1;

const {
    weth, poolManager, positionDescriptor, positionManager, quoter, stateView, permit2, universalRouter
} = await UniswapV4MainnetForkSetup(targetChainId);

UniswapV3V4DeploymentFixture()

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

const {
    uniswapFactory, descriptorLibrary, tokenDescriptor, nonfungiblePositionManager, swapRouter01, swapRouter02,
    quoter01, quoter02, tickLens, multicall, multicall2, uniswapV4Deployer, weth, poolManager, positionDescriptor,
    positionManager, quoter, stateView, permit2, universalRouter, uniswapV3PoolBytecode, uniswapV3PoolAbi
} = await loadFixture(UniswapV3V4DeploymentFixture);

createUniswapV3V4DeploymentFixtureCustomWETH()

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

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

// call function with custom WETH's address
const UniswapV3V4DeploymentFixtureCustomWETH = createUniswapV3V4DeploymentFixtureCustomWETH(wethAddress);

// initialize fixture
const {
    uniswapFactory, descriptorLibrary, tokenDescriptor, nonfungiblePositionManager, swapRouter01, swapRouter02, 
    quoter01, quoter02, tickLens, multicall, multicall2, uniswapV4Deployer, weth, poolManager, positionDescriptor,
    positionManager, quoter, stateView, permit2, universalRouter, uniswapV3PoolBytecode, uniswapV3PoolAbi
} = await loadFixture(UniswapV3V4DeploymentFixtureCustomWETH);

UniswapV3V4MainnetForkSetup()

const { UniswapV3V4MainnetForkSetup } = require("@revxchain/uniswap-v4-utils/test/UniswapV3V4DeploymentFixture.js");

const targetChainId = 1;

const {
    uniswapFactory, descriptorLibrary, tokenDescriptor, nonfungiblePositionManager, swapRouter01, swapRouter02,
    quoter01, quoter02, tickLens, multicall, multicall2, weth, poolManager, positionDescriptor, positionManager,
    quoter, stateView, permit2, universalRouter, uniswapV3PoolBytecode, uniswapV3PoolAbi
} = await UniswapV3V4MainnetForkSetup(targetChainId);

[!NOTE] For Uniswap V3-V4 fixtures uniswapV4Deployer is actually uniswapV3Deployer account too.


Security Considerations

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

  • TWAP requires a compatible hook. The getTimeWeightedAmountOut function relies on a hook deployed at poolKey.hooks that implements the IGeomeanOracle interface. If no such hook is available and force=false, the call will revert. With force=true, it falls back to the spot price. You MUST ensure the hook matches the logic of the built-in UniswapV3.Oracle and ensure the hook is not malicious.

  • Spot price susceptibility. Functions that use the current tick (getAmountOut) or the force fallback path 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, getLiquidityForAmounts) 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 hook does not have sufficient historical observations for the requested secondsAgo window. Ensure adequate observation cardinality before relying on TWAP.

  • Gas limit during observation. The observeGasLimit parameter controls the gas allocated to the staticcall to the hook's observe function. Setting it too low will cause the call to fail silently (with force=true falling back to spot price quote). Set a reasonable gas limit to avoid unexpected fallback behaviour.


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