Signing Transactions

Market, limit, stop and TWAP orders; modify, cancel and batch; leverage and margin.

Traders can trade programmatically using the sendTx and sendTxBatch REST endpoints, or json/sendTx and json/sendTxBatch types via Websocket. Creating an API key is required to sign transactions.

Orders that have the correct syntax will be accepted by the API servers, returning code=200. This does not guarantee the execution of your order, as the sequencer could still reject it if parameters are not set properly. To monitor orders execution, see Confirming execution.

Sign Transactions

Every transaction needs to be signed using the SignerClient. Using the Python SDK, you can initialize it as follows:

 client = lighter.SignerClient(  
        url=BASE_URL,  
        api_private_keys={API_KEY_INDEX:PRIVATE_KEY},  
        account_index=ACCOUNT_INDEX
    )

The code for the signer can be found in the same repo, in the signer_client.py file. You may notice that it uses a binary for the signer: the code for it can be found in the lighter-go public repo, and you can compile it yourself using the justfile.

The TypeScript examples on this page use the WASM signer from the TypeScript quickstart and this helper:

async function sendTx(tx: { txType: number; txInfo: string }) {
  const r = await fetch(`${BASE_URL}/api/v1/sendTx`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({ tx_type: String(tx.txType), tx_info: tx.txInfo }),
  }).then(r => r.json());
  if (r.code !== 200) throw new Error(`${r.code}: ${r.message}`);
  return r; // { tx_hash, predicted_execution_time_ms, volume_quota_remaining }
}

Every order carries a nonce and uses integer prices and sizes. Both are explained in Core concepts: see Nonce (including which rejected transactions consume a nonce) and Integer prices and sizes (including minimum order sizes).

Order parameters

ParameterNotes
marketIndexmarket_id from orderBookDetails
clientOrderIndexYour ID (uint48, ≤ 2^48−1), unique across all markets. Used to cancel/modify.
baseAmountInteger: size × 10^size_decimals
priceInteger: price × 10^price_decimals. For taker orders = worst acceptable price: if the sequencer cannot offer an equal or better price, the order is cancelled
isAsk0 buy, 1 sell
orderTypeSee Order types
timeInForce0 Immediate-or-cancel · 1 Good-till-time · 2 Post-only
reduceOnly1 to only reduce a position
triggerPriceFor stop-loss / take-profit
orderExpiryUnix ms timestamp, between 5 minutes and 30 days ahead. 0 for IOC orders

