Skip to main content
Denim is a tentative draft published to elicit feedback. The design is mostly finalized, but this is not a production specification and may change. Denim is not active on Base Sepolia or Base Mainnet; activation times and required client versions remain undecided.

Summary

Denim changes Base block production from one canonical block every two seconds to five complete canonical blocks per second. Each 200ms block has its own block number, hash, state root, receipts, forkchoice updates, and unsafe, safe, and finalized lifecycle. Denim replaces Flashblocks, which publish incremental pending-state updates for a single block. As part of the Denim rollout, Base will stop producing Flashblocks and instead produce a canonical block every 200ms. Applications using Flashblocks must migrate to canonical block and RPC streams. Denim keeps the Ethereum block header and its seconds-based timestamp unchanged. A BaseTime metadata deposit supplies the sub-second component. Together, these values identify a canonical block’s full millisecond timestamp.

Tentative activation

No Denim activation has been scheduled. A later node upgrade guide will provide release and rollout instructions.

Execution

Full block timestamp

Denim leaves block.header.timestamp as Unix time in whole seconds. For an activated block b, its full timestamp is: Tms(b)=1000×b.header.timestamp+b.tx[1].timestamp_millis_partT_{ms}(b) = 1000 \times b.header.timestamp + b.tx[1].timestamp\_millis\_part The BaseTime deposit at tx[1] carries timestamp_millis_part. The only valid values are 0, 200, 400, 600, and 800. Consecutive activated blocks satisfy: Tms(child)=Tms(parent)+200T_{ms}(child) = T_{ms}(parent) + 200 Blocks cannot skip slots. The seconds header and millisecond part must come from the same scheduled timestamp; wall-clock time controls when the sequencer starts a build, not the timestamp assigned to that block. EVM block.timestamp remains the whole-second header value.

BaseTime metadata deposit

After activation, every block contains the canonical BaseTime update at tx[1], immediately after the L1 information deposit at tx[0] and before user transactions. The deposit is bound to the current block number by source-hash domain 3. Before activation, blocks must not contain this metadata transaction or the Engine millisecond field. After activation, implementations validate the transaction’s position, source hash, sender, recipient, mint, value, gas limit, system flag, calldata shape, and lattice value.

BaseTime predeploy

The initial design exposes the current block’s millisecond part to contracts through a predeploy. The BaseTime deposit executes before L1 user deposits, and user transactions, so all later transactions can read the updated value. Fresh chains install the linked BaseTime predeploy in genesis. On existing chains, the reserved address already contains the canonical proxy runtime and uses the Base ProxyAdmin, but its implementation slot is unset. Calls therefore revert until activation. At activation, before transaction execution, the protocol installs the canonical BaseTime implementation and links the existing proxy. It preserves the proxy admin and any implementation already set through governance.

Engine payload attributes

The Engine API uses BasePayloadAttributes, which flattens the standard PayloadAttributes fields alongside Base-specific fields. For payloads at or after Denim activation, BasePayloadAttributes.timestampMillisPart MUST be present and equal 0, 200, 400, 600, or 800. Before activation, it MUST NOT be present. BasePayloadAttributes.transactions[1] MUST contain the BaseTime metadata deposit. The deposit MUST be sent by the protocol depositor to the BaseTime predeploy, and its calldata MUST contain the canonical encoding of setTimestampMillisPart(uint16). The encoded value MUST equal timestampMillisPart. An execution client MUST reject malformed forkchoiceUpdated payload attributes with JSON-RPC Invalid params (-32602). It MUST report an execution payload with a missing or invalid BaseTime deposit as invalid during newPayload validation. The payload ID includes timestampMillisPart, so builds that differ only in the millisecond part receive different IDs. When the field is absent, legacy payload-ID calculation remains unchanged. All existing Engine timestamp fields remain seconds-based.

Validation

