gmx-trading

Trade perpetuals and swap tokens on GMX V2 — a decentralized exchange with oracle-based pricing on Arbitrum, Avalanche, and Botanix. Supports market/limit/stop orders, leverage up to 100x, and programmable position management via TypeScript SDK or REST API.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "gmx-trading" with this command: npx skills add gmx-io/gmx-ai/gmx-io-gmx-ai-gmx-trading

GMX Trading Skill

Overview

GMX V2 is a decentralized perpetual and spot exchange using oracle-based pricing (Chainlink Data Streams) instead of AMM curves. Traders get CEX-like execution with onchain settlement.

Supported chains:

ChainChain IDNative Token
Arbitrum42161ETH
Avalanche43114AVAX
Botanix3637BTC

Two integration paths:

  • SDK (@gmx-io/sdk) — Full read + write: fetch markets, create orders, manage positions
  • REST API — Read-only: prices, markets, positions, trade history

Trading modes:

  • Classic — User signs each transaction directly
  • Express — Gelato relay pays gas, user signs EIP-712 message (frontend only)
  • Express + One-Click — Subaccount delegates signing for instant execution (frontend only)

SDK Quick Start

Install the SDK:

npm install @gmx-io/sdk viem

Import note: The SDK's ESM build has broken imports (missing file extensions). Use CommonJS require or configure your bundler to resolve extensionless imports. In Node.js scripts, use const { GmxSdk } = require("@gmx-io/sdk").

Create an SDK instance:

const { GmxSdk } = require("@gmx-io/sdk");

const sdk = new GmxSdk({
  chainId: 42161,
  rpcUrl: "https://arb1.arbitrum.io/rpc",
  oracleUrl: "https://arbitrum-api.gmxinfra.io",
  subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql",
});

Chain configuration:

ChainchainIdoracleUrlsubsquidUrl
Arbitrum42161https://arbitrum-api.gmxinfra.iohttps://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql
Avalanche43114https://avalanche-api.gmxinfra.iohttps://gmx.squids.live/gmx-synthetics-avalanche:prod/api/graphql
Botanix3637https://botanix-api.gmxinfra.iohttps://gmx.squids.live/gmx-synthetics-botanix:prod/api/graphql

Set up a wallet for write operations:

const { createWalletClient, http } = require("viem");
const { privateKeyToAccount } = require("viem/accounts");
const { arbitrum } = require("viem/chains");

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const walletClient = createWalletClient({
  account,
  chain: arbitrum,
  transport: http("https://arb1.arbitrum.io/rpc"),
});

const sdk = new GmxSdk({
  chainId: 42161,
  rpcUrl: "https://arb1.arbitrum.io/rpc",
  oracleUrl: "https://arbitrum-api.gmxinfra.io",
  subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql",
  account: account.address,
  walletClient,
});

Lightweight alternative (read-only, no RPC needed):

const { GmxApiSdk } = require("@gmx-io/sdk/v2");

const apiSdk = new GmxApiSdk({ chainId: 42161 });
const markets = await apiSdk.fetchMarketsInfo(); // Returns array-like of MarketInfo objects
// Access: markets[0].marketTokenAddress, markets[0].indexTokenAddress, etc.

Order Helpers

The SDK provides convenience methods that handle amount calculation and transaction submission automatically.

Important: Never hardcode market or token addresses. Always fetch them dynamically — addresses differ per chain and can change between deployments.

Step 1: Resolve market and token addresses

// Fetch all markets and tokens
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();

// Find ETH/USD market by index token symbol
// Note: On Arbitrum, perpetual markets use "WETH" as the index token symbol.
// Markets with symbol "ETH" are spot-only swap pools.
const ethUsdMarket = Object.values(marketsInfoData).find(
  (m) => tokensData[m.indexTokenAddress]?.symbol === "WETH" && !m.isSpotOnly
);

// Get token addresses from market
const marketAddress = ethUsdMarket.marketTokenAddress;
const longToken = ethUsdMarket.longTokenAddress;   // e.g., WETH
const shortToken = ethUsdMarket.shortTokenAddress;  // e.g., USDC

