Skip to content
2 changes: 2 additions & 0 deletions bots/archived/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
28 changes: 24 additions & 4 deletions bots/controllers/generic/arbitrage_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,24 @@
from hummingbot.core.data_type.common import MarketDict
from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient
from hummingbot.strategy_v2.controllers.controller_base import ControllerBase, ControllerConfigBase
from hummingbot.strategy_v2.executors.arbitrage_executor.arbitrage_executor import ArbitrageExecutor
from hummingbot.strategy_v2.executors.arbitrage_executor.data_types import ArbitrageExecutorConfig
from hummingbot.strategy_v2.executors.data_types import ConnectorPair
from hummingbot.strategy_v2.models.base import RunnableStatus
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction

_original_are_tokens_interchangeable = ArbitrageExecutor._are_tokens_interchangeable


@staticmethod
def _patched_are_tokens_interchangeable(first_token: str, second_token: str):
if {first_token, second_token} <= {"WAE", "AE"}:
return True
return _original_are_tokens_interchangeable(first_token, second_token)


ArbitrageExecutor._are_tokens_interchangeable = _patched_are_tokens_interchangeable


class ArbitrageControllerConfig(ControllerConfigBase):
controller_name: str = "arbitrage_controller"
Expand Down Expand Up @@ -126,21 +139,28 @@ def determine_executor_actions(self) -> List[ExecutorAction]:
self.config.exchange_pair_1))
return [action for action in executor_actions if action is not None]

@staticmethod
def _get_gas_conversion_price(base_token: str, gas_token: str, market_data_provider):
if _patched_are_tokens_interchangeable(base_token, gas_token):
return Decimal("1")
pair = f"{base_token}-{gas_token}"
return market_data_provider.get_rate(pair) or Decimal("1")

def create_arbitrage_executor_action(self, buying_exchange_pair: ConnectorPair,
selling_exchange_pair: ConnectorPair):
try:
if buying_exchange_pair.is_amm_connector():
gas_token = self.get_gas_token(buying_exchange_pair.connector_name)
if gas_token:
pair = buying_exchange_pair.trading_pair.split("-")[0] + "-" + gas_token
gas_conversion_price = self.market_data_provider.get_rate(pair)
base = buying_exchange_pair.trading_pair.split("-")[0]
gas_conversion_price = self._get_gas_conversion_price(base, gas_token, self.market_data_provider)
else:
gas_conversion_price = None
elif selling_exchange_pair.is_amm_connector():
gas_token = self.get_gas_token(selling_exchange_pair.connector_name)
if gas_token:
pair = selling_exchange_pair.trading_pair.split("-")[0] + "-" + gas_token
gas_conversion_price = self.market_data_provider.get_rate(pair)
base = selling_exchange_pair.trading_pair.split("-")[0]
gas_conversion_price = self._get_gas_conversion_price(base, gas_token, self.market_data_provider)
else:
gas_conversion_price = None
else:
Expand Down
8 changes: 4 additions & 4 deletions database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ class GatewaySwap(Base):
timestamp = Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False, index=True)

# Network and connector info (unified format)
network = Column(String, nullable=False, index=True) # chain-network format: solana-mainnet-beta, ethereum-mainnet
connector = Column(String, nullable=False, index=True) # jupiter, 0x, etc.
network = Column(String, nullable=False, index=True) # chain-network format: solana-mainnet-beta, ethereum-mainnet, aeternity-mainnet
connector = Column(String, nullable=False, index=True) # jupiter, 0x, superhero, etc.
wallet_address = Column(String, nullable=False, index=True)

# Swap details
Expand Down Expand Up @@ -253,8 +253,8 @@ class GatewayCLMMPosition(Base):
pool_address = Column(String, nullable=False, index=True)

# Network and connector info (unified format)
network = Column(String, nullable=False, index=True) # chain-network format: solana-mainnet-beta, ethereum-mainnet
connector = Column(String, nullable=False, index=True) # meteora, raydium, uniswap
network = Column(String, nullable=False, index=True) # chain-network format: solana-mainnet-beta, ethereum-mainnet, aeternity-mainnet
connector = Column(String, nullable=False, index=True) # meteora, raydium, uniswap, superhero
wallet_address = Column(String, nullable=False, index=True)

# Position pair
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ services:
hummingbot-api:
container_name: hummingbot-api
image: hummingbot/hummingbot-api:latest
build: # Uncomment this build section and comment image to build Hummingbot locally
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
volumes:
Expand Down
16 changes: 8 additions & 8 deletions models/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@ class GatewayStatus(BaseModel):

class CreateWalletRequest(BaseModel):
"""Request to create a new wallet in Gateway"""
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum')")
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum', 'aeternity')")
set_default: bool = Field(default=True, description="Set as default wallet for this chain")