When processing forkchoiceUpdated, the execution client performs checks that do not require reading contract state. Before Denim, timestampMillisPart must be absent. After Denim, it must be present and equal 0, 200, 400, 600, or 800. The block’s whole-second timestamp must not be earlier than its parent’s. Because block headers do not store milliseconds, the client checks exact 200ms progression after execution. After execution, the client MUST verify that the block’s full timestamp is exactly 200ms after its parent’s. Blocks that do not satisfy this requirement are invalid.

Derivation

Scheduled timestamps

After activation, the derivation pipeline computes each block’s timestamp from the Denim activation schedule and absolute L2 block number. It does not read the millisecond part from batch data or derive it from the local wall clock. The sequence is: p (.000)child (.200)child (.400)child (.600)child (.800)child (.000 in the next second) The pipeline splits the scheduled timestamp into the seconds-based header timestamp and the BaseTime millisecond part, then reconstructs the BaseTime deposit at tx[1].

Block lifecycle

The sequencer builds and executes a complete block for every selected 200ms slot. Each block receives its own hash, state root, receipts, Engine payload, forkchoice update, and unsafe-to-safe-to-finalized lifecycle. The block begins with the L1 information deposit at tx[0] and the BaseTime metadata deposit at tx[1]. User transactions and other applicable transactions follow. The design intends the payload attribute, tx[1], and the value written to the BaseTime predeploy to represent the same planned millisecond part.

RPC

The RPC behavior below is planned for Denim and is not available on production endpoints.

Seconds compatibility

Denim keeps the existing timestamp JSON-RPC field in Unix seconds. Engine timestamps, transaction-validity timestamps, eth_call timestamps, and EVM block.timestamp also remain seconds-based. RPC responses do not expose the internal timestampMillisPart field. After the Denim rollout, use canonical block responses and subscriptions such as eth_subscribe("newHeads") for sub-second updates. Flashblocks streams will stop.

Block and header timestamps

The following responses will add optional timestampMs, encoded as a JSON-RPC quantity containing the full Unix timestamp in milliseconds:
  • eth_getBlockByHash
  • eth_getBlockByNumber
  • eth_getHeaderByHash
  • eth_getHeaderByNumber
  • eth_subscribe("newHeads")
For example, a block at 42.200 seconds has: Clients will derive timestampMs from authenticated BaseTime metadata rather than estimate it from the seconds field. If a historical block body or its BaseTime metadata has been pruned or is otherwise unavailable, the response will omit timestampMs.

Transaction timestamps

Mined transaction objects will add optional blockTimestampMs for these methods:
  • eth_getTransactionByHash
  • eth_getTransactionByBlockHashAndIndex
  • eth_getTransactionByBlockNumberAndIndex
Full transaction objects nested in block responses will follow the same mined-transaction behavior. Pending transactions will omit blockTimestampMs because they do not yet belong to a canonical block.

Log and receipt timestamps

Mined log objects will add optional blockTimestampMs when returned by:
  • eth_getLogs
  • eth_getFilterChanges
  • eth_getFilterLogs
  • eth_getTransactionReceipt
  • eth_getBlockReceipts
  • eth_subscribe("logs")
  • eth_subscribe("transactionReceipts")
Receipts will expose the field on their nested logs, not at the receipt’s top level. A removed log will retain its original block timestamp along with its original block provenance. If the originating block’s authenticated BaseTime metadata is unavailable, the log will omit the field rather than estimate it. As with block responses, pruned or unprovenanced transaction and log data will omit blockTimestampMs. The fields are optional so pre-Denim history and clients without the required body data remain representable.

Tooling

Foundry’s AnyRpcBlock and OtherFields paths can preserve an unknown block-level timestampMs, while EVM execution continues to use seconds. Plain Alloy AnyRpcHeader drops unknown fields; consumers that need Denim timestamps can use WithOtherFields<AnyRpcHeader> or a typed Base response. Locally mined Anvil blocks are not expected to produce BaseTime metadata in the initial rollout.