SPB Git

Repository Public

wire.ts 12.0 KB 147 lines typescript

1// GMX v2 wire formats (zod) and exact unit conversions.2// Sources: GMX API OpenAPI spec https://arbitrum.gmxapi.io/swagger.json, Oracle API https://docs.gmx.io/docs/api/rest-api/oracle-prices/3// Shapes checked against the live endpoints on 2026-11-10 (fixtures in packages/derivatives/test/fixtures/gmx/).4//5// Units (all integers in decimal strings, converted with bigint only):6//   USD value                      × 10^30                         sizeInUsd, collateralUsd, open interest, liquidity, pnl, fees in USD7//   GMX API price                  × 10^30 per WHOLE token         /markets/tickers, /prices/tickers, positions (mark, entry, liquidation)8//   contract price                 × 10^(30 − tokenDecimals)       Oracle API /prices/tickers, order trigger/acceptable price, trade execution price9//   token amount                   × 10^tokenDecimals              sizeInTokens, collateralAmount, fee amounts10//   factor / rate                  × 10^30                         funding, borrowing, impact and fee factors11//   leverage, percentage           basis points (10 000 = 1×)      leverage, leverageTiers[].maxLeverage, pnlPercentage12import { z } from 'zod';13import { fixed, JsonNumber } from '../http.ts';1415export const USD_DECIMALS = 30;16export const MAX_UINT256 = (1n << 256n) - 1n;17const intStr = z.string().regex(/^-?\d+$/);18const uintStr = z.string().regex(/^\d+$/);19export const address = z.string().regex(/^0x[0-9a-fA-F]{40}$/);20const bytes32 = z.string().regex(/^0x[0-9a-fA-F]{64}$/);21const jsonNumber = z.instanceof(JsonNumber);2223export const market = z.object({24  symbol: z.string().min(1), marketTokenAddress: address, indexTokenAddress: address, longTokenAddress: address, shortTokenAddress: address,25  isListed: z.boolean(), isSpotOnly: z.boolean(),26  leverageTiers: z.array(z.object({ maxLeverage: uintStr, minCollateralFactor: uintStr, maxPositionSize: uintStr.optional() })).min(1),27  minPositionSizeUsd: uintStr, minCollateralUsd: uintStr,28});29export type Market = z.infer<typeof market>;30export const markets = z.array(market);3132const capacity = z.object({ availableLiquidity: intStr, baseAvailableLiquidity: intStr, jitAvailableLiquidity: intStr, limitingFactor: z.string(), jitDataStatus: z.string(), marketDataStatus: z.string() });33export const marketTicker = z.object({34  symbol: z.string().min(1), marketTokenAddress: address, minPrice: uintStr, maxPrice: uintStr, markPrice: uintStr,35  priceChangePercent24hBps: intStr, longInterestInTokens: uintStr, shortInterestInTokens: uintStr, longInterestUsd: uintStr, shortInterestUsd: uintStr,36  availableLiquidityLong: intStr, availableLiquidityShort: intStr,   // negative when a side is over its reserve (seen live)37  fundingRateLong: intStr, fundingRateShort: intStr, borrowingRateLong: intStr, borrowingRateShort: intStr, netRateLong: intStr, netRateShort: intStr,38  capacityLong: capacity, capacityShort: capacity,39});40export type MarketTicker = z.infer<typeof marketTicker>;41export const marketTickers = z.array(marketTicker);4243export const marketConfig = z.object({44  name: z.string(), marketTokenAddress: address, isDisabled: z.boolean(),45  maxOpenInterestLong: uintStr, maxOpenInterestShort: uintStr,46  minCollateralFactor: uintStr, minCollateralFactorForLiquidation: uintStr, minCollateralFactorForOpenInterestLong: uintStr, minCollateralFactorForOpenInterestShort: uintStr,47  positionFeeFactorForBalanceWasImproved: uintStr, positionFeeFactorForBalanceWasNotImproved: uintStr,48  positionImpactFactorPositive: uintStr, positionImpactFactorNegative: uintStr,49  positionImpactExponentFactorPositive: uintStr, positionImpactExponentFactorNegative: uintStr,50  maxPositionImpactFactorPositive: uintStr, maxPositionImpactFactorNegative: uintStr, maxPositionImpactFactorForLiquidations: uintStr,51  /** when true the contracts measure the long/short imbalance (price impact, fee tier) on open interest IN TOKENS × price, not on the USD cost basis */52  useOpenInterestInTokensForBalance: z.boolean().optional(),53  /** non-zero: the index token has a virtual inventory shared by every market on it (a negative price impact is the worse of the market's own and the virtual one) */54  virtualIndexTokenId: z.string().regex(/^0x[0-9a-fA-F]{64}$/).optional(),55});56export type MarketConfig = z.infer<typeof marketConfig>;57export const marketConfigs = z.array(marketConfig);58/** GET /markets/values — only what the price-impact formula needs. Signed: negative = traders are net LONG across the index's markets (contract convention). */59export const marketValues = z.array(z.object({ marketTokenAddress: address, virtualInventoryForPositionsInTokens: intStr, virtualInventoryForPositions: intStr }));6061export const tokenInfo = z.object({ symbol: z.string().min(1), address, decimals: z.number().int().min(0).max(30), isSynthetic: z.boolean().optional(), isNative: z.boolean().optional() });62export const tokensInfo = z.array(tokenInfo);6364/** /pairs is a listing feed with plain JSON numbers: parsed with `parseJsonExact`, so numbers arrive as their source text */65export const pairs = z.array(z.object({ ticker_id: z.string(), pool_id: z.string(), product_type: z.string(), target_volume: jsonNumber }));6667export const ohlcv = z.array(z.object({ timestamp: z.number().int().positive(), open: z.string(), high: z.string(), low: z.string(), close: z.string() }));68/** Oracle API candles: `[timestamp s, open, high, low, close]` with JSON numbers (parsed exact) */69export const oracleCandles = z.object({ period: z.string(), candles: z.array(z.tuple([jsonNumber, jsonNumber, jsonNumber, jsonNumber, jsonNumber])) });70export const oracleTickers = z.array(z.object({ tokenAddress: address, tokenSymbol: z.string().min(1), minPrice: uintStr, maxPrice: uintStr, updatedAt: z.number(), timestamp: z.number().optional() }));71export const oracleTokens = z.object({ tokens: z.array(z.object({ symbol: z.string().min(1), address, decimals: z.number().int().min(0).max(30), synthetic: z.boolean().optional() })) });7273export const rates = z.array(z.object({ marketAddress: address, ratesSnapshots: z.array(z.object({74  timestamp: z.number().int().positive(), fundingRateLong: intStr, fundingRateShort: intStr, borrowingRateLong: intStr, borrowingRateShort: intStr, netRateLong: intStr, netRateShort: intStr,75})) }));7677export const position = z.object({78  key: z.string().min(1), contractKey: bytes32, account: address, marketAddress: address, collateralTokenAddress: address, isLong: z.boolean(),79  sizeInUsd: uintStr, sizeInTokens: uintStr, collateralAmount: uintStr, increasedAtTime: uintStr, decreasedAtTime: uintStr,80  markPrice: uintStr, entryPrice: uintStr.optional(), liquidationPrice: uintStr.optional(), leverage: uintStr.optional(), leverageWithPnl: uintStr.optional(), leverageWithoutPnl: uintStr.optional(),81  collateralUsd: uintStr, remainingCollateralUsd: intStr, remainingCollateralAmount: intStr, hasLowCollateral: z.boolean(),82  pnl: intStr, pnlAfterFees: intStr, pnlAfterAllFees: intStr, netValue: intStr, netValueAfterAllFees: intStr, positionValueInUsd: intStr,83  pendingBorrowingFeesUsd: intStr, pendingFundingFeesUsd: intStr, pendingClaimableFundingFeesUsd: intStr, fundingFeeAmount: intStr,84  claimableLongTokenAmount: intStr, claimableShortTokenAmount: intStr,85  positionFeeAmount: intStr, traderDiscountAmount: intStr, uiFeeAmount: intStr, closingFeeUsd: intStr, uiFeeUsd: intStr,86  pendingImpactAmount: intStr, pendingImpactUsd: intStr, closePriceImpactDeltaUsd: intStr, priceImpactDiffUsd: intStr, netPriceImapctDeltaUsd: intStr,87});88export type WirePosition = z.infer<typeof position>;89export const positions = z.array(position);9091export const order = z.object({92  key: bytes32, account: address, receiver: address, cancellationReceiver: address, callbackContract: address, uiFeeReceiver: address,93  marketAddress: address, initialCollateralTokenAddress: address, swapPath: z.array(address), orderType: z.number().int().min(0).max(9), decreasePositionSwapType: z.number().int(),94  sizeDeltaUsd: uintStr, initialCollateralDeltaAmount: uintStr, triggerPrice: uintStr, acceptablePrice: uintStr, executionFee: uintStr, callbackGasLimit: uintStr,95  minOutputAmount: uintStr, updatedAtTime: uintStr, validFromTime: uintStr, isLong: z.boolean(), shouldUnwrapNativeToken: z.boolean(), isFrozen: z.boolean(), autoCancel: z.boolean(),96});97export type WireOrder = z.infer<typeof order>;98export const orders = z.array(order);99100export const tradeAction = z.object({101  id: z.string().min(1), eventName: z.string().min(1), account: address, marketAddress: address.optional(), orderType: z.number().int().min(0).max(9), orderKey: bytes32,102  timestamp: z.number().int().positive(), transactionHash: z.string(), swapPath: z.array(address), initialCollateralTokenAddress: address, initialCollateralDeltaAmount: uintStr,103  isLong: z.boolean().optional(), sizeDeltaUsd: uintStr.optional(), sizeDeltaInTokens: uintStr.optional(), triggerPrice: uintStr.optional(), acceptablePrice: uintStr.optional(),104  executionPrice: uintStr.optional(), indexTokenPriceMin: uintStr.optional(), indexTokenPriceMax: uintStr.optional(),105  positionFeeAmount: intStr.optional(), borrowingFeeAmount: intStr.optional(), fundingFeeAmount: intStr.optional(), liquidationFeeAmount: intStr.optional(), traderDiscountAmount: intStr.optional(),106  priceImpactUsd: intStr.optional(), totalImpactUsd: intStr.optional(), proportionalPendingImpactUsd: intStr.optional(), priceImpactDiffUsd: intStr.optional(),107  swapImpactUsd: intStr.optional(), swapFeeUsd: intStr.optional(), basePnlUsd: intStr.optional(), pnlUsd: intStr.optional(), reason: z.string().optional(),108});109export type TradeAction = z.infer<typeof tradeAction>;110export const tradesList = z.object({ trades: z.array(tradeAction), hasMore: z.boolean(), nextCursor: z.string().nullable() });111112export const graphqlTrades = z.object({ data: z.object({ tradeActions: z.array(z.object({113  id: z.string().min(1), orderType: z.number().int(), isLong: z.boolean(), sizeDeltaInTokens: uintStr, executionPrice: uintStr, timestamp: z.number().int().positive(),114})) }) });115116/** GMX order types (contracts/order/Order.sol, enum order) */117export const ORDER_TYPE = ['MarketSwap', 'LimitSwap', 'MarketIncrease', 'LimitIncrease', 'MarketDecrease', 'LimitDecrease', 'StopLossDecrease', 'Liquidation', 'StopIncrease', 'Deposit'] as const;118export const isIncrease = (t: number): boolean => t === 2 || t === 3 || t === 8;119export const isDecrease = (t: number): boolean => t === 4 || t === 5 || t === 6 || t === 7;120121// ---- exact conversions ----122export const usd = (raw: string | bigint): string => fixed(BigInt(raw), USD_DECIMALS);123/** GMX API price (30 decimals per whole token) → USD per token */124export const apiPrice = usd;125/** contract price (× 10^(30 − tokenDecimals)) → USD per whole token */126export function contractPrice(raw: string | bigint, tokenDecimals: number): string {127  if (!Number.isInteger(tokenDecimals) || tokenDecimals < 0 || tokenDecimals > USD_DECIMALS) throw new Error('token decimals out of range');128  return fixed(BigInt(raw), USD_DECIMALS - tokenDecimals);129}130/** USD per whole token → contract price; throws if the price has more precision than the contract can hold */131export function toContractPrice(price: string, tokenDecimals: number): bigint {132  const m = /^(\d+)(?:\.(\d+))?$/.exec(price);133  if (!m) throw new Error(`not a price: ${price.slice(0, 40)}`);134  const d = USD_DECIMALS - tokenDecimals, frac = (m[2] ?? '').replace(/0+$/, '');135  if (frac.length > d) throw new Error(`${price} has more than ${d} decimals`);136  return BigInt(m[1]! + frac.padEnd(d, '0'));137}138export const tokenAmount = (raw: string | bigint, decimals: number): string => fixed(BigInt(raw), decimals);139/** 30-decimal factor or rate → decimal fraction */140export const factor = usd;141/** basis points → multiple (384930 → "38.493") */142export const bps = (raw: string | bigint): string => fixed(BigInt(raw), 4);143/** per-second 30-decimal rate → per-hour fraction (exact: × 3600) */144export const perSecondToHourly = (raw: string | bigint): string => fixed(BigInt(raw) * 3600n, USD_DECIMALS);145/** smallest price step the contracts can represent for a token, in USD per whole token */146export const priceStep = (tokenDecimals: number): string => fixed(1n, USD_DECIMALS - tokenDecimals);147
Files main
Menu

Theme