When cancelling, you may see order_index instead of client_order_index as an argument – you can use the same value. If client_order_index is missing (e.g. if you trade from the front-end, that'll be 0), you can use trade_id (assigned by the exchange) to reference the order.

Order types

ValueTypeUse
0LimitResting or IOC limit order
1MarketImmediate execution up to price
2Stop-lossMarket order when triggerPrice is hit
3Stop-loss limitLimit order when triggerPrice is hit
4Take-profitMarket order when triggerPrice is hit
5Take-profit limitLimit order when triggerPrice is hit
6TWAPTime-weighted execution

Additionally, there are two internal order types: TWAPSubOrder (7) and LiquidationOrder (8).

Other order enums

EnumValues
Grouping type1 OTO · 2 OCO · 3 OTOCO
Self-trade behavior0 Expire maker · 1 Expire taker · 2 Expire both · 3 Reduce
Self-trade equality0 Same account index · 1 Same master account
Cancel-all TIF0 Immediate · 1 Scheduled · 2 Abort
Margin mode0 Cross · 1 Isolated
Route type0 Perps · 1 Spot

These are also defined in signer_client.py in the Python SDK, and in constants.go in the GO SDK.

Market order

// Buy 0.05 ETH, paying at most $3,150
const tx = check(lighter.SignCreateOrder(
  0, clientOrderIndex, toInt(0.05, 4), toInt(3150, 2),
  0,      // buy
  1,      // MARKET
  0,      // IOC
  0, 0,
  0,      // orderExpiry: 0 for IOC
  0, 0, 0, 0, 0,
  0, await nextNonce(), API_KEY_INDEX, ACCOUNT_INDEX,
));
await sendTx(tx);

Pick price from the order book with a slippage buffer (e.g. best ask × 1.01 for buys). If the book can't fill at that price or better, the rest is cancelled.

Post-only limit order

const tx = check(lighter.SignCreateOrder(
  0, clientOrderIndex, toInt(0.05, 4), toInt(2900, 2),
  0, 0 /* LIMIT */, 2 /* POST_ONLY */, 0, 0,
  Date.now() + 7 * 24 * 3600e3,
  0, 0, 0, 0, 0,
  0, await nextNonce(), API_KEY_INDEX, ACCOUNT_INDEX,
));

Stop-loss and take-profit

Set triggerPrice and price. price is the worst fill price once triggered, i.e. the allowed slippage, with the same logic as taker orders. Use reduceOnly = 1 to protect a position.

To attach TP/SL to an entry in one go, use SignCreateGroupedOrders (OTO / OCO / OTOCO groupings). Grouped orders count as one transaction for quota. See WASM signer functions.

Modify

const tx = check(lighter.SignModifyOrder(
  marketIndex, clientOrderIndex, newBaseAmount, newPrice, newTriggerPrice,
  0, 0, 0,  // integrator fields (keep the same as the original order)
  0, 0,     // self-trade modes
  0, await nextNonce(),
  0,        // orderVersion: optional; if set, must be > current version
  API_KEY_INDEX, ACCOUNT_INDEX,
));

Order Version

You can specify an order_version when modifying orders, so that the sequencer will reject any version that is not strictly greater than the order's current stored version, returning AppErrInvalidOrderOrderVersion. This is optional, and examples can be found here (http) and here (ws). Order events will include order_version, and Trade events will include ask_order_version, bid_order_version.

Self-trade prevention

selfTradeBehaviorMode and selfTradeEqualityMode control what happens when your order would match another order of yours. The default 0, 0 expires the resting (maker) order when both orders come from the same account index. Other values are listed in Other order enums.

🚧

Not combinable with partner fees

A non-default self-trade mode can't be combined with non-zero integrator fees on the same order (error 21159). If you charge partner fees, leave both self-trade modes at 0.

Cancel

// One order
lighter.SignCancelOrder(marketIndex, clientOrderIndex, 0, nonce, API_KEY_INDEX, ACCOUNT_INDEX);

// All orders (timeInForce 0 = now; cancelAllMarketIndex 255 = all markets)
lighter.SignCancelAllOrders(0, 0, 255, 0, nonce, API_KEY_INDEX, ACCOUNT_INDEX);

Batch

Send up to 50 transactions in one request. All transactions in a batch must use the same account and API key, with consecutive nonces:

const n = await nextNonce();
const txs = [0, 1, 2].map(i => check(lighter.SignCreateOrder(/* … */ 0, n + i, API_KEY_INDEX, ACCOUNT_INDEX)));

await fetch(`${BASE_URL}/api/v1/sendTxBatch`, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    tx_types: JSON.stringify(txs.map(t => t.txType)),
    tx_infos: JSON.stringify(txs.map(t => t.txInfo)),
  }),
});
  • The whole batch waits for the longest speed bump of any transaction in it. Mixing a taker order into a batch of post-only orders delays all of them.
  • Large batches: request-body size limits at the CDN edge can reject a large REST batch before it reaches the API (you'll get an HTTP error without Lighter's JSON body). REST batches of up to ~15 orders are safe. For bigger batches, use WebSocket jsonapi/sendtxbatch (below), which accepts messages up to 64 KB, enough for 50 orders.

Python example: send_batch_tx_http.py.

Leverage and margin

// Initial margin fraction in basis points of 10,000: 10x leverage -> 1000
lighter.SignUpdateLeverage(marketIndex, 1000, 0 /* 0 cross, 1 isolated */, 0, nonce, API_KEY_INDEX, ACCOUNT_INDEX);

// Add/remove isolated margin
lighter.SignUpdateMargin(marketIndex, usdcAmount, direction /* 1 add, 0 remove */, 0, nonce, API_KEY_INDEX, ACCOUNT_INDEX);

Sending over WebSocket

Lower latency than REST, same limits:

{ "type": "jsonapi/sendtx", "data": { "id": "my-req-1", "tx_type": 14, "tx_info": { … } } }

jsonapi/sendtxbatch takes tx_types and tx_infos arrays (max 50). Messages are limited to 64 KB after decompression. permessage-deflate is supported, but it doesn't raise that limit. Full message format: Send Tx and Send Batch Tx.

Confirming execution

sendTx returning code: 200 only means the transaction was accepted. Subscribe to account_orders/{market}/{account} or account_tx/{account} to see fills and rejections. See Account data & positions.


Did this page help you?