From 82175b29681c6df207e15c686099bc7e42461878 Mon Sep 17 00:00:00 2001 From: mihaimarcu Date: Mon, 6 Jul 2026 14:23:19 +0200 Subject: [PATCH 1/5] rh-profiles --- examples/system_setup.py | 13 +++++--- examples/utils.py | 33 +++++++++++++------ lighter/__init__.py | 1 + lighter/endpoint_profiles.py | 61 ++++++++++++++++++++++++++++++++++++ lighter/signer_client.py | 6 ++-- lighter/ws_client.py | 12 ++++--- 6 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 lighter/endpoint_profiles.py diff --git a/examples/system_setup.py b/examples/system_setup.py index 859274b..ab026ef 100644 --- a/examples/system_setup.py +++ b/examples/system_setup.py @@ -4,11 +4,13 @@ import eth_account import lighter from utils import save_api_key_config +from lighter import get_endpoint_profile logging.basicConfig(level=logging.DEBUG) # this is a dummy private key registered on Testnet. # It serves as a good example +ENDPOINT_PROFILE = get_endpoint_profile["TESTNET"] BASE_URL = "https://testnet.zklighter.elliot.ai" ETH_PRIVATE_KEY = "1234567812345678123456781234567812345678123456781234567812345678" API_KEY_INDEX = 3 @@ -21,7 +23,9 @@ async def main(): # verify that the account exists & fetch account index - api_client = lighter.ApiClient(configuration=lighter.Configuration(host=BASE_URL)) + api_client = lighter.ApiClient( + configuration=lighter.Configuration(host=ENDPOINT_PROFILE.api_url) + ) eth_acc = eth_account.Account.from_key(ETH_PRIVATE_KEY) eth_address = eth_acc.address @@ -63,9 +67,10 @@ async def main(): private_keys[API_KEY_INDEX + i] = private_key tx_client = lighter.SignerClient( - url=BASE_URL, + url=ENDPOINT_PROFILE.api_url, account_index=account_index, api_private_keys=private_keys, + chain_id=ENDPOINT_PROFILE.chain_id, ) # change all API keys @@ -73,7 +78,7 @@ async def main(): response, err = await tx_client.change_api_key( eth_private_key=ETH_PRIVATE_KEY, new_pubkey=public_keys[i], - api_key_index=API_KEY_INDEX + i + api_key_index=API_KEY_INDEX + i, ) if err is not None: raise Exception(err) @@ -86,7 +91,7 @@ async def main(): if err is not None: raise Exception(err) - save_api_key_config(BASE_URL, account_index, private_keys) + save_api_key_config(ENDPOINT_PROFILE, account_index, private_keys) await tx_client.close() await api_client.close() diff --git a/examples/utils.py b/examples/utils.py index 3c63a41..42e4615 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -9,10 +9,10 @@ def trim_exception(e: Exception) -> str: return str(e).strip().split("\n")[-1] -def save_api_key_config(base_url, account_index, private_keys, config_file="./api_key_config.json"): +def save_api_key_config(endpoint_profile, account_index, private_keys, config_file="./api_key_config.json"): with open(config_file, "w", encoding="utf-8") as f: json.dump({ - "baseUrl": base_url, + "endpointProfile": endpoint_profile.name, "accountIndex": account_index, "privateKeys": private_keys, }, f, ensure_ascii=False, indent=2) @@ -27,28 +27,43 @@ def get_api_key_config(config_file="./api_key_config.json"): for key in private_keys_original.keys(): private_key[int(key)] = private_keys_original[key] + profile_name = cfg.get("endpointProfile") + if profile_name: + profile = lighter.get_endpoint_profile(profile_name) + return profile, cfg["accountIndex"], private_key + return cfg["baseUrl"], cfg["accountIndex"], private_key -def default_example_setup(config_file="./api_key_config.json", nonce_management_type=lighter.nonce_manager.NonceManagerType.OPTIMISTIC) -> Optional[Tuple[lighter.SignerClient, lighter.ApiClient, websockets.connect]]: +def default_example_setup(config_file="./api_key_config.json",nonce_management_type=lighter.nonce_manager.NonceManagerType.OPTIMISTIC,endpoint_profile=None,) -> Optional[Tuple[lighter.SignerClient, lighter.ApiClient, websockets.connect]]: logging.basicConfig(level=logging.DEBUG) - base_url, account_index, private_keys = get_api_key_config(config_file) - api_client = lighter.ApiClient(configuration=lighter.Configuration(host=base_url)) + config_endpoint, account_index, private_keys = get_api_key_config(config_file) + + profile = endpoint_profile or config_endpoint + + api_url = profile.api_url + chain_id = profile.chain_id + ws_url = profile.ws_url + + api_client = lighter.ApiClient( + configuration=lighter.Configuration(host=api_url) + ) + client = lighter.SignerClient( - url=base_url, + url=api_url, account_index=account_index, api_private_keys=private_keys, nonce_management_type=nonce_management_type, + chain_id=chain_id, ) err = client.check_client() if err is not None: print(f"CheckClient error: {trim_exception(err)}") - return - - return client, api_client, websockets.connect(f"{base_url.replace('https', 'wss')}/stream") + return None + return client, api_client, websockets.connect(ws_url) async def ws_ping(ws_client: websockets.ClientConnection): await ws_client.send(json.dumps({"type": "pong"})) diff --git a/lighter/__init__.py b/lighter/__init__.py index 3f84d93..41726b2 100644 --- a/lighter/__init__.py +++ b/lighter/__init__.py @@ -170,6 +170,7 @@ from lighter.models.withdraw_history import WithdrawHistory from lighter.models.withdraw_history_item import WithdrawHistoryItem from lighter.models.zk_lighter_info import ZkLighterInfo +from lighter.endpoint_profiles import (EndpointProfile, MAINNET, ROBINHOOD, DEFAULT_ENDPOINT_PROFILE, ENDPOINT_PROFILES, get_endpoint_profile) # manual additions from lighter.ws_client import WsClient diff --git a/lighter/endpoint_profiles.py b/lighter/endpoint_profiles.py new file mode 100644 index 0000000..225bf8e --- /dev/null +++ b/lighter/endpoint_profiles.py @@ -0,0 +1,61 @@ +# lighter/endpoint_profiles.py + +from dataclasses import dataclass + + +def normalize_base_url(url: str) -> str: + return url.rstrip("/") + + +def join_url(base_url: str, path: str) -> str: + return f"{base_url.rstrip('/')}/{path.lstrip('/')}" + + +@dataclass(frozen=True) +class EndpointProfile: + name: str + api_url: str + ws_url: str + chain_id: int + + def __post_init__(self): + object.__setattr__(self, "api_url", normalize_base_url(self.api_url)) + object.__setattr__(self, "ws_url", normalize_base_url(self.ws_url)) + + +MAINNET = EndpointProfile( + name="mainnet", + api_url="https://mainnet.zklighter.elliot.ai", + ws_url="wss://mainnet.zklighter.elliot.ai/stream", + chain_id=304, +) + +ROBINHOOD = EndpointProfile( + name="robinhood", + api_url="https://api.rh.lighter.xyz", + ws_url="wss://api.rh.lighter.xyz/stream", + chain_id=304, +) + + +TESTNET = EndpointProfile( + name="testnet", + api_url="https://testnet.zklighter.elliot.ai", + ws_url="wss://testnet.zklighter.elliot.ai/stream", + chain_id=300, +) + +DEFAULT_ENDPOINT_PROFILE = MAINNET + +ENDPOINT_PROFILES = { + MAINNET.name: MAINNET, + ROBINHOOD.name: ROBINHOOD, + TESTNET.name: TESTNET, +} + + +def get_endpoint_profile(name: str) -> EndpointProfile: + try: + return ENDPOINT_PROFILES[name] + except KeyError as exc: + raise ValueError(f"Unknown endpoint profile: {name}") from exc \ No newline at end of file diff --git a/lighter/signer_client.py b/lighter/signer_client.py index de2e41d..90d3e3e 100644 --- a/lighter/signer_client.py +++ b/lighter/signer_client.py @@ -333,10 +333,12 @@ def __init__( account_index, api_private_keys: Dict[int, str], nonce_management_type=nonce_manager.NonceManagerType.OPTIMISTIC, + chain_id: Optional[int] = None, ): self.url = url - self.chain_id = 304 if ("mainnet" in url or "api" in url) else 300 - + self.chain_id = chain_id if chain_id is not None else ( + 304 if ("mainnet" in self.url or "api" in self.url) else 300 + ) self.validate_api_private_keys(api_private_keys) self.api_key_dict = api_private_keys self.account_index = account_index diff --git a/lighter/ws_client.py b/lighter/ws_client.py index 1369417..14abdf5 100644 --- a/lighter/ws_client.py +++ b/lighter/ws_client.py @@ -2,6 +2,7 @@ from websockets.sync.client import connect from websockets.client import connect as connect_async from lighter.configuration import Configuration +from lighter.endpoint_profiles import join_url class WsClient: def __init__( @@ -12,11 +13,14 @@ def __init__( account_ids=[], on_order_book_update=print, on_account_update=print, + ws_url=None, ): - if host is None: - host = Configuration.get_default().host.replace("https://", "") - - self.base_url = f"wss://{host}{path}" + if ws_url is not None: + self.base_url = ws_url.rstrip("/") + else: + if host is None: + host = Configuration.get_default().host.replace("https://", "") + self.base_url = join_url(f"wss://{host}", path) self.subscriptions = { "order_books": order_book_ids, From 66d2b7746c359c636f31839b6ff06df84d27a554 Mon Sep 17 00:00:00 2001 From: mihaimarcu Date: Mon, 6 Jul 2026 14:31:51 +0200 Subject: [PATCH 2/5] fixes --- examples/system_setup.py | 4 ++-- lighter/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/system_setup.py b/examples/system_setup.py index ab026ef..294c41d 100644 --- a/examples/system_setup.py +++ b/examples/system_setup.py @@ -4,13 +4,13 @@ import eth_account import lighter from utils import save_api_key_config -from lighter import get_endpoint_profile +from lighter import get_endpoint_profile, MAINNET, ROBINHOOD, TESTNET logging.basicConfig(level=logging.DEBUG) # this is a dummy private key registered on Testnet. # It serves as a good example -ENDPOINT_PROFILE = get_endpoint_profile["TESTNET"] +ENDPOINT_PROFILE = TESTNET BASE_URL = "https://testnet.zklighter.elliot.ai" ETH_PRIVATE_KEY = "1234567812345678123456781234567812345678123456781234567812345678" API_KEY_INDEX = 3 diff --git a/lighter/__init__.py b/lighter/__init__.py index 41726b2..d7550a2 100644 --- a/lighter/__init__.py +++ b/lighter/__init__.py @@ -170,7 +170,7 @@ from lighter.models.withdraw_history import WithdrawHistory from lighter.models.withdraw_history_item import WithdrawHistoryItem from lighter.models.zk_lighter_info import ZkLighterInfo -from lighter.endpoint_profiles import (EndpointProfile, MAINNET, ROBINHOOD, DEFAULT_ENDPOINT_PROFILE, ENDPOINT_PROFILES, get_endpoint_profile) +from lighter.endpoint_profiles import (EndpointProfile, MAINNET, ROBINHOOD, TESTNET, DEFAULT_ENDPOINT_PROFILE, ENDPOINT_PROFILES, get_endpoint_profile) # manual additions from lighter.ws_client import WsClient From ca18c7ecd12cbc17e0d9fc3b137bba2664e1c64a Mon Sep 17 00:00:00 2001 From: mihaimarcu Date: Mon, 6 Jul 2026 14:48:34 +0200 Subject: [PATCH 3/5] fix --- examples/utils.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/utils.py b/examples/utils.py index 42e4615..0b39259 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -30,9 +30,17 @@ def get_api_key_config(config_file="./api_key_config.json"): profile_name = cfg.get("endpointProfile") if profile_name: profile = lighter.get_endpoint_profile(profile_name) - return profile, cfg["accountIndex"], private_key + else: + # Backward compat: build a profile from the raw baseUrl + base_url = cfg["baseUrl"] + profile = lighter.EndpointProfile( + name="custom", + api_url=base_url, + ws_url=base_url.replace("https", "wss") + "/stream", + chain_id=304 if ("mainnet" in base_url or "api" in base_url) else 300, + ) - return cfg["baseUrl"], cfg["accountIndex"], private_key + return profile, cfg["accountIndex"], private_key def default_example_setup(config_file="./api_key_config.json",nonce_management_type=lighter.nonce_manager.NonceManagerType.OPTIMISTIC,endpoint_profile=None,) -> Optional[Tuple[lighter.SignerClient, lighter.ApiClient, websockets.connect]]: From e4ff6c30cf003406adce00d0aab551c4f0b9e15d Mon Sep 17 00:00:00 2001 From: mihaimarcu Date: Tue, 7 Jul 2026 10:34:31 +0200 Subject: [PATCH 4/5] correct chain_id --- lighter/endpoint_profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighter/endpoint_profiles.py b/lighter/endpoint_profiles.py index 225bf8e..a335d60 100644 --- a/lighter/endpoint_profiles.py +++ b/lighter/endpoint_profiles.py @@ -34,7 +34,7 @@ def __post_init__(self): name="robinhood", api_url="https://api.rh.lighter.xyz", ws_url="wss://api.rh.lighter.xyz/stream", - chain_id=304, + chain_id=4663, ) From 79d52c501c449fdc6798b6d8b97977c0fec3d82d Mon Sep 17 00:00:00 2001 From: mihaimarcu Date: Tue, 7 Jul 2026 11:39:01 +0200 Subject: [PATCH 5/5] correct chain_id --- lighter/endpoint_profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighter/endpoint_profiles.py b/lighter/endpoint_profiles.py index a335d60..1024c52 100644 --- a/lighter/endpoint_profiles.py +++ b/lighter/endpoint_profiles.py @@ -34,7 +34,7 @@ def __post_init__(self): name="robinhood", api_url="https://api.rh.lighter.xyz", ws_url="wss://api.rh.lighter.xyz/stream", - chain_id=4663, + chain_id=466324, )