class ShowPrivateKeyRequest(BaseModel):
"""Request to show private key for a wallet"""
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum')")
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum', 'aeternity')")
address: str = Field(description="Wallet address")
passphrase: str = Field(description="Gateway passphrase for decryption")


class SendTransactionRequest(BaseModel):
"""Request to send a native token transaction"""
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum')")
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum', 'aeternity')")
network: str = Field(description="Network (e.g., 'mainnet-beta', 'mainnet')")
address: str = Field(description="Sender wallet address")
to_address: str = Field(description="Recipient address")
Expand All @@ -51,7 +51,7 @@ class SendTransactionRequest(BaseModel):

class GatewayWalletCredential(BaseModel):
"""Credentials for adding an existing wallet to Gateway"""
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum')")
chain: str = Field(description="Blockchain chain (e.g., 'solana', 'ethereum', 'aeternity')")
private_key: str = Field(description="Wallet private key")
set_default: bool = Field(default=True, description="Set as default wallet for this chain")

Expand All @@ -75,14 +75,14 @@ class SetDefaultWalletRequest(BaseModel):

class AddPoolRequest(BaseModel):
"""Request to add a liquidity pool"""
connector_name: str = Field(description="DEX connector name (e.g., 'raydium', 'meteora')")
connector_name: str = Field(description="DEX connector name (e.g., 'raydium', 'meteora', 'superhero')")
type: str = Field(description="Pool type ('clmm' or 'amm')")
network: str = Field(description="Network name (e.g., 'mainnet-beta')")
network: str = Field(description="Network name (e.g., 'mainnet-beta', 'mainnet')")
address: str = Field(description="Pool contract address")
base: str = Field(description="Base token symbol")
quote: str = Field(description="Quote token symbol")
base_address: str = Field(description="Base token contract address")
quote_address: str = Field(description="Quote token contract address")
base_address: Optional[str] = Field(default=None, description="Base token contract address (resolved from pool if not provided)")
quote_address: Optional[str] = Field(default=None, description="Quote token contract address (resolved from pool if not provided)")
fee_pct: Optional[float] = Field(default=None, description="Pool fee percentage (e.g., 0.25)")


Expand Down
12 changes: 6 additions & 6 deletions models/gateway_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

class SwapQuoteRequest(BaseModel):
"""Request for swap price quote"""
connector: str = Field(description="DEX router connector (e.g., 'jupiter', '0x')")
network: str = Field(description="Network ID in 'chain-network' format (e.g., 'solana-mainnet-beta', 'ethereum-mainnet')")
connector: str = Field(description="DEX router connector (e.g., 'jupiter', '0x', 'superhero')")
network: str = Field(description="Network ID in 'chain-network' format (e.g., 'solana-mainnet-beta', 'ethereum-mainnet', 'aeternity-mainnet')")
trading_pair: str = Field(description="Trading pair in BASE-QUOTE format (e.g., 'SOL-USDC')")
side: str = Field(description="Trade side: 'BUY' or 'SELL'")
amount: Decimal = Field(description="Amount to swap (in base token for SELL, quote token for BUY)")
Expand All @@ -38,8 +38,8 @@ class SwapQuoteResponse(BaseModel):

class SwapExecuteRequest(BaseModel):
"""Request to execute a swap"""
connector: str = Field(description="DEX router connector (e.g., 'jupiter', '0x')")
network: str = Field(description="Network ID in 'chain-network' format (e.g., 'solana-mainnet-beta')")
connector: str = Field(description="DEX router connector (e.g., 'jupiter', '0x', 'superhero')")
network: str = Field(description="Network ID in 'chain-network' format (e.g., 'solana-mainnet-beta', 'aeternity-mainnet')")
trading_pair: str = Field(description="Trading pair (e.g., 'SOL-USDC')")
side: str = Field(description="Trade side: 'BUY' or 'SELL'")
amount: Decimal = Field(description="Amount to swap")
Expand Down Expand Up @@ -241,8 +241,8 @@ class CLMMPoolInfoResponse(BaseModel):

class GetPoolInfoRequest(BaseModel):
"""Request to get pool information"""
connector: str = Field(description="DEX connector (e.g., 'meteora', 'raydium', 'jupiter')")
network: str = Field(description="Network ID in 'chain-network' format (e.g., 'solana-mainnet-beta')")
connector: str = Field(description="DEX connector (e.g., 'meteora', 'raydium', 'jupiter', 'superhero')")
network: str = Field(description="Network ID in 'chain-network' format (e.g., 'solana-mainnet-beta', 'aeternity-mainnet')")
trading_pair: str = Field(description="Trading pair (e.g., 'SOL-USDC')")


