[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6GQRTP5CBSJ25A8YNQP9D5P":3,"skill-same:01M6GQRTP5CBSJ25A8YNQP9D5P":88},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":36,"linkOnly":37,"markdownContent":38,"markdownBody":39,"frontMatter":40,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":6,"ownerHtmlUrl":57,"ownerBlogUrl":58,"ownerDescription":59,"updatedAt":60,"install":61,"resources":73},"01M6GQRTP5CBSJ25A8YNQP9D5P","gmx-liquidity",null,"Provide liquidity on GMX V2 — deposit into GM pools and GLV vaults, withdraw, shift between pools, and query pool data across Arbitrum, Avalanche, and Botanix.","jiayaoqijia\u002Fcryptoskill","jiayaoqijia","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1453879?v=4","01M6GMYTX7Q7S504VC6E756ENR","YQ","01M6GMYV1QKJ1CC75MTTGT9PR0",81,"MIT","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fgmx-official-liquidity\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"gmx liquidity","liquidity","vaults","pool","true","io","avalanche","arbitrum","yield","github","withdraw","deposit","across","query","data","defi","https","2026-11-10 10:12:30",false,"---\nname: gmx-liquidity\ndescription: \"Provide liquidity on GMX V2 — deposit into GM pools and GLV vaults, withdraw, shift between pools, and query pool data across Arbitrum, Avalanche, and Botanix.\"\nlicense: MIT\nmetadata:\n  author: gmx-io\n  version: \"0.1\"\n  chains: \"arbitrum, avalanche, botanix\"\n  openclaw:\n    tags: [gmx, liquidity, defi, gm-pools, glv, arbitrum, avalanche, yield]\n    official: true\n    source: \"https:\u002F\u002Fgithub.com\u002Fgmx-io\u002Fgmx-ai\"\n---\n\n# GMX Liquidity Skill\n\n## Overview\n\nGMX V2 has two liquidity layers that back perpetual and spot trading:\n\n- **GM Pools** — Single-market liquidity pools. Each GM pool backs one market (e.g., ETH\u002FUSD) and is composed of a long token and short token. Depositing mints GM tokens (LP tokens); withdrawing burns them.\n- **GLV Vaults** (GMX Liquidity Vaults) — Multi-market vaults that hold multiple GM tokens and auto-rebalance across constituent pools. Depositing mints GLV tokens; withdrawing burns them.\n\n**Supported chains:**\n| Chain | Chain ID | Native Token | GLV Vaults |\n|-------|----------|-------------|------------|\n| Arbitrum | 42161 | ETH | Yes |\n| Avalanche | 43114 | AVAX | Yes |\n| Botanix | 3637 | BTC | No (contracts deployed, no vaults configured) |\n\n**Execution model:** All write operations use a two-step async pattern:\n1. User creates a request (deposit\u002Fwithdrawal\u002Fshift) via `ExchangeRouter` or `GlvRouter` multicall\n2. Keeper executes the request with oracle prices (typically 1–30 seconds later)\n\nThe user pays an execution fee (in native token) upfront to cover keeper gas costs. Excess fee is refunded.\n\n**Integration paths:**\n- **SDK** (`@gmx-io\u002Fsdk`) — Read pool data, balances, and market info. Write operations (deposit\u002Fwithdraw) are not yet in the SDK.\n- **Contract-level** (viem) — Full read + write via `ExchangeRouter.multicall()` and `GlvRouter.multicall()`.\n\nThis skill complements [gmx-trading](..\u002Fgmx-official-trading\u002FSKILL.md) which covers perpetual trading and swaps.\n\n## GM Pools\n\nEach GM pool is a pair of tokens backing a specific market:\n- **Long token** — The asset (e.g., WETH for ETH\u002FUSD)\n- **Short token** — The stablecoin (e.g., USDC)\n- **GM token** — The LP token representing a share of the pool\n\n**Deposits** can be:\n- **Balanced** — Deposit both long and short tokens proportionally. Lower fees (helps balance the pool).\n- **Single-sided** — Deposit only one token. The protocol swaps a portion internally. Higher fees if it imbalances the pool.\n\n**Withdrawals** burn GM tokens and return the underlying long and short tokens. A withdrawal can specify `minLongTokenAmount` and `minShortTokenAmount` for slippage protection.\n\n**Pricing:** GM token price = pool value \u002F GM token supply. Pool value is the sum of long and short token values at oracle prices plus accrued PnL.\n\nGM pool addresses are dynamic — discover them via `sdk.markets.getMarkets()`.\n\n## GLV Vaults\n\nGLV vaults hold multiple GM tokens and auto-rebalance across markets:\n\n**Known GLV vaults** (source: [`sdk\u002Fsrc\u002Fconfigs\u002Fmarkets.ts`](https:\u002F\u002Fgithub.com\u002Fgmx-io\u002Fgmx-interface\u002Fblob\u002Frelease\u002Fsdk\u002Fsrc\u002Fconfigs\u002Fmarkets.ts)):\n| Chain | Vault | Long Token | Short Token | Address |\n|-------|-------|-----------|-------------|---------|\n| Arbitrum | GLV [WETH-USDC] | WETH | USDC | `0x528A5bac7E746C9A509A1f4F6dF58A03d44279F9` |\n| Arbitrum | GLV [WBTC-USDC] | WBTC | USDC | `0xdF03EEd325b82bC1d4Db8b49c30ecc9E05104b96` |\n| Avalanche | GLV [WAVAX-USDC] | WAVAX | USDC | `0x901eE57f7118A7be56ac079cbCDa7F22663A3874` |\n\n**Deposit modes:**\n- **Raw tokens** — Deposit long\u002Fshort tokens. The vault creates a GM deposit into the selected constituent market, then deposits the resulting GM tokens into the GLV.\n- **GM tokens** — Deposit existing GM tokens directly (`isMarketTokenDeposit: true`). Skips the intermediate deposit step.\n\n**Withdrawals** burn GLV tokens. The vault withdraws from the specified constituent market and returns the underlying long\u002Fshort tokens.\n\n## Reading Pool Data (SDK)\n\nUse `sdk.markets.getMarketsInfo()` to query all pool data:\n\n```typescript\nconst { GmxSdk } = require(\"@gmx-io\u002Fsdk\");\n\nconst sdk = new GmxSdk({\n  chainId: 42161,\n  rpcUrl: \"https:\u002F\u002Farb1.arbitrum.io\u002Frpc\",\n  oracleUrl: \"https:\u002F\u002Farbitrum-api.gmxinfra.io\",\n  subsquidUrl: \"https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-arbitrum:prod\u002Fapi\u002Fgraphql\",\n});\n\nconst { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();\n\n\u002F\u002F Find a specific pool\nconst ethPool = Object.values(marketsInfoData).find(\n  (m) => tokensData[m.indexTokenAddress]?.symbol === \"WETH\" && !m.isSpotOnly\n);\n\n\u002F\u002F Pool liquidity data\nconsole.log(\"Long pool:\", ethPool.longPoolAmount);       \u002F\u002F Long token amount in pool\nconsole.log(\"Short pool:\", ethPool.shortPoolAmount);      \u002F\u002F Short token amount in pool\nconsole.log(\"Pool value (max):\", ethPool.poolValueMax);   \u002F\u002F Total pool value USD\nconsole.log(\"Pool value (min):\", ethPool.poolValueMin);\n\n\u002F\u002F Pool capacity\nconsole.log(\"Max long:\", ethPool.maxLongPoolAmount);      \u002F\u002F Max long token capacity\nconsole.log(\"Max short:\", ethPool.maxShortPoolAmount);     \u002F\u002F Max short token capacity\n\n\u002F\u002F GM token address\nconsole.log(\"Market token:\", ethPool.marketTokenAddress);\n```\n\n**Check GM token balances:**\n\n```typescript\nconst { tokensData } = await sdk.tokens.getTokensBalances();\n\u002F\u002F GM tokens appear as regular tokens — filter by market token addresses\n```\n\n**List all pools:**\n\n```typescript\nconst { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();\n\nfor (const market of Object.values(marketsInfoData)) {\n  const indexSymbol = tokensData[market.indexTokenAddress]?.symbol ?? \"SPOT\";\n  const longSymbol = tokensData[market.longTokenAddress]?.symbol;\n  const shortSymbol = tokensData[market.shortTokenAddress]?.symbol;\n  console.log(`${indexSymbol}: ${longSymbol}\u002F${shortSymbol} — Pool: ${market.poolValueMax}`);\n}\n```\n\n## Reading Pool Data (REST \u002F GraphQL)\n\n**REST API** — Get market info including pool sizes:\n\n```\nGET https:\u002F\u002Farbitrum-api.gmxinfra.io\u002Fmarkets\u002Finfo\n```\n\nReturns extended market data including pool sizes, utilization, open interest, and fee factors.\n\n**GraphQL (Subsquid)** — Query historical deposit\u002Fwithdrawal events:\n\n| Chain | Endpoint |\n|-------|----------|\n| Arbitrum | `https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-arbitrum:prod\u002Fapi\u002Fgraphql` |\n| Avalanche | `https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-avalanche:prod\u002Fapi\u002Fgraphql` |\n| Botanix | `https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-botanix:prod\u002Fapi\u002Fgraphql` |\n\n## Prerequisites for Write Operations\n\nBefore calling `ExchangeRouter.multicall()` or `GlvRouter.multicall()`:\n\n**1. Token approvals:**\n- For **all operations** (GM and GLV): approve tokens to `SyntheticsRouter`\n- This applies to GM deposits\u002Fwithdrawals\u002Fshifts via `ExchangeRouter` and GLV deposits\u002Fwithdrawals via `GlvRouter`\n\n```typescript\n\u002F\u002F Approve USDC to SyntheticsRouter (works for both GM and GLV operations)\nawait usdcContract.write.approve([syntheticsRouterAddress, amount]);\n```\n\n**2. Default parameter values:**\n```typescript\ncallbackContract: zeroAddress,   \u002F\u002F No callback\ncallbackGasLimit: 0n,            \u002F\u002F No callback gas\ndataList: [],                     \u002F\u002F Reserved for future use\nuiFeeReceiver: zeroAddress,      \u002F\u002F No UI fee (set to your address if building a frontend)\n```\n\n**3. Native token handling:**\nWhen depositing the wrapped native token (WETH on Arbitrum, WAVAX on Avalanche, PBTC on Botanix) and `shouldUnwrapNativeToken` is true, add the deposit amount to the `sendWnt` call value instead of using `sendTokens`. The `sendWnt` call wraps native token automatically.\n\n```typescript\n\u002F\u002F If depositing ETH (native) on Arbitrum:\nconst wntAmount = executionFee + longTokenAmount; \u002F\u002F execution fee + deposit amount\n\u002F\u002F Use sendWnt for the full amount, skip sendTokens for the long token\n```\n\n**4. Slippage:**\nApply slippage client-side before passing to the contract:\n```typescript\n\u002F\u002F Apply 0.3% slippage to minimum output\nconst minMarketTokens = expectedMarketTokens * 997n \u002F 1000n;\n```\n\n## Execution Fee Calculation\n\nAll operations require an execution fee in native token. The formula is the same for all operations:\n\n```\nestimatedGasLimit = (per-operation formula)\noraclePriceCount = (per-operation formula)\n\n\u002F\u002F adjustGasLimitForEstimate (mirrors contract logic)\ngasLimit = estimatedGasFeeBaseAmount\n         + (estimatedGasFeePerOraclePrice × oraclePriceCount)\n         + applyFactor(estimatedGasLimit, estimatedFeeMultiplierFactor)\n\nexecutionFee = gasLimit × gasPrice\n```\n\n**Per-operation formulas:**\n\n| Operation | estimatedGasLimit | oraclePriceCount |\n|-----------|------------------|-----------------|\n| GM Deposit | `depositToken + swaps × singleSwap` | `3 + swapsCount` |\n| GM Withdrawal | `withdrawalMultiToken + swaps × singleSwap` | `3 + swapsCount` |\n| Shift | `shift` | `4` |\n| GLV Deposit (raw tokens) | `glvDepositGasLimit + markets × glvPerMarketGasLimit + depositToken + swaps × singleSwap` | `2 + marketsCount + swapsCount` |\n| GLV Deposit (GM tokens) | `glvDepositGasLimit + markets × glvPerMarketGasLimit` | `2 + marketsCount` |\n| GLV Withdrawal | `glvWithdrawalGasLimit + markets × glvPerMarketGasLimit + withdrawalMultiToken + swaps × singleSwap` | `2 + marketsCount + swapsCount` |\n\n**Important: `marketsCount` for GLV operations.**\nGLV vaults contain many constituent GM markets — GLV [WETH-USDC] on Arbitrum has **40+ markets**. The contract validates the execution fee against the actual constituent count, and reverts with `InsufficientExecutionFee` if too low. The SDK does not expose a method to query the GLV constituent market count. Use these values:\n\n| GLV Vault | Chain | Recommended `marketsCount` |\n|-----------|-------|---------------------------|\n| GLV [WETH-USDC] | Arbitrum | 53 |\n| GLV [WBTC-USDC] | Arbitrum | 53 |\n| GLV [WAVAX-USDC] | Avalanche | 20 |\n\nExcess execution fee is always refunded, so overestimating `marketsCount` is safe. GLV execution fees are typically ~0.001 ETH — roughly 10x higher than GM operations (~0.0001 ETH) due to the large number of constituent markets.\n\n**Using the SDK to get gas parameters:**\n\n```typescript\nconst gasLimits = await sdk.utils.getGasLimits();\nconst gasPrice = await sdk.utils.getGasPrice();\n\n\u002F\u002F --- Per-operation gas limit field names from sdk.utils.getGasLimits() ---\n\u002F\u002F GM Deposit:       gasLimits.depositToken\n\u002F\u002F GM Withdrawal:    gasLimits.withdrawalMultiToken\n\u002F\u002F Shift:            gasLimits.shift\n\u002F\u002F GLV per-market:   gasLimits.glvPerMarketGasLimit\n\u002F\u002F GLV Deposit:      gasLimits.glvDepositGasLimit\n\u002F\u002F GLV Withdrawal:   gasLimits.glvWithdrawalGasLimit\n\u002F\u002F Swap (per swap):  gasLimits.singleSwap\n\u002F\u002F Base fee fields:  gasLimits.estimatedGasFeeBaseAmount,\n\u002F\u002F                   gasLimits.estimatedGasFeePerOraclePrice,\n\u002F\u002F                   gasLimits.estimatedFeeMultiplierFactor\n\nfunction calculateExecutionFee(estimatedGasLimit: bigint, oraclePriceCount: bigint): bigint {\n  let gasLimit = gasLimits.estimatedGasFeeBaseAmount;\n  gasLimit += gasLimits.estimatedGasFeePerOraclePrice * oraclePriceCount;\n  gasLimit += estimatedGasLimit * gasLimits.estimatedFeeMultiplierFactor \u002F 10n ** 30n;\n  return gasLimit * gasPrice;\n}\n\n\u002F\u002F Example: GM deposit (no swaps)\nconst gmDepositFee = calculateExecutionFee(gasLimits.depositToken, 3n);\n\n\u002F\u002F Example: GLV deposit (raw tokens, 53 markets, no swaps)\nconst marketsCount = 53n;\nconst glvDepositGas = gasLimits.glvDepositGasLimit\n  + marketsCount * gasLimits.glvPerMarketGasLimit\n  + gasLimits.depositToken;\nconst glvDepositFee = calculateExecutionFee(glvDepositGas, 2n + marketsCount);\n```\n\n## Depositing into GM Pools\n\nUse `ExchangeRouter.multicall()` to batch send tokens + create deposit in one transaction:\n\n```typescript\nimport { encodeFunctionData, zeroAddress } from \"viem\";\n\nconst exchangeRouterAddress = \"0x1C3fa76e6E1088bCE750f23a5BFcffa1efEF6A41\"; \u002F\u002F Arbitrum\nconst depositVaultAddress = \"0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55\";   \u002F\u002F Arbitrum\n\n\u002F\u002F Step 1: Approve tokens to SyntheticsRouter (one-time)\n\u002F\u002F await longToken.write.approve([syntheticsRouterAddress, longTokenAmount]);\n\u002F\u002F await shortToken.write.approve([syntheticsRouterAddress, shortTokenAmount]);\n\n\u002F\u002F Step 2: Build multicall\nconst wntAmount = executionFee; \u002F\u002F Add longTokenAmount if depositing native token\n\nconst multicall = [\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendWnt\",\n    args: [depositVaultAddress, wntAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [longTokenAddress, depositVaultAddress, longTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [shortTokenAddress, depositVaultAddress, shortTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"createDeposit\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        market: marketTokenAddress,         \u002F\u002F GM pool address\n        initialLongToken: longTokenAddress,\n        initialShortToken: shortTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minMarketTokens: minGmTokensOut,      \u002F\u002F Apply slippage\n      shouldUnwrapNativeToken: false,\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\n\u002F\u002F Step 3: Send transaction\nconst hash = await walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: wntAmount,\n});\n```\n\n**Native token deposits:** If depositing WETH\u002FWAVAX\u002FPBTC with `shouldUnwrapNativeToken: true`, add the deposit amount to `wntAmount` (`executionFee + longTokenAmount`) and skip the `sendTokens` call for that token. The `sendWnt` call wraps native ETH\u002FAVAX\u002FBTC automatically.\n\n## Withdrawing from GM Pools\n\n```typescript\nconst withdrawalVaultAddress = \"0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55\"; \u002F\u002F Arbitrum\n\n\u002F\u002F Approve GM tokens to SyntheticsRouter first\n\u002F\u002F await gmToken.write.approve([syntheticsRouterAddress, marketTokenAmount]);\n\nconst multicall = [\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendWnt\",\n    args: [withdrawalVaultAddress, executionFee], \u002F\u002F Only execution fee, no deposit\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [marketTokenAddress, withdrawalVaultAddress, marketTokenAmount], \u002F\u002F GM tokens\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"createWithdrawal\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        market: marketTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minLongTokenAmount: minLongOut,       \u002F\u002F Apply slippage\n      minShortTokenAmount: minShortOut,     \u002F\u002F Apply slippage\n      shouldUnwrapNativeToken: true,        \u002F\u002F Unwrap WETH to ETH on receive\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: executionFee,\n});\n```\n\n## GLV Deposits\n\nUse `GlvRouter.multicall()` (not ExchangeRouter):\n\n```typescript\nconst glvRouterAddress = \"0x7EAdEE2ca1b4D06a0d82fDF03D715550c26AA12F\";  \u002F\u002F Arbitrum\nconst glvVaultAddress = \"0x393053B58f9678C9c28c2cE941fF6cac49C3F8f9\";   \u002F\u002F Arbitrum\n\n\u002F\u002F Approve tokens to SyntheticsRouter (same as GM operations)\n\nconst multicall = [\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendWnt\",\n    args: [glvVaultAddress, wntAmount],\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendTokens\",\n    args: [longTokenAddress, glvVaultAddress, longTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"createGlvDeposit\",\n    args: [{\n      addresses: {\n        glv: glvTokenAddress,                \u002F\u002F GLV vault address\n        market: constituentMarketAddress,    \u002F\u002F Which GM market to deposit through\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        initialLongToken: longTokenAddress,\n        initialShortToken: shortTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minGlvTokens: minGlvTokensOut,        \u002F\u002F Note: minGlvTokens, not minMarketTokens\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      shouldUnwrapNativeToken: false,\n      isMarketTokenDeposit: false,           \u002F\u002F true if depositing GM tokens directly\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: glvRouterAddress,\n  abi: glvRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: wntAmount,\n});\n```\n\n**Depositing GM tokens directly:** Set `isMarketTokenDeposit: true` and send GM tokens to `GlvVault` instead of long\u002Fshort tokens. This skips the intermediate GM deposit step and uses less gas.\n\n## GLV Withdrawals\n\n```typescript\n\u002F\u002F Approve GLV tokens to SyntheticsRouter first\n\nconst multicall = [\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendWnt\",\n    args: [glvVaultAddress, executionFee],\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendTokens\",\n    args: [glvTokenAddress, glvVaultAddress, glvTokenAmount], \u002F\u002F GLV tokens\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"createGlvWithdrawal\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        market: constituentMarketAddress,    \u002F\u002F Which GM market to withdraw from\n        glv: glvTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minLongTokenAmount: minLongOut,\n      minShortTokenAmount: minShortOut,\n      shouldUnwrapNativeToken: true,\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: glvRouterAddress,\n  abi: glvRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: executionFee,\n});\n```\n\n## Shift Operations\n\nShifts move GM tokens from one pool to another atomically — without withdrawing first. Lower fees than manual withdraw + deposit.\n\n```typescript\nconst shiftVaultAddress = \"0xfe99609C4AA83ff6816b64563Bdffd7fa68753Ab\"; \u002F\u002F Arbitrum\n\n\u002F\u002F Approve from-market GM tokens to SyntheticsRouter first\n\nconst multicall = [\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendWnt\",\n    args: [shiftVaultAddress, executionFee],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [fromMarketTokenAddress, shiftVaultAddress, fromMarketTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"createShift\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        fromMarket: fromMarketTokenAddress,\n        toMarket: toMarketTokenAddress,\n      },\n      minMarketTokens: minToMarketTokens,  \u002F\u002F Apply slippage\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: executionFee,\n});\n```\n\nNote: `CreateShiftParams` does not have a `shouldUnwrapNativeToken` field (unlike deposit\u002Fwithdrawal).\n\n## Cancelling Pending Operations\n\nIf a request hasn't been executed by a keeper yet, the creator can cancel it using the request key (bytes32) returned by the create function:\n\n**Via ExchangeRouter:**\n```typescript\nawait walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"cancelDeposit\",    \u002F\u002F or cancelWithdrawal, cancelShift\n  args: [requestKey],\n});\n```\n\n**Via GlvRouter:**\n```typescript\nawait walletClient.writeContract({\n  address: glvRouterAddress,\n  abi: glvRouterAbi,\n  functionName: \"cancelGlvDeposit\",  \u002F\u002F or cancelGlvWithdrawal\n  args: [requestKey],\n});\n```\n\nCancellation refunds tokens to the receiver address. Only the account that created the request can cancel it.\n\n## Fees\n\n**Deposit\u002Fwithdrawal fees:** Same balancing incentive model as swap fees. Deposits that balance the pool (move it closer to 50\u002F50) pay lower fees. Deposits that imbalance the pool pay higher fees. The fee is deducted from the minted GM\u002FGLV tokens.\n\n**Execution fees:** Paid in native token upfront. Covers keeper gas costs. Calculated using the formula in the [Execution Fee Calculation](#execution-fee-calculation) section. Excess fee is refunded to the receiver.\n\n## Limitations\n\n- **SDK write operations:** `@gmx-io\u002Fsdk` does not yet expose convenience methods for deposit\u002Fwithdraw\u002Fshift. Use contract-level multicall as shown above.\n- **APY:** Not directly exposed via API. Derived client-side from trading fees, borrowing rates, and funding rates flowing through each pool.\n- **GLV on Botanix:** GLV contracts are deployed on Botanix but no vaults are configured yet. GM pool operations work on all three chains.\n- **Multichain deposits:** Cross-chain deposits (from a different chain via LayerZero) are not covered in this skill.\n- **Atomic withdrawals:** `ExchangeRouter.executeAtomicWithdrawal()` exists but requires oracle price params — intended for advanced\u002Fkeeper use.\n\n## References\n\n- [Liquidity Operations Reference](references\u002Fliquidity-operations.md) — Contract structs, flows, gas formulas, GLV addresses\n- [Contract Addresses](..\u002Fgmx-official-trading\u002Freferences\u002Fcontract-addresses.md) — All deployed contracts per chain (shared with gmx-trading)\n- [SDK Reference](..\u002Fgmx-official-trading\u002Freferences\u002Fsdk-reference.md) — SDK module and method documentation (shared with gmx-trading)\n- [API Endpoints](..\u002Fgmx-official-trading\u002Freferences\u002Fapi-endpoints.md) — Oracle, OpenAPI, and GraphQL endpoints (shared with gmx-trading)\n- [GMX Documentation](https:\u002F\u002Fdocs.gmx.io) — Official protocol documentation\n- [GMX App — Pools](https:\u002F\u002Fapp.gmx.io\u002F#\u002Fpools) — GM pool interface\n- [GMX App — Vaults](https:\u002F\u002Fapp.gmx.io\u002F#\u002Fvaults) — GLV vault interface\n- [`@gmx-io\u002Fsdk` on npm](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@gmx-io\u002Fsdk) — SDK package\n","# GMX Liquidity Skill\n\n## Overview\n\nGMX V2 has two liquidity layers that back perpetual and spot trading:\n\n- **GM Pools** — Single-market liquidity pools. Each GM pool backs one market (e.g., ETH\u002FUSD) and is composed of a long token and short token. Depositing mints GM tokens (LP tokens); withdrawing burns them.\n- **GLV Vaults** (GMX Liquidity Vaults) — Multi-market vaults that hold multiple GM tokens and auto-rebalance across constituent pools. Depositing mints GLV tokens; withdrawing burns them.\n\n**Supported chains:**\n| Chain | Chain ID | Native Token | GLV Vaults |\n|-------|----------|-------------|------------|\n| Arbitrum | 42161 | ETH | Yes |\n| Avalanche | 43114 | AVAX | Yes |\n| Botanix | 3637 | BTC | No (contracts deployed, no vaults configured) |\n\n**Execution model:** All write operations use a two-step async pattern:\n1. User creates a request (deposit\u002Fwithdrawal\u002Fshift) via `ExchangeRouter` or `GlvRouter` multicall\n2. Keeper executes the request with oracle prices (typically 1–30 seconds later)\n\nThe user pays an execution fee (in native token) upfront to cover keeper gas costs. Excess fee is refunded.\n\n**Integration paths:**\n- **SDK** (`@gmx-io\u002Fsdk`) — Read pool data, balances, and market info. Write operations (deposit\u002Fwithdraw) are not yet in the SDK.\n- **Contract-level** (viem) — Full read + write via `ExchangeRouter.multicall()` and `GlvRouter.multicall()`.\n\nThis skill complements [gmx-trading](..\u002Fgmx-official-trading\u002FSKILL.md) which covers perpetual trading and swaps.\n\n## GM Pools\n\nEach GM pool is a pair of tokens backing a specific market:\n- **Long token** — The asset (e.g., WETH for ETH\u002FUSD)\n- **Short token** — The stablecoin (e.g., USDC)\n- **GM token** — The LP token representing a share of the pool\n\n**Deposits** can be:\n- **Balanced** — Deposit both long and short tokens proportionally. Lower fees (helps balance the pool).\n- **Single-sided** — Deposit only one token. The protocol swaps a portion internally. Higher fees if it imbalances the pool.\n\n**Withdrawals** burn GM tokens and return the underlying long and short tokens. A withdrawal can specify `minLongTokenAmount` and `minShortTokenAmount` for slippage protection.\n\n**Pricing:** GM token price = pool value \u002F GM token supply. Pool value is the sum of long and short token values at oracle prices plus accrued PnL.\n\nGM pool addresses are dynamic — discover them via `sdk.markets.getMarkets()`.\n\n## GLV Vaults\n\nGLV vaults hold multiple GM tokens and auto-rebalance across markets:\n\n**Known GLV vaults** (source: [`sdk\u002Fsrc\u002Fconfigs\u002Fmarkets.ts`](https:\u002F\u002Fgithub.com\u002Fgmx-io\u002Fgmx-interface\u002Fblob\u002Frelease\u002Fsdk\u002Fsrc\u002Fconfigs\u002Fmarkets.ts)):\n| Chain | Vault | Long Token | Short Token | Address |\n|-------|-------|-----------|-------------|---------|\n| Arbitrum | GLV [WETH-USDC] | WETH | USDC | `0x528A5bac7E746C9A509A1f4F6dF58A03d44279F9` |\n| Arbitrum | GLV [WBTC-USDC] | WBTC | USDC | `0xdF03EEd325b82bC1d4Db8b49c30ecc9E05104b96` |\n| Avalanche | GLV [WAVAX-USDC] | WAVAX | USDC | `0x901eE57f7118A7be56ac079cbCDa7F22663A3874` |\n\n**Deposit modes:**\n- **Raw tokens** — Deposit long\u002Fshort tokens. The vault creates a GM deposit into the selected constituent market, then deposits the resulting GM tokens into the GLV.\n- **GM tokens** — Deposit existing GM tokens directly (`isMarketTokenDeposit: true`). Skips the intermediate deposit step.\n\n**Withdrawals** burn GLV tokens. The vault withdraws from the specified constituent market and returns the underlying long\u002Fshort tokens.\n\n## Reading Pool Data (SDK)\n\nUse `sdk.markets.getMarketsInfo()` to query all pool data:\n\n```typescript\nconst { GmxSdk } = require(\"@gmx-io\u002Fsdk\");\n\nconst sdk = new GmxSdk({\n  chainId: 42161,\n  rpcUrl: \"https:\u002F\u002Farb1.arbitrum.io\u002Frpc\",\n  oracleUrl: \"https:\u002F\u002Farbitrum-api.gmxinfra.io\",\n  subsquidUrl: \"https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-arbitrum:prod\u002Fapi\u002Fgraphql\",\n});\n\nconst { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();\n\n\u002F\u002F Find a specific pool\nconst ethPool = Object.values(marketsInfoData).find(\n  (m) => tokensData[m.indexTokenAddress]?.symbol === \"WETH\" && !m.isSpotOnly\n);\n\n\u002F\u002F Pool liquidity data\nconsole.log(\"Long pool:\", ethPool.longPoolAmount);       \u002F\u002F Long token amount in pool\nconsole.log(\"Short pool:\", ethPool.shortPoolAmount);      \u002F\u002F Short token amount in pool\nconsole.log(\"Pool value (max):\", ethPool.poolValueMax);   \u002F\u002F Total pool value USD\nconsole.log(\"Pool value (min):\", ethPool.poolValueMin);\n\n\u002F\u002F Pool capacity\nconsole.log(\"Max long:\", ethPool.maxLongPoolAmount);      \u002F\u002F Max long token capacity\nconsole.log(\"Max short:\", ethPool.maxShortPoolAmount);     \u002F\u002F Max short token capacity\n\n\u002F\u002F GM token address\nconsole.log(\"Market token:\", ethPool.marketTokenAddress);\n```\n\n**Check GM token balances:**\n\n```typescript\nconst { tokensData } = await sdk.tokens.getTokensBalances();\n\u002F\u002F GM tokens appear as regular tokens — filter by market token addresses\n```\n\n**List all pools:**\n\n```typescript\nconst { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo();\n\nfor (const market of Object.values(marketsInfoData)) {\n  const indexSymbol = tokensData[market.indexTokenAddress]?.symbol ?? \"SPOT\";\n  const longSymbol = tokensData[market.longTokenAddress]?.symbol;\n  const shortSymbol = tokensData[market.shortTokenAddress]?.symbol;\n  console.log(`${indexSymbol}: ${longSymbol}\u002F${shortSymbol} — Pool: ${market.poolValueMax}`);\n}\n```\n\n## Reading Pool Data (REST \u002F GraphQL)\n\n**REST API** — Get market info including pool sizes:\n\n```\nGET https:\u002F\u002Farbitrum-api.gmxinfra.io\u002Fmarkets\u002Finfo\n```\n\nReturns extended market data including pool sizes, utilization, open interest, and fee factors.\n\n**GraphQL (Subsquid)** — Query historical deposit\u002Fwithdrawal events:\n\n| Chain | Endpoint |\n|-------|----------|\n| Arbitrum | `https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-arbitrum:prod\u002Fapi\u002Fgraphql` |\n| Avalanche | `https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-avalanche:prod\u002Fapi\u002Fgraphql` |\n| Botanix | `https:\u002F\u002Fgmx.squids.live\u002Fgmx-synthetics-botanix:prod\u002Fapi\u002Fgraphql` |\n\n## Prerequisites for Write Operations\n\nBefore calling `ExchangeRouter.multicall()` or `GlvRouter.multicall()`:\n\n**1. Token approvals:**\n- For **all operations** (GM and GLV): approve tokens to `SyntheticsRouter`\n- This applies to GM deposits\u002Fwithdrawals\u002Fshifts via `ExchangeRouter` and GLV deposits\u002Fwithdrawals via `GlvRouter`\n\n```typescript\n\u002F\u002F Approve USDC to SyntheticsRouter (works for both GM and GLV operations)\nawait usdcContract.write.approve([syntheticsRouterAddress, amount]);\n```\n\n**2. Default parameter values:**\n```typescript\ncallbackContract: zeroAddress,   \u002F\u002F No callback\ncallbackGasLimit: 0n,            \u002F\u002F No callback gas\ndataList: [],                     \u002F\u002F Reserved for future use\nuiFeeReceiver: zeroAddress,      \u002F\u002F No UI fee (set to your address if building a frontend)\n```\n\n**3. Native token handling:**\nWhen depositing the wrapped native token (WETH on Arbitrum, WAVAX on Avalanche, PBTC on Botanix) and `shouldUnwrapNativeToken` is true, add the deposit amount to the `sendWnt` call value instead of using `sendTokens`. The `sendWnt` call wraps native token automatically.\n\n```typescript\n\u002F\u002F If depositing ETH (native) on Arbitrum:\nconst wntAmount = executionFee + longTokenAmount; \u002F\u002F execution fee + deposit amount\n\u002F\u002F Use sendWnt for the full amount, skip sendTokens for the long token\n```\n\n**4. Slippage:**\nApply slippage client-side before passing to the contract:\n```typescript\n\u002F\u002F Apply 0.3% slippage to minimum output\nconst minMarketTokens = expectedMarketTokens * 997n \u002F 1000n;\n```\n\n## Execution Fee Calculation\n\nAll operations require an execution fee in native token. The formula is the same for all operations:\n\n```\nestimatedGasLimit = (per-operation formula)\noraclePriceCount = (per-operation formula)\n\n\u002F\u002F adjustGasLimitForEstimate (mirrors contract logic)\ngasLimit = estimatedGasFeeBaseAmount\n         + (estimatedGasFeePerOraclePrice × oraclePriceCount)\n         + applyFactor(estimatedGasLimit, estimatedFeeMultiplierFactor)\n\nexecutionFee = gasLimit × gasPrice\n```\n\n**Per-operation formulas:**\n\n| Operation | estimatedGasLimit | oraclePriceCount |\n|-----------|------------------|-----------------|\n| GM Deposit | `depositToken + swaps × singleSwap` | `3 + swapsCount` |\n| GM Withdrawal | `withdrawalMultiToken + swaps × singleSwap` | `3 + swapsCount` |\n| Shift | `shift` | `4` |\n| GLV Deposit (raw tokens) | `glvDepositGasLimit + markets × glvPerMarketGasLimit + depositToken + swaps × singleSwap` | `2 + marketsCount + swapsCount` |\n| GLV Deposit (GM tokens) | `glvDepositGasLimit + markets × glvPerMarketGasLimit` | `2 + marketsCount` |\n| GLV Withdrawal | `glvWithdrawalGasLimit + markets × glvPerMarketGasLimit + withdrawalMultiToken + swaps × singleSwap` | `2 + marketsCount + swapsCount` |\n\n**Important: `marketsCount` for GLV operations.**\nGLV vaults contain many constituent GM markets — GLV [WETH-USDC] on Arbitrum has **40+ markets**. The contract validates the execution fee against the actual constituent count, and reverts with `InsufficientExecutionFee` if too low. The SDK does not expose a method to query the GLV constituent market count. Use these values:\n\n| GLV Vault | Chain | Recommended `marketsCount` |\n|-----------|-------|---------------------------|\n| GLV [WETH-USDC] | Arbitrum | 53 |\n| GLV [WBTC-USDC] | Arbitrum | 53 |\n| GLV [WAVAX-USDC] | Avalanche | 20 |\n\nExcess execution fee is always refunded, so overestimating `marketsCount` is safe. GLV execution fees are typically ~0.001 ETH — roughly 10x higher than GM operations (~0.0001 ETH) due to the large number of constituent markets.\n\n**Using the SDK to get gas parameters:**\n\n```typescript\nconst gasLimits = await sdk.utils.getGasLimits();\nconst gasPrice = await sdk.utils.getGasPrice();\n\n\u002F\u002F --- Per-operation gas limit field names from sdk.utils.getGasLimits() ---\n\u002F\u002F GM Deposit:       gasLimits.depositToken\n\u002F\u002F GM Withdrawal:    gasLimits.withdrawalMultiToken\n\u002F\u002F Shift:            gasLimits.shift\n\u002F\u002F GLV per-market:   gasLimits.glvPerMarketGasLimit\n\u002F\u002F GLV Deposit:      gasLimits.glvDepositGasLimit\n\u002F\u002F GLV Withdrawal:   gasLimits.glvWithdrawalGasLimit\n\u002F\u002F Swap (per swap):  gasLimits.singleSwap\n\u002F\u002F Base fee fields:  gasLimits.estimatedGasFeeBaseAmount,\n\u002F\u002F                   gasLimits.estimatedGasFeePerOraclePrice,\n\u002F\u002F                   gasLimits.estimatedFeeMultiplierFactor\n\nfunction calculateExecutionFee(estimatedGasLimit: bigint, oraclePriceCount: bigint): bigint {\n  let gasLimit = gasLimits.estimatedGasFeeBaseAmount;\n  gasLimit += gasLimits.estimatedGasFeePerOraclePrice * oraclePriceCount;\n  gasLimit += estimatedGasLimit * gasLimits.estimatedFeeMultiplierFactor \u002F 10n ** 30n;\n  return gasLimit * gasPrice;\n}\n\n\u002F\u002F Example: GM deposit (no swaps)\nconst gmDepositFee = calculateExecutionFee(gasLimits.depositToken, 3n);\n\n\u002F\u002F Example: GLV deposit (raw tokens, 53 markets, no swaps)\nconst marketsCount = 53n;\nconst glvDepositGas = gasLimits.glvDepositGasLimit\n  + marketsCount * gasLimits.glvPerMarketGasLimit\n  + gasLimits.depositToken;\nconst glvDepositFee = calculateExecutionFee(glvDepositGas, 2n + marketsCount);\n```\n\n## Depositing into GM Pools\n\nUse `ExchangeRouter.multicall()` to batch send tokens + create deposit in one transaction:\n\n```typescript\nimport { encodeFunctionData, zeroAddress } from \"viem\";\n\nconst exchangeRouterAddress = \"0x1C3fa76e6E1088bCE750f23a5BFcffa1efEF6A41\"; \u002F\u002F Arbitrum\nconst depositVaultAddress = \"0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55\";   \u002F\u002F Arbitrum\n\n\u002F\u002F Step 1: Approve tokens to SyntheticsRouter (one-time)\n\u002F\u002F await longToken.write.approve([syntheticsRouterAddress, longTokenAmount]);\n\u002F\u002F await shortToken.write.approve([syntheticsRouterAddress, shortTokenAmount]);\n\n\u002F\u002F Step 2: Build multicall\nconst wntAmount = executionFee; \u002F\u002F Add longTokenAmount if depositing native token\n\nconst multicall = [\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendWnt\",\n    args: [depositVaultAddress, wntAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [longTokenAddress, depositVaultAddress, longTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [shortTokenAddress, depositVaultAddress, shortTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"createDeposit\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        market: marketTokenAddress,         \u002F\u002F GM pool address\n        initialLongToken: longTokenAddress,\n        initialShortToken: shortTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minMarketTokens: minGmTokensOut,      \u002F\u002F Apply slippage\n      shouldUnwrapNativeToken: false,\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\n\u002F\u002F Step 3: Send transaction\nconst hash = await walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: wntAmount,\n});\n```\n\n**Native token deposits:** If depositing WETH\u002FWAVAX\u002FPBTC with `shouldUnwrapNativeToken: true`, add the deposit amount to `wntAmount` (`executionFee + longTokenAmount`) and skip the `sendTokens` call for that token. The `sendWnt` call wraps native ETH\u002FAVAX\u002FBTC automatically.\n\n## Withdrawing from GM Pools\n\n```typescript\nconst withdrawalVaultAddress = \"0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55\"; \u002F\u002F Arbitrum\n\n\u002F\u002F Approve GM tokens to SyntheticsRouter first\n\u002F\u002F await gmToken.write.approve([syntheticsRouterAddress, marketTokenAmount]);\n\nconst multicall = [\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendWnt\",\n    args: [withdrawalVaultAddress, executionFee], \u002F\u002F Only execution fee, no deposit\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [marketTokenAddress, withdrawalVaultAddress, marketTokenAmount], \u002F\u002F GM tokens\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"createWithdrawal\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        market: marketTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minLongTokenAmount: minLongOut,       \u002F\u002F Apply slippage\n      minShortTokenAmount: minShortOut,     \u002F\u002F Apply slippage\n      shouldUnwrapNativeToken: true,        \u002F\u002F Unwrap WETH to ETH on receive\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: executionFee,\n});\n```\n\n## GLV Deposits\n\nUse `GlvRouter.multicall()` (not ExchangeRouter):\n\n```typescript\nconst glvRouterAddress = \"0x7EAdEE2ca1b4D06a0d82fDF03D715550c26AA12F\";  \u002F\u002F Arbitrum\nconst glvVaultAddress = \"0x393053B58f9678C9c28c2cE941fF6cac49C3F8f9\";   \u002F\u002F Arbitrum\n\n\u002F\u002F Approve tokens to SyntheticsRouter (same as GM operations)\n\nconst multicall = [\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendWnt\",\n    args: [glvVaultAddress, wntAmount],\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendTokens\",\n    args: [longTokenAddress, glvVaultAddress, longTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"createGlvDeposit\",\n    args: [{\n      addresses: {\n        glv: glvTokenAddress,                \u002F\u002F GLV vault address\n        market: constituentMarketAddress,    \u002F\u002F Which GM market to deposit through\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        initialLongToken: longTokenAddress,\n        initialShortToken: shortTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minGlvTokens: minGlvTokensOut,        \u002F\u002F Note: minGlvTokens, not minMarketTokens\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      shouldUnwrapNativeToken: false,\n      isMarketTokenDeposit: false,           \u002F\u002F true if depositing GM tokens directly\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: glvRouterAddress,\n  abi: glvRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: wntAmount,\n});\n```\n\n**Depositing GM tokens directly:** Set `isMarketTokenDeposit: true` and send GM tokens to `GlvVault` instead of long\u002Fshort tokens. This skips the intermediate GM deposit step and uses less gas.\n\n## GLV Withdrawals\n\n```typescript\n\u002F\u002F Approve GLV tokens to SyntheticsRouter first\n\nconst multicall = [\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendWnt\",\n    args: [glvVaultAddress, executionFee],\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"sendTokens\",\n    args: [glvTokenAddress, glvVaultAddress, glvTokenAmount], \u002F\u002F GLV tokens\n  }),\n  encodeFunctionData({\n    abi: glvRouterAbi,\n    functionName: \"createGlvWithdrawal\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        market: constituentMarketAddress,    \u002F\u002F Which GM market to withdraw from\n        glv: glvTokenAddress,\n        longTokenSwapPath: [],\n        shortTokenSwapPath: [],\n      },\n      minLongTokenAmount: minLongOut,\n      minShortTokenAmount: minShortOut,\n      shouldUnwrapNativeToken: true,\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: glvRouterAddress,\n  abi: glvRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: executionFee,\n});\n```\n\n## Shift Operations\n\nShifts move GM tokens from one pool to another atomically — without withdrawing first. Lower fees than manual withdraw + deposit.\n\n```typescript\nconst shiftVaultAddress = \"0xfe99609C4AA83ff6816b64563Bdffd7fa68753Ab\"; \u002F\u002F Arbitrum\n\n\u002F\u002F Approve from-market GM tokens to SyntheticsRouter first\n\nconst multicall = [\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendWnt\",\n    args: [shiftVaultAddress, executionFee],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"sendTokens\",\n    args: [fromMarketTokenAddress, shiftVaultAddress, fromMarketTokenAmount],\n  }),\n  encodeFunctionData({\n    abi: exchangeRouterAbi,\n    functionName: \"createShift\",\n    args: [{\n      addresses: {\n        receiver: account.address,\n        callbackContract: zeroAddress,\n        uiFeeReceiver: zeroAddress,\n        fromMarket: fromMarketTokenAddress,\n        toMarket: toMarketTokenAddress,\n      },\n      minMarketTokens: minToMarketTokens,  \u002F\u002F Apply slippage\n      executionFee: executionFee,\n      callbackGasLimit: 0n,\n      dataList: [],\n    }],\n  }),\n];\n\nconst hash = await walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"multicall\",\n  args: [multicall],\n  value: executionFee,\n});\n```\n\nNote: `CreateShiftParams` does not have a `shouldUnwrapNativeToken` field (unlike deposit\u002Fwithdrawal).\n\n## Cancelling Pending Operations\n\nIf a request hasn't been executed by a keeper yet, the creator can cancel it using the request key (bytes32) returned by the create function:\n\n**Via ExchangeRouter:**\n```typescript\nawait walletClient.writeContract({\n  address: exchangeRouterAddress,\n  abi: exchangeRouterAbi,\n  functionName: \"cancelDeposit\",    \u002F\u002F or cancelWithdrawal, cancelShift\n  args: [requestKey],\n});\n```\n\n**Via GlvRouter:**\n```typescript\nawait walletClient.writeContract({\n  address: glvRouterAddress,\n  abi: glvRouterAbi,\n  functionName: \"cancelGlvDeposit\",  \u002F\u002F or cancelGlvWithdrawal\n  args: [requestKey],\n});\n```\n\nCancellation refunds tokens to the receiver address. Only the account that created the request can cancel it.\n\n## Fees\n\n**Deposit\u002Fwithdrawal fees:** Same balancing incentive model as swap fees. Deposits that balance the pool (move it closer to 50\u002F50) pay lower fees. Deposits that imbalance the pool pay higher fees. The fee is deducted from the minted GM\u002FGLV tokens.\n\n**Execution fees:** Paid in native token upfront. Covers keeper gas costs. Calculated using the formula in the [Execution Fee Calculation](#execution-fee-calculation) section. Excess fee is refunded to the receiver.\n\n## Limitations\n\n- **SDK write operations:** `@gmx-io\u002Fsdk` does not yet expose convenience methods for deposit\u002Fwithdraw\u002Fshift. Use contract-level multicall as shown above.\n- **APY:** Not directly exposed via API. Derived client-side from trading fees, borrowing rates, and funding rates flowing through each pool.\n- **GLV on Botanix:** GLV contracts are deployed on Botanix but no vaults are configured yet. GM pool operations work on all three chains.\n- **Multichain deposits:** Cross-chain deposits (from a different chain via LayerZero) are not covered in this skill.\n- **Atomic withdrawals:** `ExchangeRouter.executeAtomicWithdrawal()` exists but requires oracle price params — intended for advanced\u002Fkeeper use.\n\n## References\n\n- [Liquidity Operations Reference](references\u002Fliquidity-operations.md) — Contract structs, flows, gas formulas, GLV addresses\n- [Contract Addresses](..\u002Fgmx-official-trading\u002Freferences\u002Fcontract-addresses.md) — All deployed contracts per chain (shared with gmx-trading)\n- [SDK Reference](..\u002Fgmx-official-trading\u002Freferences\u002Fsdk-reference.md) — SDK module and method documentation (shared with gmx-trading)\n- [API Endpoints](..\u002Fgmx-official-trading\u002Freferences\u002Fapi-endpoints.md) — Oracle, OpenAPI, and GraphQL endpoints (shared with gmx-trading)\n- [GMX Documentation](https:\u002F\u002Fdocs.gmx.io) — Official protocol documentation\n- [GMX App — Pools](https:\u002F\u002Fapp.gmx.io\u002F#\u002Fpools) — GM pool interface\n- [GMX App — Vaults](https:\u002F\u002Fapp.gmx.io\u002F#\u002Fvaults) — GLV vault interface\n- [`@gmx-io\u002Fsdk` on npm](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@gmx-io\u002Fsdk) — SDK package\n",{"name":5,"license":15,"metadata":41,"description":7},{"author":42,"chains":43,"version":44,"openclaw":45},"gmx-io","arbitrum, avalanche, botanix","0.1",{"tags":46,"source":50,"official":51},[47,20,34,48,49,26,25,27],"gmx","gm-pools","glv","https:\u002F\u002Fgithub.com\u002Fgmx-io\u002Fgmx-ai",true,"main","3df1d0d67b30ff15236561f8c0f9f06d33f42dfd","skills\u002Fdefi\u002Fgmx-official-liquidity",17,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia","https:\u002F\u002Feth2030.com","Hardcore infra. Building @alt-research and applied cryptography.","2026-11-12 02:28:42",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":51},[63,66,69],{"tool":64,"command":65},"npx","npx skills add jiayaoqijia\u002Fcryptoskill --skill gmx-liquidity",{"tool":67,"command":68},"bunx","bunx skills add jiayaoqijia\u002Fcryptoskill --skill gmx-liquidity",{"tool":70,"command":71},"pnpm","pnpm dlx skills add jiayaoqijia\u002Fcryptoskill --skill gmx-liquidity","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fgmx-official-liquidity\u002FSKILL.md",[74,80,84],{"path":75,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":78,"sourceHtmlUrl":79},"references\u002Fliquidity-operations.md","REFERENCE","参考文档",12753,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fgmx-official-liquidity\u002Freferences\u002Fliquidity-operations.md",{"path":81,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":82,"sourceHtmlUrl":83},"SOURCE.md",146,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fgmx-official-liquidity\u002FSOURCE.md",{"path":85,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":86,"sourceHtmlUrl":87},"TRUST.auto.yaml",2198,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fgmx-official-liquidity\u002FTRUST.auto.yaml",[89,117,140,157,170,180],{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":93,"sourceHtmlUrl":94,"categories":95,"keywords":96,"publishedAt":36},"01M712C436EQYE43342Q4E7GG1","recipe-performance","Diagnose a specific MetaMask user flow's real-app performance and Sentry critical-user-flow telemetry with deterministic mm-harness recipes. Use for CUF boundaries, cold\u002Fwarm\u002Fbackground-resume behavior, latency regressions, lifecycle tags, duplicate or blocking network requests, long tasks, layout\u002Fpaint cost, screen redraws, React rerenders, or browser trace analysis where tests alone cannot prove the real app.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FSKILL.md",[],[97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116],"performance","recipe performance","recipe","mm","sentry","harness","app","deterministic","recipes","flow","cold","critical","analysis","tasks","react","browser","layout","screen","network","metamask",{"skillNo":118,"name":119,"nameZh":6,"description":120,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":121,"licenseFileUrl":122,"sourceHtmlUrl":123,"categories":124,"keywords":125,"publishedAt":36},"01M70XT2F8A5CQZ81JBP98KSW3","mexc-spot-websocket","MEXC Spot WebSocket workflows for public streams, protobuf handling, depth maintenance, listen keys, and private streams without exposing API keys.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FSKILL.md",[],[126,127,128,129,130,131,132,133,134,135,136,137,138,139],"mexc spot websocket","spot","websocket","api","workflows","public","listen","streams","depth","keys","for","api keys","maintenance","protobuf",{"skillNo":141,"name":142,"nameZh":6,"description":143,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":121,"licenseFileUrl":144,"sourceHtmlUrl":145,"categories":146,"keywords":147,"publishedAt":36},"01M70XT0MBXN3WZY1BKN0CPNKR","mexc-spot-rest","MEXC Spot REST workflows for public data, signed account requests, orders, wallet\u002Fadmin actions, and secure signing without exposing API keys.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FSKILL.md",[],[148,127,149,150,151,152,153,137,136,135,154,155,33,131,156,130,129],"mexc spot rest","REST","admin","signing","Wallet","requests","actions","secure","orders",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":121,"licenseFileUrl":161,"sourceHtmlUrl":162,"categories":163,"keywords":164,"publishedAt":36},"01M70XSYSVZ2N0N91XCAR2CAK0","mexc-futures-websocket","MEXC Futures WebSocket workflows for public streams, private account\u002Forder\u002Fposition streams, ping\u002Fpong, subscriptions, and depth maintenance.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FSKILL.md",[],[165,166,128,167,168,138,136,169,134,133,131,130],"mexc futures websocket","futures","position","ping","subscriptions",{"skillNo":171,"name":172,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":173,"sourceHtmlUrl":174,"categories":175,"keywords":176,"publishedAt":36},"01M6H0ZWHVE5W04WG2DKQE2SQC","0xklen-version-and-refresh-sanctions-lists","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FSKILL.md",[],[177,178,179],"refresh","0xklen version and refresh sanctions lists","version",{"skillNo":181,"name":182,"nameZh":6,"description":183,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":184,"sourceHtmlUrl":185,"categories":186,"keywords":187,"publishedAt":36},"01M6H0Z28Q3R8J0S799GJQWXM3","verify-tests-cover-changed-lines","Use when a PR adds code that the existing suite may not exercise. Measures patch coverage — the share of newly changed lines hit by tests — before approving.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FSKILL.md",[],[188,189,190,191,192,193,194,195,196,197,198],"tests","cover","verify tests cover changed lines","verify","coverage","pr","exercise","use","patch","code","share"]