// Find a token by symbol
const usdcAddress = Object.values(tokensData).find((t) => t.symbol === "USDC")?.address;

Step 2: Place orders

Open a long position:

await sdk.orders.long({
  marketAddress,                         // from step 1
  payTokenAddress: usdcAddress,          // token you're paying with
  collateralTokenAddress: longToken,     // WETH as collateral for longs
  payAmount: 100000000n,                 // 100 USDC (6 decimals)
  leverage: 50000n,                      // 5x leverage (basis points)
  allowedSlippageBps: 100,               // 1% slippage
});

Open a short position:

await sdk.orders.short({
  marketAddress,
  payTokenAddress: usdcAddress,
  collateralTokenAddress: shortToken,    // USDC as collateral for shorts
  payAmount: 100000000n,
  leverage: 50000n,
});

Swap tokens:

const arbAddress = Object.values(tokensData).find((t) => t.symbol === "ARB")?.address;
const linkAddress = Object.values(tokensData).find((t) => t.symbol === "LINK")?.address;

await sdk.orders.swap({
  fromTokenAddress: arbAddress,
  toTokenAddress: linkAddress,
  fromAmount: 1000000000000000000n, // 1 ARB (18 decimals)
  allowedSlippageBps: 100,
});

Limit orders — add limitPrice for positions or triggerPrice for swaps:

await sdk.orders.long({
  marketAddress,
  payTokenAddress: usdcAddress,
  collateralTokenAddress: longToken,
  payAmount: 100000000n,
  leverage: 50000n,
  limitPrice: 3000000000000000000000000000000000n, // $3000 (30 decimals)
});

Key parameters:

  • leverage — In basis points: 10000n = 1x, 50000n = 5x, 1000000n = 100x
  • allowedSlippageBps — Default 100 (1%). Range: 1-500
  • payAmount — Pay this much collateral. Alternative: use sizeAmount to specify position size
  • fromAmount / toAmount — For swaps, specify input or desired output amount

Step 3: Close a position

There is no convenience close() method. Closing requires computing decrease amounts via getDecreasePositionAmounts() from @gmx-io/sdk/utils/trade, then calling createDecreaseOrder().

Important: Always re-fetch marketsInfoData and tokensData right before closing. These contain oracle prices that go stale within seconds — using old data produces an acceptablePrice the keeper will reject.

const { getDecreasePositionAmounts } = require("@gmx-io/sdk/utils/trade");

// 1. Fetch FRESH market data (prices go stale quickly)
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();

// 2. Get the position to close
const positionsInfo = await sdk.positions.getPositionsInfo({
  marketsInfoData, tokensData, showPnlInLeverage: false,
});
const position = Object.values(positionsInfo).find(
  (p) => p.marketAddress === marketAddress && p.isLong === true
);

// 3. Compute decrease amounts
const marketInfo = marketsInfoData[position.marketAddress];
const collateralToken = tokensData[position.collateralTokenAddress];
const { minCollateralUsd, minPositionSizeUsd } = await sdk.positions.getPositionsConstants();
const uiFeeFactor = await sdk.utils.getUiFeeFactor();

const decreaseAmounts = getDecreasePositionAmounts({
  marketInfo,
  collateralToken,
  isLong: position.isLong,
  position,
  closeSizeUsd: position.sizeInUsd,   // Full close. Use a smaller value for partial close.
  keepLeverage: false,
  userReferralInfo: undefined,
  minCollateralUsd,
  minPositionSizeUsd,
  uiFeeFactor,
  isSetAcceptablePriceImpactEnabled: false,
});

// 4. Submit the decrease order
await sdk.orders.createDecreaseOrder({
  marketInfo,
  marketsInfoData,
  tokensData,
  isLong: position.isLong,
  allowedSlippage: 300,    // 3% — use higher slippage for decrease to avoid keeper rejection
  decreaseAmounts,
  collateralToken,
});

SDK Modules

