Gmx trading
Agent skills for trading perpetuals and swapping tokens on GMX V2.
npx -y skills add gmx-io/gmx-ai --skill gmx-tradingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 10 stars10 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
21.4 KB, as published. Nobody here has run it
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:
| Chain | Chain ID | Native Token |
|---|---|---|
| Arbitrum | 42161 | ETH |
| Avalanche | 43114 | AVAX |
| Botanix | 3637 | BTC |
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:
| Chain | chainId | oracleUrl | subsquidUrl |
|---|---|---|---|
| Arbitrum | 42161 | https://arbitrum-api.gmxinfra.io | https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql |
| Avalanche | 43114 | https://avalanche-api.gmxinfra.io | https://gmx.squids.live/gmx-synthetics-avalanche:prod/api/graphql |
| Botanix | 3637 | https://botanix-api.gmxinfra.io | https://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= 100xallowedSlippageBps— Default100(1%). Range: 1-500payAmount— Pay this much collateral. Alternative: usesizeAmountto specify position sizefromAmount/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
marketsInfoDataandtokensDataright before closing. These contain oracle prices that go stale within seconds — using old data produces anacceptablePricethe 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
| Module | Key Methods | Description |
|---|---|---|
sdk.markets | getMarkets(), getMarketsInfo(), getDailyVolumes() | Market data and liquidity info |
sdk.tokens | getTokensData(), getTokensBalances() | Token metadata, prices, balances |
sdk.positions | getPositions(), getPositionsInfo(), getPositionsConstants() | Open position data |
sdk.orders | long(), short(), swap(), getOrders(), cancelOrders() | Order creation and management |
sdk.trades | getTradeHistory() | Historical trade actions |
sdk.utils | getGasLimits(), getGasPrice(), getExecutionFee(), getUiFeeFactor() | Gas and fee estimation |
sdk.oracle | getTickers(), 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:
| Method | Purpose | Key Params |
|---|---|---|
sdk.orders.long() | Open long position | marketAddress, payTokenAddress, collateralTokenAddress, payAmount, leverage |
sdk.orders.short() | Open short position | Same as long() |
sdk.orders.swap() | Swap tokens | fromTokenAddress, toTokenAddress, fromAmount |
sdk.orders.cancelOrders() | Cancel pending orders | orderKeys: 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):
| Method | Purpose | Required Setup |
|---|---|---|
sdk.orders.createIncreaseOrder() | Open position (full control) | IncreasePositionAmounts, marketInfo, tokensData |
sdk.orders.createDecreaseOrder() | Close/reduce position | DecreasePositionAmounts via getDecreasePositionAmounts() |
sdk.orders.createSwapOrder() | Swap (full control) | SwapAmounts, swap path |
Key gap: There is no
sdk.orders.close(). To close a position, usegetDecreasePositionAmounts()from@gmx-io/sdk/utils/trade+createDecreaseOrder(). See Step 3: Close a position for the full pattern.
Order Types
| Type | Enum | Behavior |
|---|---|---|
| Market | MarketSwap(0), MarketIncrease(2), MarketDecrease(4) | Execute immediately at current oracle price |
| Limit | LimitSwap(1), LimitIncrease(3), LimitDecrease(5) | Execute when oracle price reaches trigger price |
| Stop Increase | StopIncrease(8) | Open position when price moves past trigger (breakout entry) |
| Stop-Loss | StopLossDecrease(6) | Auto-close position to limit losses |
| Liquidation | Liquidation(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
| Endpoint | Method | Description |
|---|---|---|
/prices/tickers | GET | Current min/max prices for all tokens |
/prices/candles | GET | OHLC price candles (?tokenSymbol=ETH&period=1h) |
/signed_prices/latest | GET | Signed oracle prices for order execution |
/tokens | GET | Token list with addresses and decimals |
/markets | GET | Market configuration (index/long/short tokens) |
/markets/info | GET | Extended 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
| Chain | Primary | Fallback 1 | Fallback 2 |
|---|---|---|---|
| Arbitrum | arbitrum-api.gmxinfra.io | arbitrum-api-fallback.gmxinfra.io | arbitrum-api-fallback.gmxinfra2.io |
| Avalanche | avalanche-api.gmxinfra.io | avalanche-api-fallback.gmxinfra.io | avalanche-api-fallback.gmxinfra2.io |
| Botanix | botanix-api.gmxinfra.io | botanix-api-fallback.gmxinfra.io | botanix-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
skipSimulationparameter exists but simulation is deprecated. SetskipSimulation: true.
References
- SDK Reference — Full module and method documentation
- API Endpoints — Oracle, OpenAPI, and GraphQL endpoint details
- Contract Addresses — Deployed contracts per chain
- Order Types — Detailed order type behavior and trigger logic
- GMX Documentation — Official protocol documentation
- GMX App — Trading interface
@gmx-io/sdkon npm — SDK package- gmx-io/gmx-interface — Frontend source code