Skip to main content
All FX Forward Protocol contracts use Solidity custom errors (introduced in Solidity 0.8.4) instead of require() with string messages. Custom errors are ABI-encoded using the error selector (first 4 bytes of keccak256(errorSignature)), making reverts cheaper than string reverts and enabling structured error decoding in frontends and off-chain services. Every custom error is defined in the centralized Errors library and imported by the contracts that use them.
Custom errors save roughly 50-200 gas per revert compared to require("string message") because they avoid storing and copying string data in memory. They also produce deterministic 4-byte selectors that clients can decode without access to source code.

Errors by Contract

Configuration parameter validation errors. These fire when an admin attempts to set invalid parameters or modify parameters at an inappropriate time.
ErrorDescription
ImMustExceedMm()Initial margin (IM) factor must be strictly greater than maintenance margin (MM) factor. The protocol requires imFactorBps > mmFactorBps to ensure a liquidation buffer exists between the margin posted at open and the threshold that triggers liquidation.
InvalidParameter()A parameter value is outside its valid range. This is a general-purpose validation error used across multiple setter functions when the provided value violates documented constraints (e.g., basis point values exceeding 10,000, zero durations, or other boundary violations).
OpenPositionsExist()Cannot modify this parameter while open positions exist. Certain parameters (tenor durations, fixing times, margin mode) affect position economics at open time. Changing them with outstanding positions would create inconsistencies, so the protocol requires all positions to be closed first.
ZeroAddress()An address parameter cannot be the zero address (address(0)). Used when setting fee receivers, contract references, or any address that will be called or receive funds.
Collateral and margin management errors. These protect trader funds and ensure margin accounting invariants are maintained.
ErrorDescription
InsufficientAvailableBalance()The trader does not have enough free (unlocked) collateral to complete the operation. Available balance is total deposited collateral minus all locked margin across open positions.
InsufficientCollateral()Total collateral is below the required minimum. This is a broader check than InsufficientAvailableBalance and applies when the overall account collateral is too low for a requested action.
MarginAlreadyLocked()Margin for this position is already locked. Prevents double-locking of margin for the same position.
MarginNotLocked()Margin for this position is not currently locked. Fires when attempting to unlock or modify margin that was never locked.
WithdrawalExceedsAvailable()The requested withdrawal amount exceeds the available (unlocked) balance. Traders can only withdraw collateral that is not locked as margin for any open position.
ZeroAmount()Amount parameter cannot be zero. Applies to deposits, withdrawals, and margin operations.
Unauthorized()The caller is not authorized to perform this operation on the account. Only the account owner or protocol contracts with the appropriate role can modify an account’s margin.
Operating mode state machine errors. The protocol operates in one of four modes: NORMAL, DEGRADED, REDUCE_ONLY, or PAUSED.
ErrorDescription
InvalidModeTransition()The requested mode transition is not allowed by the state machine. Not all mode transitions are valid. For example, transitioning directly from PAUSED to NORMAL may not be permitted depending on the protocol’s current state and the transition rules.
ProtocolPaused()The protocol is in PAUSED mode. No trading operations, settlements, or liquidations can be performed. This is the most restrictive mode, typically activated in response to critical issues.
ReduceOnlyMode()The protocol is in REDUCE_ONLY or DEGRADED mode. Opening new positions and increasing existing positions are blocked. Only position reductions, closures, settlements, and liquidations are permitted.
DegradedMode()The protocol is in DEGRADED mode. Similar restrictions to REDUCE_ONLY but may have additional constraints on certain operations.
Oracle and price feed errors. These enforce data quality and safety checks on all price data entering the protocol.
ErrorDescription
ArrayLengthMismatch()Batch input arrays have different lengths. When publishing multiple forward prices in a single call, the arrays of fixing timestamps, prices, and other parameters must all have the same length.
DeviationExceedsLimit()The submitted forward price deviates too far from the spot price. Controlled by maxDeviationVsPriorBps and maxDeviationVsLastCloseBps safeguard parameters. Prevents erroneous or manipulated forward prices from entering the system.
FixingPriceAlreadySet()A fixing price has already been recorded for this (pairId, fixingTimestamp) combination. Fixing prices are immutable once set and cannot be overwritten.
InvalidFixingTimestamp()The fixing timestamp is not valid. Either the position has not matured yet, the timestamp does not correspond to a valid business day fixing time, or the format is incorrect.
InvalidPair()The pair ID is not registered in the OracleModule. Only registered currency pairs (currently EUR/USD) can have prices published.
InvalidPrice()The price value is not valid. Prices must be positive and non-zero. A zero or negative price indicates corrupted data or an oracle failure.
NotMatured()The position or forward contract has not yet reached its maturity (fixing) timestamp. Settlement operations require that block.timestamp >= fixingTimestamp.
OracleInvalid()Oracle data is not available or is invalid. The Pyth price feed returned no data, or the internal oracle state indicates an unrecoverable error for the requested pair.
OracleStale()Oracle data is too old. The time elapsed since the last price update exceeds maxForwardAge (for forward prices) or maxOracleAge (for spot prices). Fresh price data must be published before operations can proceed.
PriceMoveExceedsLimit()The price movement between consecutive updates exceeds the maxOracleMovePerUpdateBps safety limit. This safeguard prevents sudden large price jumps that could indicate oracle manipulation or data errors.
UpdateSpacingViolation()Forward price updates are arriving too frequently. The time between consecutive updates must be at least minForwardUpdateSpacing seconds. This prevents spam and ensures orderly price publication.
InsufficientFee()The transaction did not include enough ETH to cover the Pyth oracle update fee. The caller must send at least the fee amount returned by pyth.getUpdateFee().
ForwardNotPublished()No forward price has been published for the requested (pairId, fixingTimestamp) combination. The publisher must submit a forward price before positions referencing that maturity can be opened or priced.
Liquidity pool errors. These protect LP deposits and ensure the pool remains solvent.
ErrorDescription
InsufficientPoolLiquidity()The pool cannot cover a trader’s profit. When a position closes with a profit for the trader, the pool must have enough total assets to pay out. If traderProfit > totalAssets(), the settlement reverts.
NotAllowlisted()The caller is not on the deposit allowlist. During early protocol operation, deposits may be restricted to approved addresses.
NotionalUtilizationTooHigh()The operation would cause total notional exposure to exceed the maximum utilization cap relative to pool assets. Controlled by maxUtilizationBps. This prevents over-leveraging the pool.
ExceedsMaxUtilization()Similar to NotionalUtilizationTooHigh — the utilization ratio would exceed the configured maximum. Used in withdrawal and redemption paths to ensure LPs cannot withdraw below the utilization floor.
DivisionByZero()Division by zero encountered in share price or conversion calculations. This is a math safety error that should not occur under normal conditions.
Position lifecycle errors. These validate all operations related to opening, modifying, and closing positions.
ErrorDescription
AccountHasLiquidatablePosition()Cannot open a new position while the account has another position that is currently liquidatable. The trader must close or have the liquidatable position resolved first.
ExceedsPositionCap()The position’s notional value exceeds the per-position notional cap set by the RiskManager. Controlled by perPositionCapFactorBps applied to pool total assets.
InvalidTenor()The requested tenor is not enabled. Check that the tenor enum value is valid and that the corresponding tenor duration is configured and non-zero.
MarginBelowMinimum()The margin provided is below the minimum required for the given notional size. Minimum margin is notional * imFactorBps / BPS_DENOMINATOR.
MarginExceedsNotional()Cannot lock more margin than the position’s notional value. This is a sanity check — margin should always be a fraction of notional.
MarginRemovalCausesLiquidation()Removing the requested amount of margin would cause the position’s equity to fall below the maintenance margin threshold, making it immediately liquidatable.
NotionalTooSmall()The notional value is below minPositionNotional. The protocol enforces a minimum position size to ensure positions are economically meaningful and worth the gas cost of settlement.
PositionMatured()The position has matured and cannot be modified (increased, reduced, or margin-adjusted). It must be settled or liquidated instead.
PositionNotOpen()The position is not in OPEN status. Operations like increase, reduce, add/remove margin, and early termination require the position to be open.
ProtocolPaused()The protocol is in PAUSED mode. All position operations are blocked.
ReduceOnlyMode()The protocol is in REDUCE_ONLY or DEGRADED mode. Only position reductions and closures are allowed. Opening new positions and increasing existing ones are blocked.
Risk validation errors. The RiskManager enforces caps on notional exposure at the position, account, and pool level.
ErrorDescription
ExceedsAccountCap()The account’s total notional exposure across all open positions would exceed the per-account cap. Controlled by perAccountCapFactorBps applied to pool total assets.
ExceedsPoolExposureCap()The pool’s net directional exposure would exceed the configured cap. Controlled by netExposureCapFactorBps. This prevents the pool from becoming too heavily skewed to one side.
ExceedsPositionCap()The individual position’s notional exceeds the per-position cap. This is the RiskManager’s own check, applied in addition to PositionManager’s validation.
RateOfChangeExceeded()The rate of notional change exceeds the configured clamp. Controlled by maxGrossNotionalDeltaPerWindow and maxNetExposureDeltaPerWindow over rateWindowSeconds. This throttles rapid position accumulation that could destabilize the pool.
Settlement, liquidation, and termination errors. These govern the end-of-life processing of positions.
ErrorDescription
EarlyTerminationNotAllowed()Early termination is not enabled for this position or the protocol configuration does not permit it. Early termination allows traders to close positions before maturity at a price that includes a termination fee.
FixingPriceNotSet()The fixing price has not yet been recorded for this position’s maturity timestamp. Settlement requires a fixing price to calculate final PnL. The publisher or admin must record the fixing price first.
NotLiquidatable()The position’s equity is at or above the maintenance margin threshold. Liquidation is only permitted when equity < maintenanceMargin (strict less-than).
NotMatured()The position has not yet reached its fixing timestamp. Maturity settlement requires block.timestamp >= fixingTimestamp.
SettlementNotAllowed()Settlement conditions are not met. This is a general guard that covers cases where settlement is blocked for reasons beyond maturity and fixing price checks.
AlreadySettled()The position has already been settled or closed. Prevents double-settlement of the same position.
SettlementFailed()The settlement operation failed during execution. This indicates an unexpected error in the settlement flow after all precondition checks passed.
Role-based access control errors. The protocol uses a custom role system for admin operations.
ErrorDescription
MissingRole(bytes32 role)The caller does not have the required role. This is the only parameterized error in the protocol — it includes the missing role’s identifier to aid debugging.
RoleAlreadyGranted()The role has already been granted to the target address. Prevents redundant role assignments.
CannotRevokeOwnRole()An admin cannot revoke their own role. This prevents accidental lockout of admin capabilities.
Additional margin-related validation errors used across contracts during position lifecycle operations.
ErrorDescription
MarginExceedsNotional()Margin locked for a position cannot exceed the position’s notional value. This is a sanity bound.
MarginBelowMinimum()Margin is below the minimum required for the position size, calculated as notional * imFactorBps / BPS_DENOMINATOR.
MarginRemovalCausesLiquidation()Removing the requested margin amount would immediately make the position liquidatable by pushing equity below the maintenance margin threshold.
Low-level math safety errors. These should not be encountered under normal protocol operation and indicate internal invariant violations.
ErrorDescription
NegativeValue()An operation produced a negative value where only non-negative values are valid. Used in internal math functions.
Overflow()An arithmetic operation would overflow. Used in internal math functions as an additional safety layer beyond Solidity’s built-in overflow checks.
DivisionByZero()Division by zero detected. Used in internal math helpers and share price calculations.
When integrating with the protocol, decode revert data using the error selectors from the Errors library ABI. Libraries like viem and ethers.js can automatically decode custom errors when provided with the contract ABI. The TypeScript SDK (@nile-markets/sdk) exports all ABIs with error definitions included.