ModuleKey MethodsDescription
sdk.marketsgetMarkets(), getMarketsInfo(), getDailyVolumes()Market data and liquidity info
sdk.tokensgetTokensData(), getTokensBalances()Token metadata, prices, balances
sdk.positionsgetPositions(), getPositionsInfo(), getPositionsConstants()Open position data
sdk.orderslong(), short(), swap(), getOrders(), cancelOrders()Order creation and management
sdk.tradesgetTradeHistory()Historical trade actions
sdk.utilsgetGasLimits(), getGasPrice(), getExecutionFee(), getUiFeeFactor()Gas and fee estimation
sdk.oraclegetTickers(), getMarkets(), getTokens()Direct oracle data access

Typical read flow:

const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();
const positionsInfo = await sdk.positions.getPositionsInfo({
  marketsInfoData, tokensData, showPnlInLeverage: false,
});
const { ordersInfoData } = await sdk.orders.getOrders({
  marketsInfoData, tokensData,
});

Convenience vs Low-level Methods

The SDK has two tiers for order creation:

Convenience methods — handle amount calculation, execution fee, and tx submission automatically. Use these for opening positions and swaps:

MethodPurposeKey Params
sdk.orders.long()Open long positionmarketAddress, payTokenAddress, collateralTokenAddress, payAmount, leverage
sdk.orders.short()Open short positionSame as long()
sdk.orders.swap()Swap tokensfromTokenAddress, toTokenAddress, fromAmount
sdk.orders.cancelOrders()Cancel pending ordersorderKeys: string[]

Low-level methods — require you to pre-compute amounts, provide full market/token objects, and handle execution fees. Required for closing positions (no convenience close() method exists):

MethodPurposeRequired Setup
sdk.orders.createIncreaseOrder()Open position (full control)IncreasePositionAmounts, marketInfo, tokensData
sdk.orders.createDecreaseOrder()Close/reduce positionDecreasePositionAmounts via getDecreasePositionAmounts()
sdk.orders.createSwapOrder()Swap (full control)SwapAmounts, swap path

Key gap: There is no sdk.orders.close(). To close a position, use getDecreasePositionAmounts() from @gmx-io/sdk/utils/trade + createDecreaseOrder(). See Step 3: Close a position for the full pattern.

Order Types

TypeEnumBehavior
MarketMarketSwap(0), MarketIncrease(2), MarketDecrease(4)Execute immediately at current oracle price
LimitLimitSwap(1), LimitIncrease(3), LimitDecrease(5)Execute when oracle price reaches trigger price
Stop IncreaseStopIncrease(8)Open position when price moves past trigger (breakout entry)
Stop-LossStopLossDecrease(6)Auto-close position to limit losses
LiquidationLiquidation(7)System-triggered when position falls below maintenance margin

Trigger conditions:

  • Long Limit: oracle price <= trigger price (buy the dip)
  • Long Stop-Loss: oracle price <= trigger price (exit on drop)
  • Long Take-Profit (LimitDecrease): oracle price >= trigger price (exit on rise)
  • Short Limit: oracle price >= trigger price (sell the rally)
  • Short Stop-Loss: oracle price >= trigger price (exit on rise)
  • Short Take-Profit (LimitDecrease): oracle price <= trigger price (exit on drop)

Auto-cancel limits: Maximum concurrent auto-cancel orders per position: 11 on Arbitrum, 6 on Avalanche and Botanix.

Sidecar orders: Stop-loss and take-profit orders can be attached to increase orders via createSltpEntries, cancelSltpEntries, and updateSltpEntries parameters in createIncreaseOrder().

TWAP orders: Split a large order into 2–30 parts executed over a configurable duration. TWAP utilities (getTwapDurationInSeconds, getIsValidTwapParams) are exported from @gmx-io/sdk/utils/twap but full TWAP order creation is only available via the frontend UI.

Fees

Position fees:

  • Opening/closing: 0.04% if the trade balances long/short OI, 0.06% if it imbalances
  • Applied to position size (notional value)

Swap fees:

  • Standard pairs: 0.05% (balancing) / 0.07% (imbalancing)
  • Stablecoin pairs: 0.005% (balancing) / 0.02% (imbalancing)

Funding rate:

  • Adaptive rate that flows from the larger open interest side to the smaller side
  • Rebalances long/short exposure over time
  • Accrues continuously, settled on position changes

Borrowing rate:

  • Kink model: low rate below utilization threshold, steep above
  • Approximately 45–55% APR at 75% utilization
  • Paid by all positions proportional to size

