Skip to main content

Contracts for V2

Docs for the GMX V2 contracts.

Overview

The contracts repo provides an overview of the contracts. Examples for interacting with the contracts can be found in the test folder.

Do note that the main branch may not be the latest version of the contracts, it may be necessary to check the other branches for updates.

Important Notes

Please see the Known Issues list for some points that should be noted.

Additionally, these docs are meant just as an overview, please check the actual contract code for the exact implementation and for any possible edge cases if building an application, integration or similar using the contracts.

Code for contracts such as readers and events may not be audited, please check the scopes of the audits for more information, extra caution should be taken when using or depending on these contracts.

Deployments

A list of known deployments can be found in the deployments folder.

Reading Values

Values can be read using two contracts:

The reader contract provides convenience functions for retrieving information such as markets and positions lists.

Additional information can be retrieved using the Datastore and Keys contracts. Some examples of using the Datastore can be found in the test folder. There is also a keys file that provides examples of how keys can be constructed to read from the Datastore.

Multiple values can be retrieved in a single query using a Multicall contract.

For retrieving prices, please see the REST V2 docs.

ExchangeRouter

The main functions should be accessible through the ExchangeRouter contract.

Creating an Order

To create a swap / increase position order, collateral needs to first be transferred to the OrderVault, ExchangeRouter.createOrder can then be called after. The transfer of tokens for collateral and the calling of ExchangeRouter.createOrder should be done in a single transaction, otherwise the tokens may be transferred out by other users. An example of how to do this can be found in the ExchangeRouter tests.

ExchangeRouter.createOrder(BaseOrderUtils.CreateOrderParams calldata params)

  • CreateOrderParams:

    • CreateOrderParamsAddresses:
      • receiver: The receiver of any output amounts
      • callbackContract: The contract to call on order execution / cancellation
      • uiFeeReceiver: The receiver of the UI fee
      • market: The market to trade in
      • initialCollateralToken: The initial collateral token sent into the contract
      • swapPath: An array of market addresses to swap the initialCollateralToken through for increase / swap orders, for decrease orders, the output amount will be swapped through these specified markets
    • CreateOrderParamsNumbers:
      • sizeDeltaUsd: The position size to increase / decrease
      • initialCollateralDeltaAmount: The amount of tokens to withdraw for decrease orders
      • triggerPrice: The trigger price for limit / stop-loss / take-profit orders, the order will be attempted to be executed if price reaches the trigger price
      • acceptablePrice: The acceptable price at which the order can be executed. For market orders, the order will be cancelled if it cannot be executed at the acceptable price. For limit / stop-loss / take-profit orders, the order will not be executed if the trigger price is reached but the acceptable price cannot be fulfilled.
      • executionFee: The amount of native token that is included for the execution fee, e.g. on Arbitrum this would be ETH, this is the maximum execution fee that keepers can use to execute the order. When the order is executed, any excess execution fee is sent back to the order's account. Please see the Execution Fee section for more details.
      • callbackGasLimit: The gas limit to be passed to the callback contract on order execution / cancellation
      • minOutputAmount: For swap orders this is the minimum token output amount. For increase orders this is the minimum token amount after the initialCollateralDeltaAmount is swapped through the swapPath. NOTE: For decrease orders this is the minimum USD value, USD is used in this case because it is possible for decrease orders to have two output tokens, one being the profit token and the other being the withdrawn collateral token
    • orderType:
      • MarketSwap: a market swap order
      • LimitSwap: a limit swap order, this will be executed if the minOutputAmount can be fulfilled
      • MarketIncrease: a market order to increase a long / short position
      • LimitIncrease: a limit order to increase a long / short position
      • MarketDecrease: a market order to decrease a long / short position
      • LimitDecrease: a limit order to decrease a long / short position
      • StopLossDecrease: a stop-loss order to decrease a long / short position
    • decreasePositionSwapType
      • NoSwap: no swap will be performed
      • SwapPnlTokenToCollateralToken: the profit token will be attempted to be swapped to the collateral token
      • SwapCollateralTokenToPnlToken: the withdrawn collateral will be attempted to be swapped to the profit token
    • isLong: specifies if this order is for a long or short position
    • shouldUnwrapNativeToken: specifies whether to unwrap the native token, e.g. if the output token is WETH, setting this to true would convert the WETH token to ETH
    • referralCode: the referral code to use

Creating a Deposit

To create a deposit, tokens need to first be transferred to the DepositVault, ExchangeRouter.createDeposit can then be called after. The transfer of tokens and the calling of ExchangeRouter.createDeposit should be done in a single transaction, otherwise the tokens may be transferred out by other users. An example of how to do this can be found in the ExchangeRouter tests.

