--- name: lp-agent description: Manage concentrated liquidity (CLMM) positions on DEXs like Meteora and Raydium. Create, monitor, and rebalance LP positions automatically. metadata: author: hummingbot --- # lp-agent This skill manages **concentrated liquidity (CLMM) positions** on decentralized exchanges like Meteora (Solana) and Raydium. It provides automated LP position management with rebalancing capabilities similar to the LP Manager controller. ## Prerequisites Before using this skill, ensure hummingbot-api and MCP are running: ```bash bash <(curl -s https://raw.githubusercontent.com/hummingbot/skills/main/skills/lp-agent/scripts/check_prerequisites.sh) ``` If not installed, use the `hummingbot-deploy` skill first. ## Quick Start ### 1. Find a Pool Use the `manage_gateway_clmm` MCP tool: ``` # List popular pools on Meteora manage_gateway_clmm(action="list_pools", connector="meteora") # Search for specific pools manage_gateway_clmm(action="list_pools", connector="meteora", search_term="SOL") # Get detailed pool info manage_gateway_clmm(action="get_pool_info", connector="meteora", network="solana-mainnet-beta", pool_address="
") ``` ### 2. Create LP Position ``` # First, see the LP executor config schema manage_executors(executor_type="lp_executor") # Create position with quote only (buy base as price drops) manage_executors( action="create", executor_config={ "type": "lp_executor", "connector_name": "meteora/clmm", "pool_address": "", "trading_pair": "SOL-USDC", "base_token": "SOL", "quote_token": "USDC", "base_amount": 0, "quote_amount": 100, "lower_price": 180, "upper_price": 200, "side": 1 } ) ``` **Side values:** - `0` = Both-sided (base + quote) - `1` = Buy (quote-only, range below current price) - `2` = Sell (base-only, range above current price) **IMPORTANT - Verify Position Creation:** After creating an executor, you MUST verify the position was actually created on-chain. Follow these steps: **Step 1: Get the executor ID from the creation response** The `manage_executors(action="create")` call returns the executor_id. Use this ID for verification. **Step 2: Poll executor state until it changes from OPENING** ``` manage_executors(action="get", executor_id="") ``` Check `custom_info.state`: - `OPENING` → Transaction in progress, **wait 5-10 seconds and check again** - `IN_RANGE` or `OUT_OF_RANGE` → Position created successfully ✓ - `FAILED` or `RETRIES_EXCEEDED` → Transaction failed ✗ **Step 3: Confirm position exists on-chain** Even if state shows success, verify the position actually exists: ``` manage_gateway_clmm( action="get_positions", connector="meteora", network="solana-mainnet-beta", pool_address="" ) ``` The response should contain a position with matching `lower_price` and `upper_price`. **If verification fails:** 1. Stop the failed executor: `manage_executors(action="stop", executor_id="", keep_position=false)` 2. Check the error in `custom_info.error` if available 3. Common issues: range too wide (reduce width), insufficient balance, network congestion **IMPORTANT - Range Width Limits (check BEFORE opening position):** Meteora DLMM pools have bin limits. Each bin represents a small price increment based on `bin_step`: - `bin_step=1` → Each bin is 0.01% apart - `bin_step=10` → Each bin is 0.1% apart - `bin_step=100` → Each bin is 1% apart **Maximum bins per position is ~69 due to Solana account size limits.** Calculate maximum range width: ``` max_width_pct = bin_step * 69 / 100 user_width_pct = (upper_price - lower_price) / lower_price * 100 ``` **Before creating any position, the agent MUST:** 1. Get pool info: `manage_gateway_clmm(action="get_pool_info", connector="meteora", network="solana-mainnet-beta", pool_address="
")` 2. Extract `bin_step` from response 3. Calculate `max_width_pct = bin_step * 69 / 100` 4. If user's range exceeds max, **warn user and suggest narrower range** 5. Check wallet balance: user needs token amounts + **at least 0.06 SOL for position rent** - Use `get_portfolio_overview` to check balances - Warn if insufficient SOL or token balance Examples: - `bin_step=1`: max ~0.69% width - `bin_step=10`: max ~6.9% width - `bin_step=100`: max ~69% width ### 3. Set Default Preferences (Optional) Save commonly-used settings to avoid repeating them. Ask user which values they want to default: ``` # View current preferences manage_executors(action="get_preferences") # Save connector/pair defaults when creating manage_executors( action="create", executor_config={ "type": "lp_executor", "connector_name": "meteora/clmm", "trading_pair": "SOL-USDC", ... }, save_as_default=true ) ``` **What can be defaulted:** - `connector_name` - e.g., `meteora/clmm` (must include `/clmm` suffix) - `trading_pair` - e.g., `SOL-USDC` - `extra_params.strategyType` - Meteora only: 0=Spot, 1=Curve, 2=Bid-Ask **What should NOT be defaulted:** - `side` - Determined by amounts at creation time - `base_token`/`quote_token` - Inferred from trading_pair - `lower_price`/`upper_price` - Market-dependent Defaults stored at `~/.hummingbot_mcp/executor_preferences.md`. ### 4. Monitor Positions ``` # List all LP positions manage_executors(action="search", executor_types=["lp_executor"]) # Get specific position details manage_executors(action="get", executor_id="") # Get positions summary manage_executors(action="get_summary") ``` ### 5. Manage Positions ``` # Collect fees (via Gateway CLMM) manage_gateway_clmm( action="collect_fees", connector="meteora", network="solana-mainnet-beta", position_address="" ) # Close position manage_executors(action="stop", executor_id="", keep_position=false) ``` ## Position Types ### Double-Sided (Both Tokens) Provide liquidity with both base and quote tokens. Best when you expect price to stay within range. ``` Lower Current Upper |---------------|---------------| |<-- Quote zone | Base zone -->| ``` - Price goes UP: You sell base, accumulate quote - Price goes DOWN: You buy base with quote ### Single-Sided: Quote Only (side=1) Position entire range BELOW current price. You're buying base as price drops. ``` Lower Upper Current |---------------|--------| |<---- Buy zone ---->| ``` ### Single-Sided: Base Only (side=2) Position entire range ABOVE current price. You're selling base as price rises. ``` Current Lower Upper |--------|---------------| |<-- Sell zone -->| ``` ## Rebalancing Strategy (Agent-Driven) When price moves out of your position range, the agent handles rebalancing automatically. ### Step 1: Monitor Position State ``` manage_executors(action="get", executor_id="") ``` Check `custom_info.state`: - `IN_RANGE` → No action needed - `OUT_OF_RANGE` → Wait for rebalance delay, then rebalance **Rebalance delay:** Wait for position to be out of range for a set time (default: 60 seconds). Ask user to confirm delay before starting. ### Step 2: Determine Rebalance Direction Compare `custom_info.current_price` with `custom_info.lower_price` and `custom_info.upper_price`: **If current_price < lower_price (price dropped below range):** - You're now holding mostly BASE tokens - Strategy: Create BASE-ONLY position ABOVE current price - This lets you sell base as price recovers **If current_price > upper_price (price rose above range):** - You're now holding mostly QUOTE tokens - Strategy: Create QUOTE-ONLY position BELOW current price - This lets you buy base if price drops ### Step 3: Close Old Position ``` manage_executors(action="stop", executor_id="", keep_position=false) ``` This returns tokens to wallet. Use the returned amounts for the new position. ### Step 4: Create New Single-Sided Position Use tokens received from close. For position width W% (ask user, check bin_step limits): **Price below range → base-only position ABOVE current price (side=2):** ``` new_lower_price = current_price new_upper_price = current_price * (1 + W/100) base_amount = quote_amount = 0 side = 2 ``` **Price above range → quote-only position BELOW current price (side=1):** ``` new_lower_price = current_price * (1 - W/100) new_upper_price = current_price base_amount = 0 quote_amount = side = 1 ``` ``` manage_executors(action="create", executor_config={ "type": "lp_executor", "connector_name": "", "pool_address": "", "trading_pair": "", "base_amount": , "quote_amount": , "lower_price": , "upper_price": , "side": }) ``` ### Step 5: Verify New Position ``` manage_executors(action="get", executor_id="") ``` Confirm `custom_info.state` is `IN_RANGE` or `OUT_OF_RANGE` (not `OPENING` or `FAILED`). ## MCP Tools Reference ### manage_gateway_clmm | Action | Parameters | Description | |--------|------------|-------------| | `list_pools` | connector, search_term, sort_key, limit | Browse available pools | | `get_pool_info` | connector, network, pool_address | Get pool details | | `get_positions` | connector, network, pool_address | Get positions in a pool | | `open_position` | connector, network, pool_address, lower_price, upper_price, base_token_amount, quote_token_amount | Open position directly | | `close_position` | connector, network, position_address | Close position | | `collect_fees` | connector, network, position_address | Collect accumulated fees | ### manage_executors | Action | Parameters | Description | |--------|------------|-------------| | (none) | executor_type="lp_executor" | Show config schema with your defaults | | `create` | executor_config, save_as_default | Create LP executor (optionally save as default) | | `search` | executor_types=["lp_executor"] | List LP executors | | `get` | executor_id | Get executor details | | `stop` | executor_id, keep_position | Stop executor | | `get_summary` | - | Get overall summary | | `get_preferences` | - | View preferences file | | `save_preferences` | preferences_content | Save edited preferences | | `reset_preferences` | - | Reset to default template | ## Scripts | Script | Purpose | |--------|---------| | `check_prerequisites.sh` | Verify API, Gateway, wallet setup | ## LP Executor Config Schema ```json { "type": "lp_executor", "connector_name": "meteora/clmm", "pool_address": "2sfXxxxx...", "trading_pair": "SOL-USDC", "base_token": "SOL", "quote_token": "USDC", "base_amount": 0, "quote_amount": 100, "lower_price": 70, "upper_price": 90, "side": 1, "extra_params": { "strategyType": 0 } } ``` **Fields:** - `connector_name`: CLMM connector - append `/clmm` to connector name (e.g., `meteora/clmm`) - `pool_address`: Pool contract address - `trading_pair`: Format "BASE-QUOTE" - `base_amount` / `quote_amount`: Token amounts (set one to 0 for single-sided) - `lower_price` / `upper_price`: Position price bounds - `side`: 0=both, 1=buy (quote-only), 2=sell (base-only) - `extra_params`: Connector-specific (Meteora strategyType: 0=Spot, 1=Curve, 2=Bid-Ask) **Supported Connectors:** - `meteora/clmm` - Tested and fully supported - Other Gateway CLMM connectors - Should work but not yet tested To list available CLMM connectors: ``` manage_gateway_config(resource_type="connectors", action="list") ``` Append `/clmm` to any CLMM connector name when creating executors. ## Example: Full LP Management Flow ``` # 1. Check prerequisites bash <(curl -s https://raw.githubusercontent.com/hummingbot/skills/main/skills/lp-agent/scripts/check_prerequisites.sh) # 2. Find a pool manage_gateway_clmm(action="list_pools", connector="meteora", search_term="SOL", sort_key="volume") # 3. Get pool details (note the bin_step for range calculation) manage_gateway_clmm(action="get_pool_info", connector="meteora", network="solana-mainnet-beta", pool_address="2sfXxxxx") # Example response shows: bin_step=10, current_price=190 # Max range width = 10 * 69 / 100 = 6.9% # Use conservative 5% width: lower=185.5, upper=194.5 # 4. Create position manage_executors(action="create", executor_config={ "type": "lp_executor", "connector_name": "meteora/clmm", "pool_address": "2sfXxxxx", "trading_pair": "SOL-USDC", "base_token": "SOL", "quote_token": "USDC", "base_amount": 0, "quote_amount": 100, "lower_price": 185.5, "upper_price": 194.5, "side": 1 }) # 5. VERIFY position was created (critical step!) manage_executors(action="get", executor_id="") # Check custom_info.state: # - "OPENING" → wait and check again # - "IN_RANGE" or "OUT_OF_RANGE" → success! # - "FAILED" → check error, possibly reduce range width # 6. Monitor position manage_executors(action="get", executor_id="") # 7. If out of range for 60+ seconds, rebalance (see Rebalancing Strategy) # - Close: manage_executors(action="stop", executor_id="", keep_position=false) # - Reopen with tokens received as single-sided position # 8. When done, close manage_executors(action="stop", executor_id="", keep_position=false) ``` ## Error Handling | Error | Cause | Solution | |-------|-------|----------| | "Prerequisites not met" | API or MCP not running | Run `hummingbot-deploy` skill | | "Pool not found" | Invalid pool address | Use list_pools to find valid pools | | "Insufficient balance" | Not enough tokens | Check wallet balance, reduce amounts | | "Position not in range" | Price outside bounds | Wait or rebalance | | "InvalidRealloc" | Position range spans too many bins | Reduce range width (see bin_step limits above) | | State stuck at "OPENING" | Transaction failed silently | Stop executor and retry with narrower range |