Developers

Errors

View as Markdown

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.

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

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

Vault9
AppUnregistered()
0xeb385691
CurrencyNotSettled()
0x5212cba1
FeeCurrencySynced()
0xf22c9825
LockerAlreadySet(address locker)
0xc6742a3f
MustClearExactPositiveDelta()
0xbda73abf
NoLocker()
0x05185ab0
OwnableInvalidOwner(address owner)
0x1e4fbdf7
OwnableUnauthorizedAccount(address account)
0x118cdaa7
SettleNonNativeCurrencyWithValue()
0x11e56171
CLPoolManager29
CannotUpdateEmptyPosition()
0xaefeb924
CurrenciesInitializedOutOfOrder(address currency0, address currency1)
0xcaf75f49
EnforcedPause()
0xd93c0665
HookConfigValidationError()
0x7f7506dc
HookDeltaExceedsSwapAmount()
0xfa0b71d6
HookPermissionsValidationError()
0x1dd09583
InvalidCaller()
0x48f5c3ed
InvalidFeeForExactOut()
0x96206246
InvalidHookResponse()
0x1e048e1d
InvalidSqrtPriceLimit(uint160 sqrtPriceCurrentX96, uint160 sqrtPriceLimitX96)
0xb5c41efe
InvalidSqrtRatio(uint160 sqrtPriceX96)
0x1599f82c
InvalidTick(int24 tick)
0x8b86327a
LPFeeTooLarge(uint24 fee)
0x14002113
NoLiquidityToReceiveFees()
0xa74f97ab
PoolAlreadyInitialized()
0x7983c051
PoolManagerMismatch()
0x8379e322
PoolNotInitialized()
0x486aa307
PoolPaused()
0x51bd770c
ProtocolFeeCannotBeFetched()
0x1ee49702
ProtocolFeeTooLarge(uint24 fee)
0xa7abe2f7
SwapAmountCannotBeZero()
0xbe8b8507
TickLiquidityOverflow(int24 tick)
0xb8e3c385
TickLowerOutOfBounds(int24 tickLower)
0xd5e2f7ab
TicksMisordered(int24 tickLower, int24 tickUpper)
0xc4433ed5
TickSpacingTooLarge(int24 tickSpacing)
0xb70024f8
TickSpacingTooSmall(int24 tickSpacing)
0xe9e90588
TickUpperOutOfBounds(int24 tickUpper)
0x1ad777f8
UnauthorizedDynamicLPFeeUpdate()
0x30d21641
UnusedBitsNonZero()
0xbd2c84eb
CLPositionManager31
AlreadySubscribed(uint256 tokenId, address subscriber)
0x25fbd8be
BurnNotificationReverted(address subscriber, bytes reason)
0xace94481
ContractLocked()
0x6f5ffb7e
DeadlinePassed(uint256 deadline)
0xbfb22adf
DeltaNotNegative(address currency)
0x3351b260
DeltaNotPositive(address currency)
0x4c085bf1
GasLimitTooLow()
0xed43c3a6
InputLengthMismatch()
0xaaad13f7
InsufficientBalance()
0xf4d678b8
InvalidContractSignature()
0xb0669cbc
InvalidEthSender()
0x38bbd576
InvalidSignature()
0x8baa579f
InvalidSignatureLength()
0x4be6321b
InvalidSigner()
0x815e1d64
InvalidTick(int24 tick)
0x8b86327a
InvalidTokenID()
0x6aa2a937
MaximumAmountExceeded(uint128 maximumAmount, uint128 amountRequested)
0x31e30ad0
MinimumAmountInsufficient(uint128 minimumAmount, uint128 amountReceived)
0x12816f22
ModifyLiquidityNotificationReverted(address subscriber, bytes reason)
0xe94f10e2
NoCodeSubscriber()
0x7c402b21
NonceAlreadyUsed()
0x1fb09b80
NoSelfPermit()
0x80e05c00
NotApproved(address caller)
0x0ca968d8
NotSubscribed()
0x237e6c28
NotVault()
0x62df0545
SafeCastOverflow()
0x93dafdf1
SignatureDeadlineExpired()
0x5a9165ff
SubscriptionReverted(address subscriber, bytes reason)
0x81ea5e9e
Unauthorized()
0x82b42900
UnsupportedAction(uint256 action)
0x5cda29d7
VaultMustBeUnlocked()
0x07f5f526
CLQuoter6
NotEnoughLiquidity(bytes32 poolId)
0x7a5ed734
NotSelf()
0x29c3b7ee
NotVault()
0x62df0545
QuoteSwap(uint256 amount)
0xecbd9804
UnexpectedCallSuccess()
0xe0752a5a
UnexpectedRevertBytes(bytes revertData)
0x6190b2b0
CLTickLens1
PoolNotInitialized()
0x486aa307
UniversalRouter46
BalanceTooLow()
0xa3281672
ContractLocked()
0x6f5ffb7e
DeltaNotNegative(address currency)
0x3351b260
DeltaNotPositive(address currency)
0x4c085bf1
EnforcedPause()
0xd93c0665
ETHNotAccepted()
0x1231ae40
ExecutionFailed(uint256 commandIndex, bytes message)
0x2c4029e9
ExpectedPause()
0x8dfc202b
FromAddressIsNotOwner()
0xe7002877
InputLengthMismatch()
0xaaad13f7
InsufficientBalance()
0xf4d678b8
InsufficientETH()
0x6a12f104
InsufficientToken()
0x675cae38
InvalidAction(bytes4 action)
0xf801e525
InvalidBips()
0xdeaa01e6
InvalidCommandType(uint256 commandType)
0xd76a1e9e
InvalidEthSender()
0x38bbd576
InvalidPath()
0x20db8267
InvalidPoolAddress()
0xda6a56c3
InvalidPoolLength()
0xa22caccc
InvalidReserves()
0x7b9c8916
LengthMismatch()
0xff633a38
NotAuthorizedForToken(uint256 tokenId)
0xbb25d4c5
NotVault()
0x62df0545
OnlyAddLiqudityAllowed()
0x103b4710
OnlyMintAllowed()
0x5d1d0f9f
OwnableInvalidOwner(address owner)
0x1e4fbdf7
OwnableUnauthorizedAccount(address account)
0x118cdaa7
SafeCastOverflow()
0x93dafdf1
SliceOutOfBounds()
0x3b99b53d
StableInvalidPath()
0xf8637a39
StableTooLittleReceived()
0xcd7d97e4
StableTooMuchRequested()
0xbf1e94da
TooLittleReceived(uint256 minAmountOutReceived, uint256 amountReceived)
0x4e86d23a
TooMuchRequested(uint256 maxAmountInRequested, uint256 amountRequested)
0x79cb628f
TransactionDeadlinePassed()
0x5bf6f916
UnsafeCast()
0xc4bd89a9
UnsupportedAction(uint256 action)
0x5cda29d7
V2InvalidPath()
0xae52ad0c
V2TooLittleReceived()
0x849eaf98
V2TooMuchRequested()
0x8ab0bc16
V3InvalidAmountOut()
0xd4e0248e
V3InvalidCaller()
0x32b13d91
V3InvalidSwap()
0x316cf0eb
V3TooLittleReceived()
0x39d35496
V3TooMuchRequested()
0x739dbe52
Permit211
AllowanceExpired(uint256 deadline)
0xd81b2f2e
ExcessiveInvalidation()
0x24d35a26
InsufficientAllowance(uint256 amount)
0xf96fb071
InvalidAmount(uint256 maxAmount)
0x3728b83d
InvalidContractSignature()
0xb0669cbc
InvalidNonce()
0x756688fe
InvalidSignature()
0x8baa579f
InvalidSignatureLength()
0x4be6321b
InvalidSigner()
0x815e1d64
LengthMismatch()
0xff633a38
SignatureExpired(uint256 signatureDeadline)
0xcd21db4f
Previous
Math
Next
Basics