Skip to main content
The Open Nile Protocol uses fixed-point integer arithmetic throughout its smart contracts. There are no floating-point numbers onchain. Understanding the three precision constants is essential for correctly interpreting contract data and building integrations.

Precision Constants

BPS_DENOMINATOR

10,000Basis point calculations. 1 bps = 0.01%. Used for margin factors, fee rates, exposure caps, and all percentage-based parameters.

PRICE_PRECISION

1e18Internal price representation. All prices use 18 decimal fixed-point arithmetic. EUR/USD 1.08 is stored as 1,080,000,000,000,000,000.

USDC_PRECISION

1e6Collateral token precision. Matches the USDC standard of 6 decimals. 1 USDC = 1,000,000 raw units. All notional and margin values are denominated in raw USDC units.

BPS_DENOMINATOR (10,000)

Basis points are the standard unit for all percentage-based parameters in the protocol. Using an integer denominator of 10,000 avoids floating-point math entirely.
ParameterTypical Value (bps)Percentage
Initial Margin (IM)2002.0%
Maintenance Margin (MM)1001.0%
Trading Fee50.05%
Liquidation Penalty300.3%
Max Utilization8,00080.0%
Fee Split (Pool)7,00070.0%
Fee Split (Treasury)3,00030.0%
The formula for any basis-point calculation is:
result = value * factorBps / BPS_DENOMINATOR

PRICE_PRECISION (1e18)

All prices in the protocol are represented as 18-decimal fixed-point integers. This matches the common EVM convention (same precision as ether in wei). Why 18 decimals? FX rates like EUR/USD typically move in the 4th-5th decimal place (pips). Using 18 decimals provides more than enough precision for accurate PnL calculations without rounding errors accumulating over multiple operations.
Real ValueOnchain Representation
EUR/USD = 1.080001,080,000,000,000,000,000
EUR/USD = 1.085501,085,500,000,000,000,000
EUR/USD = 0.99123991,230,000,000,000,000
Pyth oracle prices use a different exponent (typically -5 for FX pairs). The OracleModule converts Pyth prices to 18-decimal precision before storing them onchain.

USDC_PRECISION (1e6)

All monetary values — notional amounts, margin deposits, PnL, fees — are denominated in raw USDC units with 6 decimals of precision.
Real ValueOnchain Representation
1 USDC1,000,000
1,000 USDC1,000,000,000
0.50 USDC500,000
10,000 USDC10,000,000,000

Conversion Examples

Initial Margin Calculation

For a 1,000 USDC notional position with 200 bps (2%) initial margin:
notional     = 1,000 * 1e6       = 1,000,000,000  (raw USDC)
imFactorBps  = 200
imRequired   = 1,000,000,000 * 200 / 10,000
             = 20,000,000                          (20 USDC)

PnL Calculation

PnL is computed from the price difference scaled by notional:
notional     = 1,000,000,000     (1,000 USDC)
entryPrice   = 1,080,000,000,000,000,000  (1.08)
currentPrice = 1,090,000,000,000,000,000  (1.09)

// For a LONG position:
pnl = notional * (currentPrice - entryPrice) / PRICE_PRECISION
    = 1,000,000,000 * (1,090,000,000,000,000,000 - 1,080,000,000,000,000,000)
      / 1,000,000,000,000,000,000
    = 1,000,000,000 * 10,000,000,000,000,000 / 1,000,000,000,000,000,000
    = 10,000,000    (10 USDC profit)

// For a SHORT position:
pnl = notional * (entryPrice - currentPrice) / PRICE_PRECISION
    = -10,000,000   (10 USDC loss)

Trading Fee Calculation

For a 1,000 USDC notional with 5 bps (0.05%) trading fee:
notional       = 1,000,000,000   (1,000 USDC)
tradingFeeBps  = 5
fee            = 1,000,000,000 * 5 / 10,000
               = 500,000         (0.50 USDC)

Maintenance Margin Threshold

The MM threshold determines when a position becomes liquidatable:
notional    = 1,000,000,000      (1,000 USDC)
mmFactorBps = 100                (1%)
mmThreshold = 1,000,000,000 * 100 / 10,000
            = 10,000,000          (10 USDC)

// Position is liquidatable when:
// equity (imLocked + unrealizedPnl) < mmThreshold

Enum Reference

All enums are defined in the Types library (see Types Reference) and mirrored in both the TypeScript SDK (@nile-markets/sdk) and the Rust SDK (fx-contracts).
EnumValuesDescription
SideLONG (0), SHORT (1)Trade direction. LONG profits when EUR/USD rises.
TenorONE_DAY (0), ONE_WEEK (1), ONE_MONTH (2), TEST_60S (3)Forward contract duration. TEST_60S is dev/local only.
PositionStatusOPEN (0), CLOSED (1)One-way transition: OPEN to CLOSED only.
CloseReasonNONE (0), MATURED (1), LIQUIDATED (2), EARLY_TERMINATION (3)Why a position was closed. NONE for open positions.
ModeNORMAL (0), DEGRADED (1), REDUCE_ONLY (2), PAUSED (3)Protocol operating mode. Severity increases with ordinal.
MarginModeISOLATED (0), CROSS (1)Margin model. M2 uses ISOLATED only.

Tenor Durations

TenorDurationSecondsM2 Status
ONE_DAY24 hours86,400Enabled
ONE_WEEK7 days604,800Enabled
ONE_MONTH30 days2,592,000Enabled
TEST_60S60 seconds60Dev/local only

Mode Transitions

NORMAL <-> DEGRADED <-> REDUCE_ONLY <-> PAUSED
Each mode restricts progressively more operations:
ModeOpenIncreaseClose/ReduceSettleLiquidate
NORMALYesYesYesYesYes
DEGRADEDYesYesYesYesYes
REDUCE_ONLYNoNoYesYesYes
PAUSEDNoNoNoNoNo
In DEGRADED mode, the protocol uses a wider spread and may have degraded oracle quality, but all operations remain available. REDUCE_ONLY prevents new exposure but allows existing positions to be closed. PAUSED halts all operations.

Rounding Behavior

All integer math in the protocol uses Solidity’s default truncation (round toward zero). There is no explicit rounding mode applied. Key rounding implications:
  • Margin calculations: notional * factorBps / BPS_DENOMINATOR truncates, meaning the actual margin required may be slightly less than the theoretical percentage. This is negligible for practical position sizes.
  • PnL calculations: notional * priceDiff / PRICE_PRECISION truncates. For small positions, this can result in rounding to zero.
  • Fee calculations: Fees truncate toward zero, slightly favoring the trader over the protocol.
  • ERC-4626 vault: The redeem function rounds down shares-to-assets per the ERC-4626 standard. LPs may receive up to 1 wei less than the theoretical value.
When writing tests that assert exact values after vault redemption, use assertApproxEqAbs(actual, expected, 1) to account for the ERC-4626 rounding-down behavior.