diff --git a/hyperliquid/info.py b/hyperliquid/info.py index a86ac80..e886e3b 100644 --- a/hyperliquid/info.py +++ b/hyperliquid/info.py @@ -40,17 +40,24 @@ def __init__( self.asset_to_sz_decimals = {} # spot assets start at 10000 + # Build index-based token lookup to handle cases where token indices don't match array positions + # Fixes issue #275: https://github.com/hyperliquid-dex/hyperliquid-python-sdk/issues/275 + token_by_index = {token["index"]: token for token in spot_meta["tokens"]} + for spot_info in spot_meta["universe"]: asset = spot_info["index"] + 10000 self.coin_to_asset[spot_info["name"]] = asset self.name_to_coin[spot_info["name"]] = spot_info["name"] - base, quote = spot_info["tokens"] - base_info = spot_meta["tokens"][base] - quote_info = spot_meta["tokens"][quote] - self.asset_to_sz_decimals[asset] = base_info["szDecimals"] - name = f'{base_info["name"]}/{quote_info["name"]}' - if name not in self.name_to_coin: - self.name_to_coin[name] = spot_info["name"] + base_idx, quote_idx = spot_info["tokens"] + # Use index-based lookup instead of array position to avoid IndexError + # when testnet API returns malformed spot_meta with token indices exceeding array bounds + base_info = token_by_index.get(base_idx) + quote_info = token_by_index.get(quote_idx) + if base_info is not None and quote_info is not None: + self.asset_to_sz_decimals[asset] = base_info["szDecimals"] + name = f'{base_info["name"]}/{quote_info["name"]}' + if name not in self.name_to_coin: + self.name_to_coin[name] = spot_info["name"] perp_dex_to_offset = {"": 0} if perp_dexs is None: