Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides12 min read

Base OP Stack L1 Derivation and Timestamps over RPC

How OP Stack derivation ties every Base L2 block timestamp to an L1 origin, and how to read that provenance over RPC.

TL;DR

In the OP Stack, the derivation pipeline reads L1 batches and deposit transactions to produce L2 blocks, and each L2 block header carries an L1 origin (an L1 block number and L1 timestamp). Because L1 timestamps are the canonical time source, an L2 block timestamp is bounded by the timestamp of its L1 origin, so L2 time is a derivative of L1 time. You can read this provenance over RPC by combining the standard L2 eth_getBlockByNumber timestamp with the op-node rollup namespace, especially optimism_syncStatus, which exposes unsafe, safe, and finalized L2 heads together with their L1 origins. Timestamp-only indexing is unsafe across reorgs because the same wall-clock second can map to different L2 blocks before and after a reorg; anchoring to an L1 origin makes the mapping reproducible. This guide shows the mechanism, runnable Node.js readers, a results table to fill against your own endpoint, and the limits of unsafe versus safe versus finalized provenance.

Base L2 Block Production and the Derivation Pipeline

Base is an OP Stack L2. A sequencer orders user transactions and produces L2 blocks quickly; these blocks are initially unsafe because they have not yet been derived from data posted to L1. The OP Stack derivation pipeline then reads L1 data, including batch submissions and deposit transactions, and reconstructs the canonical L2 chain from it. The OP Stack Specification - Derivation defines this pipeline as the authoritative source of L2 chain state.

The practical consequence is that L2 block production and L2 block finality are separate concerns. The sequencer gives low-latency inclusion, while derivation against L1 gives the chain its canonical, re-derivable history. For an RPC consumer, that means a block you read as latest may later be reorganized, while a block that has been derived and finalized on L1 is stable.

Base documentation describes the L2 execution engine and node RPC surface in Base Documentation - L2 Execution Engine / Node RPC. The execution engine stores L2 blocks; the op-node (rollup node) drives derivation and exposes rollup-specific RPC methods that reveal L1 provenance.

  • Sequencer: orders transactions, emits unsafe L2 blocks.
  • Batcher: posts L2 transaction data to L1 in batches.
  • Derivation pipeline: reads L1 batches and deposits, reconstructs L2 blocks.
  • op-node: exposes rollup RPC such as optimism_syncStatus and optimism_outputAtBlock.

L1 Origin as the Canonical Time Source for L2 Timestamps

Each L2 block header carries an L1 origin: the L1 block number and L1 timestamp that the L2 block is derived from. L1 timestamps are the canonical time source for L2 block timestamps, so an L2 block timestamp is bounded by the timestamp of its L1 origin. In other words, L2 time is a derivative of L1 time, not an independent clock.

This design keeps L2 timestamps consistent with the L1 chain that ultimately secures them. If you index L2 data by timestamp alone, you are indexing by a value that is meaningful only in relation to an L1 origin. Two L2 blocks with the same timestamp can exist in different derivation contexts, and a reorg can change which L2 block occupies a given timestamp.

The op-node rollup RPC exposes this relationship. optimism_syncStatus returns unsafe, safe, and finalized L2 heads along with their L1 origins, and optimism_outputAtBlock returns the output root and block reference for a given L2 block. These methods let you attach an L1 block number to any L2 block you read.

  • L2 block header includes L1 origin (L1 block number and L1 timestamp).
  • L2 timestamp is bounded by the L1 origin timestamp.
  • optimism_syncStatus exposes unsafe/safe/finalized L2 heads and their L1 origins.
  • optimism_outputAtBlock returns output root and block reference for an L2 block.

Reading L2 Block Provenance with Standard and Rollup RPC

The standard L2 eth_getBlockByNumber returns the L2 block timestamp, number, hash, and transactions. That alone does not tell you the L1 origin. To attach provenance, call the op-node rollup namespace. optimism_syncStatus gives you the current unsafe, safe, and finalized L2 heads and their L1 origins; optimism_outputAtBlock gives you the output root for a specific L2 block.

A practical pattern is to read the L2 block with eth_getBlockByNumber, then read optimism_syncStatus to learn the current L1 origins for each head, and then use optimism_outputAtBlock for the specific block you care about. This lets you state, for any L2 block, which L1 block it is derived from and whether it is currently unsafe, safe, or finalized.

If you are using a managed endpoint, the rollup namespace may be exposed on the same URL as the L2 execution RPC, or on a separate op-node URL. Check your provider's documentation. OnFinality's Base RPC endpoints (RPC Assistant) page describes the available Base endpoints, and the Base network page covers the network context.

  • eth_getBlockByNumber: L2 block timestamp, number, hash, transactions.
  • optimism_syncStatus: unsafe/safe/finalized L2 heads and L1 origins.
  • optimism_outputAtBlock: output root and block reference for an L2 block.
  • Rollup namespace may be on the same or a separate endpoint; verify with your provider.

