diff --git a/.gitignore b/.gitignore index 0a19790..4726657 100644 --- a/.gitignore +++ b/.gitignore @@ -172,3 +172,7 @@ cython_debug/ # PyPI configuration file .pypirc + +# Local config +.mcp.json +.vscode/ diff --git a/hummingbot_mcp/executor_preferences.py b/hummingbot_mcp/executor_preferences.py index 98397f9..cd838bf 100644 --- a/hummingbot_mcp/executor_preferences.py +++ b/hummingbot_mcp/executor_preferences.py @@ -97,6 +97,20 @@ # price: "95000" ``` +### Lp Executor Defaults + +```yaml +lp_executor: + # Set your preferred defaults here (all optional, ask user if not set): + # connector_name: meteora/clmm # Must include /clmm suffix + # trading_pair: SOL-USDC + # extra_params: + # strategyType: 0 # Meteora only: 0=Spot, 1=Curve, 2=Bid-Ask + # + # Note: base_token/quote_token are inferred from trading_pair + # Note: side is determined by amounts at creation time, not defaulted +``` + --- *Last updated: Never* diff --git a/hummingbot_mcp/formatters/executors.py b/hummingbot_mcp/formatters/executors.py index 196d30e..cf0fc9d 100644 --- a/hummingbot_mcp/formatters/executors.py +++ b/hummingbot_mcp/formatters/executors.py @@ -204,6 +204,12 @@ def format_executor_detail(executor: dict[str, Any]) -> str: if close_timestamp is not None and close_timestamp != "N/A" and close_timestamp != 0: output += f"Closed: {format_timestamp(close_timestamp, '%Y-%m-%d %H:%M:%S')}\n" + # Always show custom_info if present + if custom_info: + output += "\nCustom Info:\n" + for key, value in custom_info.items(): + output += f" {key}: {value}\n" + return output diff --git a/hummingbot_mcp/guides/lp_executor.md b/hummingbot_mcp/guides/lp_executor.md new file mode 100644 index 0000000..4e5ec16 --- /dev/null +++ b/hummingbot_mcp/guides/lp_executor.md @@ -0,0 +1,120 @@ +### LP Executor +Manages liquidity provider positions on CLMM DEXs (Meteora, Raydium). +Opens positions within price bounds, monitors range status, tracks fees. + +**Use when:** +- Providing liquidity on Solana DEXs +- Want automated position monitoring and fee tracking +- Earning trading fees from LP positions + +**Avoid when:** +- Trading on CEX (use other executors) +- Want directional exposure only +- Not familiar with impermanent loss risks + +#### State Machine + +``` +NOT_ACTIVE → OPENING → IN_RANGE ↔ OUT_OF_RANGE → CLOSING → COMPLETE +``` + +- **NOT_ACTIVE**: Initial state, no position yet +- **OPENING**: Transaction submitted to open position +- **IN_RANGE**: Position active, current price within bounds +- **OUT_OF_RANGE**: Position active but price outside bounds (no fees earned) +- **CLOSING**: Transaction submitted to close position +- **COMPLETE**: Position closed, executor finished + +#### Key Parameters + +**Required:** +- `connector_name`: CLMM connector in `connector/clmm` format (e.g., `meteora/clmm`, `raydium/clmm`) + - **IMPORTANT:** Must include the `/clmm` suffix — using just `meteora` will fail +- `trading_pair`: Token pair (e.g., `SOL-USDC`) +- `pool_address`: Pool contract address +- `lower_price` / `upper_price`: Price range bounds + +**Liquidity:** +- `base_amount`: Amount of base token to provide (default: 0) +- `quote_amount`: Amount of quote token to provide (default: 0) +- `side`: Position side (0=BOTH, 1=BUY/quote-only, 2=SELL/base-only) + +**Auto-Close (Limit Range Orders):** +- `auto_close_above_range_seconds`: Close when price >= upper_price for this many seconds +- `auto_close_below_range_seconds`: Close when price <= lower_price for this many seconds +- Set to `null` (default) to disable auto-close + +#### Single-Sided vs Double-Sided Positions + +**Single-sided (one asset only):** +- **Base token only** (e.g., 0.2 SOL): Creates a SELL position (`side=2`) with range ABOVE current price + - Position starts out-of-range, enters range when price rises + - SOL converts to USDC as price moves up through the range +- **Quote token only** (e.g., 50 USDC): Creates a BUY position (`side=1`) with range BELOW current price + - Position starts out-of-range, enters range when price falls + - USDC converts to SOL as price moves down through the range + +**Double-sided (both assets):** +- When user specifies both `base_amount` and `quote_amount`, ask: + 1. **Centered range** around current price? (±50% of position width above/below current price) + 2. **Custom range**? (user specifies exact lower/upper bounds) +- Set `side=0` (BOTH) for double-sided positions + +**Position Management:** +- `keep_position=false` (default): Close LP position when executor stops +- `keep_position=true`: Leave position open on-chain, stop monitoring only +- `position_offset_pct`: Offset from current price for single-sided positions (default: 0.01%) + +#### Limit Range Orders (Auto-Close Feature) + +Use `auto_close_above_range_seconds` and `auto_close_below_range_seconds` to create limit-order-style LP positions that automatically close when price moves through the range. + +**SELL Limit (Take Profit on Long):** +``` +side=2, base_amount=X, quote_amount=0 +lower_price > current_price (range above current price) +auto_close_above_range_seconds=60 +``` +- Position starts OUT_OF_RANGE (price below range) +- When price rises into range: base → quote conversion, fees earned +- When price rises above range for 60s: position auto-closes with quote tokens + +**BUY Limit (Accumulate on Dip):** +``` +side=1, base_amount=0, quote_amount=X +upper_price < current_price (range below current price) +auto_close_below_range_seconds=60 +``` +- Position starts OUT_OF_RANGE (price above range) +- When price falls into range: quote → base conversion, fees earned +- When price falls below range for 60s: position auto-closes with base tokens + +**Key Benefits:** +- Earn LP fees while price moves through your target range +- Automatic execution without monitoring +- Better fills than traditional limit orders (continuous conversion vs single fill) + +#### Meteora Strategy Types (extra_params.strategyType) + +- `0`: **Spot** — Uniform liquidity across range +- `1`: **Curve** — Concentrated around current price +- `2`: **Bid-Ask** — Liquidity at range edges + +#### Important: Managing Positions + +**Always use the executor tool (`manage_executors`) to open and close LP positions — NOT the gateway CLMM tool directly.** + +- Opening/closing via `manage_gateway_clmm` bypasses the executor state machine and leaves the database out of sync +- Use `manage_executors` with `action="stop"` to properly close positions and update executor status +- If a position is closed externally (via gateway or UI), manually mark the executor as `TERMINATED` in the database + +**Verifying position status:** +- If uncertain about position status, use `manage_gateway_clmm` with `action="get_positions"` to check on-chain state +- Compare on-chain positions with executor `custom_info.position_address` +- If position is closed on-chain but executor still shows `RUNNING`, manually update executor status in database to `TERMINATED` +- If position is open on-chain but executor still shows `OPENING`, the executor should eventually sync — if stuck, check API logs for errors + +**Exception: Executor not found in API (404 error):** +- If API was restarted, executors may no longer exist in memory but positions remain on-chain +- In this case, use `manage_gateway_clmm` with `action="close_position"` to close the on-chain position directly +- Then manually update the executor status in the database to `TERMINATED` diff --git a/hummingbot_mcp/server.py b/hummingbot_mcp/server.py index 5c1d688..3e10b99 100644 --- a/hummingbot_mcp/server.py +++ b/hummingbot_mcp/server.py @@ -958,11 +958,17 @@ async def manage_gateway_config( Resource Types: - chains: Get all blockchain chains - networks: List/get/update network configurations (format: 'chain-network') + - GET returns merged chain + network config including: defaultWallet, defaultNetwork, rpcProvider, nodeURL, chainID, etc. + - UPDATE can modify both network-level fields (nodeURL, chainID) and chain-level fields (defaultWallet, defaultNetwork) - tokens: List/add/delete tokens per network - connectors: List/get/update DEX connector configurations - pools: List/add liquidity pools per connector/network - wallets: Add/delete wallets for blockchain chains + Examples: + - Get network config with defaultWallet: resource_type="networks", action="get", network_id="solana-mainnet-beta" + - Set default wallet: resource_type="networks", action="update", network_id="solana-mainnet-beta", config_updates={"defaultWallet": "wallet_address"} + Args: resource_type: Type of resource to manage action: Action to perform on the resource diff --git a/hummingbot_mcp/tools/executors.py b/hummingbot_mcp/tools/executors.py index c8ec356..04774f3 100644 --- a/hummingbot_mcp/tools/executors.py +++ b/hummingbot_mcp/tools/executors.py @@ -76,7 +76,8 @@ async def manage_executors(client: Any, request: ManageExecutorsRequest) -> dict "- **position_executor** — Directional trading with entry, stop-loss, and take-profit\n" "- **dca_executor** — Dollar-cost averaging for gradual position building\n" "- **grid_executor** — Grid trading across multiple price levels in ranging markets\n" - "- **order_executor** — Simple BUY/SELL order with execution strategy\n\n" + "- **order_executor** — Simple BUY/SELL order with execution strategy\n" + "- **lp_executor** — Liquidity provision on CLMM DEXs (Meteora, Raydium)\n\n" "Provide `executor_type` to see the configuration schema." ) @@ -174,8 +175,10 @@ async def manage_executors(client: Any, request: ManageExecutorsRequest) -> dict if request.save_as_default: executor_preferences.update_defaults(executor_type, request.executor_config) + executor_id = result.get("executor_id") or result.get("id") + formatted = f"Executor created successfully!\n\n" - formatted += f"Executor ID: {result.get('id', 'N/A')}\n" + formatted += f"Executor ID: {executor_id or 'N/A'}\n" formatted += f"Type: {executor_type}\n" formatted += f"Account: {account}\n" @@ -184,7 +187,7 @@ async def manage_executors(client: Any, request: ManageExecutorsRequest) -> dict return { "action": "create", - "executor_id": result.get("id"), + "executor_id": executor_id, "executor_type": executor_type, "account": account, "config_used": merged_config, diff --git a/hummingbot_mcp/tools/gateway_clmm.py b/hummingbot_mcp/tools/gateway_clmm.py index f0ad242..85cb747 100644 --- a/hummingbot_mcp/tools/gateway_clmm.py +++ b/hummingbot_mcp/tools/gateway_clmm.py @@ -250,6 +250,7 @@ async def manage_gateway_clmm_positions(client: Any, request: GatewayCLMMRequest "connector": request.connector, "network": request.network, "pool_address": request.pool_address, + "wallet_address": request.wallet_address or "(default)", "price_range": { "lower_price": request.lower_price, "upper_price": request.upper_price @@ -281,6 +282,7 @@ async def manage_gateway_clmm_positions(client: Any, request: GatewayCLMMRequest "connector": request.connector, "network": request.network, "position_address": request.position_address, + "wallet_address": request.wallet_address or "(default)", "result": result } @@ -308,6 +310,7 @@ async def manage_gateway_clmm_positions(client: Any, request: GatewayCLMMRequest "connector": request.connector, "network": request.network, "position_address": request.position_address, + "wallet_address": request.wallet_address or "(default)", "result": result } @@ -335,6 +338,7 @@ async def manage_gateway_clmm_positions(client: Any, request: GatewayCLMMRequest "connector": request.connector, "network": request.network, "pool_address": request.pool_address, + "wallet_address": request.wallet_address or "(default)", "result": result } diff --git a/hummingbot_mcp/tools/gateway_swap.py b/hummingbot_mcp/tools/gateway_swap.py index 335e916..5fb5949 100644 --- a/hummingbot_mcp/tools/gateway_swap.py +++ b/hummingbot_mcp/tools/gateway_swap.py @@ -101,6 +101,7 @@ async def manage_gateway_swaps(client: Any, request: GatewaySwapRequest) -> dict "trading_pair": request.trading_pair, "side": request.side, "amount": request.amount, + "wallet_address": request.wallet_address or "(default)", "result": result }