Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 2 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,10 @@ async def lifespan(app: FastAPI):
try:
from hummingbot.strategy_v2.executors.lp_executor.data_types import LPExecutorConfig
from hummingbot.strategy_v2.executors.lp_executor.lp_executor import LPExecutor
print(f"[LP-FIX] imports OK. Registry before: {list(ExecutorService.EXECUTOR_REGISTRY.keys())}", flush=True)
ExecutorService.EXECUTOR_REGISTRY["lp_executor"] = (LPExecutor, LPExecutorConfig)
print(f"[LP-FIX] Registry after: {list(ExecutorService.EXECUTOR_REGISTRY.keys())}", flush=True)
logging.debug("lp_executor registered in ExecutorService")
except Exception as e:
import traceback
print(f"[LP-FIX] FAILED: {e}", flush=True)
traceback.print_exc()
logging.warning(f"Failed to register lp_executor: {e}")

# =========================================================================
# 5. Other Services
Expand Down
4 changes: 4 additions & 0 deletions models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
StopAndArchiveResponse,
StopBotAction,
V2ControllerDeployment,
V2ScriptDeployment,
)

# Connector models
Expand Down Expand Up @@ -84,6 +85,7 @@
GatewayWalletCredential,
GatewayWalletInfo,
SendTransactionRequest,
SetDefaultWalletRequest,
ShowPrivateKeyRequest,
)

Expand Down Expand Up @@ -213,6 +215,7 @@
"StopAndArchiveRequest",
"StopAndArchiveResponse",
"V2ControllerDeployment",
"V2ScriptDeployment",
# Trading models
"TradeRequest",
"TradeResponse",
Expand Down Expand Up @@ -282,6 +285,7 @@
"CreateWalletRequest",
"ShowPrivateKeyRequest",
"SendTransactionRequest",
"SetDefaultWalletRequest",
"GatewayWalletCredential",
"GatewayWalletInfo",
"GatewayBalanceRequest",
Expand Down
10 changes: 10 additions & 0 deletions models/bot_orchestration.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ class StopAndArchiveResponse(BaseModel):


# Bot deployment models
class V2ScriptDeployment(BaseModel):
"""Configuration for deploying a bot with a script"""
instance_name: str = Field(description="Unique name for the bot instance")
credentials_profile: str = Field(description="Name of the credentials profile to use")
image: str = Field(default="hummingbot/hummingbot:latest", description="Docker image for the Hummingbot instance")
script: Optional[str] = Field(default=None, description="Script name to run (without .py extension)")
script_config: Optional[str] = Field(default=None, description="Script configuration file name (without .yml extension)")
headless: bool = Field(default=False, description="Run in headless mode (no UI)")


class V2ControllerDeployment(BaseModel):
"""Configuration for deploying a bot with controllers"""
instance_name: str = Field(description="Unique name for the bot instance")
Expand Down
10 changes: 8 additions & 2 deletions models/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ class SendTransactionRequest(BaseModel):


class GatewayWalletCredential(BaseModel):
"""Credentials for connecting a Gateway wallet"""
"""Credentials for adding an existing wallet to Gateway"""
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum')")
private_key: str = Field(description="Wallet private key")
network: Optional[str] = Field(default=None, description="Network to use (defaults to chain's default)")
set_default: bool = Field(default=True, description="Set as default wallet for this chain")


class GatewayWalletInfo(BaseModel):
Expand All @@ -63,6 +63,12 @@ class GatewayWalletInfo(BaseModel):
network: str = Field(description="Network the wallet is configured for")


class SetDefaultWalletRequest(BaseModel):
"""Request to set the default wallet for a chain"""
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum')")
address: str = Field(description="Wallet address to set as default")


# ============================================
# Pool and Token Management Models
# ============================================
Expand Down
90 changes: 70 additions & 20 deletions routers/accounts.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from typing import Dict, List, Optional
from datetime import datetime
from typing import Dict, List

from fastapi import APIRouter, HTTPException, Depends, Query
from fastapi import APIRouter, Depends, HTTPException
from starlette import status

from services.accounts_service import AccountsService
from deps import get_accounts_service
from models import PaginatedResponse, GatewayWalletCredential, GatewayWalletInfo
from models import GatewayWalletCredential, SetDefaultWalletRequest
from services.accounts_service import AccountsService

router = APIRouter(tags=["Accounts"], prefix="/accounts")

