diff --git a/hummingbot_mcp/executor_preferences.py b/hummingbot_mcp/executor_preferences.py index cd838bf..a23fae1 100644 --- a/hummingbot_mcp/executor_preferences.py +++ b/hummingbot_mcp/executor_preferences.py @@ -111,6 +111,18 @@ # Note: side is determined by amounts at creation time, not defaulted ``` +### Swap Executor Defaults + +```yaml +swap_executor: + # Set your preferred defaults here (all optional, ask user if not set): + # connector_name: jupiter/router + # trading_pair: SOL-USDC + # side: BUY # BUY or SELL + # amount: "1.0" # Base token amount + # slippage_pct: "0.5" # Optional: override default slippage +``` + --- *Last updated: Never* diff --git a/hummingbot_mcp/guides/swap_executor.md b/hummingbot_mcp/guides/swap_executor.md new file mode 100644 index 0000000..35ef1c5 --- /dev/null +++ b/hummingbot_mcp/guides/swap_executor.md @@ -0,0 +1,110 @@ +### Swap Executor +**Execute single swaps on Gateway AMM connectors with retry logic.** + +Executes token swaps on Gateway-connected DEXs (Jupiter, Raydium, etc.) with +built-in retry handling for transaction timeouts and failures. + +**Use when:** +- Executing a single token swap on Solana DEXs +- Need reliable swap execution with automatic retries +- Trading via Gateway AMM connectors + +**Avoid when:** +- Need complex trading strategies (use other executors) +- Want to manage LP positions (use lp_executor) +- Trading on CEX (use order_executor) + +#### State Machine + +``` +NOT_STARTED → EXECUTING → COMPLETED (success) + → FAILED (max retries) +``` + +- **NOT_STARTED**: Initial state, swap not yet attempted +- **EXECUTING**: Swap submitted, waiting for confirmation (with retries) +- **COMPLETED**: Swap successfully completed +- **FAILED**: Swap failed after max retry attempts + +#### Key Parameters + +**Required:** +- `connector_name`: Gateway router connector (e.g., `jupiter/router`) +- `trading_pair`: Token pair (e.g., `SOL-USDC`) +- `side`: `BUY` (1) or `SELL` (2) +- `amount`: Amount of base token to swap + +**Optional:** +- `slippage_pct`: Override default slippage tolerance + +#### Side (Trade Direction) + +**BUY (side=1 or "BUY"):** +- Buy base token using quote token +- Example: BUY SOL-USDC means buy SOL, pay USDC +- `amount` specifies how much SOL you want to receive + +**SELL (side=2 or "SELL"):** +- Sell base token to receive quote token +- Example: SELL SOL-USDC means sell SOL, receive USDC +- `amount` specifies how much SOL you want to sell + +#### Example Configurations + +**Buy SOL with USDC:** +```yaml +swap_executor: + connector_name: jupiter/router + trading_pair: SOL-USDC + side: BUY + amount: "1.0" # Buy 1 SOL +``` + +**Sell SOL for USDC:** +```yaml +swap_executor: + connector_name: jupiter/router + trading_pair: SOL-USDC + side: SELL + amount: "0.5" # Sell 0.5 SOL +``` + +**With custom slippage:** +```yaml +swap_executor: + connector_name: jupiter/router + trading_pair: SOL-USDC + side: BUY + amount: "1.0" + slippage_pct: "1.0" # 1% slippage tolerance +``` + +#### Retry Behavior + +The SwapExecutor uses the GatewayRetryMixin for robust error handling: + +- **Transaction timeouts**: Automatically retries (chain congestion) +- **Price movement errors**: Retries without counting against max retries +- **Slippage errors**: Retries without counting against max retries +- **Max retries**: Default 10 attempts before failing + +#### Custom Info (Executor Response) + +When querying an executor, the `custom_info` field contains: +- `state`: Current state (NOT_STARTED, EXECUTING, COMPLETED, FAILED) +- `side`: BUY or SELL +- `amount`: Requested amount +- `executed_amount`: Actual executed amount +- `executed_price`: Average execution price (quote per base) +- `tx_fee`: Transaction fee paid +- `tx_hash`: Transaction signature/hash (Solana transaction ID) +- `exchange_order_id`: Same as tx_hash (for compatibility) +- `current_retries`: Number of retries so far +- `max_retries_reached`: True if executor gave up + +#### Important Notes + +- **Gateway Required**: SwapExecutor requires a running Gateway instance with the AMM connector configured +- **Atomic Operation**: Swaps are atomic - they either complete fully or not at all +- **No P&L Tracking**: Single swaps don't track P&L (no entry/exit pair) +- **Fee Tracking**: Transaction fees are tracked and reported diff --git a/hummingbot_mcp/schemas.py b/hummingbot_mcp/schemas.py index 24d1ba0..8941c83 100644 --- a/hummingbot_mcp/schemas.py +++ b/hummingbot_mcp/schemas.py @@ -534,12 +534,17 @@ class GatewaySwapRequest(BaseModel): "Example: '1.5' for 1.5% slippage tolerance" ) - # Execute-specific parameter + # Execute-specific parameters wallet_address: str | None = Field( default=None, description="Wallet address for execute action (optional, uses default wallet if not provided)" ) + account_name: str | None = Field( + default=None, + description="Account name for execute action (optional, default: 'master_account')" + ) + # Get status parameter transaction_hash: str | None = Field( default=None, diff --git a/hummingbot_mcp/server.py b/hummingbot_mcp/server.py index f23106b..b86a8d6 100644 --- a/hummingbot_mcp/server.py +++ b/hummingbot_mcp/server.py @@ -665,6 +665,7 @@ async def manage_executors( ⭐ PRIORITY: This is the DEFAULT tool for ALL trading operations: - **Buying/Selling**: Use `order_executor` — supports MARKET, LIMIT, LIMIT_MAKER, LIMIT_CHASER strategies, USD amounts ('$100'), leverage, position_action OPEN/CLOSE. To cancel: use action="stop" on the executor. + - **DEX Swaps**: Use `swap_executor` — single swaps on Gateway AMM connectors (Jupiter, Raydium) with retry logic. - **LP Positions**: Use `lp_executor` — opens/manages CLMM positions on Meteora, Raydium with full lifecycle tracking. Use `explore_dex_pools` to discover pools first (list_pools, get_pool_info). - **Grid/DCA/Position strategies**: Use grid_executor, dca_executor, position_executor. @@ -689,6 +690,14 @@ async def manage_executors( Supports single-sided (base or quote only) and double-sided positions. Auto-close feature enables limit-order-style LP positions. + ## swap_executor + **Execute single swaps on Gateway AMM connectors with retry logic.** + Use when: Executing a single token swap on Solana DEXs (Jupiter, Raydium), need reliable execution with retries. + Avoid when: Need complex trading strategies, want LP positions (use lp_executor), trading on CEX (use order_executor). + Connector format: `jupiter/router` (Gateway router connector). + Parameters: connector_name, trading_pair, side (BUY/SELL), amount (base token). + Swaps are atomic — they either complete fully or not at all. + ## position_executor Takes directional positions with defined entry, stop-loss, and take-profit levels. Use when: Clear directional view, want automated SL/TP, defined risk/reward. @@ -722,6 +731,7 @@ async def manage_executors( - dca_executor: `amounts_quote` (list of quote amounts per level, e.g., [100, 100, 150]) - order_executor: `amount` (base currency, or '$100' for USD value) - lp_executor: `base_amount` and/or `quote_amount` (token amounts to provide as liquidity) + - swap_executor: `amount` (base token amount to swap) Never assume or default these values. Always check the guide first via progressive disclosure. IMPORTANT - Grid Executor Side: diff --git a/hummingbot_mcp/tools/executors.py b/hummingbot_mcp/tools/executors.py index bfbc94a..5ced451 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 "- **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" - "- **lp_executor** — Liquidity provision on CLMM DEXs (Meteora, Raydium)\n\n" + "- **lp_executor** — Liquidity provision on CLMM DEXs (Meteora, Raydium)\n" + "- **swap_executor** — Single swap on Gateway AMM connectors (Jupiter, Raydium)\n\n" "Provide `executor_type` to see the configuration schema." ) diff --git a/hummingbot_mcp/tools/gateway_swap.py b/hummingbot_mcp/tools/gateway_swap.py index 5fb5949..3aed5c0 100644 --- a/hummingbot_mcp/tools/gateway_swap.py +++ b/hummingbot_mcp/tools/gateway_swap.py @@ -67,14 +67,12 @@ async def manage_gateway_swaps(client: Any, request: GatewaySwapRequest) -> dict } # ============================================ - # EXECUTE - Execute swap transaction + # EXECUTE - Execute swap via swap_executor # ============================================ elif request.action == "execute": # Validate required parameters if not request.connector: raise ToolError("connector is required for execute action") - if not request.network: - raise ToolError("network is required for execute action") if not request.trading_pair: raise ToolError("trading_pair is required for execute action") if not request.side: @@ -86,14 +84,27 @@ async def manage_gateway_swaps(client: Any, request: GatewaySwapRequest) -> dict if "-" not in request.trading_pair: raise ToolError(f"Invalid trading_pair format. Expected 'BASE-QUOTE', got '{request.trading_pair}'") - result = await client.gateway_swap.execute_swap( - connector=request.connector, - network=request.network, - trading_pair=request.trading_pair, - side=request.side, - amount=Decimal(request.amount), - slippage_pct=Decimal(request.slippage_pct or "1.0"), - wallet_address=request.wallet_address + # Build connector name in gateway format: {connector}/router + connector_name = f"{request.connector}/router" + + # Convert side to numeric (BUY=1, SELL=2) + side_value = 1 if request.side.upper() == "BUY" else 2 + + # Build executor config + executor_config = { + "type": "swap_executor", + "connector_name": connector_name, + "trading_pair": request.trading_pair, + "side": side_value, + "amount": str(request.amount), + } + if request.slippage_pct: + executor_config["slippage_pct"] = str(request.slippage_pct) + + # Create swap executor + result = await client.executors.create_executor( + executor_config=executor_config, + account_name=request.account_name or "master_account" ) return { @@ -101,7 +112,9 @@ 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)", + "connector_name": connector_name, + "executor_id": result.get("executor_id"), + "status": result.get("status"), "result": result }