Runnable Node.js Reader: Mapping an L2 Block to Its L1 Origin

The following Node.js script reads an L2 block by number, then reads optimism_syncStatus to obtain the L1 origins for the unsafe, safe, and finalized heads, and finally reads optimism_outputAtBlock for the specific block. It prints the L2 block timestamp, the L1 origin block number and timestamp, and the L1-to-L2 inclusion latency in seconds.

Set BASE_L2_RPC_URL to your L2 execution endpoint and BASE_ROLLUP_RPC_URL to your op-node rollup endpoint. If your provider exposes both namespaces on one URL, set both variables to the same value. The script uses only the built-in fetch available in modern Node.js.

// map-l2-to-l1-origin.js
// Usage: BASE_L2_RPC_URL=... BASE_ROLLUP_RPC_URL=... node map-l2-to-l1-origin.js 12345678

const L2_URL = process.env.BASE_L2_RPC_URL;
const ROLLUP_URL = process.env.BASE_ROLLUP_RPC_URL || L2_URL;
const blockNumber = process.argv[2] ? parseInt(process.argv[2], 10) : null;

async function rpc(url, method, params) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(method + ': ' + JSON.stringify(json.error));
  return json.result;
}

async function main() {
  if (blockNumber === null) throw new Error('Pass an L2 block number');

  const l2Block = await rpc(L2_URL, 'eth_getBlockByNumber', [
    '0x' + blockNumber.toString(16), false
  ]);
  if (!l2Block) throw new Error('L2 block not found');

  const sync = await rpc(ROLLUP_URL, 'optimism_syncStatus', []);
  const output = await rpc(ROLLUP_URL, 'optimism_outputAtBlock', [
    '0x' + blockNumber.toString(16)
  ]);

  const l2Ts = parseInt(l2Block.timestamp, 16);
  const l1Origin = output.blockRef ? output.blockRef.l1origin : null;
  const l1Number = l1Origin ? parseInt(l1Origin.number, 16) : null;
  const l1Ts = l1Origin ? parseInt(l1Origin.timestamp, 16) : null;

  console.log('L2 block number:', blockNumber);
  console.log('L2 block hash:', l2Block.hash);
  console.log('L2 timestamp:', l2Ts, new Date(l2Ts * 1000).toISOString());
  console.log('L1 origin number:', l1Number);
  console.log('L1 origin timestamp:', l1Ts, l1Ts ? new Date(l1Ts * 1000).toISOString() : null);
  if (l1Ts !== null) {
    console.log('L1-to-L2 inclusion latency (s):', l2Ts - l1Ts);
  }
  console.log('Unsafe L2 head:', sync.unsafe_l2 ? sync.unsafe_l2.number : null);
  console.log('Safe L2 head:', sync.safe_l2 ? sync.safe_l2.number : null);
  console.log('Finalized L2 head:', sync.finalized_l2 ? sync.finalized_l2.number : null);
}

main().catch((err) => { console.error(err); process.exit(1); });

Results Table: Measuring L1-to-L2 Inclusion Latency on Your Endpoint

Use the script above to sample several L2 blocks and record the results. The table below is intentionally empty; fill it with values measured against your own endpoint. Do not treat any single sample as a benchmark. Latency varies with L1 block time, batch submission cadence, and the block you choose.

For each row, record the L2 block number, the L2 timestamp, the L1 origin number, the L1 origin timestamp, and the difference in seconds. Also record whether the block was unsafe, safe, or finalized at the time of measurement. Repeat the measurement at different times to see how the values move.

  • L2 block number: the block you queried.
  • L2 timestamp: from eth_getBlockByNumber.
  • L1 origin number: from optimism_outputAtBlock blockRef.l1origin.number.
  • L1 origin timestamp: from optimism_outputAtBlock blockRef.l1origin.timestamp.
  • L1-to-L2 inclusion latency (s): L2 timestamp minus L1 origin timestamp.
  • Provenance state: unsafe, safe, or finalized at measurement time.

Why Timestamp-Only Indexing Breaks Across Reorgs

A reorg on L1 can change which L2 blocks are derived from which L1 block. If you index L2 data by timestamp alone, a reorg can silently remap your index: the same wall-clock second may correspond to a different L2 block after the reorg. Without an L1-origin anchor, you cannot tell whether a given L2 block is still canonical.

Anchoring to the L1 origin makes the mapping reproducible. When you store the L1 origin block number alongside the L2 block, you can re-check derivation after a reorg and detect that the L2 block's provenance changed. This is especially important for applications that reconcile deposits, withdrawals, or state proofs.

The Base OP-Stack finality: safe, finalized, and latest block tags article explains how safe and finalized tags relate to derivation, and the Checking Base OP-Stack node sync status article covers sync-status reading in more depth.

  • Timestamp-only index: same second can map to different L2 blocks after a reorg.
  • L1-origin anchor: lets you re-check derivation and detect provenance changes.
  • Critical for deposits, withdrawals, and state proofs that must reconcile with L1.

