Contracts for V1
Docs for the GMX V1 contracts.
Important Notes
Please note that 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.
Please subscribe to the channels in the Updates page for important contract update notifications.
Overview
A technical overview of GMX V1 can be found in this Notion Doc.
Arbitrum
- GMX: 0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a
- Vault: 0x489ee077994B6658eAfA855C308275EAd8097C4A
- Router: 0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064
- PositionRouter: 0xb87a436B93fFE9D75c5cFA7bAcFff96430b09868
- OrderBook: 0x09f77e8a13de9a35a7231028187e9fd5db8a2acb
- Reader: 0x22199a49A999c351eF7927602CFB187ec3cae489
- RewardReader: 0x8BFb8e82Ee4569aee78D03235ff465Bd436D40E0
- OrderBookReader: 0xa27C20A7CF0e1C68C0460706bB674f98F362Bc21
- StakedGmx: 0xd2D1162512F927a7e282Ef43a362659E4F2a728F
- StakedGlp: 0x5402B5F40310bDED796c7D0F3FF6683f5C0cFfdf
- GlpManager: 0x3963FfC9dff443c2A94f21b129D429891E32ec18
- RewardRouter: 0x5E4766F932ce00aA4a1A82d3Da85adf15C5694A1
- GlpRewardRouter: 0xB95DB5B167D75e6d04227CfFFA61069348d271F5
- ReferralStorage: 0xe6fab3F0c7199b0d34d7FbE83394fc0e0D06e99d
- GMX-ETH Uniswap Pool: 0x80A9ae39310abf666A87C743d6ebBD0E8C42158E
Avalanche
- GMX: 0x62edc0692BD897D2295872a9FFCac5425011c661
- Vault: 0x9ab2De34A33fB459b538c43f251eB825645e8595
- Router: 0x5F719c2F1095F7B9fc68a68e35B51194f4b6abe8
- PositionRouter: 0xffF6D276Bc37c61A23f06410Dce4A400f66420f8
- OrderBook: 0x4296e307f108B2f583FF2F7B7270ee7831574Ae5
- Reader: 0x67b789D48c926006F5132BFCe4e976F0A7A63d5D
- RewardReader: 0x04Fc11Bd28763872d143637a7c768bD96E44c1b6
- OrderBookReader: 0xccFE3E576f8145403d3ce8f3c2f6519Dae40683B
- StakedGmx: 0x4d268a7d4C16ceB5a606c173Bd974984343fea13
- StakedGlp: 0xaE64d55a6f09E4263421737397D1fdFA71896a69
- GlpManager: 0xD152c7F25db7F4B95b7658323c5F33d176818EE4
- RewardRouter: 0x091eD806490Cc58Fd514441499e58984cCce0630
- GlpRewardRouter: 0xB70B91CE0771d3f4c81D87660f71Da31d48eB3B3
- ReferralStorage: 0x827ED045002eCdAbEb6e2b0d1604cf5fC3d322F8
- GMX-AVAX Trader Joe Pool: 0x0c91a070f862666bBcce281346BE45766d874D98
Swap
To execute a swap:
- Approve the Router contract for the token and amount you would like to swap
- Call Router.swap with parameters:
- _path: [tokenIn, tokenOut]
- _amountIn: amount of tokenIn to swap
- _minOut: minimum expected output amount
- _receiver: address of the receiver of tokenOut
- The function will revert if the amount of tokenOut sent to the receiver is less than _minOut
To get swap amounts before execution:
- Call Reader.getMaxAmountIn with parameters:
- _vault: address of the vault
- _tokenIn: address of token that will be given
- _tokenOut: address of token to be received
- The max amount of tokenIn that can be swapped will be returned
- Call Reader.getAmountOut with parameters:
- _vault: address of the vault
- _tokenIn: address of token that will be given
- _tokenOut: address of token to be received
- _amountIn: amount of tokenIn to swap
- Two values will be returned, the first is the amount out after fees, and the second is the fee amount
- The fee amount will be in terms of tokenOut
Tokens have a usdgAmount in the Vault contract used for some calculations, this amount is updated on minting of GLP, redemption of GLP and swaps based on the price of the token at the time. Due to price fluctuations this value may drift slightly from the actual USD value of the tokens in the pool, the usdgAmount is periodically updated to re-align values.
Query Available Amounts
The maximum sum of all position sizes is limited by the amount of tokens there are in the pool and any additional caps.
To calculate the available amount of liquidity for long positions:
- indexToken: the address of the token to long
- Available amount in tokens: Vault.poolAmounts(indexToken) - Vault.reservedAmounts(indexToken)
- Available amount in USD: PositionRouter.maxGlobalLongSizes(indexToken) - Vault.guaranteedUsd(indexToken)
- The available liquidity will be the lower of these two values
- PositionRouter.maxGlobalLongSizes(indexToken) can be zero, in which case there is no additional cap, and available liquidity is based only on the available amount of tokens
To calculate the available amount of liquidity for short positions:
- indexToken: the address of the token to short
- collateralToken: the address of the stablecoin token to be used as collateral
- Available amount in tokens: Vault.poolAmounts(collateralToken) - Vault.reservedAmounts(collateralToken)
- Available amount in USD: PositionRouter.maxGlobalShortSizes(indexToken) - Vault.globalShortSizes(indexToken)
- The available liquidity will be the lower of these two values
- PositionRouter.maxGlobalShortSizes(indexToken) can be zero, in which case there is no additional cap, and available liquidity is based only on the available amount of tokens
Opening / Increasing a Position
To open or increase the size of an existing position:
- Approve the PositionRouter as a Router plugin for your account
- Router.approvePlugin(PositionRouter address)
- Approve the Router contract for the token and amount you would deposit as collateral for the position
- Call PositionRouter.createIncreasePosition with parameters:
- _path: [collateralToken] or [tokenIn, collateralToken] if a swap is needed
- _indexToken: the address of the token you want to long or short
- _amountIn: the amount of tokenIn you want to deposit as collateral
- _minOut: the min amount of collateralToken to swap for
- _sizeDelta: the USD value of the change in position size
- _isLong: whether to long or short
- _acceptablePrice: the USD value of the max (for longs) or min (for shorts) index price acceptable when executing the request
- _executionFee: can be set to PositionRouter.minExecutionFee
- _referralCode: referral code for affiliate rewards and rebates
- _callbackTarget: an optional callback contract, this contract will be called on request execution or cancellation
- After this transaction is sent a keeper will execute the request, the request will either be executed or cancelled
- If the position cannot be increased for reasons such as the _acceptablePrice not being fulfillable or there being insufficient liquidity then the request will be cancelled and funds will be sent back to the msg.sender that called PositionRouter.createIncreasePosition
- _minOut can be zero if no swap is required
- USD values for _sizeDelta and _price are multiplied by (10 ** 30), so for example to open a long position of size 1000 USD, the value 1000 * (10 ** 30) should be used
Note that if the position increase request is created with PositionRouter.createIncreasePositionETH then in case of cancellation the PositionRouter would send ETH to the receiver using receiver.send(amount)
, this has a limit of 2300 gas. If the receiver is a contract and the receive function invoked on the transfer requires more than 2300 gas, this call can fail which would cause the ETH to be left in the PositionRouter.
WETH and PositionRouter.createIncreasePosition should be used instead to avoid this issue.
Closing / Decreasing a Position
To close or decrease an existing position:
- Call PositionRouter.createDecreasePosition with parameters:
- _path: [collateralToken] or [collateralToken, tokenOut] if a swap is needed
- _indexToken: the index token of the position
- _collateralDelta: the amount of collateral in USD value to withdraw
- _sizeDelta: the USD value of the change in position size
- _isLong: whether the position is a long or short
- _receiver: the address to receive the withdrawn tokens
- _acceptablePrice: the USD value of the min (for longs) or max (for shorts) index price acceptable when executing the request
- _minOut: the min output token amount
- _executionFee: can be set to PositionRouter.minExecutionFee
- _withdrawETH: only applicable if WETH will be withdrawn, the WETH will be unwrapped to ETH if this is set to true
- _callbackTarget: an optional callback contract, this contract will be called on request execution or cancellation
- After this transaction is sent a keeper will execute the request, the request will either be executed or cancelled
- If the position cannot be decreased for reasons such as the _acceptablePrice not being fulfillable then the request will be cancelled and there will be no change to the position
- _minOut can be zero if no swap is required
Note that if _withdrawETH
is true
then the position decrease request would send ETH to the receiver using receiver.send(amount)
, this has a limit of 2300 gas. If the receiver is a contract and the receive function invoked on the transfer requires more than 2300 gas, this call can fail which would cause the ETH to be left in the PositionRouter.
Set _withdrawETH
to false
instead to avoid this issue.
Positions List
A list of position details can be retrieved by calling Reader.getPositions with params:
- _vault: the vault contract address
- _account: the account of the user
- _collateralTokens: an array of collateralTokens
- _indexTokens: an array of indexTokens
- _isLong: an array of whether the position is a long position
The returned positions will be in the order of the query, for example, given the following inputs:
- _collateralTokens: [WBTC.address, WETH.address, USDC.address]
- _indexTokens: [WBTC.address, WETH.address, WBTC.address]
- _isLong: [true, true, false]
The position details would be returned for:
- Long BTC position, positionIndex: 0
- Long ETH position, positionIndex: 1
- Short BTC position, positionIndex: 2
The returned array would be a list of values ordered by the positions:
- size
- position size in USD
- value at: positionIndex * 9
- collateral
- position collateral in USD
- value at: positionIndex * 9 + 1
- averagePrice
- average entry price of the position in USD
- value at: positionIndex * 9 + 2
- entryFundingRate
- a snapshot of the cumulative funding rate at the time the position was entered
- value at: positionIndex * 9 + 3
- hasRealisedProfit
- 1 if the position has a positive realised profit, 0 otherwise
- value at: positionIndex * 9 + 4
- realisedPnl
- the realised PnL for the position in USD
- value at: positionIndex * 9 + 5
- lastIncreasedTime
- timestamp of the last time the position was increased
- value at: positionIndex * 9 + 6
- hasProfit
- 1 if the position is currently in profit, 0 otherwise
- value at: positionIndex * 9 + 7
- delta
- amount of current profit or loss of the position in USD
- value at: positionIndex * 9 + 8
Buying / Selling GLP
Buying and selling GLP can be done through the GlpRewardRouter.
To buy GLP, call mintAndStakeGlp with params:
- _token: the token to buy GLP with
- _amount: the amount of token to use for the purchase
- _minUsdg: the minimum acceptable USD value of the GLP purchased
- _minGlp: the minimum acceptable GLP amount
To sell GLP, call unstakeAndRedeemGlp with params:
- _tokenOut: the token to sell GLP for
- _glpAmount: the amount of GLP to sell
- _minOut: the minimum acceptable amount of tokenOut to be received
- _receiver: the address to send tokenOut to
Note that GLP can only be redeemed up to the reservedAmount, which is based on the amount of open interest, if the pool has been fully redeemed up to the reservedAmount then redeemers will need to wait for positions to close before further redemptions can be done, in this scenario the borrowing fee APR would be very high so liquidity providers will be incentivised to mint GLP and traders will be incentivised to close positions
GLP Price
The price of GLP can be retrieved using the getPrice(_maximise)
function of the GlpManager.
- To get the price of GLP for buying GLP use
GlpManager.getPrice(true)
- To get the price of GLP for selling GLP use
GlpManager.getPrice(false)
The price of GLP factors in the pending PnL of the open long and short positions.
If you are calculating the pending PnL for shorts manually please use the glpManager.shortsTracker.globalShortAveragePrices
value instead of the vault.globalShortAveragePrices
value.
Transferring Staked GLP
When GLP is bought it is automatically staked and when it is sold it is automatically unstaked, for integrations adding GLP the StakedGlp contract can be used to transfer staked GLP tokens.
StakedGlp behaves like a regular ERC20 token, the user can call approve on it to approve your contract, then your contract can call transferFrom to transfer the GLP tokens to any receiving account or contract. When transferring, the StakedGlp contract will unstake GLP from the user and stake the GLP for the receiving account, the receiving account or contract would then start earning rewards which can be compounded or claimed by calling handleRewards on the RewardRouter contract.
Staking
The RewardRouter contract handles the necessary actions needed for staking in a single transaction.
When staking GMX:
- The RewardRouter deposits the GMX token into the StakedGmxTracker contract
- The StakedGmxTracker issues itself as a token for each token deposited
- esGMX can similarly be deposited into the StakedGmxTracker
- The StakedGmxTracker distributes esGMX to staked tokens
- After this step, the RewardRouter deposits the StakedGmxTracker tokens into the BonusGmxTracker
- The BonusGmxTracker distributes Multiplier Points to staked tokens
- Finally the BonusGmxTracker tokens are deposited into the FeeGmxTracker which distributes ETH or AVAX to staked tokens
When buying GLP:
- The RewardRouter sends the funds to be deposited to the GlpManager and mints GLP tokens
- The RewardRouter then deposits the GLP tokens to the FeeGlpTracker which distributes ETH or AVAX to the staked tokens
- Finally the RewardRouter deposits the FeeGlpTracker tokens into the StakedGlpTracker which distributes esGMX to staked tokens
Addresses for contracts can be found in the interface code.
To get the deposit balances for an account you can use RewardTracker.depositBalances(account, token), or RewardReader.getDepositBalances(account, depositTokens, rewardTrackers).
To get claimable rewards you can use RewardReader.getStakingInfo(account rewardTrackers), this returns an array of uint256 values in the order:
- Claimable rewards
- Amount of reward token distribution per second
- Average staked amount for account
- Total rewards distributed to account
- Total staked tokens in the rewardTracker
Transferring a Referral Code
Steps to transfer a referral code:
- Get the hash of the referral code using an online tool like DEVoven with the "Append Zeros" option checked, e.g. the hash of "code" would be "0x636f646500000000000000000000000000000000000000000000000000000000"
- Open the ReferralStorage contract in a block explorer, using the ReferralStorage links above
- Navigate to the "Write Contract" tab
- Click "Connect to Web3" and connect the account that owns the referral code
- Click on "setCodeOwner" and key in the hash from step 1 for code, key in the new owner address for newAccount
- Click on "Write" to send the transaction