diff --git a/intentkit/utils/chain.py b/intentkit/utils/chain.py index ed2bb7fa..3c508142 100644 --- a/intentkit/utils/chain.py +++ b/intentkit/utils/chain.py @@ -159,6 +159,14 @@ class NetworkId(IntEnum): BeraMainnet = 80094 +# QuickNode may return short chain/network identifiers that map to existing enums. +QUICKNODE_CHAIN_ALIASES: dict[str, str] = { + "arb": Chain.Arbitrum.value, +} +QUICKNODE_NETWORK_ALIASES: dict[str, str] = { + "optimism": QuickNodeNetwork.OptimismMainnet.value, +} + # Mapping of QuickNodeNetwork enum members to their corresponding NetworkId enum members. # This dictionary facilitates efficient lookup of network IDs given a network name. # Note: SolanaMainnet is intentionally excluded as it does not have a numeric chain ID. @@ -517,9 +525,13 @@ def init_chain_configs(self, limit: int = 100, offset: int = 0) -> None: continue try: - chain_value = item["chain"] - network_value = item["network"] + chain_value = str(item["chain"]).lower() + network_value = str(item["network"]).lower() rpc_url = item["http_url"] + chain_value = QUICKNODE_CHAIN_ALIASES.get(chain_value, chain_value) + network_value = QUICKNODE_NETWORK_ALIASES.get( + network_value, network_value + ) chain = Chain(chain_value) network = QuickNodeNetwork(network_value) ens_url = item.get("ens_url", rpc_url) diff --git a/tests/utils/test_chain.py b/tests/utils/test_chain.py index 93f19101..00e219ec 100644 --- a/tests/utils/test_chain.py +++ b/tests/utils/test_chain.py @@ -1,10 +1,12 @@ import pytest +import intentkit.utils.chain as chain_utils from intentkit.utils.chain import ( Chain, ChainConfig, ChainProvider, NetworkId, + QuicknodeChainProvider, QuickNodeNetwork, ) @@ -26,6 +28,31 @@ def init_chain_configs(self, api_key: str = ""): self.api_key = api_key +class DummyResponse: + def __init__(self, payload: dict[str, list[dict[str, str]]]): + self._payload = payload + + def raise_for_status(self) -> None: + return None + + def json(self) -> dict[str, list[dict[str, str]]]: + return self._payload + + +class DummyClient: + def __init__(self, payload: dict[str, list[dict[str, str]]]): + self._payload = payload + + def __enter__(self) -> "DummyClient": + return self + + def __exit__(self, exc_type, exc, traceback) -> None: + return None + + def get(self, *_, **__) -> DummyResponse: + return DummyResponse(self._payload) + + def test_chain_config_properties(): config = ChainConfig( chain=Chain.Ethereum, @@ -53,6 +80,34 @@ def test_chain_provider_fetch_by_network_and_id(): assert config_by_id is config +def test_quicknode_chain_provider_alias_mapping(monkeypatch: pytest.MonkeyPatch): + # QuickNode can return chain "arb" with network "optimism"; both should map. + payload = { + "data": [ + { + "chain": "arb", + "network": "optimism", + "http_url": "https://quicknode", + "ens_url": "https://ens", + "wss_url": "wss://quicknode", + } + ] + } + + monkeypatch.setattr( + chain_utils.httpx, + "Client", + lambda *_, **__: DummyClient(payload), + ) + + provider = QuicknodeChainProvider("test-key") + provider.init_chain_configs() + + config = provider.chain_configs[QuickNodeNetwork.OptimismMainnet] + assert config.chain is Chain.Arbitrum + assert config.network is QuickNodeNetwork.OptimismMainnet + + def test_chain_provider_missing_network(): provider = DummyChainProvider()