ExchangeRouter.createDeposit(DepositUtils.CreateDepositParams calldata params)

  • CreateDepositParams:

    • receiver: The receiver of GM tokens
    • callbackContract: The contract to call on deposit execution / cancellation
    • uiFeeReceiver: The receiver of the UI fee
    • market: The market to deposit in
    • initialLongToken: The initial long token sent into the contract
    • initialShortToken: The initial short token sent into the contract
    • longTokenSwapPath: An array of market addresses to swap the initialLongToken through for deposits
    • shortTokenSwapPath: An array of market addresses to swap the initialShortToken through for deposits
    • minMarketTokens: The minimum output amount of GM token
    • shouldUnwrapNativeToken: specifies whether to unwrap the native token, e.g. if the deposit is cancelled and the initialLongToken token is WETH, setting this to true would convert the WETH token to ETH before it is refunded
    • executionFee: The amount of native token that is included for the execution fee, e.g. on Arbitrum this would be ETH, this is the maximum execution fee that keepers can use to execute the deposit. When the deposit is executed, any excess execution fee is sent back to the deposit's account. Please see the Execution Fee section for more details.
    • callbackGas

Creating a Withdrawal

ExchangeRouter.createWithdrawal(DepositUtils.CreateWithdrawalParams calldata params)

  • CreateWithdrawalParams:

    • receiver: The receiver of GM tokens
    • callbackContract: The contract to call on withdrawal execution / cancellation
    • uiFeeReceiver: The receiver of the UI fee
    • market: The market to withdraw from
    • longTokenSwapPath: An array of market addresses to swap the withdrawn long token through
    • shortTokenSwapPath: An array of market addresses to swap the withdrawn short token through
    • minLongTokenAmount: The minimum output amount of long token after it is swapped through longTokenSwapPath. For example, if WETH token is swapped to BTC then minLongTokenAmount specifies minimum amount of BTC tokens
    • minShortTokenAmount: The minimum output amount of long token after it is swapped through shortTokenSwapPath
    • shouldUnwrapNativeToken: specifies whether to unwrap the native token, e.g. if the output token is WETH, setting this to true would convert the WETH token to ETH
    • executionFee: The amount of native token that is included for the execution fee, e.g. on Arbitrum this would be ETH, this is the maximum execution fee that keepers can use to execute the withdrawal. When the withdrawal is executed, any excess execution fee is sent back to the withdrawal's account. Please see the Execution Fee section for more details.
    • callbackGasLimit: The gas limit to be passed to the callback contract on withdrawal execution / cancellationLimit: The gas limit to be passed to the callback contract on withdrawal execution / cancellation

UI Fee

UI fees will be charged on top of the base fee, the percentage amount for this is based on the amount configured for the passed in uiFeeReceiver address. More info can be found in the Running a Frontend section.

The percentage UI fee to be charged for a uiFeeReceiver can be configured using ExchangeRouter.setUiFeeFactor, the uiFeeFactor is a percentage value over 10^30, e.g. if the uiFeeFactor is 2 10^25, then the percentage charged would be (2 10^25) / (10^30) = 0.00002 = 0.002%.

The max uiFeeFactor that can be set is based on the configured value of dataStore.getUint(Keys.MAX_UI_FEE_FACTOR).

The uiFeeReceiver value can be passed in for any of the following actions:

  • Deposits
  • Withdrawals
  • Swap Orders
  • Increase / Decrease Position Orders
  • Market Orders / Limit Orders / Stop-Loss / Take-Profit Orders

For deposits, withdrawals and swaps the fee will be the percentage of the input amount, for increase / decrease position orders the fee will be the percentage of the increase / decrease size.

UI fees are credited on execution of deposits, withdrawals, orders. ExchangeRouter.claimUiFees can be used to claim the received fees at anytime.

Execution Fee

Creating a deposit, order, withdrawal request requires sending an executionFee.

In the creation transaction, the contracts will verify that the provided executionFee is equal to or more than the estimated execution fee, which is calculated by tx.gasprice * GasUtils.adjustGasLimitForEstimate(datastore, estimatedGasLimit).

To calculate the estimatedGasLimit:

  • For deposits: GasUtils.estimateExecuteDepositGasLimit
  • For orders: GasUtils.estimateExecuteOrderGasLimit
  • For withdrawals: GasUtils.estimateExecuteWithdrawalGasLimit

Note that since the tx.gasprice value may fluctuate based on blockchain usage, some buffer could be added to ensure that the creation transaction does not revert. Upon execution, any excess execution fee will be sent back to the deposit.account / order.account / withdrawal.account.

Reader

Market List

Reader.getMarkets

  • dataStore: Address of the DataStore contract
  • start: Start index, can be used for pagination
  • end: End index, can be used for pagination

Returns array of elements of type Market.Prop

Market.Props:

  • marketToken: Address of the market token
  • indexToken: Address of the index token
  • longToken: Address of the long token
  • shortToken: Address of the short token

Detailed Market List

Returns markets with borrowing rate, funding rate and flag indicating whether market is disabled

Reader.getMarketInfoList

  • dataStore: Address of the DataStore contract
  • marketPricesList: Array of market prices of type MarketUtils.MarketPrices
  • start: Start index, can be used for pagination
  • end: End index, can be used for pagination

MarketUtils.MarketPrices:

  • indexTokenPrice
  • longTokenPrice
  • shortTokenPrice