Expand All @@ -15,7 +14,7 @@
async def list_accounts(accounts_service: AccountsService = Depends(get_accounts_service)):
"""
Get a list of all account names in the system.

Returns:
List of account names
"""
Expand Down Expand Up @@ -51,13 +50,13 @@ async def list_account_credentials(account_name: str,
async def add_account(account_name: str, accounts_service: AccountsService = Depends(get_accounts_service)):
"""
Create a new account with default configuration files.

Args:
account_name: Name of the new account to create

Returns:
Success message when account is created

Raises:
HTTPException: 400 if account already exists
"""
Expand All @@ -72,13 +71,13 @@ async def add_account(account_name: str, accounts_service: AccountsService = Dep
async def delete_account(account_name: str, accounts_service: AccountsService = Depends(get_accounts_service)):
"""
Delete an account and all its associated credentials.

Args:
account_name: Name of the account to delete

Returns:
Success message when account is deleted

Raises:
HTTPException: 400 if trying to delete master account, 404 if account not found
"""
Expand All @@ -95,14 +94,14 @@ async def delete_account(account_name: str, accounts_service: AccountsService =
async def delete_credential(account_name: str, connector_name: str, accounts_service: AccountsService = Depends(get_accounts_service)):
"""
Delete a specific connector credential for an account.

Args:
account_name: Name of the account
connector_name: Name of the connector to delete credentials for

Returns:
Success message when credential is deleted

Raises:
HTTPException: 404 if credential not found
"""
Expand Down Expand Up @@ -168,10 +167,11 @@ async def add_gateway_wallet(
accounts_service: AccountsService = Depends(get_accounts_service)
):
"""
Add a wallet to Gateway. Gateway handles encryption and storage internally.
Add an existing wallet to Gateway using its private key.
Gateway handles encryption and storage internally.

Args:
wallet_credential: Wallet credentials (chain and private_key)
wallet_credential: Wallet credentials (chain, private_key, and optional set_default)

Returns:
Wallet information from Gateway including address
Expand All @@ -182,7 +182,8 @@ async def add_gateway_wallet(
try:
result = await accounts_service.add_gateway_wallet(
chain=wallet_credential.chain,
private_key=wallet_credential.private_key
private_key=wallet_credential.private_key,
set_default=wallet_credential.set_default
)
return result
except HTTPException:
Expand All @@ -191,6 +192,57 @@ async def add_gateway_wallet(
raise HTTPException(status_code=500, detail=str(e))


@router.post("/gateway/wallet/set-default")
async def set_default_gateway_wallet(
request: SetDefaultWalletRequest,
accounts_service: AccountsService = Depends(get_accounts_service)
) -> Dict:
"""
Set the default wallet for a chain in Gateway.

When multiple wallets are configured for a chain, this endpoint allows
switching which wallet is used as the default for operations.

Args:
request: Contains chain and wallet address to set as default

Returns:
Dict with success status and updated wallet info.

Example: POST /accounts/gateway/wallet/set-default
{
"chain": "solana",
"address": "82SggYRE2Vo4jN4a2pk3aQ4SET4ctafZJGbowmCqyHx5"
}
"""
try:
if not await accounts_service.gateway_client.ping():
raise HTTPException(status_code=503, detail="Gateway service is not available")

result = await accounts_service.gateway_client.set_default_wallet(
chain=request.chain,
address=request.address
)

if result is None:
raise HTTPException(status_code=502, detail="Failed to set default wallet: Gateway returned no response")

if "error" in result:
raise HTTPException(status_code=400, detail=f"Failed to set default wallet: {result.get('error')}")

return {
"success": True,
"message": f"Set {request.address} as default wallet for {request.chain}",
"chain": request.chain,
"address": request.address
}

except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error setting default wallet: {str(e)}")


@router.delete("/gateway/{chain}/{address}")
async def remove_gateway_wallet(
chain: str,
Expand All @@ -217,5 +269,3 @@ async def remove_gateway_wallet(
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


64 changes: 63 additions & 1 deletion routers/bot_orchestration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from database import AsyncDatabaseManager, BotRunRepository
from deps import get_bot_archiver, get_bots_orchestrator, get_database_manager, get_docker_service
from models import StartBotAction, StopBotAction, V2ControllerDeployment
from models import StartBotAction, StopBotAction, V2ControllerDeployment, V2ScriptDeployment
from services.bots_orchestrator import BotsOrchestrator
from services.docker_service import DockerService
from utils.bot_archiver import BotArchiver
Expand Down Expand Up @@ -683,3 +683,65 @@ async def deploy_v2_controllers(
except Exception as e:
logging.error(f"Error deploying V2 controllers: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))


@router.post("/deploy-v2-script")
async def deploy_v2_script(
deployment: V2ScriptDeployment,
docker_manager: DockerService = Depends(get_docker_service),
db_manager: AsyncDatabaseManager = Depends(get_database_manager)
):
"""
Deploy a V2 script bot with optional script configuration.
This endpoint creates and starts a Hummingbot instance running the specified script.

Args:
deployment: V2ScriptDeployment configuration containing instance name, credentials,
optional script name and configuration
docker_manager: Docker service dependency
db_manager: Database manager dependency

Returns:
Dictionary with deployment response including instance details

Raises:
HTTPException: 500 if deployment fails
"""
try:
# Generate unique instance name with timestamp
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
unique_instance_name = f"{deployment.instance_name}-{timestamp}"

# Update deployment with unique name
deployment.instance_name = unique_instance_name

# Create the hummingbot instance
response = docker_manager.create_hummingbot_instance(deployment)

if response.get("success"):
response["unique_instance_name"] = unique_instance_name

# Track bot run if deployment was successful
try:
async with db_manager.get_session_context() as session:
bot_run_repo = BotRunRepository(session)
await bot_run_repo.create_bot_run(
bot_name=unique_instance_name,
instance_name=unique_instance_name,
strategy_type="script",
strategy_name=deployment.script or "default",
account_name=deployment.credentials_profile,
config_name=deployment.script_config,
image_version=deployment.image,
deployment_config=deployment.dict()
)
logger.info(f"Created bot run record for script deployment {unique_instance_name}")
except Exception as e:
logger.error(f"Failed to create bot run record: {e}")
# Don't fail the deployment if bot run creation fails

return response

except Exception as e:
logging.error(f"Error deploying V2 script: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
15 changes: 8 additions & 7 deletions routers/gateway.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
from fastapi import APIRouter, HTTPException, Depends, Query
from typing import Optional, Dict, List
import re
from typing import Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query

from deps import get_accounts_service, get_gateway_service
from models import (
GatewayConfig,
GatewayStatus,
AddPoolRequest,
AddTokenRequest,
CreateWalletRequest,
ShowPrivateKeyRequest,
GatewayConfig,
GatewayStatus,
SendTransactionRequest,
ShowPrivateKeyRequest,
)
from services.gateway_service import GatewayService
from services.accounts_service import AccountsService
from deps import get_gateway_service, get_accounts_service
from services.gateway_service import GatewayService

router = APIRouter(tags=["Gateway"], prefix="/gateway")

Expand Down
Loading
Loading