@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
JavaScript
;
/**
* 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