UNPKG

@gala-chain/launchpad-mcp-server

Version:

MCP server for Gala Launchpad - 102 tools (pool management, event watchers, GSwap DEX trading, price history, token creation, wallet management, DEX pool discovery, liquidity positions, token locks, locked token queries, composite pool data, cross-chain b

670 lines 29.3 kB
"use strict"; /** * DEX Liquidity Position Management Tools * * Tools for managing liquidity positions on GalaSwap DEX, including: * - Querying user positions and position details * - Estimating liquidity removal * - Adding liquidity by price or tick range * - Removing liquidity and collecting fees */ Object.defineProperty(exports, "__esModule", { value: true }); exports.liquidityPositionTools = exports.collectPositionFeesTool = exports.fetchSwapPositionDirectTool = exports.removeLiquidityTool = exports.addLiquidityByTicksTool = exports.addLiquidityByPriceTool = exports.estimateRemoveLiquidityTool = exports.getLiquidityPositionTool = exports.getLiquidityPositionByIdTool = exports.getAllUserLiquidityPositionsTool = exports.getUserLiquidityPositionsTool = void 0; const response_formatter_js_1 = require("../../utils/response-formatter.js"); const error_handler_js_1 = require("../../utils/error-handler.js"); const common_schemas_js_1 = require("../../schemas/common-schemas.js"); // Token symbol for DEX trading const TOKEN_SYMBOL_SCHEMA = { type: 'string', minLength: 1, maxLength: 20, description: 'Token symbol (e.g., "GALA", "GUSDC")', }; // Fee tier for GalaSwap (in basis points) const FEE_TIER_SCHEMA = { type: 'number', enum: [500, 3000, 10000], description: 'Fee tier in basis points: 500 (0.05%), 3000 (0.30%), 10000 (1.00%)', }; /** * Helper function to format position data for MCP response * Extracts and maps position fields consistently across multiple tools */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK position type not fully exported function formatPositions(positions) { return (positions && Array.isArray(positions)) // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK position type not fully exported ? positions.map((pos) => ({ positionId: pos.positionId, token0: pos.token0, token1: pos.token1, feeTier: pos.feeTier, liquidity: pos.liquidity, amount0: pos.amount0, amount1: pos.amount1, feeAmount0: pos.feeAmount0, feeAmount1: pos.feeAmount1, tickLower: pos.tickLower, tickUpper: pos.tickUpper, })) : []; } // 1. Get User Liquidity Positions exports.getUserLiquidityPositionsTool = { name: 'gala_launchpad_get_user_liquidity_positions', description: 'Get all open liquidity positions for a wallet address with optional pagination and real-time pricing. Note: bookmark pagination and pricing options are mutually exclusive - use getAllUserLiquidityPositionsTool for pricing with auto-pagination', inputSchema: { type: 'object', properties: { ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address to query positions for (e.g., "0x1234..." or "eth|1234...")', }, limit: { type: 'number', minimum: 1, maximum: 100, description: 'Maximum number of positions to return (optional)', }, bookmark: { type: 'string', description: 'Pagination bookmark from previous response (optional). Cannot be used together with withPrices - if you need pricing, use getAllUserLiquidityPositionsTool instead', }, withPrices: { type: 'boolean', description: 'Whether to fetch real-time pricing for each position (optional, default: false). Cannot be used together with bookmark - if you need pagination, use bookmark without withPrices. Pricing concurrency is controlled by SDK-level pricingConcurrency configuration', }, }, required: ['ownerAddress'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return type varies by invocation let result; // Determine which parameters to pass based on what's provided if (args.withPrices) { // When pricing is requested, pass pricing options as 3rd parameter (can't use bookmark with options) // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK method options not fully typed const pricingOptions = { withPrices: true }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK method overloads not exported result = await sdk.getSwapUserLiquidityPositions(args.ownerAddress, args.limit, pricingOptions); } else if (args.bookmark) { // When only bookmark pagination is used, pass bookmark as 3rd parameter // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK method overloads not exported result = await sdk.getSwapUserLiquidityPositions(args.ownerAddress, args.limit, args.bookmark); } else { // No pagination or pricing, just fetch positions // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK method overloads not exported result = await sdk.getSwapUserLiquidityPositions(args.ownerAddress, args.limit); } // Handle both array and object returns for backward compatibility const positions = Array.isArray(result) ? result : result?.items || []; const prices = !Array.isArray(result) && result?.prices ? result.prices : new Map(); const pricesCount = prices.size; return (0, response_formatter_js_1.formatSuccess)({ ownerAddress: args.ownerAddress, positionCount: positions.length, positionsWithPricing: pricesCount, positions: formatPositions(positions), pricingStatus: args.withPrices ? `Pricing fetched for ${pricesCount}/${positions.length} positions` : 'No pricing requested', message: positions.length > 0 ? `${positions.length} open positions found${args.withPrices ? ` with pricing data for ${pricesCount}` : ''}` : 'No open positions found', }); }), }; // 2. Get ALL User Liquidity Positions (Auto-Paginated) exports.getAllUserLiquidityPositionsTool = { name: 'gala_launchpad_get_all_user_liquidity_positions', description: 'Get ALL open liquidity positions for a wallet address with automatic pagination and optional real-time pricing', inputSchema: { type: 'object', properties: { ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address to query all positions for (e.g., "0x1234..." or "eth|1234...")', }, withPrices: { type: 'boolean', description: 'Whether to fetch real-time pricing for each position (optional, default: false). Pricing concurrency is controlled by SDK-level pricingConcurrency configuration', }, }, required: ['ownerAddress'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // Prepare pricing options object if requested // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK method options not fully typed const pricingOptions = args.withPrices ? { withPrices: true } : undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return type varies by invocation const result = await sdk.getAllSwapUserLiquidityPositions(args.ownerAddress, pricingOptions); // Handle both array and object returns for backward compatibility const positions = Array.isArray(result) ? result : result?.items || []; const prices = !Array.isArray(result) && result?.prices ? result.prices : new Map(); const pricesCount = prices.size; return (0, response_formatter_js_1.formatSuccess)({ ownerAddress: args.ownerAddress, positionCount: positions.length, positionsWithPricing: pricesCount, positions: formatPositions(positions), pricingStatus: args.withPrices ? `Pricing fetched for ${pricesCount}/${positions.length} positions` : 'No pricing requested', message: positions.length > 0 ? `${positions.length} open positions found (auto-paginated)${args.withPrices ? ` with pricing data for ${pricesCount}` : ''}` : 'No open positions found', }); }), }; // 3. Get Liquidity Position by ID exports.getLiquidityPositionByIdTool = { name: 'gala_launchpad_get_liquidity_position_by_id', description: 'Get specific liquidity position details by position ID', inputSchema: { type: 'object', properties: { ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address that owns the position', }, positionId: { type: 'string', minLength: 1, description: 'Unique position identifier', }, }, required: ['ownerAddress', 'positionId'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { const position = await sdk.getSwapLiquidityPositionById(args.ownerAddress, args.positionId); if (!position) { return (0, response_formatter_js_1.formatSuccess)({ found: false, message: `Position ${args.positionId} not found`, }); } return (0, response_formatter_js_1.formatSuccess)({ found: true, positionId: position.positionId, ownerAddress: position.ownerAddress, token0: position.token0, token1: position.token1, feeTier: position.feeTier, liquidity: position.liquidity, amount0: position.amount0, amount1: position.amount1, feeAmount0: position.feeAmount0, feeAmount1: position.feeAmount1, tickLower: position.tickLower, tickUpper: position.tickUpper, createdAt: position.createdAt ? new Date(position.createdAt).toISOString() : undefined, updatedAt: position.updatedAt ? new Date(position.updatedAt).toISOString() : undefined, message: `Position ${args.positionId}: ${position.liquidity} liquidity in ${position.token0}/${position.token1}`, }); }), }; // 4. Get Liquidity Position by Token Pair exports.getLiquidityPositionTool = { name: 'gala_launchpad_get_liquidity_position', description: 'Get liquidity position for a specific token pair and price range (tick boundaries)', inputSchema: { type: 'object', properties: { ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address that owns the position', }, token0: TOKEN_SYMBOL_SCHEMA, token1: TOKEN_SYMBOL_SCHEMA, fee: FEE_TIER_SCHEMA, tickLower: { type: 'number', description: 'Lower tick boundary of the position', }, tickUpper: { type: 'number', description: 'Upper tick boundary of the position', }, }, required: ['ownerAddress', 'token0', 'token1', 'fee', 'tickLower', 'tickUpper'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // SDK validates tick spacing - no need for MCP-level validation const position = await sdk.getSwapLiquidityPosition(args.ownerAddress, { token0: args.token0, token1: args.token1, fee: args.fee, tickLower: args.tickLower, tickUpper: args.tickUpper, }); if (!position) { return (0, response_formatter_js_1.formatSuccess)({ found: false, message: `No position found for ${args.token0}/${args.token1} at ticks ${args.tickLower}-${args.tickUpper}`, }); } return (0, response_formatter_js_1.formatSuccess)({ found: true, positionId: position.positionId, token0: position.token0, token1: position.token1, feeTier: position.feeTier, liquidity: position.liquidity, amount0: position.amount0, amount1: position.amount1, feeAmount0: position.feeAmount0, feeAmount1: position.feeAmount1, tickLower: position.tickLower, tickUpper: position.tickUpper, message: `Found position: ${position.liquidity} liquidity with ${position.feeAmount0} ${args.token0} and ${position.feeAmount1} ${args.token1} in fees`, }); }), }; // 5. Estimate Liquidity Removal exports.estimateRemoveLiquidityTool = { name: 'gala_launchpad_estimate_remove_liquidity', description: 'Estimate tokens and fees received when removing liquidity at current market prices', inputSchema: { type: 'object', properties: { token0: TOKEN_SYMBOL_SCHEMA, token1: TOKEN_SYMBOL_SCHEMA, fee: FEE_TIER_SCHEMA, liquidity: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Liquidity amount to estimate removal for', }, tickLower: { type: 'number', description: 'Lower tick boundary of the position', }, tickUpper: { type: 'number', description: 'Upper tick boundary of the position', }, ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address that owns the position', }, }, required: ['token0', 'token1', 'fee', 'liquidity', 'tickLower', 'tickUpper', 'ownerAddress'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // SDK validates tick spacing - no need for MCP-level validation const estimate = await sdk.getSwapEstimateRemoveLiquidity({ token0: args.token0, token1: args.token1, fee: args.fee, liquidity: args.liquidity, tickLower: args.tickLower, tickUpper: args.tickUpper, owner: args.ownerAddress, }); // API only returns amount0 and amount1; other fields are optional return (0, response_formatter_js_1.formatSuccess)({ amount0: estimate.amount0, amount1: estimate.amount1, message: `Removing ${args.liquidity} liquidity would return approximately ${estimate.amount0} ${args.token0} + ${estimate.amount1} ${args.token1}`, }); }), }; // 6. Add Liquidity by Price Range exports.addLiquidityByPriceTool = { name: 'gala_launchpad_add_liquidity_by_price', description: 'Add liquidity to a pool by specifying min/max price boundaries (SDK calculates tick boundaries)', inputSchema: { type: 'object', properties: { token0: TOKEN_SYMBOL_SCHEMA, token1: TOKEN_SYMBOL_SCHEMA, fee: FEE_TIER_SCHEMA, minPrice: { type: 'string', description: 'Minimum price boundary (e.g., "0.90")', }, maxPrice: { type: 'string', description: 'Maximum price boundary (e.g., "1.10")', }, amount0Desired: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Desired amount of token0 to provide', }, amount1Desired: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Desired amount of token1 to provide', }, amount0Min: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Minimum acceptable amount0 (optional, defaults to 0)', }, amount1Min: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Minimum acceptable amount1 (optional, defaults to 0)', }, }, required: [ 'token0', 'token1', 'fee', 'minPrice', 'maxPrice', 'amount0Desired', 'amount1Desired', ], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // Validate wallet before execution try { sdk.getAddress(); } catch { throw new Error('Wallet not configured - required for adding liquidity'); } const typedArgs = args; const result = await sdk.addSwapLiquidityByPrice({ token0: typedArgs.token0, token1: typedArgs.token1, fee: typedArgs.fee, minPrice: typedArgs.minPrice, maxPrice: typedArgs.maxPrice, amount0Desired: typedArgs.amount0Desired, amount1Desired: typedArgs.amount1Desired, amount0Min: typedArgs.amount0Min || '0', amount1Min: typedArgs.amount1Min || '0', }); return (0, response_formatter_js_1.formatSuccess)({ transactionId: result.transactionId, status: result.status, token0: typedArgs.token0, token1: typedArgs.token1, priceRange: `${typedArgs.minPrice} - ${typedArgs.maxPrice}`, amount0: result.amount0 || typedArgs.amount0Desired, amount1: result.amount1 || typedArgs.amount1Desired, liquidity: result.liquidity, positionId: result.positionId, message: `Liquidity added! Position: ${result.positionId || result.transactionId}`, }); }), }; // 7. Add Liquidity by Tick Range exports.addLiquidityByTicksTool = { name: 'gala_launchpad_add_liquidity_by_ticks', description: 'Add liquidity to a pool by specifying exact tick boundaries (advanced usage)', inputSchema: { type: 'object', properties: { token0: TOKEN_SYMBOL_SCHEMA, token1: TOKEN_SYMBOL_SCHEMA, feeTier: FEE_TIER_SCHEMA, tickLower: { type: 'number', description: 'Lower tick boundary (must be multiple of tickSpacing)', }, tickUpper: { type: 'number', description: 'Upper tick boundary (must be multiple of tickSpacing)', }, amount0Desired: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Desired amount of token0 to provide', }, amount1Desired: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Desired amount of token1 to provide', }, amount0Min: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Minimum acceptable amount0 (optional, defaults to 0)', }, amount1Min: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Minimum acceptable amount1 (optional, defaults to 0)', }, }, required: [ 'token0', 'token1', 'feeTier', 'tickLower', 'tickUpper', 'amount0Desired', 'amount1Desired', ], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // Validate wallet before execution try { sdk.getAddress(); } catch { throw new Error('Wallet not configured - required for adding liquidity'); } const typedArgs = args; const result = await sdk.addSwapLiquidityByTicks({ token0: typedArgs.token0, token1: typedArgs.token1, feeTier: typedArgs.feeTier, tickLower: typedArgs.tickLower, tickUpper: typedArgs.tickUpper, amount0Desired: typedArgs.amount0Desired, amount1Desired: typedArgs.amount1Desired, amount0Min: typedArgs.amount0Min || '0', amount1Min: typedArgs.amount1Min || '0', }); return (0, response_formatter_js_1.formatSuccess)({ transactionId: result.transactionId, status: result.status, token0: typedArgs.token0, token1: typedArgs.token1, tickRange: `${typedArgs.tickLower} - ${typedArgs.tickUpper}`, amount0: result.amount0 || typedArgs.amount0Desired, amount1: result.amount1 || typedArgs.amount1Desired, liquidity: result.liquidity, positionId: result.positionId, message: `Liquidity added! Position: ${result.positionId || result.transactionId}`, }); }), }; // 8. Remove Liquidity exports.removeLiquidityTool = { name: 'gala_launchpad_remove_liquidity', description: 'Remove liquidity from an open position and withdraw underlying tokens', inputSchema: { type: 'object', properties: { ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address that owns the position', }, positionId: { type: 'string', minLength: 1, description: 'Position identifier to remove liquidity from', }, liquidity: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Amount of liquidity to remove (full liquidity to close position)', }, amount0Min: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Minimum acceptable amount of token0 to receive', }, amount1Min: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Minimum acceptable amount of token1 to receive', }, deadline: { type: 'number', description: 'Transaction deadline (unix timestamp, optional)', }, }, required: ['ownerAddress', 'positionId', 'liquidity', 'amount0Min', 'amount1Min'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // Validate wallet before execution try { sdk.getAddress(); } catch { throw new Error('Wallet not configured - required for removing liquidity'); } const ownerAddress = args.ownerAddress; const positionId = args.positionId; const liquidity = args.liquidity; const amount0Min = args.amount0Min; const amount1Min = args.amount1Min; // Fetch position details first to get all required parameters const position = await sdk.getSwapLiquidityPositionById(ownerAddress, positionId); if (!position) { throw new Error(`Position ${positionId} not found for owner ${ownerAddress}`); } const result = await sdk.removeSwapLiquidity({ token0: position.token0, token1: position.token1, fee: position.feeTier, tickLower: position.tickLower, tickUpper: position.tickUpper, liquidity, amount0Min, amount1Min, positionId, }); return (0, response_formatter_js_1.formatSuccess)({ transactionId: result.transactionId, status: result.status, positionId, token0: position.token0, token1: position.token1, liquidity, amount0: result.amount0, amount1: result.amount1, message: `Liquidity removed! Received ${result.amount0} ${position.token0} and ${result.amount1} ${position.token1}`, }); }), }; // 9. Fetch Swap Position Direct (compound key lookup - most efficient) exports.fetchSwapPositionDirectTool = { name: 'gala_launchpad_fetch_swap_position_direct', description: 'Fetch a single liquidity position using ONLY the compound key (most efficient). Makes a direct HTTP call to GalaChain Gateway using token0, token1, fee, and tick range.', inputSchema: { type: 'object', properties: { token0: TOKEN_SYMBOL_SCHEMA, token1: TOKEN_SYMBOL_SCHEMA, fee: FEE_TIER_SCHEMA, tickLower: { type: 'number', description: 'Lower tick boundary of the position', }, tickUpper: { type: 'number', description: 'Upper tick boundary of the position', }, ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address that owns the position', }, }, required: ['token0', 'token1', 'fee', 'tickLower', 'tickUpper', 'ownerAddress'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { const position = await sdk.fetchSwapPositionDirect({ token0: args.token0, token1: args.token1, fee: args.fee, tickLower: args.tickLower, tickUpper: args.tickUpper, owner: args.ownerAddress, }); if (!position) { return (0, response_formatter_js_1.formatSuccess)({ found: false, message: `No position found for ${args.token0}/${args.token1} at ticks ${args.tickLower}-${args.tickUpper}`, }); } return (0, response_formatter_js_1.formatSuccess)({ found: true, position, message: `Position found: ${position.liquidity} liquidity in ${args.token0}/${args.token1}`, }); }), }; // 10. Collect Position Fees exports.collectPositionFeesTool = { name: 'gala_launchpad_collect_position_fees', description: 'Collect accumulated trading fees from a liquidity position without modifying the position', inputSchema: { type: 'object', properties: { ownerAddress: { ...common_schemas_js_1.ADDRESS_SCHEMA, description: 'Wallet address that owns the position', }, positionId: { type: 'string', minLength: 1, description: 'Position identifier to collect fees from', }, amount0Max: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Maximum amount of token0 fees to collect (optional)', }, amount1Max: { ...common_schemas_js_1.DECIMAL_AMOUNT_SCHEMA, description: 'Maximum amount of token1 fees to collect (optional)', }, }, required: ['ownerAddress', 'positionId'], }, handler: (0, error_handler_js_1.withErrorHandling)(async (sdk, args) => { // Validate wallet before execution try { sdk.getAddress(); } catch { throw new Error('Wallet not configured - required for collecting fees'); } const ownerAddress = args.ownerAddress; const positionId = args.positionId; const amount0Max = args.amount0Max; const amount1Max = args.amount1Max; // Fetch position details first to get token class keys const position = await sdk.getSwapLiquidityPositionById(ownerAddress, positionId); if (!position) { throw new Error(`Position ${positionId} not found for owner ${ownerAddress}`); } const result = await sdk.collectSwapPositionFees({ ownerAddress, positionId, amount0Max, amount1Max, }); return (0, response_formatter_js_1.formatSuccess)({ transactionId: result.transactionId, status: result.status, positionId, token0: position.token0, token1: position.token1, amount0Collected: result.amount0, amount1Collected: result.amount1, message: `Fees collected! ${result.amount0} ${position.token0} and ${result.amount1} ${position.token1} harvested`, }); }), }; // Export all liquidity position tools exports.liquidityPositionTools = [ exports.getUserLiquidityPositionsTool, exports.getAllUserLiquidityPositionsTool, exports.getLiquidityPositionByIdTool, exports.getLiquidityPositionTool, exports.estimateRemoveLiquidityTool, exports.addLiquidityByPriceTool, exports.addLiquidityByTicksTool, exports.removeLiquidityTool, exports.fetchSwapPositionDirectTool, exports.collectPositionFeesTool, ]; //# sourceMappingURL=liquidity-positions.js.map