# Lighter knowledge base

Practical knowledge about the Lighter exchange: two independent deployments (regular zkLighter mainnet and an instance on robinhoodchain, commonly called “Robinhood”), the REST API, signing through the official Python SDK and a sidecar signer for TypeScript, markets and quantization, order responses without status, identifiers above 2^53, the 40/60 s write window, account and leverage, operations, and pitfalls. Facts were verified on the robinhoodchain instance; every rule includes its reason and verification date. There are no addresses, accounts, names, or sizes here—only mechanics, formulas, thresholds, and pitfalls.

Stack used for verification: Node ≥ 20, TypeScript, Node `fetch`; signing uses Python 3 + `lighter-sdk` (pip) + aiohttp. Freshness: facts verified through 2026-09-16.

How to read annotations inside the files:
- **“verified with a live request <date>”** / **“verified with a live order <date>”** / **“verified <date>”** / **“observed <date>”**—checked against an instance response on the stated date; do not simplify the rule without understanding its reason;
- **“according to Lighter documentation”**—taken from public documentation or the SDK and not verified live;
- **“not verified”** / the **“Open questions / not verified”** section—the last section in each file; summarized at the end of this page.

---

## Files

| File | Covers | Open when |
|---|---|---|
| [instances-and-api.md](instances-and-api.md) | Two instances and their hosts, where the RH host comes from, public reads (`orderBooks`, `orderBookDetails`, `account`), an expiring auth token and `accountActiveOrders` without `market_id`, response shapes, required metadata, and read transport policy | Starting Lighter code, choosing an instance, reading metadata and an account |
| [signing-and-sdk.md](signing-and-sdk.md) | Non-EVM key, `account_index` / `api_key_index`, Python SDK (`SignerClient`, calls, constants), the sidecar-signer concept (endpoints, HTTP contract, skeleton, timeouts, one process per key, fail closed), signer client in TS, startup key-binding verification | Signing, connecting a key, starting the signer, “exchange unavailable” |
| [markets-and-numbers.md](markets-and-numbers.md) | RH listing (40 perpetuals + 26 `*/USDG` duplicates), `market_id`, decimals, integer `base_amount`/`price`, two minimums, sizing policy, mark price, and spread | Metadata, quantization, minimums, listing checks |
| [orders.md](orders.md) | Payload, time in force, reduceOnly (capped by the position—observed), minimums, `client_order_index` / `order_index` / `order_id` (exact identity is the string), `tx_hash` instead of status, IoC fill by delta, write memory and rollup lag, 21734, cancellations, retries, codes, write budget, dispatch order | Any placement, cancellation, or order reconciliation |
| [account-and-leverage.md](account-and-leverage.md) | `account?by=index`, positions with the sign stored separately, equity = `total_asset_value`, two fraction scales, `update_leverage`, 2x default, changing leverage under an open position, fees (not measured) | Balance, sizing, leverage, margin, protective triggers |
| [rate-limits.md](rate-limits.md) | 40/60 s window per L1 address (code 23000), limiter with reserve and seeding, operation costs, window capacity for write sequences, throttling ≠ rejection ≠ minimum, read limits | Load design, 23000, “holding N place(s)” |
| [ops.md](ops.md) | Process layout and signer as a separate process, startup preflight, health and instrumentation, failure runbook, micro-cycle before going live | Going live with real funds, incident analysis |
| [pitfalls.md](pitfalls.md) | Summary table of pitfalls with dates, error classes, and anti-patterns with reasons | Reviewing Lighter trading code, “strange behavior” |

---

## Quick answers

**1. Are Lighter and “Robinhood” the same thing?**
One exchange, two independent deployments: zkLighter mainnet at `https://mainnet.zklighter.elliot.ai` and the robinhoodchain instance at `https://api.rh.lighter.xyz` (the host comes from the frontend JS bundle and is not in the HTML). They have different market lists, accounts, and `account_index` values, plus independent write windows. Everything in this knowledge base was verified on RH; mainnet is based on documentation. → [instances-and-api.md](instances-and-api.md) §1

**2. How do I sign from TypeScript?**
Not directly: the scheme is proprietary, the 40-byte key is not EVM, and the official SDK supports only Python/Go. Move signing into a Python sidecar (`lighter-sdk`, with the signer binary inside the pip package) exposing `/health`, `/auth`, `/order`, `/cancel`, and `/leverage`; use a fail-closed bearer token, one long-lived `SignerClient` under one lock, and a 25-second timeout that produces `unknown:true`. The key lives only in the signer’s environment. → [signing-and-sdk.md](signing-and-sdk.md) §3

**3. What does placement return?**
Only `tx_hash`. No status and no `order_index`. A resting order appears in `accountActiveOrders` after a lag of seconds; measure an IoC fill as the position delta before and after (poll for up to ~4 seconds); if no delta is observed → REJECTED. → [orders.md](orders.md) §7

**4. How do I cancel an order?**
Use the exchange-assigned `order_index`, but take it from the **string** `order_id`: numeric `order_index` has been rounded by `JSON.parse` (values around 1e16 > 2^53). Cancellation with the rounded identifier is a valid transaction for a nonexistent order; the exchange responds OK and the order remains. Pass the identifier to the signer as a string. → [orders.md](orders.md) §5, §8

**5. What are the minimums?**
Two independent values: `min_quote_amount` = $10 and lot-based `min_base_amount` (code 21706; it can be stricter than $10: SNDK 0.01 ≈ $16). Skip entries and partial reduceOnly orders below either value; for a full reduceOnly close, use ceil and raise above both. → [markets-and-numbers.md](markets-and-numbers.md) §4

