Skip to main content

Architecture

This page covers the GMX smart contract architecture, execution model, keeper network, and integration considerations. For contract function references, see Reader, ExchangeRouter, and GLV Reader.

Contract structure

The GMX protocol separates concerns across four contract categories. These categories describe the internal contract architecture, not the subset of contracts most integrations call directly day to day.

CategoryRoleExamples
Bank contractsHold funds. Only these contracts custody tokens.Bank, StrictBank, MarketToken, DepositVault, OrderVault, WithdrawalVault, ShiftVault, GlvVault
Data storageMaintain all protocol state in a single store.DataStore, RoleStore, OracleStore
Logic contractsStateless execution logic. Read from DataStore, compute results, emit events.ExchangeRouter, GlvRouter, Reader, GlvReader, OrderHandler, DepositHandler, WithdrawalHandler, ShiftHandler, LiquidationHandler, AdlHandler, all *Utils contracts
Event utilitiesGeneralized event emission for indexing and monitoring.EventEmitter

This separation enables contract upgrades without migrating funds. When a logic contract is upgraded, a new version is deployed and granted the appropriate roles in RoleStore. The bank contracts and DataStore remain unchanged — no fund migration is required.

For most integrations, the main entry points are ExchangeRouter for writes and Reader / GlvReader for reads. GlvRouter handles GLV-specific operations (deposits, withdrawals). Many of the other contracts listed above are supporting handlers, storage contracts, or utilities used internally by those entry points.

Struct data is serialized into DataStore through *StoreUtils contracts (for example, OrderStoreUtils, PositionStoreUtils, ShiftStoreUtils, MarketStoreUtils, GlvStoreUtils), which allows new keys to be added to structs without requiring storage migration. Order and position lists are stored on-chain using EnumerableSet rather than relying on indexers, so interfaces and keepers can query accurate data without waiting for indexer sync delays.

warning

RoleStore and DataStore must not change after deployment. Replacing either requires migrating all funds and reconfiguring every dependent contract. New handlers and routers receive different addresses; integrations must support multiple handler versions temporarily during transitions.

Role-based access control

