# Errors

> Decoding reverts, the errors integrators hit most, and every custom error with its selector.

The contracts revert with Solidity custom errors: a 4-byte selector followed by the ABI-encoded arguments. This page shows how to turn that data back into a name and arguments, lists the reverts an integration actually runs into, and ends with every custom error the deployed ABIs declare.

## Decoding a Revert

One call reaches several contracts. A swap through UniversalRouter runs inside a Vault lock, swaps on CLPoolManager and pulls tokens through Permit2, and a revert from any of them comes back unchanged as the router's revert. Decode against the errors of every contract a call can reach, not just the one you called. viem already decodes errors in the ABI you passed to the call; for the rest, take the raw data and decode it against the combined set.

Three cases need more than one ABI lookup:

- **Wrapped reverts.** CLQuoter reports any failure other than its own quote as `UnexpectedRevertBytes(bytes revertData)`, and UniversalRouter wraps a failed command that is not run inline — a `PERMIT2_PERMIT`, for example — in `ExecutionFailed(uint256 commandIndex, bytes message)`. The inner bytes are another revert; decode them again.
- **Library errors.** `TickMisaligned(int24 tick, int24 tickSpacing)`, selector `0xd4d8f3e6`, comes from a CLPoolManager library and appears in no ABI. Add it by hand.
- **Token reverts.** An ERC-20 transfer that fails reverts with the token's own error, often a plain `Error(string)`. Both libraries decode `Error(string)` and `Panic(uint256)` without an ABI.

viem:

```ts
import {
  BaseError,
  ContractFunctionRevertedError,
  decodeErrorResult,
  parseAbi,
  type Abi,
  type Hex,
} from "viem";
// The JSON ABIs from the Contracts page: Vault, CLPoolManager, CLPositionManager, CLQuoter, UniversalRouter, Permit2.
import { abis } from "./abis";

const errorAbi: Abi = [
  ...(abis as Abi[]).flat().filter((item) => item.type === "error"),
  ...parseAbi(["error TickMisaligned(int24 tick, int24 tickSpacing)"]),
];

export function decodeRevertData(data: Hex): { errorName: string; args: readonly unknown[] } | undefined {
  let decoded: { errorName: string; args: readonly unknown[] };
  try {
    const { errorName, args = [] } = decodeErrorResult({ abi: errorAbi, data });
    decoded = { errorName, args };
  } catch {
    return undefined; // not an error any of these contracts declares
  }
  if (decoded.errorName === "UnexpectedRevertBytes") return decodeRevertData(decoded.args[0] as Hex) ?? decoded;
  if (decoded.errorName === "ExecutionFailed") return decodeRevertData(decoded.args[1] as Hex) ?? decoded;
  return decoded;
}

// Pass the error thrown by simulateContract, writeContract or estimateContractGas.
export function decodeRevert(error: unknown) {
  if (!(error instanceof BaseError)) return undefined;
  const reverted = error.walk((cause) => cause instanceof ContractFunctionRevertedError);
  if (!(reverted instanceof ContractFunctionRevertedError) || !reverted.raw) return undefined;
  return decodeRevertData(reverted.raw);
}
```

ethers v6:

```ts
import { Interface, isCallException, type ErrorDescription } from "ethers";
import { abis } from "./abis";

const errors = new Interface([
  ...abis.flat().filter((item) => item.type === "error"),
  "error TickMisaligned(int24 tick, int24 tickSpacing)",
]);

export function decodeRevertData(data: string): ErrorDescription | null {
  const decoded = errors.parseError(data);
  if (decoded?.name === "UnexpectedRevertBytes") return decodeRevertData(decoded.args[0]) ?? decoded;
  if (decoded?.name === "ExecutionFailed") return decodeRevertData(decoded.args[1]) ?? decoded;
  return decoded;
}

// Pass the error thrown by a contract call, staticCall or estimateGas.
export function decodeRevert(error: unknown) {
  return isCallException(error) && error.data ? decodeRevertData(error.data) : null;
}
```

