Skip to content

Commit aadc7c2

Browse files
Add read-only-auth example for pre-generating authentication tokens
- Add setup.py to configure API key 253 for all accounts - Add generate.py to create auth tokens at 6-hour intervals - Add README.md with documentation and usage examples - Tokens generated for configurable duration with 8-hour expiry - Output format: auth-tokens.json with account_index -> timestamp -> token mapping Co-Authored-By: Alexandru Velea <dev@velea.cc>
1 parent d000979 commit aadc7c2

3 files changed

Lines changed: 468 additions & 0 deletions

File tree

examples/read-only-auth/README.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# Read-Only Auth Token Pre-Generation
2+
3+
This example demonstrates how to pre-generate authentication tokens for read-only operations on the Lighter platform. By generating tokens ahead of time, you can avoid needing access to your API private keys during runtime for read-only queries.
4+
5+
## Overview
6+
7+
Authentication tokens on Lighter have a maximum expiry of 8 hours. This example allows you to:
8+
9+
1. Configure a dedicated API key (index 253) for all your accounts
10+
2. Pre-generate authentication tokens for future time periods
11+
3. Use these tokens for read-only operations without exposing your private keys
12+
13+
The tokens are generated at 6-hour intervals (aligned to Unix timestamp // 6 hours), with each token valid for 8 hours. This provides an overlap period ensuring continuous coverage.
14+
15+
## Setup
16+
17+
The setup script configures API key 253 for all accounts associated with your Ethereum private key.
18+
19+
### Running Setup
20+
21+
```bash
22+
cd examples/read-only-auth
23+
python3 setup.py > config.json
24+
```
25+
26+
This will:
27+
- Query all accounts for your L1 address
28+
- Generate new API key pairs for each account
29+
- Change API key 253 to use the new keys
30+
- Output configuration in JSON format
31+
32+
### Configuration Variables
33+
34+
Edit the constants in `setup.py`:
35+
36+
```python
37+
BASE_URL = "https://testnet.zklighter.elliot.ai"
38+
ETH_PRIVATE_KEY = "your_ethereum_private_key_here"
39+
API_KEY_INDEX = 253 # Using 253 as it's typically unused
40+
```
41+
42+
### Output Format
43+
44+
```json
45+
{
46+
"BASE_URL": "https://testnet.zklighter.elliot.ai",
47+
"ACCOUNTS": [
48+
{
49+
"api_key_private_key": "...",
50+
"account_index": 0,
51+
"api_key_index": 253
52+
},
53+
{
54+
"api_key_private_key": "...",
55+
"account_index": 1,
56+
"api_key_index": 253
57+
}
58+
]
59+
}
60+
```
61+
62+
## Generating Tokens
63+
64+
The generation script creates authentication tokens for future time periods.
65+
66+
### Running Generation
67+
68+
```bash
69+
python3 generate.py [config_file]
70+
```
71+
72+
If no config file is specified, it defaults to `config.json`.
73+
74+
### Duration Configuration
75+
76+
You can specify the duration in your config file:
77+
78+
```json
79+
{
80+
"BASE_URL": "https://testnet.zklighter.elliot.ai",
81+
"DURATION_IN_DAYS": 7,
82+
"ACCOUNTS": [...]
83+
}
84+
```
85+
86+
Or modify the default in `generate.py`:
87+
88+
```python
89+
DURATION_IN_DAYS = 7 # Generate tokens for 7 days
90+
```
91+
92+
This will generate `4 * DURATION_IN_DAYS` tokens (4 per day, one every 6 hours).
93+
94+
### Output Format
95+
96+
The script generates `auth-tokens.json`:
97+
98+
```json
99+
{
100+
"0": {
101+
"1697184000": "auth_token_string_1",
102+
"1697205600": "auth_token_string_2",
103+
"1697227200": "auth_token_string_3"
104+
},
105+
"1": {
106+
"1697184000": "auth_token_string_1",
107+
"1697205600": "auth_token_string_2"
108+
}
109+
}
110+
```
111+
112+
Where:
113+
- First level key: account index
114+
- Second level key: Unix timestamp (aligned to 6-hour boundaries)
115+
- Value: authentication token
116+
117+
## Usage
118+
119+
### Looking Up Tokens
120+
121+
Use this code to look up the appropriate token for the current time:
122+
123+
```python
124+
import json
125+
import time
126+
127+
# Load pre-generated tokens
128+
with open('auth-tokens.json') as f:
129+
auth_tokens = json.load(f)
130+
131+
# Get current aligned timestamp (6-hour boundary)
132+
current_timestamp = (int(time.time()) // (6 * 3600)) * (6 * 3600)
133+
134+
# Look up token for specific account
135+
account_index = 0
136+
auth_token = auth_tokens[str(account_index)][str(current_timestamp)]
137+
138+
# Use the token for authentication
139+
# (implementation depends on your API client)
140+
```
141+
142+
### Time Alignment
143+
144+
All timestamps are aligned to 6-hour boundaries:
145+
- Timestamps are divisible by 21600 seconds (6 hours)
146+
- Calculation: `unix_timestamp // (6 * 3600) * (6 * 3600)`
147+
- This ensures consistent token lookup across different systems
148+
149+
### Token Expiry
150+
151+
Each token is valid for 8 hours from its timestamp:
152+
- Token timestamp: aligned to 6-hour boundary
153+
- Valid until: timestamp + 8 hours
154+
- This provides 2 hours of overlap between consecutive tokens
155+
156+
## Security
157+
158+
### API Key 253
159+
160+
We use API key index 253 because:
161+
- It's the last available index (0-255)
162+
- It's not typically used by other applications
163+
- Easy to remember for this specific use case
164+
165+
### Invalidating Tokens
166+
167+
To invalidate all existing tokens:
168+
169+
```bash
170+
python3 setup.py > config.json
171+
```
172+
173+
Re-running the setup script generates new API keys for index 253, which invalidates all previously generated authentication tokens. This is useful if:
174+
- You suspect your tokens have been compromised
175+
- You want to rotate your API keys periodically
176+
- You need to revoke access immediately
177+
178+
### Best Practices
179+
180+
1. **Store tokens securely**: The `auth-tokens.json` file contains sensitive authentication data
181+
2. **Regenerate regularly**: Set up a cron job to regenerate tokens periodically
182+
3. **Monitor usage**: Keep track of which tokens are being used
183+
4. **Separate keys**: Use different API keys for different purposes (253 for read-only)
184+
185+
## Example Workflow
186+
187+
Complete workflow for setting up and using pre-generated tokens:
188+
189+
```bash
190+
# 1. Configure accounts (one-time setup)
191+
cd examples/read-only-auth
192+
python3 setup.py > config.json
193+
194+
# 2. Generate tokens for the next 7 days
195+
python3 generate.py
196+
197+
# 3. Use the tokens in your application
198+
python3 your_app.py # Uses auth-tokens.json
199+
200+
# 4. Regenerate tokens when needed (e.g., daily cron job)
201+
python3 generate.py
202+
```
203+
204+
## Troubleshooting
205+
206+
### "Account not found" error
207+
208+
Make sure your Ethereum private key corresponds to an account registered on the Lighter platform.
209+
210+
### "Failed to change API key" error
211+
212+
This could happen if:
213+
- The API key change transaction failed
214+
- Network connectivity issues
215+
- The account is not active
216+
217+
### "Token not found for timestamp" error
218+
219+
This means you don't have a token for the current time period. Run:
220+
221+
```bash
222+
python3 generate.py
223+
```
224+
225+
to generate fresh tokens.
226+
227+
## Additional Notes
228+
229+
- Tokens are specific to each account index
230+
- Each account has its own set of time-aligned tokens
231+
- The system uses the SignerClient's native `create_auth_token_with_expiry` method
232+
- No modifications to the core lighter-python SDK are required
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import asyncio
2+
import json
3+
import logging
4+
import time
5+
import sys
6+
import lighter
7+
8+
logging.basicConfig(level=logging.INFO)
9+
10+
DURATION_IN_DAYS = 7
11+
12+
13+
def create_auth_token_for_timestamp(signer_client, timestamp, expiry_hours=8):
14+
deadline = timestamp + (expiry_hours * 3600)
15+
auth_token, error = signer_client.create_auth_token_with_expiry(deadline)
16+
if error is not None:
17+
raise Exception(f"Failed to create auth token: {error}")
18+
return auth_token
19+
20+
21+
async def generate_tokens_for_account(account_info, base_url, duration_days):
22+
account_index = account_info["account_index"]
23+
api_key_private_key = account_info["api_key_private_key"]
24+
api_key_index = account_info["api_key_index"]
25+
26+
logging.info(f"Generating tokens for account {account_index}")
27+
28+
signer_client = lighter.SignerClient(
29+
url=base_url,
30+
private_key=api_key_private_key,
31+
account_index=account_index,
32+
api_key_index=api_key_index,
33+
)
34+
35+
current_time = int(time.time())
36+
interval_seconds = 6 * 3600
37+
start_timestamp = (current_time // interval_seconds) * interval_seconds
38+
39+
num_tokens = 4 * duration_days
40+
expiry_hours = 8
41+
42+
tokens = {}
43+
for i in range(num_tokens):
44+
timestamp = start_timestamp + (i * interval_seconds)
45+
try:
46+
auth_token = create_auth_token_for_timestamp(
47+
signer_client, timestamp, expiry_hours
48+
)
49+
tokens[str(timestamp)] = auth_token
50+
logging.debug(f"Generated token for timestamp {timestamp}")
51+
except Exception as e:
52+
logging.error(f"Failed to generate token for timestamp {timestamp}: {e}")
53+
54+
await signer_client.close()
55+
56+
return account_index, tokens
57+
58+
59+
async def main():
60+
config_file = "config.json"
61+
if len(sys.argv) > 1:
62+
config_file = sys.argv[1]
63+
64+
try:
65+
with open(config_file, "r") as f:
66+
config = json.load(f)
67+
except FileNotFoundError:
68+
logging.error(f"Config file '{config_file}' not found")
69+
logging.error("Run setup.py first: python3 setup.py > config.json")
70+
sys.exit(1)
71+
except json.JSONDecodeError as e:
72+
logging.error(f"Invalid JSON in config file: {e}")
73+
sys.exit(1)
74+
75+
base_url = config.get("BASE_URL")
76+
accounts = config.get("ACCOUNTS", [])
77+
duration_days = config.get("DURATION_IN_DAYS", DURATION_IN_DAYS)
78+
79+
if not base_url:
80+
logging.error("BASE_URL not found in config")
81+
sys.exit(1)
82+
83+
if not accounts:
84+
logging.error("No accounts found in config")
85+
sys.exit(1)
86+
87+
logging.info(f"Generating tokens for {len(accounts)} account(s)")
88+
logging.info(f"Duration: {duration_days} days ({4 * duration_days} tokens per account)")
89+
90+
tasks = []
91+
for account_info in accounts:
92+
tasks.append(generate_tokens_for_account(account_info, base_url, duration_days))
93+
94+
results = await asyncio.gather(*tasks, return_exceptions=True)
95+
96+
auth_tokens = {}
97+
for result in results:
98+
if isinstance(result, Exception):
99+
logging.error(f"Error generating tokens: {result}")
100+
else:
101+
account_index, tokens = result
102+
auth_tokens[str(account_index)] = tokens
103+
104+
output_file = "auth-tokens.json"
105+
with open(output_file, "w") as f:
106+
json.dump(auth_tokens, f, indent=2)
107+
108+
logging.info(f"Successfully generated tokens and saved to {output_file}")
109+
logging.info(f"Total accounts: {len(auth_tokens)}")
110+
for account_index, tokens in auth_tokens.items():
111+
logging.info(f" Account {account_index}: {len(tokens)} tokens")
112+
113+
114+
if __name__ == "__main__":
115+
asyncio.run(main())

0 commit comments

Comments
 (0)