All privileged operations are gated through RoleStore, which maps addresses to roles using an EnumerableSet-backed store with a roleCache mapping for gas-efficient lookups. Handlers, keepers, and governance controllers each hold specific roles that authorize their actions. The Config contract applies parameter changes through a ConfigTimelockController (extending OpenZeppelin's TimelockController) that enforces time delays on sensitive updates.

EventEmitter

Rather than emitting events from each contract individually, the protocol routes all events through a shared EventEmitter contract. This provides a single address to monitor for all protocol activity. The EventEmitter uses three structured event types (EventLog, EventLog1, EventLog2) with different numbers of indexed topics for efficient filtering, plus raw emitDataLog1 through emitDataLog4 functions for general-purpose log emission.

For monitoring integration examples, see Events.

Execution model

Most user actions follow a two-phase request-execution pattern:

Phase 1 — Request: The user submits a transaction (via ExchangeRouter) that stores the request parameters on-chain — order type, size, collateral, acceptable price, and execution fee. No oracle prices are needed at this stage.

Phase 2 — Execution: A keeper observes the request, fetches signed oracle prices from archive nodes, and submits an execution transaction that bundles the prices with the request key. The contract validates the prices and executes the order atomically.

Execution uses a try/catch pattern in OrderHandler.executeOrder. If the error is a keeper issue (insufficient gas, missing oracle prices), the entire transaction reverts so that the user's request is not unnecessarily cancelled. For market orders, user-side errors (unmet conditions, insufficient funds) cancel the request and return funds. For limit and trigger orders, the order is frozen instead of cancelled to prevent gaming — see Frozen orders below.

This two-phase design prevents front-running: the user's intent is committed on-chain before oracle prices are included, so no actor can see the execution price before the request is recorded.

Execution latency

The two-phase model introduces a brief delay between request and execution (typically a few seconds), during which the market price may move. The acceptablePrice parameter protects users — if the execution price exceeds their tolerance, the order is not fulfilled at that price.

Simulations

You can dry-run actions before keeper execution by calling the simulation functions on ExchangeRouter (for example, simulateExecuteLatestOrder, simulateExecuteLatestDeposit, simulateExecuteLatestWithdrawal, simulateExecuteLatestShift, simulateExecuteLatestJitOrder) and GlvRouter (for example, simulateExecuteLatestGlvDeposit, simulateExecuteLatestGlvWithdrawal). These are public router entry points intended for preflight checks, while the actual execution functions live on keeper-gated handlers. Simulations use the withSimulatedOraclePrices modifier and revert with EndOfOracleSimulation on success (not failure). See Simulations for details.

Keeper network

Three components work together to execute orders:

Oracle keepers

Oracle keepers fetch prices from oracle providers. The primary provider is ChainlinkDataStreamProvider, which sources prices from Chainlink Data Streams. Additional providers include EdgeDataStreamProvider and GmOracleProvider. Each price report includes both a minimum price (min) and a maximum price (max), capturing the bid-ask spread. All prices use 30-decimal precision (FLOAT_PRECISION = 10^30) and represent the USD value of one full token unit, so conversions require no unit adjustment on the token amount:

fiatValue = tokenAmount * oraclePrice
tokenAmount = fiatValue / oraclePrice

The protocol validates oracle prices against Chainlink reference feeds using the MAX_ORACLE_REF_PRICE_DEVIATION_FACTOR parameter, rejecting any price that deviates beyond the configured threshold. Not all tokens have a ChainlinkPriceFeedProvider configured as a reference price source.

Order keepers

Order keepers monitor the chain for pending requests (orders, deposits, withdrawals, shifts). When a request is detected, the keeper:

  1. Fetches signed prices from the configured oracle providers.
  2. Bundles the prices with the request key into an execution transaction.
  3. Submits the transaction. The execution fee (paid by the user at request time) covers the keeper's gas cost.

If gas prices spike, the keeper may wait or the user can cancel the request after a cancellation period. Execution fees include a buffer to account for gas price volatility — any excess is refunded after execution.

Frozen orders

If an order cannot be fulfilled at execution time (for example, the order size exceeds available liquidity, or position constraints aren't met), the order is frozen rather than cancelled. This prevents gaming where a user could create a limit order with size greater than available pool liquidity, wait for the trigger price to be hit, then deposit into the pool to allow execution at a favorable price.

Frozen orders can be retried by dedicated frozen order keepers when conditions improve. Users can also call updateOrder to unfreeze and modify the order, or cancel it entirely.

Integration considerations

Contract versioning

Integrations must handle contract address changes during upgrades. When logic contracts are upgraded, new handler and router contracts are deployed at different addresses. During transition periods:

  • Multiple handler versions may be active simultaneously.
  • Callback contracts must whitelist both old and new handler addresses.
  • Use configurable contract addresses rather than hardcoding them.

Subscribe to the channels in the Updates and Support page for contract update notifications.

ETH and WETH handling

When the protocol needs to send ETH (for example, returning execution fee refunds), it first attempts a native ETH transfer with a configurable gas limit (NATIVE_TOKEN_TRANSFER_GAS_LIMIT). If the transfer fails (for example, the recipient contract lacks a receive function or doesn't have enough gas), the protocol wraps the ETH as WETH and sends that instead.

First market deposit

The first deposit into a newly created market must set the receiver to address(1) and meet a minimum market token threshold (MIN_MARKET_TOKENS_FOR_FIRST_DEPOSIT). The minted market tokens are sent to address(1), effectively locking them. This prevents manipulation of the market token price through rounding on the initial deposit. Subsequent deposits mint market tokens to the depositor normally.

Token compatibility

The protocol is designed for standard ERC-20 tokens. The following token types are not compatible:

  • Rebasing tokens — tokens that automatically adjust balances (for example, stETH in rebasing mode)
  • ERC-777 tokens — tokens with transfer hooks that could enable reentrancy
  • Fee-on-transfer tokens — tokens that deduct a fee on every transfer
  • Tokens with built-in burn mechanisms — tokens where the supply decreases on transfer

Token airdrops to GM holders

If airdropping tokens to GM token holders, the airdrop must be claimable by contracts (not just EOAs). Integrating protocols that hold GM tokens on behalf of users need to be able to claim and distribute the airdrop to their users.

Known issues

For the full list of known issues, token compatibility constraints, and integration considerations, see the dedicated Known issues page.

Next steps

  • Contract addresses — Deployment addresses for all supported chains.
  • ExchangeRouter — Create orders, deposits, withdrawals, and shifts.
  • GlvRouter — Create GLV deposits and withdrawals.
  • Reader — Query market, position, and pricing data.
  • GLV Reader — Query GLV vault data and pricing.
  • Advanced entry points — Delegated subaccount trading, Gelato relay flows, and multichain routers.
  • Simulations — Dry-run actions before submitting on-chain.
  • Events — Monitor protocol activity through EventEmitter.