diff --git a/bots/archived/.gitignore b/bots/archived/.gitignore index e69de29b..c96a04f0 100644 --- a/bots/archived/.gitignore +++ b/bots/archived/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/bots/controllers/generic/arbitrage_controller.py b/bots/controllers/generic/arbitrage_controller.py index 5cb0cac5..022baea3 100644 --- a/bots/controllers/generic/arbitrage_controller.py +++ b/bots/controllers/generic/arbitrage_controller.py @@ -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" @@ -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: diff --git a/database/models.py b/database/models.py index 524a6a0b..50d4a9ff 100644 --- a/database/models.py +++ b/database/models.py @@ -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 @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index c33f7ebb..011e6aaa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/models/gateway.py b/models/gateway.py index 849acd4c..cde92c1f 100644 --- a/models/gateway.py +++ b/models/gateway.py @@ -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") @@ -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") @@ -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)") diff --git a/models/gateway_trading.py b/models/gateway_trading.py index 02d7c422..721886fd 100644 --- a/models/gateway_trading.py +++ b/models/gateway_trading.py @@ -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)") @@ -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") @@ -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')") diff --git a/routers/accounts.py b/routers/accounts.py index a5e4106a..c7ad7352 100644 --- a/routers/accounts.py +++ b/routers/accounts.py @@ -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: diff --git a/routers/gateway.py b/routers/gateway.py index 7bd7b428..c0adef0a 100644 --- a/routers/gateway.py +++ b/routers/gateway.py @@ -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] @@ -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 ) @@ -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 """ @@ -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://..."}) @@ -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 @@ -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, @@ -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 @@ -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 diff --git a/routers/gateway_clmm.py b/routers/gateway_clmm.py index cb23e9d9..f8e8732e 100644 --- a/routers/gateway_clmm.py +++ b/routers/gateway_clmm.py @@ -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') @@ -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") diff --git a/services/accounts_service.py b/services/accounts_service.py index 4726dc19..189537de 100644 --- a/services/accounts_service.py +++ b/services/accounts_service.py @@ -446,8 +446,9 @@ class AccountsService: gateway_default_pricing_connector = { "ethereum": "uniswap/router", "solana": "jupiter/router", + "aeternity": "superhero/amm", } - potential_wrapped_tokens = ["ETH", "SOL", "BNB", "POL", "AVAX", "FTM", "ONE", "GLMR", "MOVR"] + potential_wrapped_tokens = ["ETH", "SOL", "BNB", "POL", "AVAX", "FTM", "ONE", "GLMR", "MOVR", "AE"] # Cache for storing last successful prices by trading pair _last_known_prices = {} @@ -774,7 +775,7 @@ async def update_account_state( skip_gateway: If True, skip Gateway wallet balance updates for faster CEX-only queries. account_names: If provided, only update these accounts. If None, update all accounts. connector_names: If provided, only update these connectors. If None, update all connectors. - For Gateway, this filters by chain-network (e.g., 'solana-mainnet-beta'). + For Gateway, this filters by chain-network (e.g., 'solana-mainnet-beta', 'aeternity-mainnet'). """ all_connectors = self._connector_service.get_all_trading_connectors() if self._connector_service else {} @@ -2022,7 +2023,7 @@ async def add_gateway_wallet(self, chain: str, private_key: str, set_default: bo Add a wallet to Gateway. Gateway handles encryption internally. Args: - chain: Blockchain chain (e.g., 'solana', 'ethereum') + chain: Blockchain chain (e.g., 'solana', 'ethereum', 'aeternity') private_key: Wallet private key set_default: Set as default wallet for this chain (default: True) @@ -2080,6 +2081,10 @@ async def get_gateway_balances(self, chain: str, address: str, network: Optional """ Get Gateway wallet balances with pricing from rate sources. + Uses the same RateOracle-first pricing strategy as _get_connector_tokens_info: + 1. Try RateOracle (instant, cached) -- picks up prices from any active CEX connector + 2. Fall back to Gateway DEX pricing only for tokens the oracle can't price + Args: chain: Blockchain chain address: Wallet address @@ -2116,40 +2121,93 @@ async def get_gateway_balances(self, chain: str, address: str, network: Optional "units": Decimal(str(balance)) }) - # Get prices for tokens - unique_tokens = [b["token"] for b in balances_list] - all_prices = {} - - # Fetch prices for Gateway tokens - if unique_tokens: - try: - fetched_prices = await self._fetch_gateway_prices_immediate( - chain, network, unique_tokens - ) - for token, price in fetched_prices.items(): - if price > 0: - all_prices[token] = price - except Exception as e: - logger.warning(f"Error fetching gateway prices: {e}") - - # Format final result with prices formatted_balances = [] + gateway_fallback_tokens = [] + gateway_fallback_indices = [] + for balance in balances_list: token = balance["token"] + unwrapped = self.get_unwrapped_token(token) + if "USD" in token: price = Decimal("1") else: - # all_prices is now keyed by token name directly - price = Decimal(str(all_prices.get(token, 0))) + price = None + # RateOracle first (instant, cached from active CEX connectors) + if self._market_data_service: + rate = self._market_data_service.get_rate(unwrapped, "USDT") + if rate and rate > 0: + price = rate + if price is None and self._market_data_service: + rate = self._market_data_service.get_rate(token, "USDT") + if rate and rate > 0: + price = rate + + if price is None: + gateway_fallback_tokens.append(token) + gateway_fallback_indices.append(len(formatted_balances)) formatted_balances.append({ "token": token, "units": float(balance["units"]), - "price": float(price), - "value": float(price * balance["units"]), + "price": float(price) if price is not None else 0.0, + "value": float(price * balance["units"]) if price is not None else 0.0, "available_units": float(balance["units"]) }) + # CEX connector fallback: same pattern as _get_connector_tokens_info + if gateway_fallback_tokens and self._connector_service: + all_connectors = self._connector_service.get_all_trading_connectors() + for _acct, connectors in all_connectors.items(): + if not gateway_fallback_tokens: + break + for connector_name, connector in connectors.items(): + if not gateway_fallback_tokens: + break + pairs = [] + pair_map = {} + for i, token in enumerate(gateway_fallback_tokens): + unwrapped = self.get_unwrapped_token(token) + market = self.get_default_market(unwrapped, connector_name) + pairs.append(market) + pair_map[market] = i + + try: + fetched = await self._safe_get_last_traded_prices(connector, pairs) + except Exception: + continue + + resolved = [] + for market, orig_i in pair_map.items(): + p = Decimal(str(fetched.get(market, 0))) + if p > 0: + idx = gateway_fallback_indices[orig_i] + units = Decimal(str(formatted_balances[idx]["units"])) + formatted_balances[idx]["price"] = float(p) + formatted_balances[idx]["value"] = float(p * units) + resolved.append(orig_i) + + if resolved: + for ri in sorted(resolved, reverse=True): + gateway_fallback_tokens.pop(ri) + gateway_fallback_indices.pop(ri) + + # Gateway DEX fallback for tokens no CEX connector could price + if gateway_fallback_tokens: + try: + fetched_prices = await self._fetch_gateway_prices_immediate( + chain, network, gateway_fallback_tokens + ) + for i, idx in enumerate(gateway_fallback_indices): + token = gateway_fallback_tokens[i] + price = fetched_prices.get(token, Decimal("0")) + if price > 0: + units = Decimal(str(formatted_balances[idx]["units"])) + formatted_balances[idx]["price"] = float(price) + formatted_balances[idx]["value"] = float(price * units) + except Exception as e: + logger.warning(f"Error fetching gateway fallback prices for {chain}: {e}") + return formatted_balances except HTTPException: @@ -2167,9 +2225,10 @@ async def _fetch_gateway_prices_immediate(self, chain: str, network: str, Uses the same pricing connector resolution as MarketDataProvider.update_rates_task(): - solana -> jupiter/router - ethereum -> uniswap/router + - aeternity -> superhero/amm Args: - chain: Blockchain chain (e.g., 'solana', 'ethereum') + chain: Blockchain chain (e.g., 'solana', 'ethereum', 'aeternity') network: Network name (e.g., 'mainnet-beta', 'mainnet') tokens: List of token symbols to get prices for diff --git a/services/gateway_client.py b/services/gateway_client.py index b62aec07..4f997ce0 100644 --- a/services/gateway_client.py +++ b/services/gateway_client.py @@ -25,6 +25,7 @@ def parse_network_id(network_id: str) -> tuple[str, str]: Examples: 'solana-mainnet-beta' -> ('solana', 'mainnet-beta') 'ethereum-mainnet' -> ('ethereum', 'mainnet') + 'aeternity-mainnet' -> ('aeternity', 'mainnet') """ parts = network_id.split('-', 1) if len(parts) != 2: @@ -69,7 +70,7 @@ async def _request(self, method: str, path: str, params: Dict = None, json: Dict return {"error": error_body, "status": response.status} return await response.json() elif method == "POST": - async with session.post(url, json=json) as response: + async with session.post(url, json=json, params=params) as response: if not response.ok: error_body = await self._get_error_body(response) logger.warning(f"Gateway request failed: {method} {url} - {response.status} - {error_body}") @@ -128,12 +129,12 @@ async def get_all_wallet_addresses(self, chain: Optional[str] = None) -> Dict[st Get all wallet addresses, optionally filtered by chain. Args: - chain: Optional chain filter (e.g., 'solana', 'ethereum'). + chain: Optional chain filter (e.g., 'solana', 'ethereum', 'aeternity'). If not provided, returns wallets for all chains. Returns: Dict mapping chain name to list of wallet addresses. - Example: {"solana": ["addr1", "addr2"], "ethereum": ["addr3"]} + Example: {"solana": ["addr1", "addr2"], "ethereum": ["addr3"], "aeternity": ["ak_..."]} """ try: wallets = await self.get_wallets() @@ -249,6 +250,12 @@ async def add_token(self, chain: str, network: str, address: str, symbol: str, n } }) + async def find_and_save_token(self, chain_network: str, address: str) -> Dict: + """Fetch token info (from GeckoTerminal or on-chain) and save it to the token list""" + return await self._request("POST", f"tokens/save/{address}", params={ + "chainNetwork": chain_network + }, json={}) + async def delete_token(self, chain: str, network: str, token_address: str) -> Dict: """Delete a custom token from Gateway's token list""" return await self._request("DELETE", f"tokens/{token_address}", params={ @@ -283,21 +290,23 @@ async def add_pool( address: str, base_symbol: str, quote_symbol: str, - base_token_address: str, - quote_token_address: str, + base_token_address: Optional[str] = None, + quote_token_address: Optional[str] = None, fee_pct: Optional[float] = None ) -> Dict: - """Add a new pool""" + """Add a new pool. Token addresses and fee are resolved from the pool contract if not provided.""" payload = { "connector": connector, - "type": pool_type.lower(), # Gateway expects lowercase (amm, clmm) + "type": pool_type.lower(), "network": network, "address": address, "baseSymbol": base_symbol, "quoteSymbol": quote_symbol, - "baseTokenAddress": base_token_address, - "quoteTokenAddress": quote_token_address } + if base_token_address: + payload["baseTokenAddress"] = base_token_address + if quote_token_address: + payload["quoteTokenAddress"] = quote_token_address if fee_pct is not None: payload["feePct"] = fee_pct return await self._request("POST", "pools", json=payload) @@ -588,7 +597,7 @@ async def poll_transaction( Poll transaction status on blockchain. 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') tx_hash: Transaction hash/signature wallet_address: Optional wallet address for verification diff --git a/services/gateway_service.py b/services/gateway_service.py index d7378838..fee3031e 100644 --- a/services/gateway_service.py +++ b/services/gateway_service.py @@ -20,7 +20,7 @@ class GatewayService: Ensures only one Gateway instance can exist at a time. """ - GATEWAY_CONTAINER_NAME = "gateway" + GATEWAY_CONTAINER_NAME = os.environ.get("GATEWAY_CONTAINER_NAME", "gateway") GATEWAY_DIR = "gateway-files" def __init__(self): diff --git a/services/gateway_transaction_poller.py b/services/gateway_transaction_poller.py index 991fa44d..8e181f7d 100644 --- a/services/gateway_transaction_poller.py +++ b/services/gateway_transaction_poller.py @@ -323,7 +323,8 @@ async def _check_transaction_status( "arbitrum": "ETH", "optimism": "ETH", "polygon": "MATIC", - "avalanche": "AVAX" + "avalanche": "AVAX", + "aeternity": "AE", }.get(chain, "UNKNOWN") # Transaction is confirmed if txStatus == 1 and no error