Unsafe, Safe, and Finalized Provenance: What Each Guarantees

Unsafe L2 blocks are produced by the sequencer and have not yet been derived from L1. They are fast but can be reorganized. Safe L2 blocks have been derived from L1 batches and are considered safe against L1 reorgs up to the derivation point. Finalized L2 blocks are derived from L1 blocks that are themselves finalized, giving the strongest guarantee.

optimism_syncStatus exposes all three heads with their L1 origins. When you read an L2 block, you should record which head it is at or below. A block at or below the finalized head has the strongest provenance; a block above the safe head but at or below the unsafe head is still subject to change.

For a deeper treatment of the tags, see the Base OP-Stack finality article. For sync-status mechanics, see the node sync status article.

  • Unsafe: sequencer-produced, not yet derived, can reorg.
  • Safe: derived from L1 batches, safe against L1 reorgs up to derivation point.
  • Finalized: derived from finalized L1 blocks, strongest guarantee.
  • Record the head level for every L2 block you index.

Limitations and Tradeoffs of L1-Origin Provenance

Reading L1 provenance over RPC adds calls and complexity. optimism_syncStatus and optimism_outputAtBlock are rollup-namespace methods; they may not be available on every endpoint, and they may be rate-limited differently from standard L2 methods. Verify availability with your provider before relying on them in production.

Provenance is also a moving target. A block that is unsafe now may become safe later, and a block that is safe now may become finalized later. If you cache provenance, you must re-check it. Sequencer restarts can also affect the unsafe head: after a restart, the sequencer may re-derive or re-produce blocks, and the unsafe head may move.

Finally, L1-to-L2 inclusion latency is not a fixed value. It depends on L1 block time, batch submission cadence, and the specific block. Do not treat any single measurement as a benchmark. Measure against your own endpoint and record the conditions.

  • Rollup namespace may be unavailable or rate-limited on some endpoints.
  • Provenance state changes over time; cached values must be re-checked.
  • Sequencer restarts can move the unsafe head.
  • Inclusion latency varies; measure, do not assume.

Troubleshooting Common Provenance RPC Failures

If optimism_syncStatus returns a method-not-found error, your endpoint likely does not expose the rollup namespace. Try a dedicated op-node URL or check your provider's documentation. OnFinality's Base RPC endpoints (RPC Assistant) page lists the available Base endpoints and their namespaces.

If optimism_outputAtBlock returns an error for a block number, the block may be above the current unsafe head, or the op-node may not have derived it yet. Retry after a short delay, or query a lower block number. If the L1 origin fields are missing, confirm you are reading the blockRef object correctly; field names are case-sensitive.

If your L2 block timestamp is earlier than the L1 origin timestamp, you are likely mixing up the two timestamps or reading a stale block. Re-read both values and compare. If the discrepancy persists, check whether your endpoint is serving a different chain or a cached response.

  • Method not found: rollup namespace not exposed; try a dedicated op-node URL.
  • Block not found: block may be above unsafe head or not yet derived; retry or lower the block.
  • Missing L1 origin fields: check blockRef field names and case.
  • Timestamp inversion: re-read both timestamps; check for stale or cached responses.

Operational Practices for Provenance-Aware Indexing

Store the L1 origin block number and L1 origin timestamp alongside every L2 block you index. This lets you re-check derivation after a reorg and detect provenance changes. It also lets you compute inclusion latency consistently across your dataset.

Poll optimism_syncStatus on a schedule and record the unsafe, safe, and finalized heads. When you read an L2 block, compare its number to those heads and record the provenance state. This gives you a reproducible way to classify blocks without relying on timestamps alone.

For historical data, use an archive endpoint. The Base archive node and historical RPC article explains archive access, and the OP-Stack L1 data fee and transaction cost article covers fee accounting that also depends on L1 data. For deposit and withdrawal reconciliation, see Tracking Base cross-chain deposit logs and withdrawal proofs.

  • Store L1 origin number and timestamp with every L2 block.
  • Poll optimism_syncStatus and record head levels.
  • Classify blocks by provenance state, not timestamp alone.
  • Use archive endpoints for historical provenance queries.

Next Steps: Finality, Sync Status, and Fee Context

To go deeper, read the Base OP-Stack finality article for the safe and finalized tags, and the node sync status article for sync-status mechanics. Together they explain how the heads you read in optimism_syncStatus relate to derivation and finality.

For cost and reconciliation work, see the OP-Stack L1 data fee article and the deposit and withdrawal proofs article. For historical queries, see the Base archive node article.

If you are choosing an endpoint, start from the Base network page and the Base RPC endpoints (RPC Assistant) page. For pricing and service details, see RPC pricing and the API service page. The OnFinality Learn hub collects the full set of guides.

  • Finality tags: safe, finalized, latest.
  • Sync status: unsafe, safe, finalized heads and L1 origins.
  • Fees: L1 data fee depends on L1 data availability.
  • Reconciliation: deposits and withdrawals need L1-origin anchors.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started