> For the complete documentation index, see [llms.txt](https://docs.lighter.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.lighter.xyz/developers/reference/timestamps.md).

# Timestamps

Read each timestamp in the context of its object. Lighter exposes block time, transaction execution time and message publication time, with units that vary by field.

> **Start with the object and the unit**
>
> `Trade.timestamp` is block time in **milliseconds**. `Order.timestamp` is block time in **seconds**. A WebSocket envelope's `timestamp` is the API server's frame-generation time in **milliseconds**. None of these gives the time your order arrived at the API server.

## Where the timestamps come from

The sequencer records a block timestamp when the block opens, before its first transaction. Trades in that block share this timestamp. Each transaction also has a finer-grained `transaction_time`, recorded in microseconds when it executes.

### Sequencer

Block time appears as `Trade.timestamp` and `Tx.executed_at` in milliseconds, and as `Order.timestamp` in seconds. The order field reflects the block time of the order's last update.

`transaction_time` records transaction execution time in microseconds. `last_updated_at` carries an aggregate of these execution timestamps for a market's order-book batch.

### API server

`envelope.timestamp` records frame-generation time in milliseconds on channels that expose it. The documented `trade` envelope has no publication timestamp. Record a local receive time if you need to track when it reaches your client.

Queue timing has a separate source, described below.

`queued_at` uses the millisecond component of a Redis stream ID; a pending transaction can instead carry an API-server-time placeholder. Don't treat it as another sequencer timestamp.

> **Measure client latency on your own clock**
>
> Subtracting an API-server or Redis timestamp from a sequencer timestamp includes an unknown clock offset. For client-observed latency, use a local monotonic clock at submission and at the specific response or confirmation you want to measure. An acknowledgement and an execution confirmation measure different stages.

## Seconds, milliseconds and microseconds

Choose the conversion by field and object. The service that supplies a value doesn't determine its unit.

| Field                                             | Native unit  | Convert to milliseconds |
| ------------------------------------------------- | ------------ | ----------------------- |
| `Order.timestamp`                                 | seconds      | `value * 1_000`         |
| `Trade.timestamp`, `Tx.executed_at`               | milliseconds | No conversion           |
| `envelope.timestamp`, `Tx.queued_at`              | milliseconds | No conversion           |
| `transaction_time`, book/ticker `last_updated_at` | microseconds | `value / 1_000`         |

**Python · field-specific conversions**

```python
# Trade: retain integer microseconds for exact arithmetic.
block_us = trade["timestamp"] * 1_000
execution_us = trade["transaction_time"]
since_block_timestamp_us = execution_us - block_us

# Full Order objects use seconds for this particular field.
order_block_ms = order["timestamp"] * 1_000

# Tx.executed_at and envelope.timestamp are already milliseconds.
tx_block_ms = tx["executed_at"]
frame_ms = message["timestamp"]  # only if the envelope has this field
```

Integer division such as `transaction_time // 1_000` discards sub-millisecond precision. Treat an unavailable value such as `0` as missing timing data, rather than converting it into a date.

## Field reference

| Field and context                       | Source                             | Unit | What it marks                                                                                                                                         |
| --------------------------------------- | ---------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timestamp` · WebSocket envelope        | API server                         | ms   | When the API server generates the frame. This does not give the client's receive time.                                                                |
| `timestamp` · Trade                     | Sequencer block time               | ms   | The timestamp of the block containing the trade. Trades in the same block share it.                                                                   |
| `timestamp` · Full Order                | Sequencer block time               | s    | Block time of the order's last update, in seconds. This isn't an API ingress time.                                                                    |
| `transaction_time`                      | Sequencer                          | µs   | Transaction execution time. Its presence alone doesn't establish that an order filled.                                                                |
| `last_updated_at` · Order book / ticker | Sequencer-derived aggregate        | µs   | Maximum `transaction_time` across orders changed in the latest processed order-book batch for the market. Book and ticker read the same stored value. |
| `executed_at` · Transaction             | Sequencer block time               | ms   | The block timestamp shared by transactions in the block. It isn't a millisecond conversion of the per-transaction `transaction_time`.                 |
| `queued_at` · Transaction               | Redis stream / pending placeholder | ms   | Time from the Redis stream ID. While pending, the value can be an API-server-clock placeholder.                                                       |

This table covers the fields and contexts listed here. Other time fields, including candle open times and expiry values, need their own definitions.

## Block time and execution time

### Reading a trade

Both timestamps inside a Trade come from the sequencer. `timestamp` records the block's opening time at millisecond resolution; `transaction_time` records that transaction's execution time at microsecond resolution.

For the trade in the public WebSocket example:

| Event                | Field              | Value                 |
| -------------------- | ------------------ | --------------------- |
| Block opens          | `Trade.timestamp`  | `1773854156654` ms    |
| Transaction executes | `transaction_time` | `1773854156686065` µs |

The block timestamp also supplies `Tx.executed_at`. Other trades in the same block share the block time.

After conversion, the difference from the block timestamp is **32.065 ms**. This measures time relative to the block timestamp, with millisecond precision on the block side. It does not measure API ingress delay or client network latency.

**trade/0 · abridged JSON**

```json
{
  "channel": "trade:0",
  "nonce": 8630448841,
  "trades": [{
    "trade_id": 16164557907,
    "market_id": 0,
    "size": "0.1336",
    "price": "2181.83",
    "block_height": 198321831,
    "timestamp": 1773854156654,
    "transaction_time": 1773854156686065
  }],
  "type": "update/trade"
}
```

For this block/execution pair, compare `timestamp * 1_000` with `transaction_time`. Don't compare the raw numbers in different units, or use timestamps as unique sequence identifiers.

> **No API ingress timestamp on a Trade**
>
> A trade does not expose the original request's arrival time. Liquidations, stop-loss/take-profit triggers and expiry cancellations can originate inside the sequencer; those internal actions don't pass through the API edge as client submissions.

The `trade` envelope has no publication timestamp in the public schema. A local arrival timestamp records when your client received the message; it cannot tell you when the server sent it.

### Market data publication

On `order_book` and `ticker`, the outer `timestamp` records API-server frame generation. The `last_updated_at` value describes sequencer activity represented by the market's processed batch.

**order\_book/0 · abridged JSON**

```json
{
  "channel": "order_book:0",
  "last_updated_at": 1774884082309144,
  "offset": 1558300,
  "order_book": {
    "asks": [{ "price": "2064.54", "size": "0.3285" }],
    "bids": [],
    "nonce": 9182390020,
    "begin_nonce": 9182389998,
    "last_updated_at": 1774884082309144
  },
  "timestamp": 1774884082326,
  "type": "update/order_book"
}
```

The normalized numeric difference in this example is 16.856 ms. Because the two fields use different clocks, that number cannot isolate batching, serialization or network delay.

Envelope timestamps also appear on `market_stats`, `candle`, `user_stats`, `spot_market_stats`, `mark_price_candle`, `height` and `account_all_assets`. Check the schema of the channel you consume; not every envelope includes this field.

## How book and ticker use last\_updated\_at

The standard `order_book` channel batches updates every 50 ms. The market cache takes the maximum `transaction_time` over every order changed in the processed batch, including changes away from the best bid or offer. Ticker reads that same stored value.

For example, suppose a market batch contains these order changes:

| Underlying order change            | Transaction execution time |
| ---------------------------------- | -------------------------- |
| Order A changes at the best bid    | `T + 1,000` µs             |
| Order B changes deeper in the book | `T + 8,000` µs             |

The stored market `last_updated_at` is `T + 8,000` µs. Order book and ticker both read this value for the corresponding market state, even though Order B is outside the best bid and offer.

Public price levels contain price and size, with no per-level `transaction_time`. The times above belong to the underlying order changes and illustrate the batch aggregation.

Compare book and ticker values for the corresponding nonce/state. Messages received next to each other may describe different updates, so arbitrary cross-channel pairs need not match.

> **Continuity and freshness are separate checks**
>
> For consecutive order-book updates, verify that `begin_nonce` equals the previous `nonce`. That detects missing updates; it doesn't establish how old the latest message is.
>
> Track local time since the last received update for stream freshness. An envelope timestamp can help assess publication age if you account for client/server clock offset. Neither a recent frame nor a gap-free sequence alone proves that the underlying market state is recent.

## Which field should I use?

| You need                              | Use                                                           | Limit                                                                                      |
| ------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Execution time associated with a fill | The Trade's `transaction_time`                                | µs; identify the actual fill from its trade record.                                        |
| Block time                            | `Trade.timestamp` or `Tx.executed_at`                         | ms; shared within a block. `Order.timestamp` uses seconds.                                 |
| Frame-generation time                 | Envelope `timestamp`, where present                           | ms, API-server clock. Absent from the documented trade envelope.                           |
| Client receive time                   | Your local timestamp                                          | Cannot recover server-send time from it.                                                   |
| Order-book continuity                 | Current `begin_nonce` vs previous `nonce`                     | Apply within the same market stream; recover from gaps with a new snapshot.                |
| Event ordering                        | The relevant stream's `nonce` or transaction `sequence_index` | Respect each counter's scope. `offset` belongs to an API server and can jump on reconnect. |
| Client-observed response latency      | Local monotonic clock at both ends                            | Specify whether you stop timing at ack, execution confirmation or fill.                    |

## REST endpoints and acknowledgements

`recentTrades` and `trades` return Trade objects: their `timestamp` is block time in ms, while `transaction_time` uses µs. Full Order objects use seconds for `timestamp`.

`orderBookOrders` returns `SimpleOrder` objects. This schema includes `transaction_time`, but neither `timestamp` nor `last_updated_at`.

> **Known limitation: orderBookOrders**
>
> `transaction_time` is currently not populated and returns `0`. Treat this value as unavailable timing data; don't use it as an execution timestamp.

A successful submission response from `sendTx` or `sendTxBatch` acknowledges submission. The WebSocket send response can include a `tx_hash` and `predicted_execution_time_ms`; the prediction is not an actual execution timestamp.

> **Confirm the outcome you need**
>
> Check the transaction's status and result to establish execution. To establish a fill, check the trade record and the order's status and filled amounts. An executed order-placement or cancellation transaction can have a `transaction_time` without producing a fill.

## Integration checklist

* Map each timestamp to its object and unit. Convert seconds, milliseconds and microseconds explicitly.
* Keep block time, transaction execution time, queue time and frame-generation time separate.
* Use stream-specific sequence information for ordering and `begin_nonce` for book continuity. Track freshness separately.
* Measure client latency with a local monotonic clock; don't infer server latency across different clocks.
* Handle missing or zero timing values and confirm fills from execution results and trade/order data.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.lighter.xyz/developers/reference/timestamps.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
