Batch Transactions
Lighter executes a batch of transactions back to back, with no other transaction in
between: a quote refresh that cancels and replaces orders goes through in one step. A
batch is up to 50 transactions over HTTP or 15 over WebSocket, all signed by one API key
of one account, with strictly increasing nonces. The examples use market 0, mainnet's
ETH perp; on testnet it is 4095 (Networks).
Reserve Nonces, Sign, Submit
client.tx methods take nonces for you, but a batch is signed ahead of time with
client.signer, whose methods take the same arguments as their client.tx namesakes plus
a nonce, so the nonces have to be reserved first. fast_withdraw and lit_lease have no
signer twin (they submit to their own REST endpoints), so they cannot go in a batch.
reserve_nonces(n) holds n consecutive nonces of one key; sign each transaction with
them, then submit with tx.batch inside the block:
from typed_lighter import Lighter
async with Lighter.new() as client:
async with client.tx.reserve_nonces(3) as reservation:
key = reservation.api_key_index
first, second, third = reservation.nonces
batch = [
client.signer.cancel_order(market_index=0, order_index=1001, nonce=first, api_key_index=key),
client.signer.create_order(
{
'order_type': 'limit', 'market_index': 0, 'client_order_index': 1011,
'base_amount': 500, 'is_ask': False, 'price': 199_000, 'time_in_force': 'post-only',
},
nonce=second, api_key_index=key,
),
client.signer.create_order(
{
'order_type': 'limit', 'market_index': 0, 'client_order_index': 1012,
'base_amount': 500, 'is_ask': True, 'price': 201_000, 'time_in_force': 'post-only',
},
nonce=third, api_key_index=key,
),
]
receipt = await client.tx.batch(batch, transport='ws')
print(receipt['tx_hash']) # one hash per transaction, in orderWhile the block runs, no other client.tx call on that key can take a nonce, so nothing
slips in between. How the block ends settles the reserved nonces:
- a clean exit consumes them all, whether or not anything was sent. Leaving the block
without submitting skips those nonces, so the next transaction on that key fails
once with
21104invalid nonce, after which the client refetches the nonce and the one after succeeds. Only leave the block after submitting; - a batch Lighter refused with a business code (a
BadRequest,AuthErrororRateLimitedfor whicherror_code(error)is notNone), other than21104invalid nonce, leaves them unused, ready for the next transaction; - anything else (
21104, an HTTP 5xx, a network error or timeout, an error with no business code, any other exception) makes the client refetch the key's nonce from Lighter before its next transaction.
Checks Before Sending
tx.batch checks the batch before it goes out, and raises BadRequest when it is empty,
longer than the transport allows, mixes accounts or API keys, or has nonces out of order.
Send One Pre-Signed Transaction
tx.send submits a single transaction you signed yourself:
from typed_lighter import Lighter
async with Lighter.new() as client:
async with client.tx.reserve_nonces(1) as reservation:
signed = client.signer.cancel_all_orders(
{'mode': 'immediate'}, nonce=reservation.nonces[0], api_key_index=reservation.api_key_index
)
await client.tx.send(signed, transport='ws')tx.batch and tx.send submit whatever nonces you signed with. When the transactions
are signed for the client's own account and Lighter accepts them, the client's cached
next nonce for that key moves past the last one sent (it never moves backwards), exactly
as for a client.tx call with an explicit nonce, so later client.tx calls do not
reuse them. A failure settles the cache by the same rules as a managed nonce (see
Error Handling): a refusal
carrying a business code leaves it as it was, and every other outcome (an HTTP 5xx, a
network error, a reply that fails validation) drops it, to be refetched before that key's
next client.tx call. Inside reserve_nonces, the block's outcome then settles it as
described above. For signing with no connection at all, see
Sign Offline.
Upstream reference: Trading.