Skip to content

Commit ddd6889

Browse files
committed
refactor nonce-manager and use API Key dict only in signer
- pass API key directly to signer - pass a list of arbitrary API keys to nonce manager - setup stores multiple API keys
1 parent 8125f39 commit ddd6889

22 files changed

Lines changed: 763 additions & 614 deletions

examples/README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
- the API key config is saved in a local file `./api_key_config.json`
1010

1111
## Start trading on testnet
12-
- `create_modify_cancel_order.py`
12+
- `create_modify_cancel_order_http.py`
1313
- creates an ask (sell) order for 0.1 ETH @ $4050
1414
- modified the order and increases the size to 0.11 ETH and increases the price to $4100
1515
- cancels the order
1616
- Note: all of these operations use the client order index of the order. You can use the order from the exchange as well
1717

18-
- `ws_send_tx.py`
19-
- same flow as `create_modify_cancel_order.py`
18+
- `create_modify_cancel_order_ws.py`
19+
- same flow as `create_modify_cancel_order_http.py`
2020
- sends TXs over WS instead of HTTP
2121

2222
- `create_grouped_ioc_with_attached_sl_tp.py`
@@ -43,6 +43,24 @@ What about the order types? Just as normal orders, SL/TP orders trigger an order
4343
- market order
4444
- limit IOC / GTC
4545