A revert seen only in a wallet or explorer can be decoded the same way: copy the revert data and pass it to `decodeRevertData`, or look up its first four bytes in [All Errors](#all-errors).

## Common Errors

### TooLittleReceived

`TooLittleReceived(uint256 minAmountOutReceived, uint256 amountReceived)` — UniversalRouter

An exact-input swap would pay out less than `amountOutMinimum`, or a `TAKE_ALL` less than its minimum. The price moved between the quote and the block the transaction landed in. Quote again right before sending, and set the minimum from that quote less your slippage tolerance; widen the tolerance only for volatile or thin pools.

### TooMuchRequested

`TooMuchRequested(uint256 maxAmountInRequested, uint256 amountRequested)` — UniversalRouter

The exact-output counterpart: the swap would take more than `amountInMaximum`, or a `SETTLE_ALL` more than its maximum. Requote and set the maximum from the fresh quote plus your tolerance.

### MaximumAmountExceeded

`MaximumAmountExceeded(uint128 maximumAmount, uint128 amountRequested)` — CLPositionManager

A mint or liquidity increase would take more of one token than `amount0Max` or `amount1Max`. Liquidity is fixed in the call, so a price move changes the token amounts it needs. Recompute the amounts from the current `sqrtPriceX96`, rounding up (see [Math](/docs/developers/math#liquidity-and-amounts)), and add a slippage margin to both caps.

### MinimumAmountInsufficient

`MinimumAmountInsufficient(uint128 minimumAmount, uint128 amountReceived)` — CLPositionManager

A liquidity decrease or burn would return less than `amount0Min` or `amount1Min`. The price moved and the position's mix of tokens changed. Recompute the expected amounts at the current price, rounding down, and lower the minimums by your tolerance.

### TransactionDeadlinePassed

`TransactionDeadlinePassed()` — UniversalRouter

The block timestamp is past the `deadline` argument of `execute`. The transaction waited too long in the mempool, or the deadline came from a local clock that runs behind the chain. Derive the deadline from the latest block's timestamp and resubmit with a fresh quote.

### DeadlinePassed

`DeadlinePassed(uint256 deadline)` — CLPositionManager

The same check for `modifyLiquidities(payload, deadline)`. Fix it the same way.

### InvalidSqrtPriceLimit

`InvalidSqrtPriceLimit(uint160 sqrtPriceCurrentX96, uint160 sqrtPriceLimitX96)` — CLPoolManager

A swap's price limit is on the wrong side of the current price — already exceeded — or outside `(MIN_SQRT_RATIO, MAX_SQRT_RATIO)`. The router and quoter always pass the extreme limits, so this reaches you only from your own `CLPoolManager.swap` call, or when a pool's price already sits at the bound in the swap's direction. For a `zeroForOne` swap the limit must be below the current price; for the other direction, above it.

### CurrencyNotSettled

`CurrencyNotSettled()` — Vault

A lock ended with a currency still owed to or by the Vault. Through the router or position manager, the plan is missing a settle or take action: a swap needs `SETTLE_ALL` (or `SETTLE`) for its input and `TAKE_ALL` (or `TAKE`) for its output, and a mint needs `CLOSE_CURRENCY` or `SETTLE_PAIR` for both currencies. From your own `lockAcquired`, `sync`, transfer and `settle` every negative delta and `take` every positive one before returning.

### TicksMisordered

`TicksMisordered(int24 tickLower, int24 tickUpper)` — CLPoolManager

A position's `tickLower` is not below its `tickUpper`. Its siblings cover the other range mistakes: `TickLowerOutOfBounds(int24 tickLower)` and `TickUpperOutOfBounds(int24 tickUpper)` for a tick beyond ±887272, and `TickMisaligned(int24 tick, int24 tickSpacing)` — not in the ABI — for a tick that is not a multiple of the pool's spacing. Align both ticks to the spacing, rounding the lower one down and the upper one up, and clamp them to the usable range ([Math](/docs/developers/math#ticks-and-prices)).

### NotApproved

`NotApproved(address caller)` — CLPositionManager

The caller of an increase, decrease or burn on an existing position is neither the owner of the position NFT nor approved for it. Send from the owner, or have the owner `approve` the caller for that token ID or `setApprovalForAll`.

### AllowanceExpired

`AllowanceExpired(uint256 deadline)` — Permit2

The Permit2 allowance for the token and spender (UniversalRouter or CLPositionManager) is past its `expiration`. Grant a new one with `Permit2.approve`, or include a fresh signed permit in the call. Read the current one with `Permit2.allowance(owner, token, spender)`.

### InsufficientAllowance

`InsufficientAllowance(uint256 amount)` — Permit2

The Permit2 allowance for the spender is lower than the amount the call pulls. Raise it with `Permit2.approve` or a new permit. If the token was never approved to Permit2 itself, the revert is the token's own instead: approve Permit2 on the token first.

### InvalidNonce

`InvalidNonce()` — Permit2

A signed permit reused a nonce that Permit2 has already consumed — typically a second submission of the same signature. Read the current nonce from `Permit2.allowance(owner, token, spender)` and sign again. Through UniversalRouter it arrives inside `ExecutionFailed`, as does `SignatureExpired(uint256 signatureDeadline)` for a permit whose `sigDeadline` has passed.

### NotEnoughLiquidity

`NotEnoughLiquidity(bytes32 poolId)` — CLQuoter

The quoted pool cannot fill the full amount: the swap reached the end of its liquidity before the specified amount was used up. Quote a smaller amount or route through other pools.

### PoolNotInitialized

`PoolNotInitialized()` — CLPoolManager

No pool exists for the key. Any field of the pool key — currency order, fee, hooks, `parameters`, pool manager — that differs from the deployed pool's addresses a different, empty pool. Through CLQuoter it arrives inside `UnexpectedRevertBytes`. Check the key against the pool's `Initialize` event or the [Contracts](/docs/developers/contracts) page.

## All Errors

Every custom error in the deployed ABIs, by contract, with its selector. Errors shared by several contracts, such as `PoolNotInitialized()`, have the same selector in each. WETH9 and Multicall3 declare none.

Vault:

- `0xeb385691` `AppUnregistered()`
- `0x5212cba1` `CurrencyNotSettled()`
- `0xf22c9825` `FeeCurrencySynced()`
- `0xc6742a3f` `LockerAlreadySet(address locker)`
- `0xbda73abf` `MustClearExactPositiveDelta()`
- `0x05185ab0` `NoLocker()`
- `0x1e4fbdf7` `OwnableInvalidOwner(address owner)`
- `0x118cdaa7` `OwnableUnauthorizedAccount(address account)`
- `0x11e56171` `SettleNonNativeCurrencyWithValue()`

CLPoolManager:

- `0xaefeb924` `CannotUpdateEmptyPosition()`
- `0xcaf75f49` `CurrenciesInitializedOutOfOrder(address currency0, address currency1)`
- `0xd93c0665` `EnforcedPause()`
- `0x7f7506dc` `HookConfigValidationError()`
- `0xfa0b71d6` `HookDeltaExceedsSwapAmount()`
- `0x1dd09583` `HookPermissionsValidationError()`
- `0x48f5c3ed` `InvalidCaller()`
- `0x96206246` `InvalidFeeForExactOut()`
- `0x1e048e1d` `InvalidHookResponse()`
- `0xb5c41efe` `InvalidSqrtPriceLimit(uint160 sqrtPriceCurrentX96, uint160 sqrtPriceLimitX96)`
- `0x1599f82c` `InvalidSqrtRatio(uint160 sqrtPriceX96)`
- `0x8b86327a` `InvalidTick(int24 tick)`
- `0x14002113` `LPFeeTooLarge(uint24 fee)`
- `0xa74f97ab` `NoLiquidityToReceiveFees()`
- `0x7983c051` `PoolAlreadyInitialized()`
- `0x8379e322` `PoolManagerMismatch()`
- `0x486aa307` `PoolNotInitialized()`
- `0x51bd770c` `PoolPaused()`
- `0x1ee49702` `ProtocolFeeCannotBeFetched()`
- `0xa7abe2f7` `ProtocolFeeTooLarge(uint24 fee)`
- `0xbe8b8507` `SwapAmountCannotBeZero()`
- `0xb8e3c385` `TickLiquidityOverflow(int24 tick)`
- `0xd5e2f7ab` `TickLowerOutOfBounds(int24 tickLower)`
- `0xc4433ed5` `TicksMisordered(int24 tickLower, int24 tickUpper)`
- `0xb70024f8` `TickSpacingTooLarge(int24 tickSpacing)`
- `0xe9e90588` `TickSpacingTooSmall(int24 tickSpacing)`
- `0x1ad777f8` `TickUpperOutOfBounds(int24 tickUpper)`
- `0x30d21641` `UnauthorizedDynamicLPFeeUpdate()`
- `0xbd2c84eb` `UnusedBitsNonZero()`

CLPositionManager:

- `0x25fbd8be` `AlreadySubscribed(uint256 tokenId, address subscriber)`
- `0xace94481` `BurnNotificationReverted(address subscriber, bytes reason)`
- `0x6f5ffb7e` `ContractLocked()`
- `0xbfb22adf` `DeadlinePassed(uint256 deadline)`
- `0x3351b260` `DeltaNotNegative(address currency)`
- `0x4c085bf1` `DeltaNotPositive(address currency)`
- `0xed43c3a6` `GasLimitTooLow()`
- `0xaaad13f7` `InputLengthMismatch()`
- `0xf4d678b8` `InsufficientBalance()`
- `0xb0669cbc` `InvalidContractSignature()`
- `0x38bbd576` `InvalidEthSender()`
- `0x8baa579f` `InvalidSignature()`
- `0x4be6321b` `InvalidSignatureLength()`
- `0x815e1d64` `InvalidSigner()`
- `0x8b86327a` `InvalidTick(int24 tick)`
- `0x6aa2a937` `InvalidTokenID()`
- `0x31e30ad0` `MaximumAmountExceeded(uint128 maximumAmount, uint128 amountRequested)`
- `0x12816f22` `MinimumAmountInsufficient(uint128 minimumAmount, uint128 amountReceived)`
- `0xe94f10e2` `ModifyLiquidityNotificationReverted(address subscriber, bytes reason)`
- `0x7c402b21` `NoCodeSubscriber()`
- `0x1fb09b80` `NonceAlreadyUsed()`
- `0x80e05c00` `NoSelfPermit()`
- `0x0ca968d8` `NotApproved(address caller)`
- `0x237e6c28` `NotSubscribed()`
- `0x62df0545` `NotVault()`
- `0x93dafdf1` `SafeCastOverflow()`
- `0x5a9165ff` `SignatureDeadlineExpired()`
- `0x81ea5e9e` `SubscriptionReverted(address subscriber, bytes reason)`
- `0x82b42900` `Unauthorized()`
- `0x5cda29d7` `UnsupportedAction(uint256 action)`
- `0x07f5f526` `VaultMustBeUnlocked()`

CLQuoter:

- `0x7a5ed734` `NotEnoughLiquidity(bytes32 poolId)`
- `0x29c3b7ee` `NotSelf()`
- `0x62df0545` `NotVault()`
- `0xecbd9804` `QuoteSwap(uint256 amount)`
- `0xe0752a5a` `UnexpectedCallSuccess()`
- `0x6190b2b0` `UnexpectedRevertBytes(bytes revertData)`

CLTickLens:

- `0x486aa307` `PoolNotInitialized()`

UniversalRouter:

- `0xa3281672` `BalanceTooLow()`
- `0x6f5ffb7e` `ContractLocked()`
- `0x3351b260` `DeltaNotNegative(address currency)`
- `0x4c085bf1` `DeltaNotPositive(address currency)`
- `0xd93c0665` `EnforcedPause()`
- `0x1231ae40` `ETHNotAccepted()`
- `0x2c4029e9` `ExecutionFailed(uint256 commandIndex, bytes message)`
- `0x8dfc202b` `ExpectedPause()`
- `0xe7002877` `FromAddressIsNotOwner()`
- `0xaaad13f7` `InputLengthMismatch()`
- `0xf4d678b8` `InsufficientBalance()`
- `0x6a12f104` `InsufficientETH()`
- `0x675cae38` `InsufficientToken()`
- `0xf801e525` `InvalidAction(bytes4 action)`
- `0xdeaa01e6` `InvalidBips()`
- `0xd76a1e9e` `InvalidCommandType(uint256 commandType)`
- `0x38bbd576` `InvalidEthSender()`
- `0x20db8267` `InvalidPath()`
- `0xda6a56c3` `InvalidPoolAddress()`
- `0xa22caccc` `InvalidPoolLength()`
- `0x7b9c8916` `InvalidReserves()`
- `0xff633a38` `LengthMismatch()`
- `0xbb25d4c5` `NotAuthorizedForToken(uint256 tokenId)`
- `0x62df0545` `NotVault()`
- `0x103b4710` `OnlyAddLiqudityAllowed()`
- `0x5d1d0f9f` `OnlyMintAllowed()`
- `0x1e4fbdf7` `OwnableInvalidOwner(address owner)`
- `0x118cdaa7` `OwnableUnauthorizedAccount(address account)`
- `0x93dafdf1` `SafeCastOverflow()`
- `0x3b99b53d` `SliceOutOfBounds()`
- `0xf8637a39` `StableInvalidPath()`
- `0xcd7d97e4` `StableTooLittleReceived()`
- `0xbf1e94da` `StableTooMuchRequested()`
- `0x4e86d23a` `TooLittleReceived(uint256 minAmountOutReceived, uint256 amountReceived)`
- `0x79cb628f` `TooMuchRequested(uint256 maxAmountInRequested, uint256 amountRequested)`
- `0x5bf6f916` `TransactionDeadlinePassed()`
- `0xc4bd89a9` `UnsafeCast()`
- `0x5cda29d7` `UnsupportedAction(uint256 action)`
- `0xae52ad0c` `V2InvalidPath()`
- `0x849eaf98` `V2TooLittleReceived()`
- `0x8ab0bc16` `V2TooMuchRequested()`
- `0xd4e0248e` `V3InvalidAmountOut()`
- `0x32b13d91` `V3InvalidCaller()`
- `0x316cf0eb` `V3InvalidSwap()`
- `0x39d35496` `V3TooLittleReceived()`
- `0x739dbe52` `V3TooMuchRequested()`

Permit2:

- `0xd81b2f2e` `AllowanceExpired(uint256 deadline)`
- `0x24d35a26` `ExcessiveInvalidation()`
- `0xf96fb071` `InsufficientAllowance(uint256 amount)`
- `0x3728b83d` `InvalidAmount(uint256 maxAmount)`
- `0xb0669cbc` `InvalidContractSignature()`
- `0x756688fe` `InvalidNonce()`
- `0x8baa579f` `InvalidSignature()`
- `0x4be6321b` `InvalidSignatureLength()`
- `0x815e1d64` `InvalidSigner()`
- `0xff633a38` `LengthMismatch()`
- `0xcd21db4f` `SignatureExpired(uint256 signatureDeadline)`