Expand Down
2 changes: 1 addition & 1 deletion routers/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ async def remove_gateway_wallet(
Remove a wallet from Gateway.

Args:
chain: Blockchain chain (e.g., 'solana', 'ethereum')
chain: Blockchain chain (e.g., 'solana', 'ethereum', 'aeternity')
address: Wallet address to remove

Returns:
Expand Down
52 changes: 44 additions & 8 deletions routers/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ async def list_pools(
pools = await accounts_service.gateway_client.get_pools(connector_name, network)

if not pools:
raise HTTPException(status_code=400, detail=f"No pools found for {connector_name}/{network}")
return []

# Normalize each pool
normalized_pools = [normalize_gateway_response(pool) for pool in pools]
Expand Down Expand Up @@ -326,8 +326,8 @@ async def add_pool(
address=pool_request.address,
base_symbol=pool_request.base,
quote_symbol=pool_request.quote,
base_token_address=pool_request.base_address,
quote_token_address=pool_request.quote_address,
base_token_address=pool_request.base_address or None,
quote_token_address=pool_request.quote_address or None,
fee_pct=pool_request.fee_pct
)

Expand Down Expand Up @@ -452,7 +452,7 @@ async def get_network_config(
Get configuration for a specific network.

Args:
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'ethereum-mainnet')
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'ethereum-mainnet', 'aeternity-mainnet')

Example: GET /gateway/networks/solana-mainnet-beta
"""
Expand All @@ -479,7 +479,7 @@ async def update_network_config(
Update configuration for a specific network.

Args:
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta')
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'aeternity-mainnet')
config_updates: Dict with path-value pairs to update.
Keys can be in snake_case (e.g., {"node_url": "https://..."})
or camelCase (e.g., {"nodeURL": "https://..."})
Expand Down Expand Up @@ -521,7 +521,7 @@ async def get_network_tokens(
Get available tokens for a network.

Args:
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta')
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'aeternity-mainnet')
search: Filter tokens by symbol or name

Example: GET /gateway/networks/solana-mainnet-beta/tokens?search=USDC
Expand Down Expand Up @@ -555,6 +555,42 @@ async def get_network_tokens(
raise HTTPException(status_code=500, detail=f"Error getting network tokens: {str(e)}")


@router.post("/networks/{network_id}/tokens/find/{address}")
async def find_and_save_token(
network_id: str,
address: str,
accounts_service: AccountsService = Depends(get_accounts_service)
) -> Dict:
"""
Look up a token by address (from GeckoTerminal or on-chain) and save it to Gateway's token list.

For Aeternity tokens, reads metadata directly from the blockchain.
For other chains, uses GeckoTerminal.

Args:
network_id: Network ID in format 'chain-network' (e.g., 'aeternity-mainnet', 'solana-mainnet-beta')
address: Token contract address
"""
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.find_and_save_token(
chain_network=network_id,
address=address
)

if result is None:
raise HTTPException(status_code=502, detail="Gateway returned no response")

return result

except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error looking up token: {str(e)}")


@router.post("/networks/{network_id}/tokens")
async def add_network_token(
network_id: str,
Expand All @@ -565,7 +601,7 @@ async def add_network_token(
Add a custom token to Gateway's token list for a specific network.

Args:
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'ethereum-mainnet')
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'ethereum-mainnet', 'aeternity-mainnet')
token_request: Token details (address, symbol, name, decimals)

Example: POST /gateway/networks/ethereum-mainnet/tokens
Expand Down Expand Up @@ -632,7 +668,7 @@ async def delete_network_token(
Delete a custom token from Gateway's token list for a specific network.

Args:
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'ethereum-mainnet')
network_id: Network ID in format 'chain-network' (e.g., 'solana-mainnet-beta', 'ethereum-mainnet', 'aeternity-mainnet')
token_address: Token contract address to delete

Example: DELETE /gateway/networks/solana-mainnet-beta/tokens/9QFfgxdSqH5zT7j6rZb1y6SZhw2aFtcQu2r6BuYpump
Expand Down
3 changes: 2 additions & 1 deletion routers/gateway_clmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def get_native_gas_token(chain: str) -> str:
Get the native gas token symbol for a blockchain.

Args:
chain: Blockchain name (e.g., 'solana', 'ethereum', 'polygon')
chain: Blockchain name (e.g., 'solana', 'ethereum', 'polygon', 'aeternity')

Returns:
Gas token symbol (e.g., 'SOL', 'ETH', 'MATIC')
Expand All @@ -215,6 +215,7 @@ def get_native_gas_token(chain: str) -> str:
"base": "ETH",
"bsc": "BNB",
"cronos": "CRO",
"aeternity": "AE",
}
return gas_token_map.get(chain.lower(), "UNKNOWN")

Expand Down
Loading