46+
### Modify leverage / Margin Mode (Cross, Isolated) / Add Collateral to isolated-only positions
47+
- `margin_eth_20x_cross_http`
48+
- sets ETH market to 20x leverage and cross-margin mode, using HTTP
49+
- `margin_eth_50x_isolate_ws`
50+
- sets ETH market to 50x leverage and isolated margin mode, using HTTP
51+
- `margin_eth_add_collateral_http.py`
52+
- adds $10.5 USDC to the ETH position (must be opened and in isolated mode)
53+
- `margin_eth_remove_collateral_ws.py`
54+
- removes $5 USDC from the ETH position (must be opened and in isolated mode)
55+
56+
### Batch orders
57+
- `send_batch_tx_http.py`
58+
- sends multiple orders in a single HTTP request
59+
- `send_batch_tx_ws.py`
60+
- sends multiple orders in a single WS request`
61+
62+
Batch TXs will be executed back to back, without the possibility of other TXs interfering.
63+
4664
## Setup steps for mainnet
4765
- deposit money on Lighter to create an account first
4866
- change the URL to `mainnet.zklighter.elliot.ai`

examples/create_grouped_ioc_with_attached_sl_tp.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,19 @@
77
async def main():
88
client, api_client, _ = default_example_setup()
99

10+
# Sell some ETH at $2500
11+
# The size of the SL/TP orders will be equal to the size of the executed order
12+
13+
# set SL trigger price at 5000 and limit price at 5050
14+
# set TP trigger price at 1500 and limit price at 1550
15+
# Note: set the limit price to be higher than the SL/TP trigger price to ensure the order will be filled
16+
# If the mark price of ETH reaches 1500, there might be no one willing to sell you ETH at 1500, so trying to buy at 1550 would increase the fill rate
17+
1018
ioc_order = CreateOrderTxReq(
1119
MarketIndex=0,
1220
ClientOrderIndex=0,
1321
BaseAmount=1000, # 0.1 ETH
14-
Price=300000, # $3000
22+
Price=2500_00, # $2500
1523
IsAsk=1, # sell
1624
Type=lighter.SignerClient.ORDER_TYPE_LIMIT,
1725
TimeInForce=lighter.SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
@@ -25,25 +33,25 @@ async def main():
2533
MarketIndex=0,
2634
ClientOrderIndex=0,
2735
BaseAmount=0,
28-
Price=300000,
36+
Price=1550_00,
2937
IsAsk=0,
3038
Type=lighter.SignerClient.ORDER_TYPE_TAKE_PROFIT_LIMIT,
3139
TimeInForce=lighter.SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
3240
ReduceOnly=1,
33-
TriggerPrice=300000,
41+
TriggerPrice=1500_00,
3442
OrderExpiry=-1,
3543
)
3644

3745
stop_loss_order = CreateOrderTxReq(
3846
MarketIndex=0,
3947
ClientOrderIndex=0,
4048
BaseAmount=0,
41-
Price=500000,
49+
Price=5050_00,
4250
IsAsk=0,
4351
Type=lighter.SignerClient.ORDER_TYPE_STOP_LOSS_LIMIT,
4452
TimeInForce=lighter.SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
4553
ReduceOnly=1,
46-
TriggerPrice=500000,
54+
TriggerPrice=5000_00,
4755
OrderExpiry=-1,
4856
)
4957

examples/create_modify_cancel_order.py renamed to examples/create_modify_cancel_order_http.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
async def main():
77
client, api_client, _ = default_example_setup()
8+
client.check_client()
89

910
# create order
11+
api_key_index, nonce = client.nonce_manager.next_nonce()
1012
tx, tx_hash, err = await client.create_order(
1113
market_index=0,
1214
client_order_index=123,
@@ -17,27 +19,37 @@ async def main():
1719
time_in_force=lighter.SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
1820
reduce_only=False,
1921
trigger_price=0,
22+
nonce=nonce,
23+
api_key_index=api_key_index,
2024
)
2125
print(f"Create Order {tx=} {tx_hash=} {err=}")
2226
if err is not None:
2327
raise Exception(err)
2428

25-
# create order
29+
## modify order
30+
# use the same API key so the TX goes after the create order TX
31+
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
2632
tx, tx_hash, err = await client.modify_order(
2733
market_index=0,
2834
order_index=123,
2935
base_amount=1100, # 0.11 ETH
3036
price=410000, # $4100
3137
trigger_price=0,
38+
nonce=nonce,
39+
api_key_index=api_key_index,
3240
)
3341
print(f"Modify Order {tx=} {tx_hash=} {err=}")
3442
if err is not None:
3543
raise Exception(err)
3644

37-
# cancel order
45+
## cancel order
46+
# use the same API key so the TX goes after the modify order TX
47+
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
3848
tx, tx_hash, err = await client.cancel_order(
3949
market_index=0,
4050
order_index=123,
51+
nonce=nonce,
52+
api_key_index=api_key_index,
4153
)
4254
print(f"Cancel Order {tx=} {tx_hash=} {err=}")
4355
if err is not None:
Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,21 @@
1-
import json
21
import websockets
32
import asyncio
43

54
import lighter
6-
from utils import default_example_setup
7-
8-
9-
async def ws_send_tx(ws_client: websockets.ClientConnection, tx_type, tx_info):
10-
await ws_client.send(
11-
json.dumps(
12-
{
13-
"type": "jsonapi/sendtx",
14-
"data": {
15-
"id": f"my_random_id_{12345678}", # optional, helps id the response
16-
"tx_type": tx_type,
17-
"tx_info": json.loads(tx_info),
18-
},
19-
}
20-
)
21-
)
22-
23-
print("Response:", await ws_client.recv())
5+
from utils import default_example_setup, ws_send_tx
246

257

268
# this example does the same thing as the create_modify_cancel_order.py example, but sends the TX over WS instead of HTTP
279
async def main():
2810
client, api_client, ws_client_promise = default_example_setup()
2911

30-
# setup WS client and print connected message
12+
# set up WS client and print a connected message
3113
ws_client: websockets.ClientConnection = await ws_client_promise
3214
print("Received:", await ws_client.recv())
3315

3416
# create order
35-
tx_type, tx_info, err = client.sign_create_order(
17+
api_key_index, nonce = client.nonce_manager.next_nonce()
18+
tx_type, tx_info, tx_hash, err = client.sign_create_order(
3619
market_index=0,
3720
client_order_index=123,
3821
base_amount=1000, # 0.1 ETH
@@ -42,31 +25,41 @@ async def main():
4225
time_in_force=lighter.SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
4326
reduce_only=False,
4427
trigger_price=0,
28+
nonce=nonce,
29+
api_key_index=api_key_index,
4530
)
4631
if err is not None:
4732
raise Exception(err)
48-
await ws_send_tx(ws_client, tx_type, tx_info)
33+
await ws_send_tx(ws_client, tx_type, tx_info, tx_hash)
4934

50-
# modify order
51-
tx_type, tx_info, err = client.sign_modify_order(
35+
## modify order
36+
# use the same API key so the TX goes after the create order TX
37+
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
38+
tx_type, tx_info, tx_hash, err = client.sign_modify_order(
5239
market_index=0,
5340
order_index=123,
5441
base_amount=1100, # 0.11 ETH
5542
price=410000, # $4100
5643
trigger_price=0,
44+
nonce=nonce,
45+
api_key_index=api_key_index,
5746
)
5847
if err is not None:
5948
raise Exception(err)
60-
await ws_send_tx(ws_client, tx_type, tx_info)
49+
await ws_send_tx(ws_client, tx_type, tx_info, tx_hash)
6150

62-
# cancel order
63-
tx_type, tx_info, err = client.sign_cancel_order(
51+
## cancel order
52+
# use the same API key so the TX goes after the modify order TX
53+
api_key_index, nonce = client.nonce_manager.next_nonce(api_key_index)
54+
tx_type, tx_info, tx_hash, err = client.sign_cancel_order(
6455
market_index=0,
6556
order_index=123,
57+
nonce=nonce,
58+
api_key_index=api_key_index,
6659
)
6760
if err is not None:
6861
raise Exception(err)
69-
await ws_send_tx(ws_client, tx_type, tx_info)
62+
await ws_send_tx(ws_client, tx_type, tx_info, tx_hash)
7063

7164
await client.close()
7265
await api_client.close()

examples/create_position_tied_sl_tl.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,40 @@
88
async def main():
99
client, api_client, _ = default_example_setup()
1010

11+
# Creates a position tied SL/TP pair
12+
# The SL/TP orders will close your whole position, even if you add/remove from it later on
13+
# if the positions reach 0 or switches from short -> long, the orders are canceled
14+
15+
# this particular example, sets the SL/TP for a short position
16+
# set SL trigger price at 5000 and limit price at 5050
17+
# set TP trigger price at 1500 and limit price at 1550
18+
# Note: set the limit price to be higher than the SL/TP trigger price to ensure the order will be filled
19+
# If the mark price of ETH reaches 1500, there might be no one willing to sell you ETH at 1500, so trying to buy at 1550 would increase the fill rate
20+
1121
# Create a One-Cancels-the-Other grouped order with a take-profit and a stop-loss order
1222
take_profit_order = CreateOrderTxReq(
1323
MarketIndex=0,
1424
ClientOrderIndex=0,
1525
BaseAmount=0,
16-
Price=300000,
26+
Price=1550_00,
1727
IsAsk=0,
1828
Type=lighter.SignerClient.ORDER_TYPE_TAKE_PROFIT_LIMIT,
1929
TimeInForce=lighter.SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
2030
ReduceOnly=1,
21-
TriggerPrice=300000,
31+
TriggerPrice=1500_00,
2232
OrderExpiry=-1,
2333
)
2434

2535
stop_loss_order = CreateOrderTxReq(
2636
MarketIndex=0,
2737
ClientOrderIndex=0,
2838
BaseAmount=0,
29-
Price=500000,
39+
Price=4050_00,
3040
IsAsk=0,
3141
Type=lighter.SignerClient.ORDER_TYPE_STOP_LOSS_LIMIT,
3242
TimeInForce=lighter.SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
3343
ReduceOnly=1,
34-
TriggerPrice=500000,
44+
TriggerPrice=4000_00,
3545
OrderExpiry=-1,
3646
)
3747

examples/create_with_multiple_keys.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,13 @@
1-
import asyncio
2-
import lighter
3-
1+
import time
42

5-
BASE_URL = "https://testnet.zklighter.elliot.ai"
6-
# use examples/system_setup.py or the apikeys page (for mainnet) to generate new api keys
7-
KEYS = {
8-
5: "API_PRIVATE_KEY_5",
9-
6: "API_PRIVATE_KEY_6",
10-
7: "API_PRIVATE_KEY_7",
11-
}
12-
ACCOUNT_INDEX = 100 # replace with your account_index
3+
import asyncio
4+
from utils import default_example_setup
135

146

157
async def main():
16-
client = lighter.SignerClient(
17-
url=BASE_URL,
18-
private_key=KEYS[5],
19-
account_index=ACCOUNT_INDEX,
20-
api_key_index=5,
21-
max_api_key_index=7,
22-
private_keys=KEYS,
23-
)
24-
25-
err = client.check_client()
26-
if err is not None:
27-
print(f"CheckClient error: {err}")
28-
return
8+
client, api_client, _ = default_example_setup()
9+
10+
# create 20 orders. The client will use as many API keys as it was configured.
2911

3012
for i in range(20):
3113
res_tuple = await client.create_order(
@@ -34,14 +16,16 @@ async def main():
3416
base_amount=100000 + i,
3517
price=385000 + i,
3618
is_ask=True,
37-
order_type=lighter.SignerClient.ORDER_TYPE_LIMIT,
38-
time_in_force=lighter.SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
19+
order_type=client.ORDER_TYPE_LIMIT,
20+
time_in_force=client.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
3921
reduce_only=False,
4022
trigger_price=0,
4123
)
4224
print(res_tuple)
4325

44-
await client.cancel_all_orders(time_in_force=client.CANCEL_ALL_TIF_IMMEDIATE, time=0)
26+
# wait for orders to be created
27+
time.sleep(1)
28+
await client.cancel_all_orders(time_in_force=client.CANCEL_ALL_TIF_IMMEDIATE, timestamp_ms=0)
4529

4630

4731
if __name__ == "__main__":
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import asyncio
2+
from utils import default_example_setup
3+
4+
5+
async def main():
6+
client, api_client, _ = default_example_setup()
7+
8+
# Note: the HTTP method `update_leverage` receives `leverage` as the argument,
9+
# while the WS one that calls `sign_update_leverage` to get the TX to send it directly over WS
10+
# receives `fraction` as the argument, which is 10_000 / leverage
11+
# this was kept this way to not break backwards compatibility. Ideally, they would be consistent.
12+
13+
tx, tx_hash, err = await client.update_leverage(
14+
market_index=0,
15+
leverage=20,
16+
margin_mode=client.CROSS_MARGIN_MODE
17+
)
18+
19+
print(f"Update Leverage {tx=} {tx_hash=} {err=}")
20+
if err is not None:
21+
raise Exception(err)
22+
23+
await client.close()
24+
await api_client.close()
25+
26+
27+
if __name__ == "__main__":
28+
asyncio.run(main())
29+

0 commit comments

Comments
 (0)