Each price is of type Price.Props

Price.Props:

  • min
  • max

Getting GM Token Price

Returns GM token price and information about market pool: pool value, PnL, amount of tokens in the pool, borrowing fees and impact pool

Reader.getMarketTokenPrice

  • dataStore: Address of the DataStore contract
  • market: Market data of type Market.Props
  • indexTokenPrice: Price for index token of type Price.Props
  • longTokenPrice: Price for long token of type Price.Props
  • shortTokenPrice: Price for short token of type Price.Props
  • pnlFactorType: More information can be found here
  • maximize. Whether to maximize or minimize GM price by using either maximum or minimum price

Position List

Reader.getAccountPositions

  • dataStore: Address of the DataStore contract
  • account: The account to get the positions for
  • start: Start index, can be used for pagination
  • end: End index, can be used for pagination

Detailed Position List

Reader.getAccountPositionInfoList

  • dataStore: Address of the DataStore contract
  • referralStorage: Address of the referralStorage contract
  • positionKeys: Array of position keys, can be retrieved using the DataStore
  • prices: Array of market prices of type MarketUtils.MarketPrices
  • uiFeeReceiver: The uiFeeReceiver that would be used for calculating fees

Example code to retrieve positionKeys can be found here.

Position Information

Returns position, fees, execution price and PnL

Reader.getPositionInfo

  • dataStore: Address of the DataStore contract
  • referralStorage: Address of the ReferralStorage contract
  • positionKey: Key of the position
  • prices: Prices for index, long and short tokens of type MarketUtils.MarketPrices
  • sizeDeltaUsd: Used to calculate fees and execution price for decreasing position. Optional, can be 0
  • uiFeeReceiver: The receiver of the UI fee. Can be zero address
  • usePositionSizeAsSizeDeltaUsd: Specifies if position size should be used as sizeDeltaUsd

Execution Price for Increasing / Decreasing Position

Reader.getExecutionPrice

  • dataStore: Address of the DataStore contract
  • marketKey: Address of the market
  • indexTokenPrice: Price for index token of type Price.Props
  • positionSizeInUsd: Size of open position, should be 0 if there is no position
  • positionSizeInTokens: Size of open position in tokens, should be 0 if there is no position
  • sizeDeltaUsd: Size delta, positive for increasing position, negative for decreasing position
  • isLong: Specifies if this is for a long or short position

Output Amount for Swaps

Returns amount of output tokens, impact amount, swap fees

Reader.getSwapAmountOut

  • dataStore: Address of the DataStore contract
  • market: Market data of type Market.Props
  • prices: Prices for index, long and short tokens of type MarketUtils.MarketPrices
  • tokenIn: Address of token in
  • amountIn: Amount of token in
  • uiFeeReceiver: The receiver of the UI fee. Can be zero address

Output amount for Deposits

Returns amount of output GM tokens

Reader.getDepositAmountOut

  • dataStore: Address of the DataStore contract
  • market: Market data of type Market.Props
  • prices: Prices for index, long and short tokens of type MarketUtils.MarketPrices
  • longTokenAmount: Amount of deposited long token
  • shortTokenAmount: Amount of deposited short token
  • uiFeeReceiver: The receiver of the UI fee. Can be zero address

Output Amount for Withdrawals

Returns amounts of output long tokens and short tokens

Reader.getWithdrawalAmountOut

  • dataStore: Address of the DataStore contract
  • market: Market data of type Market.Props
  • prices: Prices for index, long and short tokens of type MarketUtils.MarketPrices
  • marketTokenAmount: Amount of GM tokens to withdraw
  • uiFeeReceiver: The receiver of the UI fee. Can be zero address

Simulations

There are simulation functions such as ExchangeRouter.simulateExecuteDeposit, ExchangeRouter.simulateExecuteWithdrawal, ExchangeRouter.simulateExecuteOrder, these can be used to simulate execution to check for any errors before creating a deposit / withdrawal / order.

Event Monitoring

For convenience, all events are emitted on the EventEmitter contract. Each event from the EventEmitter will have an eventName, so events can be monitored just by specifying the EventEmitter address and the eventName to be monitored.

The address of the EventEmitter for each network can be found in the deployments folder.

An example to setup monitoring of the Timelock using OpenZeppelin Defender:

  • Select the "Sentinel" option
  • Click "Create Sentinel"
  • Type in a "Sentinel name"
  • Select the "Network"
  • Fill in the EventEmitter address for the "Addresses" field
  • For "Contract conditions" select "Events" > "EventLog1"
  • In the field below "EventLog1" fill in eventName == "SignalPendingAction" OR eventName == "ClearPendingAction"
  • Click on "Next" and setup a notification channel

With this setup, you will receive a notification whenever an action is signalled (SignalPendingAction) and executed (ClearPendingAction) on the Timelock.

Note that the specific event to monitor whether it is "EventLog", "EventLog1", "EventLog2", will depend on the event called within the contracts, so the contracts need to be checked to decide which one to use for specific events.