1. GMX v2 Architecture vs v1
GMX launched on Arbitrum in 2021 as one of the first on-chain perpetuals DEXes with deep liquidity, quickly becoming one of the largest DeFi protocols by TVL. The original GLP model (v1) pooled all assets into a single multi-asset liquidity pool that acted as counterparty to all trades. GMX v2 (launched 2023) introduced a fundamentally different architecture with isolated GM pools.
GMX v1 (GLP Model)
In GMX v1, liquidity providers deposit into a single pool (GLP) containing a basket of assets: ETH, BTC, LINK, UNI, USDC, USDT, DAI, FRAX. GLP tokens represent a share of this multi-asset pool. LPs earn trading fees + borrowing fees from traders, but also bear the PnL of all traders as the counterparty. In bull markets where traders profit, GLP holders lose relative to holding the underlying assets; in sideways/bear markets, GLP performs well as traders collectively lose.
GMX v2 (GM Pool Model)
GMX v2 introduced GM (GMX Market) pools — isolated liquidity pools per trading pair. Each market (e.g., BTC/USD, ETH/USD) has its own GM pool with two assets: a long token (e.g., WBTC) and a short token (usually USDC). This isolation means:
- LPs in BTC GM pool are only exposed to BTC trader PnL, not ETH trader PnL.
- Each market can have different fee structures, OI limits, and funding parameters set by governance.
- Synthetic markets (e.g., DOGE/USD) can exist without requiring DOGE as collateral — they use ETH or BTC as collateral with oracle price feeds for the synthetic asset.
2. GM Pools vs GLP: A Deep Dive
Understanding the difference between GLP and GM pools is essential for agents that want to act as LPs on GMX, not just traders.
GLP (v1) LP Mechanics
- GLP is a basket token containing BTC, ETH, LINK, UNI, stablecoins (USDC, USDT, DAI, FRAX)
- Target weights are set by governance; fees incentivize rebalancing toward targets
- LP yield comes from: 70% of trading fees + 70% of borrow fees, distributed as esGMX and ETH/AVAX
- Historical APR: 15–30% in high-volume periods, near 0% in low-volume periods
- Withdrawal fee: 0.1–0.8% depending on pool balance relative to target weights
GM Pool (v2) LP Mechanics
- Each GM pool holds two assets: long token (e.g., WETH) and short token (USDC)
- LPs receive GM tokens representing their proportional share of the pool
- GM LP yield comes from: position fees, borrow fees, funding fees collected from traders
- Risk: LPs absorb trader PnL for that specific market only (isolated risk)
- Deposit/withdrawal via 2-minute delayed execution (MEV protection) with a small execution fee
- Skew incentives: fees are cheaper for deposits that balance the long/short token ratio
import requests
# GMX v2 subgraph on Arbitrum
GRAPH_URL = "https://subgraph.satsuma-prod.com/gmx/synthetics-arbitrum-stats/api"
def get_gm_pool_info(market_token: str) -> dict:
query = """
query GetMarket($market: String!) {
marketInfo(id: $market) {
longTokenAmount
shortTokenAmount
longTokenUsd
shortTokenUsd
totalBorrowingFees
netPnl
marketToken {
id
totalSupply
}
}
}
"""
resp = requests.post(
GRAPH_URL,
json={"query": query, "variables": {"market": market_token}}
)
return resp.json()["data"]["marketInfo"]
# ETH/USD GM market token address on Arbitrum
ETH_GM_MARKET = "0x70d95587d40a2caf56bd97485ab3eec10bee6336"
pool = get_gm_pool_info(ETH_GM_MARKET)
print(f"ETH GM pool: ${float(pool['longTokenUsd']) + float(pool['shortTokenUsd']):,.0f} TVL")
print(f"Net PnL vs LPs: ${float(pool['netPnl']):,.0f}")
3. Fee Structure for Agents
GMX v2 has a more complex fee structure than pure order-book venues. Understanding all fee components is critical for agents to correctly calculate profitability:
| Fee Type | Rate | Notes |
|---|---|---|
| Open/Close position fee | 0.05–0.07% | Based on position size. 0.05% for BTC/ETH, up to 0.07% for smaller markets. |
| Price impact | Variable | Positive or negative. Pays you for balancing OI, charges you for skewing it. |
| Borrowing fee | ~0.01%/hour | Paid by traders holding open positions (per side, per hour) |
| Funding fee | Variable | Transfers between longs and shorts based on OI imbalance. Can be positive (you receive) or negative. |
| Swap fee (LP entry) | 0.05–0.07% | Fee to enter/exit GM LP positions |
| Execution fee | ~$0.50 | Gas cost for 2-min delayed execution on GM deposits |
Funding Rate Mechanics
GMX v2 uses a funding rate that transfers between longs and shorts hourly. The rate is determined by the ratio of open interest on each side:
def compute_funding_rate(long_oi: float, short_oi: float,
funding_factor: float = 0.0001) -> dict:
"""
Returns hourly funding rates for longs and shorts.
Positive rate = you pay. Negative rate = you receive.
"""
total_oi = long_oi + short_oi
if total_oi == 0:
return {"long_rate": 0, "short_rate": 0}
imbalance = (long_oi - short_oi) / total_oi # -1 to +1
funding_rate = imbalance * funding_factor # hourly rate
if funding_rate > 0:
# Longs pay shorts
return {
"long_rate": funding_rate,
"short_rate": -funding_rate * (long_oi / short_oi)
}
else:
# Shorts pay longs
return {
"long_rate": funding_rate * (short_oi / long_oi),
"short_rate": -funding_rate
}
# Example: ETH market with $50M long OI, $30M short OI
rates = compute_funding_rate(50_000_000, 30_000_000)
print(f"Long hourly rate: {rates['long_rate']*100:.4f}%") # positive = longs pay
print(f"Short hourly rate: {rates['short_rate']*100:.4f}%") # negative = shorts receive
4. Opening and Closing Positions
GMX v2 positions are managed via smart contract interactions. Unlike Hyperliquid or dYdX where transactions are fast, GMX uses a 2-step execution pattern: you submit an order request, then an external keeper executes it (typically within 1–3 blocks). This prevents latency-based front-running but means agents must handle the asynchronous execution pattern.
Python Setup with web3.py
pip install web3 eth-abi requests python-dotenv
from web3 import Web3
from eth_account import Account
import json, time, requests
# Arbitrum One RPC
w3 = Web3(Web3.HTTPProvider("https://arb1.arbitrum.io/rpc"))
account = Account.from_key("0xYOUR_PRIVATE_KEY")
# GMX v2 ExchangeRouter contract on Arbitrum
EXCHANGE_ROUTER = "0x7C68C7866A64FA2160F78EEaE12217FFbf871fa8"
ORDER_VAULT = "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5"
USDC_ADDRESS = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
# Minimal ABI for creating orders
ROUTER_ABI = json.loads("""[
{
"inputs": [
{
"components": [
{"name": "receiver", "type": "address"},
{"name": "callbackContract", "type": "address"},
{"name": "uiFeeReceiver", "type": "address"},
{"name": "market", "type": "address"},
{"name": "initialCollateralToken", "type": "address"},
{"name": "swapPath", "type": "address[]"},
{"name": "sizeDeltaUsd", "type": "uint256"},
{"name": "initialCollateralDeltaAmount", "type": "uint256"},
{"name": "triggerPrice", "type": "uint256"},
{"name": "acceptablePrice", "type": "uint256"},
{"name": "executionFee", "type": "uint256"},
{"name": "callbackGasLimit", "type": "uint256"},
{"name": "minOutputAmount", "type": "uint256"},
{"name": "orderType", "type": "uint8"},
{"name": "decreasePositionSwapType", "type": "uint8"},
{"name": "isLong", "type": "bool"},
{"name": "shouldUnwrapNativeToken", "type": "bool"},
{"name": "referralCode", "type": "bytes32"}
],
"name": "params",
"type": "tuple"
}
],
"name": "createOrder",
"outputs": [{"name": "", "type": "bytes32"}],
"type": "function"
}
]""")
router = w3.eth.contract(address=EXCHANGE_ROUTER, abi=ROUTER_ABI)
def create_market_increase_order(
market: str, # GM market token address
is_long: bool,
collateral_usdc: float, # USDC collateral amount
size_usd: float, # position size in USD
current_price: float,
slippage_bps: float = 50 # 0.5% slippage tolerance
) -> str:
collateral_wei = int(collateral_usdc * 1e6) # USDC has 6 decimals
size_wei = int(size_usd * 1e30) # GMX uses 1e30 for USD
execution_fee = w3.to_wei(0.0003, "ether") # ~$0.50 ETH execution fee
# Acceptable price: add slippage for longs (buy), subtract for shorts (sell)
slippage_mult = 1 + (slippage_bps / 10000) if is_long else 1 - (slippage_bps / 10000)
acceptable_price = int(current_price * slippage_mult * 1e30)
order_params = (
account.address, # receiver
"0x0000000000000000000000000000000000000000", # callbackContract
"0x0000000000000000000000000000000000000000", # uiFeeReceiver
market, # market token address
USDC_ADDRESS, # initialCollateralToken
[], # swapPath (empty)
size_wei, # sizeDeltaUsd
collateral_wei, # initialCollateralDeltaAmount
0, # triggerPrice (0 = market order)
acceptable_price, # acceptablePrice
execution_fee, # executionFee
0, # callbackGasLimit
0, # minOutputAmount
2, # orderType: MarketIncrease = 2
0, # decreasePositionSwapType
is_long,
False, # shouldUnwrapNativeToken
b"\x00" * 32 # referralCode
)
tx = router.functions.createOrder(order_params).build_transaction({
"from": account.address,
"value": execution_fee,
"gas": 500000,
"gasPrice": w3.eth.gas_price,
"nonce": w3.eth.get_transaction_count(account.address)
})
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
print(f"Order submitted: {tx_hash.hex()}")
return tx_hash.hex()
5. Liquidation Risk Management
GMX v2 liquidation mechanics differ from traditional perps. Rather than a sudden liquidation at a fixed price, GMX uses a health factor calculation that considers position PnL, funding fees accrued, borrow fees, and the current collateral value.
Liquidation Price Calculation
def compute_gmx_liq_price(
entry_price: float,
collateral_usd: float,
size_usd: float,
is_long: bool,
min_collateral_factor: float = 0.01, # 1% min collateral ratio
position_fee_factor: float = 0.0006, # 0.06% open fee
) -> float:
"""
Approximate liquidation price for a GMX v2 position.
Actual liq price includes funding/borrow fees accumulated.
"""
leverage = size_usd / collateral_usd
close_fee = size_usd * position_fee_factor
min_col = size_usd * min_collateral_factor
# Maximum loss before liquidation
max_loss = collateral_usd - min_col - close_fee
price_move_pct = max_loss / size_usd
if is_long:
liq_price = entry_price * (1 - price_move_pct)
else:
liq_price = entry_price * (1 + price_move_pct)
return liq_price
# Example: Long 1 ETH at $3,000, 5x leverage ($600 collateral, $3000 position)
liq = compute_gmx_liq_price(
entry_price=3000,
collateral_usd=600,
size_usd=3000,
is_long=True
)
print(f"Approximate liquidation price: ${liq:.2f}")
# Output: ~$2,400 (5x leverage, ~20% move liquidates)
6. Synthetic Asset Markets
One of GMX v2's most innovative features is synthetic markets — perpetual positions on assets that are not actually held in the GM pool. For example, a DOGE/USD synthetic market can be created where the long token is WETH and the short token is USDC. Traders get DOGE price exposure, while liquidity providers hold ETH/USDC.
How Synthetic Markets Work
- Price feed: GMX uses Chainlink oracles (with Pyth as a backup) to price the synthetic asset. For DOGE/USD, the oracle reports DOGE price; the PnL is settled in USDC or WETH.
- Collateral: Traders deposit USDC (for shorts) or WETH (for longs) regardless of the synthetic asset being traded.
- Settlement: When a position is closed, the PnL in synthetic asset USD terms is converted to the collateral token at current rates and paid out.
- Available synthetics: As of 2026, GMX v2 supports synthetic markets for DOGE, LTC, XRP, ATOM, NEAR, and others — long-tail assets that would be too illiquid to back with their native tokens.
import requests
GMX_API = "https://arbitrum-api.gmxinfra.io"
def get_synthetic_markets() -> list:
resp = requests.get(f"{GMX_API}/prices/tickers")
tickers = resp.json()
synthetics = []
for t in tickers:
# Synthetic markets: indexToken != longToken
if t.get('indexToken') != t.get('longToken') and t.get('isMarketDisabled') == False:
synthetics.append({
"market": t['marketToken'],
"symbol": t['indexTokenSymbol'],
"longOI": float(t['longInterestInTokens']),
"shortOI": float(t['shortInterestInUsd']) / float(t['midPrice']),
"midPrice": float(t['midPrice']) / 1e30
})
return synthetics
markets = get_synthetic_markets()
for m in markets[:5]:
print(f"{m['symbol']:8s} ${m['midPrice']:12.4f} | Long OI: {m['longOI']:.1f} | Short OI: {m['shortOI']:.1f}")
7. Complete GMX Python Agent
The following example implements a price-impact harvesting agent — it monitors GMX markets for OI imbalance and opens positions on the underrepresented side to earn the positive price impact credit:
import asyncio, requests, time
from web3 import Web3
from eth_account import Account
GMX_API = "https://arbitrum-api.gmxinfra.io"
TARGET_IMPACT_BPS = 5 # minimum +0.05% price impact to trigger entry
MAX_POSITION_USD = 2000
MIN_OI_SKEW = 0.30 # OI imbalance must be >30% for one side
def get_market_metrics() -> list:
resp = requests.get(f"{GMX_API}/prices/tickers", timeout=10)
tickers = resp.json()
metrics = []
for t in tickers:
if t.get('isMarketDisabled'):
continue
long_oi = float(t.get('longInterestUsd', 0))
short_oi = float(t.get('shortInterestUsd', 0))
total_oi = long_oi + short_oi
if total_oi < 100_000: # skip small markets
continue
skew = (long_oi - short_oi) / total_oi # +1=all long, -1=all short
metrics.append({
"symbol": t['indexTokenSymbol'],
"market": t['marketToken'],
"skew": skew,
"price": float(t['midPrice']) / 1e30,
"long_oi": long_oi,
"short_oi": short_oi
})
return sorted(metrics, key=lambda x: abs(x["skew"]), reverse=True)
async def hunt_impact_credits(agent):
"""Find markets with strong OI skew and open positions on underrepresented side."""
markets = get_market_metrics()
opportunities = []
for m in markets:
if abs(m["skew"]) > MIN_OI_SKEW:
# Longs dominant: open short (positive impact for adding short OI)
if m["skew"] > 0:
opportunities.append({**m, "side": "SHORT", "is_long": False})
# Shorts dominant: open long (positive impact for adding long OI)
else:
opportunities.append({**m, "side": "LONG", "is_long": True})
if not opportunities:
print("No impact harvesting opportunities found")
return
best = opportunities[0]
print(f"Best opportunity: {best['symbol']} {best['side']}")
print(f" OI skew: {best['skew']:+.1%} | Price: ${best['price']:,.2f}")
print(f" Long OI: ${best['long_oi']:,.0f} | Short OI: ${best['short_oi']:,.0f}")
collateral = min(MAX_POSITION_USD / 3, 500) # 3x leverage, max $500 collateral
size = collateral * 3
# Note: create_market_increase_order defined in previous example
tx = create_market_increase_order(
market=best['market'],
is_long=best['is_long'],
collateral_usdc=collateral,
size_usd=size,
current_price=best['price']
)
print(f"Position opened: {tx}")
async def main():
# agent setup omitted for brevity — see prior section
while True:
await hunt_impact_credits(agent=None)
await asyncio.sleep(300) # check every 5 minutes
asyncio.run(main())
8. GLP Liquidity Provision as a Passive Agent Strategy
For agents that do not want to actively manage perpetual positions, providing liquidity via GLP (v1) or GM pools (v2) is an alternative income stream. The key decision is understanding the risk exposure:
- + Simple: one token, broad exposure
- + Higher fee yield in high-volume periods
- - Exposed to all trader PnL (unlimited tail risk)
- - Cannot isolate risk per asset
- - Available: Arbitrum, Avalanche
- + Isolated risk per market
- + 63% of trading fees + funding fees
- + Choose markets with favorable OI balance
- - More complex (deposit delay, two-token exposure)
- - Available: Arbitrum, Avalanche
import requests
def get_glp_apy() -> float:
"""Fetch current annualized GLP APR from GMX stats API."""
resp = requests.get(
"https://api.gmx.io/api/earned",
params={"chainId": 42161} # Arbitrum
)
data = resp.json()
# feesEarnedUsd is 24h fees, totalSupplyUsd is pool TVL
daily_fee_yield = data['feesEarnedUsd'] / data['totalSupplyUsd']
apr = daily_fee_yield * 365 * 100
return apr
def get_gm_pool_aprs() -> list:
"""Get estimated APRs for top GM pools."""
resp = requests.get("https://arbitrum-api.gmxinfra.io/markets", timeout=10)
if not resp.ok:
return []
markets = resp.json().get('markets', [])
results = []
for m in markets[:10]:
results.append({
"name": m.get('name', ''),
"apr_estimate": float(m.get('apr', 0))
})
return sorted(results, key=lambda x: x['apr_estimate'], reverse=True)
glp_apr = get_glp_apy()
print(f"GLP APR: {glp_apr:.1f}%")
gm_aprs = get_gm_pool_aprs()
print("Top GM pools by APR:")
for pool in gm_aprs[:5]:
print(f" {pool['name']:20s} {pool['apr_estimate']:.1f}%")
9. GMX v2 vs Purple Flea Perpetuals
GMX v2 is a strong choice for agents that want on-chain, non-custodial perpetuals trading with LP income opportunities. Purple Flea takes a different approach, offering a broader perpetual market selection with native AI agent tooling:
| Feature | GMX v2 | Purple Flea |
|---|---|---|
| Perpetual markets | 50+ (incl. synthetics) | 275+ perpetuals |
| Open/close fee | 0.05–0.07% | 0.05% maker, flat |
| Execution speed | 2–30 second delay (keeper) | Near-instant |
| LP income opportunities | Yes (GLP + GM pools) | Referral program (15% fee share) |
| Onboarding | Requires ETH for gas + bridge | Free faucet, no gas |
| MCP native tools | No | Yes |
| Synthetic assets | Yes (DOGE, LTC, XRP etc.) | Yes (275+ markets including long-tail) |
| Non-custodial | Yes (fully on-chain) | Yes |
| Cross-service suite | No (perps + LP only) | 6 services: casino, wallet, escrow, trading, domains, faucet |
| Referral income | Standard one-level | 3-level, up to 15% of fees |
Combining GMX and Purple Flea
Sophisticated agents can run strategies across both venues simultaneously:
- GM LP + Purple Flea hedge: Provide GMX v2 GM pool liquidity (earning fees) while delta-hedging the pool's net exposure via Purple Flea perpetuals — neutralizing the counterparty PnL risk of LP while retaining the fee income.
- Synthetic arb: GMX synthetic markets (e.g., DOGE/USD) often trade at different prices than Purple Flea's DOGE-PERP market due to oracle vs spot price differences. Agents can monitor this spread and arbitrage when it exceeds both platforms' combined fees.
- Capital efficiency: Keep GMX LP as the base yield layer (10–20% APR passive), and deploy excess returns into Purple Flea casino or escrow for additional yield.
275+ Perpetual Markets for AI Agents
Trade more markets, faster, with MCP-native tools. Free faucet for new agents. 3-level referral income. No bridge required.
Register Your Agent Trading Docs