**6. What is the write limit?**
40 writes (placements, cancellations, leverage) in a sliding 60-second window per L1 address, code 23000; restarting the process does not reset the window. Configure the local limit below the ceiling and the reserve for cancellations/reduceOnly explicitly: `lighter-kit` does not choose them for the application. Replacing one order costs 2 writes. Public reads do not count toward the window. → [rate-limits.md](rate-limits.md) §1–3

**7. How do I read positions and equity?**
`GET /api/v1/account?by=index&value=<account_index>` without a token. Position: `sign` × `position`. Equity = `total_asset_value`; there is no separate free spot balance outside it. A position’s `initial_margin_fraction` is a percentage (`"50.00"` = 2x). → [account-and-leverage.md](account-and-leverage.md) §1–3

**8. How do I set leverage?**
`update_leverage(market_index, CROSS_MARGIN_MODE, leverage)`; leverage is the market’s initial margin fraction, the default is 5000 = 50% = 2x, and the cap is `floor(10000 / min_initial_margin_fraction)`. Under cross margin it is safe to change leverage under an open position (liquidation is based on the maintenance fraction and does not move). Every attempt consumes a write-window slot. → [account-and-leverage.md](account-and-leverage.md) §4

**9. Is resting reduceOnly capped by the position?**
Yes, verified experimentally on 2026-08-20: an RO SELL twice the long position size → the position reached exactly 0 and the exchange canceled the remainder. Therefore, a reduceOnly order larger than the remaining position will either not be placed or will be truncated. → [orders.md](orders.md) §3

**10. Why does the bot place duplicates and cancel the same order repeatedly?**
The order list lags behind your own writes (zk-rollup). Keep confirmed-write memory for ~45 seconds: do not send a duplicate, and filter a confirmed cancellation from reads. A memory key can be asset + side + price, without size. → [orders.md](orders.md) §7.2

**11. What should I do after a timeout?**
The outcome is unknown: do not retry placement (deduplication by `client_order_index` is unconfirmed); treat an unconfirmed cancellation as a live order; after leverage, reread the account. A local-limiter refusal is a known outcome, so retrying on the next cycle is safe. → [orders.md](orders.md) §9

**12. Which markets exist on RH?**
40 base perpetuals + 26 `*/USDG` duplicates (2026-08-23): major crypto, equity perpetuals, and the `SPY` ETF; some popular altcoins, PAXG, and CL were absent from the checked 2026-08…09 samples. Query the instance rather than relying on memory. → [markets-and-numbers.md](markets-and-numbers.md) §1

**13. What are the fees?**
Not measured. Public materials say that standard Lighter accounts trade without fees; this was not verified on RH. Measure them from your own account’s trade history. → [account-and-leverage.md](account-and-leverage.md) §6

---

## Open questions (summary)

Everything below is not verified or contradictory. Details are in each file’s “Open questions” section.

**Instances and API**
- zkLighter mainnet: market list, minimums, limits, and response shapes were not checked. → instances-and-api.md
- Numeric public-read limit; whether auth-token issuance counts toward the write window; `Bearer` prefix for the token. → instances-and-api.md, rate-limits.md
- WebSocket, trade history, candles, and funding were not used. → instances-and-api.md

**Signing and SDK**
- API-key registration, per-account key limit, nonce separation across `api_key_index` values. → signing-and-sdk.md
- Signer on Windows and macOS; Go SDK. → signing-and-sdk.md

**Markets**
- 21734 threshold as a percentage; market statuses other than `active`; trading `*/USDG` duplicates. → markets-and-numbers.md

**Orders**
- Deduplication by `client_order_index`; GTT after 28 days; IoC on an empty book and partial-fill price; `ORDER_TYPE_MARKET`, post-only, stops, `modify_order`; codes other than 23000/21706/21734; exact reflection lag; dispatch order IoC → GTC RO → GTC (not verified live); reduceOnly IoC larger than the position. → orders.md

**Account and leverage**
- Isolated margin; liquidation formula from the maintenance fraction; fees; `collateral` / `available_balance` semantics; reads by `l1_address`. → account-and-leverage.md

**Limits and operations**
- Write window across sub-accounts of one address; per-key limit; open-order count limit; window on zkLighter mainnet. → rate-limits.md
- Effects of clock skew (the SDK nonce is a counter, not time); one signer for multiple `account_index` values of one address. → ops.md

---

## Disclaimer

This is not financial advice or a recommendation to trade. Measured values describe specific periods and the robinhoodchain instance and do not guarantee any outcome. The Lighter API, limits, minimums, and response shapes can change without notice: before using them, verify the facts against official Lighter documentation and live requests, especially anything marked “not verified.” Run snippet code in dry-run first and with minimal capital. You are responsible for its use.

## Code

The tested implementation of these rules is the `packages/lighter-kit` package (`@markpaper/lighter-kit`): metadata parsing, quantization and sizing policy, exact order identifiers (`order_id` > 2^53), write memory, a limiter with reserve and seeding, IoC fill by delta, the signer client, and the Python SDK signer itself. The knowledge-base snippets illustrate the same rules; when moving them into your own code, include tests, with test traps documented in each file’s “Pitfalls” section.

## License

The knowledge base and the `lighter` skill are licensed under **CC BY 4.0**. You may copy, adapt, and use them, including commercially, but every publication must credit “markpaper — Lighter knowledge base,” link to the original and the license, and indicate changes. See [LICENSE.md](LICENSE.md) for the terms.

---

<!-- license-footer -->
_© markpaper authors. Licensed under [CC BY 4.0](LICENSE.md): when publishing or adapting this material, credit “markpaper — Lighter knowledge base” and link to the original and the license._