Execution fee:

  • Covers keeper gas cost for executing the order onchain
  • Paid upfront in native token (ETH/AVAX/BTC)
  • Surplus refunded after execution
  • Use sdk.utils.getExecutionFee() to estimate

REST API

Oracle Endpoints

Base URL: https://{network}-api.gmxinfra.io

EndpointMethodDescription
/prices/tickersGETCurrent min/max prices for all tokens
/prices/candlesGETOHLC price candles (?tokenSymbol=ETH&period=1h)
/signed_prices/latestGETSigned oracle prices for order execution
/tokensGETToken list with addresses and decimals
/marketsGETMarket configuration (index/long/short tokens)
/markets/infoGETExtended market info with pool sizes and utilization

All endpoints are served from the Oracle base URL above. The legacy gmx-api-{network}.gmx.io domain is no longer available.

GraphQL (Subsquid)

Base URL: https://gmx.squids.live/gmx-synthetics-{network}:prod/api/graphql

Example — fetch recent trade actions:

query {
  tradeActions(
    where: { account_eq: "0x..." }
    orderBy: timestamp_DESC
    limit: 10
  ) {
    id
    eventName
    orderType
    sizeDeltaUsd
    timestamp
    transactionHash
  }
}

Fallback URLs

ChainPrimaryFallback 1Fallback 2
Arbitrumarbitrum-api.gmxinfra.ioarbitrum-api-fallback.gmxinfra.ioarbitrum-api-fallback.gmxinfra2.io
Avalancheavalanche-api.gmxinfra.ioavalanche-api-fallback.gmxinfra.ioavalanche-api-fallback.gmxinfra2.io
Botanixbotanix-api.gmxinfra.iobotanix-api-fallback.gmxinfra.iobotanix-api-fallback.gmxinfra2.io

Key Concepts

Oracle-based pricing: GMX does not use an AMM. Prices come from Chainlink Data Streams, giving traders zero-slippage execution at the oracle price (subject to price impact from pool utilization).

Two-phase execution: Orders follow a create → execute pattern. The user submits an order transaction, then a keeper executes it with fresh oracle prices. This typically takes 1–5 seconds.

BigInt amounts: All amounts use BigInt. Prices are scaled to 30 decimals (1 USD = 10^30). Token amounts use their native decimals (e.g., USDC = 6, ETH = 18).

Stale data: marketsInfoData and tokensData contain oracle prices at fetch time. These go stale within seconds. Always re-fetch fresh data before operations that depend on current prices — especially createDecreaseOrder(), which computes acceptablePrice from the data you provide. Using stale prices causes keeper rejection.

Multicall batching: The SDK batches RPC calls automatically. Production chains use batchSize: 1024 * 1024 bytes per multicall with no waiting. This is configured per-chain in BATCH_CONFIGS.

GMX Account: Cross-chain trading from Ethereum, Base, or BNB Chain via LayerZero/Stargate bridge. Users can trade on Arbitrum/Avalanche without bridging manually.

Subaccounts: Delegate trading to a subaccount address for one-click trading. The subaccount can execute orders without requiring the main wallet signature each time.

Full Example: Open → Monitor → Close

End-to-end flow that opens a long position, monitors it, and closes it.

const { GmxSdk } = require("@gmx-io/sdk");
const { getDecreasePositionAmounts } = require("@gmx-io/sdk/utils/trade");
const { createWalletClient, http } = require("viem");
const { privateKeyToAccount } = require("viem/accounts");
const { arbitrum } = require("viem/chains");

// ─── Setup ───────────────────────────────────────────────────────────────────

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const sdk = new GmxSdk({
  chainId: 42161,
  rpcUrl: "https://arb1.arbitrum.io/rpc",
  oracleUrl: "https://arbitrum-api.gmxinfra.io",
  subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql",
  account: account.address,
  walletClient: createWalletClient({
    account, chain: arbitrum, transport: http("https://arb1.arbitrum.io/rpc"),
  }),
});

// ─── 1. Resolve addresses ───────────────────────────────────────────────────

const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();

