Skip to content

Commit 274f6d4

Browse files
authored
Merge pull request #1 from gmx-io/qwinndev/code-review
Clean up docs, remove gmx-liquidity, fix .well-known
2 parents 1106faf + 3b8a101 commit 274f6d4

15 files changed

Lines changed: 234 additions & 1381 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
{
88
"name": "gmx-trading",
99
"path": "skills/gmx-trading/SKILL.md"
10-
},
11-
{
12-
"name": "gmx-liquidity",
13-
"path": "skills/gmx-liquidity/SKILL.md"
1410
}
1511
]
1612
}
Lines changed: 188 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Trade perpetuals and swap tokens on GMX V2 — a decentralized exch
44
license: MIT
55
metadata:
66
author: gmx-io
7-
version: "0.1"
7+
version: "0.2"
88
chains: "arbitrum, avalanche, botanix"
99
---
1010

@@ -91,7 +91,8 @@ const sdk = new GmxSdk({
9191
const { GmxApiSdk } = require("@gmx-io/sdk/v2");
9292

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

9798
## Order Helpers
@@ -107,8 +108,10 @@ The SDK provides convenience methods that handle amount calculation and transact
107108
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();
108109

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

114117
// Get token addresses from market
@@ -180,43 +183,104 @@ await sdk.orders.long({
180183
- `payAmount` — Pay this much collateral. Alternative: use `sizeAmount` to specify position size
181184
- `fromAmount` / `toAmount` — For swaps, specify input or desired output amount
182185

186+
### Step 3: Close a position
187+
188+
There is no convenience `close()` method. Closing requires computing decrease amounts via `getDecreasePositionAmounts()` from `@gmx-io/sdk/utils/trade`, then calling `createDecreaseOrder()`.
189+
190+
> **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.
191+
192+
```typescript
193+
const { getDecreasePositionAmounts } = require("@gmx-io/sdk/utils/trade");
194+
195+
// 1. Fetch FRESH market data (prices go stale quickly)
196+
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();
197+
198+
// 2. Get the position to close
199+
const positionsInfo = await sdk.positions.getPositionsInfo({
200+
marketsInfoData, tokensData, showPnlInLeverage: false,
201+
});
202+
const position = Object.values(positionsInfo).find(
203+
(p) => p.marketAddress === marketAddress && p.isLong === true
204+
);
205+
206+
// 3. Compute decrease amounts
207+
const marketInfo = marketsInfoData[position.marketAddress];
208+
const collateralToken = tokensData[position.collateralTokenAddress];
209+
const { minCollateralUsd, minPositionSizeUsd } = await sdk.positions.getPositionsConstants();
210+
const uiFeeFactor = await sdk.utils.getUiFeeFactor();
211+
212+
const decreaseAmounts = getDecreasePositionAmounts({
213+
marketInfo,
214+
collateralToken,
215+
isLong: position.isLong,
216+
position,
217+
closeSizeUsd: position.sizeInUsd, // Full close. Use a smaller value for partial close.
218+
keepLeverage: false,
219+
userReferralInfo: undefined,
220+
minCollateralUsd,
221+
minPositionSizeUsd,
222+
uiFeeFactor,
223+
isSetAcceptablePriceImpactEnabled: false,
224+
});
225+
226+
// 4. Submit the decrease order
227+
await sdk.orders.createDecreaseOrder({
228+
marketInfo,
229+
marketsInfoData,
230+
tokensData,
231+
isLong: position.isLong,
232+
allowedSlippage: 300, // 3% — use higher slippage for decrease to avoid keeper rejection
233+
decreaseAmounts,
234+
collateralToken,
235+
});
236+
```
237+
183238
## SDK Modules
184239

185240
| Module | Key Methods | Description |
186241
|--------|------------|-------------|
187242
| `sdk.markets` | `getMarkets()`, `getMarketsInfo()`, `getDailyVolumes()` | Market data and liquidity info |
188-
| `sdk.tokens` | `getTokens()`, `getTokenBalances()` | Token metadata, prices, balances |
243+
| `sdk.tokens` | `getTokensData()`, `getTokensBalances()` | Token metadata, prices, balances |
189244
| `sdk.positions` | `getPositions()`, `getPositionsInfo()`, `getPositionsConstants()` | Open position data |
190245
| `sdk.orders` | `long()`, `short()`, `swap()`, `getOrders()`, `cancelOrders()` | Order creation and management |
191246
| `sdk.trades` | `getTradeHistory()` | Historical trade actions |
192-
| `sdk.utils` | `getGasLimits()`, `getGasPrice()`, `getExecutionFee()` | Gas and fee estimation |
247+
| `sdk.utils` | `getGasLimits()`, `getGasPrice()`, `getExecutionFee()`, `getUiFeeFactor()` | Gas and fee estimation |
193248
| `sdk.oracle` | `getTickers()`, `getMarkets()`, `getTokens()` | Direct oracle data access |
194249

195250
**Typical read flow:**
196251

197252
```typescript
198253
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();
199-
const { positionsInfoData } = await sdk.positions.getPositionsInfo({
254+
const positionsInfo = await sdk.positions.getPositionsInfo({
200255
marketsInfoData, tokensData, showPnlInLeverage: false,
201256
});
202257
const { ordersInfoData } = await sdk.orders.getOrders({
203258
marketsInfoData, tokensData,
204259
});
205260
```
206261

207-
**Write flow — use convenience helpers or low-level methods:**
262+
### Convenience vs Low-level Methods
208263

209-
```typescript
210-
// High-level (recommended)
211-
await sdk.orders.long(params);
212-
await sdk.orders.short(params);
213-
await sdk.orders.swap(params);
214-
215-
// Low-level (full control)
216-
await sdk.orders.createIncreaseOrder({ ... });
217-
await sdk.orders.createDecreaseOrder({ ... });
218-
await sdk.orders.createSwapOrder({ ... });
219-
```
264+
The SDK has two tiers for order creation:
265+
266+
**Convenience methods** — handle amount calculation, execution fee, and tx submission automatically. Use these for opening positions and swaps:
267+
268+
| Method | Purpose | Key Params |
269+
|--------|---------|-----------|
270+
| `sdk.orders.long()` | Open long position | `marketAddress`, `payTokenAddress`, `collateralTokenAddress`, `payAmount`, `leverage` |
271+
| `sdk.orders.short()` | Open short position | Same as `long()` |
272+
| `sdk.orders.swap()` | Swap tokens | `fromTokenAddress`, `toTokenAddress`, `fromAmount` |
273+
| `sdk.orders.cancelOrders()` | Cancel pending orders | `orderKeys: string[]` |
274+
275+
**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):
276+
277+
| Method | Purpose | Required Setup |
278+
|--------|---------|---------------|
279+
| `sdk.orders.createIncreaseOrder()` | Open position (full control) | `IncreasePositionAmounts`, `marketInfo`, `tokensData` |
280+
| `sdk.orders.createDecreaseOrder()` | Close/reduce position | `DecreasePositionAmounts` via `getDecreasePositionAmounts()` |
281+
| `sdk.orders.createSwapOrder()` | Swap (full control) | `SwapAmounts`, swap path |
282+
283+
> **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](#step-3-close-a-position) for the full pattern.
220284
221285
## Order Types
222286

@@ -283,24 +347,7 @@ Base URL: `https://{network}-api.gmxinfra.io`
283347
| `/markets` | GET | Market configuration (index/long/short tokens) |
284348
| `/markets/info` | GET | Extended market info with pool sizes and utilization |
285349

286-
### OpenAPI Endpoints
287-
288-
Base URL: `https://gmx-api-{network}.gmx.io/api/v1`
289-
290-
| Endpoint | Method | Description |
291-
|----------|--------|-------------|
292-
| `/markets` | GET | Market metadata |
293-
| `/tickers` | GET | Current prices per market |
294-
| `/tokens` | GET | Token information |
295-
| `/positions?account={addr}` | GET | Open positions for an account |
296-
| `/orders?account={addr}` | GET | Pending orders for an account |
297-
| `/rates` | GET | Funding and borrowing rates |
298-
| `/apy` | GET | Pool APY data |
299-
| `/performance?account={addr}` | GET | Account trading performance |
300-
301-
Swagger spec: `{base_url}/swagger.json`
302-
303-
Network slugs: `arbitrum`, `avalanche`, `botanix`
350+
All endpoints are served from the Oracle base URL above. The legacy `gmx-api-{network}.gmx.io` domain is no longer available.
304351

305352
### GraphQL (Subsquid)
306353

@@ -312,14 +359,15 @@ Example — fetch recent trade actions:
312359
query {
313360
tradeActions(
314361
where: { account_eq: "0x..." }
315-
orderBy: transaction_timestamp_DESC
362+
orderBy: timestamp_DESC
316363
limit: 10
317364
) {
318365
id
319366
eventName
320367
orderType
321368
sizeDeltaUsd
322-
transaction { timestamp, hash }
369+
timestamp
370+
transactionHash
323371
}
324372
}
325373
```
@@ -340,12 +388,114 @@ query {
340388

341389
**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).
342390

391+
**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.
392+
343393
**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`.
344394

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

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

399+
## Full Example: Open → Monitor → Close
400+
401+
End-to-end flow that opens a long position, monitors it, and closes it.
402+
403+
```typescript
404+
const { GmxSdk } = require("@gmx-io/sdk");
405+
const { getDecreasePositionAmounts } = require("@gmx-io/sdk/utils/trade");
406+
const { createWalletClient, http } = require("viem");
407+
const { privateKeyToAccount } = require("viem/accounts");
408+
const { arbitrum } = require("viem/chains");
409+
410+
// ─── Setup ───────────────────────────────────────────────────────────────────
411+
412+
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
413+
const sdk = new GmxSdk({
414+
chainId: 42161,
415+
rpcUrl: "https://arb1.arbitrum.io/rpc",
416+
oracleUrl: "https://arbitrum-api.gmxinfra.io",
417+
subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql",
418+
account: account.address,
419+
walletClient: createWalletClient({
420+
account, chain: arbitrum, transport: http("https://arb1.arbitrum.io/rpc"),
421+
}),
422+
});
423+
424+
// ─── 1. Resolve addresses ───────────────────────────────────────────────────
425+
426+
const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();
427+
428+
const ethMarket = Object.values(marketsInfoData).find(
429+
(m) => tokensData[m.indexTokenAddress]?.symbol === "WETH" && !m.isSpotOnly
430+
);
431+
const marketAddress = ethMarket.marketTokenAddress;
432+
const usdcAddress = Object.values(tokensData).find((t) => t.symbol === "USDC").address;
433+
434+
// ─── 2. Open long ───────────────────────────────────────────────────────────
435+
436+
await sdk.orders.long({
437+
marketAddress,
438+
payTokenAddress: usdcAddress,
439+
collateralTokenAddress: usdcAddress,
440+
payAmount: 10_000000n, // 10 USDC
441+
leverage: 30000n, // 3x
442+
allowedSlippageBps: 100,
443+
skipSimulation: true,
444+
});
445+
446+
// ─── 3. Wait for position to appear (keeper executes in 1-30s) ──────────────
447+
448+
let position;
449+
for (let i = 0; i < 40; i++) {
450+
await new Promise((r) => setTimeout(r, 3000));
451+
const info = await sdk.positions.getPositionsInfo({
452+
marketsInfoData, tokensData, showPnlInLeverage: false,
453+
});
454+
position = Object.values(info).find(
455+
(p) => p.marketAddress === marketAddress && p.isLong === true
456+
);
457+
if (position) break;
458+
}
459+
if (!position) throw new Error("Position did not appear within 120s");
460+
461+
console.log("Position opened:", {
462+
sizeUsd: position.sizeInUsd.toString(),
463+
leverage: position.leverage.toString(),
464+
entryPrice: position.entryPrice.toString(),
465+
});
466+
467+
// ─── 4. Close position (re-fetch fresh data first!) ─────────────────────────
468+
469+
const fresh = await sdk.markets.getMarketsInfo();
470+
const freshPositions = await sdk.positions.getPositionsInfo({
471+
marketsInfoData: fresh.marketsInfoData,
472+
tokensData: fresh.tokensData,
473+
showPnlInLeverage: false,
474+
});
475+
const pos = Object.values(freshPositions).find(
476+
(p) => p.marketAddress === marketAddress && p.isLong === true
477+
);
478+
479+
const marketInfo = fresh.marketsInfoData[pos.marketAddress];
480+
const collateralToken = fresh.tokensData[pos.collateralTokenAddress];
481+
const { minCollateralUsd, minPositionSizeUsd } = await sdk.positions.getPositionsConstants();
482+
const uiFeeFactor = await sdk.utils.getUiFeeFactor();
483+
484+
const decreaseAmounts = getDecreasePositionAmounts({
485+
marketInfo, collateralToken, isLong: pos.isLong, position: pos,
486+
closeSizeUsd: pos.sizeInUsd, keepLeverage: false,
487+
userReferralInfo: undefined, minCollateralUsd, minPositionSizeUsd, uiFeeFactor,
488+
isSetAcceptablePriceImpactEnabled: false,
489+
});
490+
491+
await sdk.orders.createDecreaseOrder({
492+
marketInfo, marketsInfoData: fresh.marketsInfoData, tokensData: fresh.tokensData,
493+
isLong: pos.isLong, allowedSlippage: 300, decreaseAmounts, collateralToken,
494+
});
495+
496+
console.log("Close order submitted — keeper will execute in 1-30s");
497+
```
498+
349499
## Limitations
350500

351501
- **GM pool deposits/withdrawals:** Not available in SDK. Use the app at `app.gmx.io` or wait for SDK v2.

0 commit comments

Comments
 (0)