Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b9db532
Add SwapExecutor support
fengtality Mar 11, 2026
f9f203d
fix: remove misleading restart message for token add/delete
fengtality Mar 12, 2026
ca57a16
Merge fix/gateway-proxy-operation-ids into feature/swap-executor
fengtality Mar 28, 2026
02ef8ea
Add /lphistory endpoint for LP position history
fengtality Mar 28, 2026
28b15ca
Add swap_executor type and improve executor handling
fengtality Mar 28, 2026
2d73083
Add GatewaySwap connector support for swap executor
fengtality Mar 29, 2026
51f48de
Revert conf_client.yml to default settings
fengtality Mar 29, 2026
f5216e3
Update Gateway poll integration for simplified endpoint
fengtality Mar 29, 2026
6aafc3b
Fix swap_executor example: add required connector_name
fengtality Mar 29, 2026
4283448
Simplify Gateway executor validation in executor_service
fengtality Apr 1, 2026
ec06708
sync: update lp_rebalancer from hummingbot
fengtality Apr 1, 2026
70db22c
debug: add logging to order completion handler
fengtality Apr 1, 2026
f21dca2
refactor: update to new gateway connector architecture
fengtality Apr 3, 2026
1921079
fix: update get_price call to use dex and trading_type params
fengtality Apr 3, 2026
13bd7d5
fix: update executor_service and lp_rebalancer for provider refactor
fengtality Apr 7, 2026
9c96838
fix: initialize rate sources for gateway executors
fengtality Apr 8, 2026
1f89d4f
fix: remove broken rate source initialization for gateway executors
fengtality Apr 8, 2026
64dc472
refactor: remove SwapExecutor references, use OrderExecutor for swaps
fengtality Apr 8, 2026
885deed
refactor: remove redundant executor validation, let Pydantic handle it
fengtality Apr 8, 2026
b29e111
cleanup: fix lint issues in orders_recorder, remove verbose debug log…
fengtality Apr 8, 2026
dddf487
Remove lphistory endpoint - not needed for API-managed executors
fengtality Apr 8, 2026
3b096e3
fix: revert lint-only changes, simplify executor connector init
fengtality Apr 8, 2026
e8397d6
refactor: remove fee fallback logic from orders_recorder
fengtality Apr 8, 2026
81e4183
revert: remove all changes from orders_recorder.py
fengtality Apr 8, 2026
65e8c21
revert: remove all changes from bots_orchestrator.py
fengtality Apr 8, 2026
87ab393
Filter out DEX providers from connectors list
fengtality Apr 8, 2026
816c2bf
Clean up debug logging in orders_recorder after order event fix
fengtality Apr 9, 2026
531a156
Improvements from prior sessions
fengtality Apr 9, 2026
6294cc2
Change side from int to TradeType enum, rename BOTH to RANGE
fengtality Apr 9, 2026
e0fca6c
Simplify price limit validation with clamp-or-switch logic
fengtality Apr 9, 2026
58938d6
Skip price limit checks for RANGE positions
fengtality Apr 9, 2026
44d20d0
Merge main into feature/connector-level-retry
fengtality Apr 9, 2026
442de66
Sync lp_rebalancer.py from hummingbot
fengtality Apr 9, 2026
17711dc
Revert formatting changes to orders_recorder.py
fengtality Apr 9, 2026
ec2be3d
Fix closed position counts to use TradeType enum
fengtality Apr 9, 2026
4f738b1
Simplify Gateway price fetching to use network swap provider
fengtality Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,137 changes: 723 additions & 414 deletions bots/controllers/generic/lp_rebalancer/lp_rebalancer.py

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions models/executors.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ class PositionsSummaryResponse(BaseModel):
"twap_executor",
"xemm_executor",
"order_executor",
"lp_executor"
"lp_executor",
]