const ethMarket = Object.values(marketsInfoData).find(
  (m) => tokensData[m.indexTokenAddress]?.symbol === "WETH" && !m.isSpotOnly
);
const marketAddress = ethMarket.marketTokenAddress;
const usdcAddress = Object.values(tokensData).find((t) => t.symbol === "USDC").address;

// ─── 2. Open long ───────────────────────────────────────────────────────────

await sdk.orders.long({
  marketAddress,
  payTokenAddress: usdcAddress,
  collateralTokenAddress: usdcAddress,
  payAmount: 10_000000n,   // 10 USDC
  leverage: 30000n,        // 3x
  allowedSlippageBps: 100,
  skipSimulation: true,
});

// ─── 3. Wait for position to appear (keeper executes in 1-30s) ──────────────

let position;
for (let i = 0; i < 40; i++) {
  await new Promise((r) => setTimeout(r, 3000));
  const info = await sdk.positions.getPositionsInfo({
    marketsInfoData, tokensData, showPnlInLeverage: false,
  });
  position = Object.values(info).find(
    (p) => p.marketAddress === marketAddress && p.isLong === true
  );
  if (position) break;
}
if (!position) throw new Error("Position did not appear within 120s");

console.log("Position opened:", {
  sizeUsd: position.sizeInUsd.toString(),
  leverage: position.leverage.toString(),
  entryPrice: position.entryPrice.toString(),
});

// ─── 4. Close position (re-fetch fresh data first!) ─────────────────────────

const fresh = await sdk.markets.getMarketsInfo();
const freshPositions = await sdk.positions.getPositionsInfo({
  marketsInfoData: fresh.marketsInfoData,
  tokensData: fresh.tokensData,
  showPnlInLeverage: false,
});
const pos = Object.values(freshPositions).find(
  (p) => p.marketAddress === marketAddress && p.isLong === true
);

const marketInfo = fresh.marketsInfoData[pos.marketAddress];
const collateralToken = fresh.tokensData[pos.collateralTokenAddress];
const { minCollateralUsd, minPositionSizeUsd } = await sdk.positions.getPositionsConstants();
const uiFeeFactor = await sdk.utils.getUiFeeFactor();

const decreaseAmounts = getDecreasePositionAmounts({
  marketInfo, collateralToken, isLong: pos.isLong, position: pos,
  closeSizeUsd: pos.sizeInUsd, keepLeverage: false,
  userReferralInfo: undefined, minCollateralUsd, minPositionSizeUsd, uiFeeFactor,
  isSetAcceptablePriceImpactEnabled: false,
});

await sdk.orders.createDecreaseOrder({
  marketInfo, marketsInfoData: fresh.marketsInfoData, tokensData: fresh.tokensData,
  isLong: pos.isLong, allowedSlippage: 300, decreaseAmounts, collateralToken,
});

console.log("Close order submitted — keeper will execute in 1-30s");

Limitations

  • GM pool deposits/withdrawals: See the gmx-liquidity skill for contract-level operations. SDK convenience methods not yet available.
  • GLV vault operations: See the gmx-liquidity skill for contract-level operations. SDK convenience methods not yet available.
  • Express orders: Frontend-only via Gelato relay. Not exposed in the SDK.
  • TWAP orders: Utility functions available (@gmx-io/sdk/utils/twap) but no SDK method to create TWAP orders programmatically.
  • Order updates: Orders cannot be modified. Cancel and recreate instead (sdk.orders.cancelOrders()).
  • Trade simulation: The skipSimulation parameter exists but simulation is deprecated. Set skipSimulation: true.

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

Agent Dev Workflow

Orchestrate coding agents (Claude Code, Codex, etc.) to implement coding tasks through a structured workflow. Use when the user gives a coding requirement, f...

Registry SourceRecently Updated
Coding

Tesla Commander

Command and monitor Tesla vehicles via the Fleet API. Check status, control climate/charging/locks, track location, and analyze trip history. Use when you ne...

Registry SourceRecently Updated
Coding

Skill Creator (Opencode)

Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize a...

Registry SourceRecently Updated
Coding

Documentation Writer

Write clear, comprehensive documentation. Covers README files, API docs, user guides, and code comments. Create documentation that users actually read and un...

Registry SourceRecently Updated