Skip to main content
In a Sauce program, swap with @sauce/router’s function for the venue (uniswapV2, uniswapV3, uniswapV4, curve, balancerV2, …; Slipstream, PancakeSwap V3 and Algebra pools through uniswapV3), with a minimum output and, where the pool takes one, a price limit (the @sauce/router reference; several pools under one limit, the distributed swap).
Singleton AMM with hooks architecture enabling custom pool logic, flash accounting, and native ETH support. All pools share a single PoolManager contract for gas savings on multi-hop swaps.

When to use it

  • Every pool lives in one PoolManager, keyed by (currency0, currency1, fee, tickSpacing, hooks). A pool can attach a hook contract that runs around swaps and liquidity changes, and a currency of 0x0 is the native coin.
  • A Sauce program swaps a pool with @sauce/router’s uniswapV4, with a price limit; no UniversalRouter commands are needed.

Pitfalls for a program

  • Exact input is a negative amountSpecified, the reverse of V3’s pool. For raw calls use -Int256(amount) when amount is unsigned; uniswapV4 takes a positive amountIn and handles the protocol’s signed convention itself.
  • A hook can change a pool’s behaviour and fee: use hookless keys (hooks: 0) unless you have reviewed the hook.
  • A protocol fee can apply on top of the LP fee; both are in the pool’s state (the distributed swap reads them).
  • Anyone can initialize a key: a pool can exist with no liquidity at a stale price. Read its liquidity before trusting its price.
  • A native-coin pool (currency0 of 0x0) is a different pair from the WETH pools.

In a Sauce program

One guarded swap on the hookless 0.05% WETH/USDC pool:
uniswapV4 builds the key from the two tokens in sort order with the fee (500), tick spacing (10) and hooks (0, none), pays recipient and reverts below minOut. The WETH the pool did not take before the price limit goes back to recipient too.

What it offers

  • swap: Execute a swap through the UniversalRouter using command-based encoding
  • addLiquidity: Modify liquidity positions through the PositionManager
  • removeLiquidity: Decrease or remove liquidity positions through the PositionManager

Notes

  • V4 uses a singleton architecture: ALL pools live in a single PoolManager contract, saving gas on multi-hop swaps
  • Hooks: each pool can attach a custom hooks contract that intercepts before/after swap and liquidity events
  • Pool key: (currency0, currency1, fee, tickSpacing, hooks) uniquely identifies a pool
  • currency0 = address(0) means native ETH; V4 supports native ETH directly (no WETH wrapping needed)
  • zeroForOne: true = swap currency0 for currency1; false = swap currency1 for currency0
  • amountSpecified: negative = exact input; positive = exact output (the reverse of Uniswap V3’s pool swap)
  • Flash accounting: V4 uses transient storage for balance tracking within a transaction, settling at the end
  • Commands/inputs for the UniversalRouter must be constructed off-chain
  • V4 is the newest Uniswap version; use it for advanced pool logic (custom hooks) or when gas efficiency on multi-hop matters most
  • From a Pot program, @sauce/router’s uniswapV4(poolManager, tokenIn, tokenOut, amountIn, fee, tickSpacing, hooks, sqrtPriceLimitX96, minOut, recipient) swaps one pool through the Pot’s Sauce Router, with no UniversalRouter or approval. The pool key is tokenIn and tokenOut in sort order (native ETH, the zero address, first), with fee, tickSpacing and hooks. Pass positive amountIn; the helper handles the signed exact-input convention and returns output. The uniswapV4Swap action is the same call.
  • On Arc (5042) the PositionManager’s WETH9() is a stub that always reverts with UnsupportedProtocolError(). Arc’s native coin is USDC at 18 decimals, so a pool keyed on address(0) is native USDC, a different currency from the 6-decimal ERC-20 0x3600000000000000000000000000000000000000 over the same balance

Its names in a program

On Ethereum, UniswapV4 is a namespace: its contracts need no import.

Deployments

ABI

Where a line says what an address it returns is, a program that calls the function through its namespace calls that contract’s methods on the result directly: <Namespace>.<Contract>.<function>(...).<method>(...). A token’s methods are the ERC-20 functions, and its writes are SafeERC20.

UniswapV4PoolManagerABI

import UniswapV4PoolManagerABI from "./protocols/uniswap-v4/UniswapV4PoolManagerABI.json"; Speaks for: poolManager.
  • initialize((address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key, uint160 sqrtPriceX96) returns (int24 tick)
  • swap((address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key, (bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96) params, bytes hookData) returns (int256 delta)
  • modifyLiquidity((address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key, (int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt) params, bytes hookData) returns (int256 delta, int256 feeDelta)
  • extsload(bytes32 slot) view returns (bytes32)
  • extsload(bytes32 startSlot, uint256 nSlots) view returns (bytes32[])

UniswapV4PositionManagerABI

import UniswapV4PositionManagerABI from "./protocols/uniswap-v4/UniswapV4PositionManagerABI.json"; Speaks for: positionManager.
  • modifyLiquidities(bytes unlockData, uint256 deadline) payable

UniswapV4StateViewABI

import UniswapV4StateViewABI from "./protocols/uniswap-v4/UniswapV4StateViewABI.json"; Speaks for: stateView.
  • getSlot0(bytes32 poolId) view returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)
  • getLiquidity(bytes32 poolId) view returns (uint128 liquidity)
  • getTickInfo(bytes32 poolId, int24 tick) view returns (uint128 liquidityGross, int128 liquidityNet, uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128)
  • getTickLiquidity(bytes32 poolId, int24 tick) view returns (uint128 liquidityGross, int128 liquidityNet)
  • getTickFeeGrowthOutside(bytes32 poolId, int24 tick) view returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128)
  • getTickBitmap(bytes32 poolId, int16 tick) view returns (uint256 tickBitmap)
  • getFeeGrowthGlobals(bytes32 poolId) view returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1)
  • getFeeGrowthInside(bytes32 poolId, int24 tickLower, int24 tickUpper) view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128)
  • getPositionInfo(bytes32 poolId, bytes32 positionId) view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
  • getPositionInfo(bytes32 poolId, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
  • getPositionLiquidity(bytes32 poolId, bytes32 positionId) view returns (uint128 liquidity)
  • poolManager() view returns (address): the address is a UniswapV4.PoolManager

UniswapV4UniversalRouterABI

import UniswapV4UniversalRouterABI from "./protocols/uniswap-v4/UniswapV4UniversalRouterABI.json"; Speaks for: universalRouter.
  • execute(bytes commands, bytes[] inputs, uint256 deadline) payable
  • execute(bytes commands, bytes[] inputs) payable

Next steps