Expand Down Expand Up @@ -248,21 +248,20 @@ class CreateExecutorRequest(BaseModel):
},
{
"summary": "LP Executor",
"description": "Create an LP position on a CLMM DEX (Meteora, Raydium)",
"description": "Create an LP position on a CLMM DEX",
"value": {
"account_name": "master_account",
"executor_config": {
"type": "lp_executor",
"connector_name": "meteora/clmm",
"connector_name": "solana-mainnet-beta",
"lp_provider": "meteora/clmm",
"trading_pair": "SOL-USDC",
"pool_address": "HTvjzsfX3yU6BUodCjZ5vZkUrAxMDTrBs3CJaq43ashR",
"lower_price": "80",
"upper_price": "100",
"base_amount": "0",
"quote_amount": "10.0",
"side": 1,
"auto_close_above_range_seconds": None,
"auto_close_below_range_seconds": 300,
"side": "BUY",
"extra_params": {"strategyType": 0},
"keep_position": False
}
Expand Down
6 changes: 4 additions & 2 deletions routers/connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ async def available_connectors():
Get a list of all available connectors.

Returns:
List of connector names supported by the system
List of connector names supported by the system (excludes DEX providers which use Gateway networks)
"""
return list(AllConnectorSettings.get_connector_settings().keys())
all_connectors = AllConnectorSettings.get_connector_settings().keys()
# Filter out DEX providers (contain '/') - these are accessed via Gateway networks
return [c for c in all_connectors if '/' not in c]


@router.get("/{connector_name}/config-map", response_model=Dict[str, dict])
Expand Down
8 changes: 2 additions & 6 deletions routers/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,7 @@ async def add_network_token(

return {
"success": True,
"message": f"Token {token_request.symbol} added to {network_id}. Restart Gateway for changes to take effect.",
"restart_required": True,
"restart_endpoint": "POST /gateway/restart",
"message": f"Token {token_request.symbol} added to {network_id}.",
"token": {
"symbol": token_request.symbol,
"address": token_request.address,
Expand Down Expand Up @@ -661,9 +659,7 @@ async def delete_network_token(

return {
"success": True,
"message": f"Token {token_address} deleted from {network_id}. Restart Gateway for changes to take effect.",
"restart_required": True,
"restart_endpoint": "POST /gateway/restart",
"message": f"Token {token_address} deleted from {network_id}.",
"token_address": token_address,
"network_id": network_id
}
Expand Down
26 changes: 7 additions & 19 deletions services/accounts_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,16 +438,12 @@ class AccountsService:
update the balances of each account.
"""
default_quotes = {
"hyperliquid": "USD",
"hyperliquid_perpetual": "USDC",
"hyperliquid": "USDC",
"hyperliquid_perpetual": "USD",
"xrpl": "RLUSD",
"kraken": "USD",
}
gateway_default_pricing_connector = {
"ethereum": "uniswap/router",
"solana": "jupiter/router",
}
potential_wrapped_tokens = ["ETH", "SOL", "BNB", "POL", "AVAX", "FTM", "ONE", "GLMR", "MOVR"]
potential_wrapped_tokens = ["ETH", "SOL", "BNB", "POL", "AVAX"]

# Cache for storing last successful prices by trading pair
_last_known_prices = {}
Expand Down Expand Up @@ -2164,10 +2160,6 @@ async def _fetch_gateway_prices_immediate(self, chain: str, network: str,
Fetch prices immediately from Gateway for the given tokens.
This is used to get prices right away instead of waiting for the background update task.

Uses the same pricing connector resolution as MarketDataProvider.update_rates_task():
- solana -> jupiter/router
- ethereum -> uniswap/router

Args:
chain: Blockchain chain (e.g., 'solana', 'ethereum')
network: Network name (e.g., 'mainnet-beta', 'mainnet')
Expand All @@ -2184,11 +2176,8 @@ async def _fetch_gateway_prices_immediate(self, chain: str, network: str,
rate_oracle = RateOracle.get_instance()
prices = {}

# Resolve pricing connector based on chain (same logic as MarketDataProvider)
pricing_connector = self.gateway_default_pricing_connector.get(chain)
if not pricing_connector:
logger.warning(f"No pricing connector configured for chain '{chain}', skipping immediate price fetch")
return prices
# Construct full network name (e.g., "solana-mainnet-beta")
full_network = f"{chain}-{network}"

# Create tasks for all tokens in parallel
tasks = []
Expand Down Expand Up @@ -2222,10 +2211,9 @@ async def _fetch_gateway_prices_immediate(self, chain: str, network: str,
continue

try:
# get_price will auto-fetch dex/trading_type from network's swap provider
task = gateway_client.get_price(
chain=chain,
network=network,
connector=pricing_connector,
network=full_network,
base_asset=token,
quote_asset=quote_asset,
amount=Decimal("1"),
Expand Down
16 changes: 11 additions & 5 deletions services/executor_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,13 @@ async def create_executor(
# Extract connector and trading pair from config
connector_name = executor_config.get("connector_name")
trading_pair = executor_config.get("trading_pair")
if not connector_name:
raise HTTPException(status_code=400, detail="connector_name is required in executor_config")
if not trading_pair:
raise HTTPException(status_code=400, detail="trading_pair is required in executor_config")

# Ensure connector and market are ready
await trading_interface.add_market(connector_name, trading_pair)
if connector_name:
if trading_pair:
await trading_interface.add_market(connector_name, trading_pair)
else:
await trading_interface.ensure_connector(connector_name)

# Set timestamp if not provided (required for time-based features like time_limit)
if "timestamp" not in executor_config or executor_config["timestamp"] is None:
Expand Down Expand Up @@ -650,6 +650,12 @@ def _format_executor_info(
result["close_type"] = executor.close_type.name if executor.close_type else None
result["is_active"] = not executor.is_closed

# Add side from executor_info (it's a property, not serialized by model_dump)
side = executor_info.side
if side is not None:
# Convert TradeType enum or int to string
result["side"] = side.name if hasattr(side, 'name') else str(side)

# For grid executors, filter out heavy fields from custom_info
if executor_type == "grid_executor" and result.get("custom_info"):
heavy_fields = {"levels_by_state", "filled_orders", "failed_orders", "canceled_orders"}
Expand Down
9 changes: 2 additions & 7 deletions services/gateway_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
from decimal import Decimal
from typing import Dict, List, Optional

import aiohttp
Expand Down Expand Up @@ -582,20 +581,19 @@ async def poll_transaction(
self,
network_id: str,
tx_hash: str,
wallet_address: Optional[str] = None
) -> Optional[Dict]:
"""
Poll transaction status on blockchain.

Args:
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'ethereum-mainnet')
tx_hash: Transaction hash/signature
wallet_address: Optional wallet address for verification

Returns:
Transaction status dict with fields:
- txStatus: 1 for confirmed, 0 for failed/pending
- txStatus: 1 for confirmed, 0 for pending, -1 for failed
- fee: Transaction fee amount
- error: Parsed error message if transaction failed (e.g., "SLIPPAGE_EXCEEDED (0x1771): ...")
- txData: Full transaction data including meta.err
Returns None if Gateway is unavailable or request fails.
"""
Expand All @@ -612,11 +610,8 @@ async def poll_transaction(
"network": network,
"signature": tx_hash
}
if wallet_address:
payload["walletAddress"] = wallet_address

return await self._request("POST", f"chains/{chain}/poll", json=payload)
except Exception as e:
logger.error(f"Error polling transaction {tx_hash}: {e}")
return None

27 changes: 15 additions & 12 deletions services/gateway_transaction_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
"""
import asyncio
import logging
from typing import Optional, Dict, List
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from typing import Dict, List, Optional

from sqlalchemy import select
from sqlalchemy.orm import selectinload

from database import AsyncDatabaseManager
from database.repositories import GatewaySwapRepository, GatewayCLMMRepository
from database.models import GatewayCLMMEvent, GatewayCLMMPosition
from database.repositories import GatewayCLMMRepository, GatewaySwapRepository
from services.gateway_client import GatewayClient

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -312,9 +312,6 @@ async def _check_transaction_status(

# Parse the response with defensive checks
tx_status = result.get("txStatus")
tx_data = result.get("txData") or {}
meta = tx_data.get("meta") if isinstance(tx_data, dict) else {}
error = meta.get("err") if isinstance(meta, dict) else None

# Determine gas token based on chain
gas_token = {
Expand All @@ -326,18 +323,25 @@ async def _check_transaction_status(
"avalanche": "AVAX"
}.get(chain, "UNKNOWN")

# Transaction is confirmed if txStatus == 1 and no error
if tx_status == 1 and error is None:
# Transaction is confirmed if txStatus == 1
if tx_status == 1:
return {
"status": "CONFIRMED",
"gas_fee": result.get("fee", 0),
"gas_token": gas_token,
"error_message": None
}

# Transaction failed if there's an error
if error is not None:
error_msg = str(error) if error else "Transaction failed on-chain"
# Transaction failed if txStatus == -1 or there's an error field
# Gateway now returns parsed error messages like "SLIPPAGE_EXCEEDED (0x1771): ..."
error_msg = result.get("error")
if tx_status == -1 or error_msg:
if not error_msg:
# Fallback to meta.err if no parsed error
tx_data = result.get("txData") or {}
meta = tx_data.get("meta") if isinstance(tx_data, dict) else {}
raw_error = meta.get("err") if isinstance(meta, dict) else None
error_msg = str(raw_error) if raw_error else "Transaction failed on-chain"
return {
"status": "FAILED",
"gas_fee": result.get("fee", 0),
Expand All @@ -352,14 +356,13 @@ async def _check_transaction_status(
logger.error(f"Error checking transaction status for {tx_hash}: {e}")
return None

async def poll_transaction_once(self, tx_hash: str, network_id: str, wallet_address: Optional[str] = None) -> Optional[Dict]:
async def poll_transaction_once(self, tx_hash: str, network_id: str) -> Optional[Dict]:
"""
Poll a specific transaction once (useful for immediate status checks).

Args:
tx_hash: Transaction hash
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta')
wallet_address: Optional wallet address for verification

Returns:
Transaction status dict or None if pending
Expand Down
14 changes: 11 additions & 3 deletions services/trading_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import logging
import time
from decimal import Decimal
from typing import Dict, List, Optional, Set, TYPE_CHECKING
from typing import TYPE_CHECKING, Dict, List, Optional, Set

from hummingbot.connector.connector_base import ConnectorBase
from hummingbot.core.data_type.common import OrderType, TradeType, PositionAction
from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType

if TYPE_CHECKING:
from services.unified_connector_service import UnifiedConnectorService
from services.market_data_service import MarketDataService
from services.unified_connector_service import UnifiedConnectorService


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -173,6 +173,14 @@ async def add_market(
# Register trading pair with connector
self._register_trading_pair_with_connector(connector, trading_pair)

# Update balances to include tokens from new trading pair
if hasattr(connector, '_update_balances'):
try:
await connector._update_balances()
logger.debug(f"Updated balances for {connector_name} after adding {trading_pair}")
except Exception as e:
logger.warning(f"Failed to update balances for {connector_name}: {e}")

logger.info(f"Market {connector_name}/{trading_pair} added to trading interface")

async def remove_market(
Expand Down
20 changes: 10 additions & 10 deletions services/unified_connector_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from hummingbot.connector.connector_base import ConnectorBase
from hummingbot.connector.connector_metrics_collector import TradeVolumeMetricCollector
from hummingbot.connector.exchange_py_base import ExchangePyBase
from hummingbot.connector.gateway.gateway_lp import GatewayLp
from hummingbot.connector.gateway.gateway import Gateway
from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase
from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType
from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState
Expand Down Expand Up @@ -635,21 +635,21 @@ def _create_trading_connector(
) -> ConnectorBase:
"""Create a trading connector with API keys.

For gateway connectors (containing '/'), creates a GatewayLp connector
which auto-detects chain/network and uses the default wallet.
For Gateway network connectors (e.g., 'solana-mainnet-beta'), creates a unified
Gateway connector which auto-detects chain/network and uses the default wallet.
The dex_name and trading_type are passed to methods, not to the connector.
"""
BackendAPISecurity.login_account(
account_name=account_name,
secrets_manager=self.secrets_manager
)

# Gateway connectors (e.g., 'meteora/clmm', 'raydium/clmm') are not in AllConnectorSettings
# They use GatewayLp which auto-detects chain/network from gateway config
if '/' in connector_name:
logger.info(f"Creating gateway connector: {connector_name}")
# GatewayLp handles chain/network auto-detection and default wallet lookup
# via start_network() call
return GatewayLp(
# Check if this is a Gateway network connector
# Gateway connectors are NOT in AllConnectorSettings (those are exchange connectors)
# Network format: "chain-network" (e.g., "solana-mainnet-beta", "ethereum-mainnet")
if connector_name not in self._conn_settings:
logger.info(f"Creating Gateway connector for network: {connector_name}")
return Gateway(
connector_name=connector_name,
trading_pairs=[],
trading_required=True,
Expand Down
Loading