Chain State Derivation
How does a transaction sent to Ethereum become part of the Facet chain? This document explains both the high-level process and detailed technical implementation. For a one-page diagram, see the Architecture Overview.
Overview
When you send a Facet transaction to Ethereum's 0xface7
address, Facet nodes automatically detect it and include it in the Facet block for the corresponding 12-second slot. Facet produces blocks every 12 seconds regardless of whether Ethereum produced an L1 block in that slot. If L1 produced a block, the slot’s Facet block includes all 0xface7
inbox transactions from that L1 block in exact order. If L1 skipped the slot (no L1 block), Facet still produces a filler block with no inbox transactions. This is a deterministic process—no sequencer or admin can interfere.

The system uses two components: facet-node
monitors L1 and builds blocks, while facet-geth
executes transactions and maintains state. This architecture ensures that anyone running these components will derive exactly the same chain state from the same Ethereum data.
Transaction Processing Pipeline
1. L1 Block Monitoring
facet-node
monitors L1 blocks using these RPC calls:
eth_getBlockByNumber
: Retrieves transaction calldataeth_getBlockReceipts
: Retrieves logs and transaction status
2. Facet Transaction Detection
Valid Facet transactions are identified by:
Calldata transactions:
to
address equalsaddress(0xface7)
with receipt status = 1Event log transactions: Exactly one topic equal to
bytes32(uint256(0xface7))
with payload in data field
One Transaction Per Ethereum Transaction Rule:
Each Ethereum transaction contributes at most ONE Facet transaction to the L2 block
If multiple valid Facet payloads exist (e.g., calldata + event, or multiple events), only the first is processed
Processing order: Calldata is checked first, then events in log index order
Additional candidates are silently ignored (not an error condition)
3. Transaction Decoding
Facet transactions must decode to this structure:
{
chain_id: uint256
to: address
value: uint256
gas_limit: uint256
data: bytes
mine_boost: uint256
}
Deposit Transaction Construction
Type 0x7D Transactions
Facet executes Facet payloads as deposit transactions of type 0x7D
(like Optimism’s 0x7E
deposit tx, with Facet-specific fields/semantics). The effective L2 transaction has the following data model:
// Pseudocode: DepositTx
SourceHash: bytes32 // The tx hash of the L1 transaction that originated the Facet payload
From: address // For calldata-originated payloads, this is the L1 EOA that signed the envelope; for log-originated payloads, this is the emitting contract address after aliasing (see below)
To: address? // Destination address; `nil` for contract creation
Mint: uint256? // FCT minted on L2 and locked on L1; nil if none
Value: uint256 // FCT transferred from L2 balance (applied after Mint)
Gas: uint64 // execution gas limit
IsSystemTransaction: bool // Legacy field, always `false`
Data: bytes // calldata
Transaction hash computation:
The typed-transaction hash uses type
0x7D
and standard serialization of the above fields.Important: The
Mint
field is excluded from the hash by treating it as zero/nil for hashing purposes. In other words, changes toMint
do not affect the transaction hash.
Address Aliasing
For log-originated transactions, the from
address is aliased:
aliased_address = original_address + 0x1111000000000000000000000000000000001111
This prevents contracts from impersonating EOAs.
Block Construction
L1 Attributes Transaction
Every Facet block begins with an L1 attributes transaction containing:
L1 block number
L1 block timestamp
L1 block hash
Sequence number
Block Timing Rules
Facet block times are fixed at 12-second intervals
When an L1 block exists for a slot, its inbox transactions appear in that slot’s Facet block
When a slot has no L1 block, Facet produces a filler block for that slot (no inbox transactions)
Engine API Communication
Blocks are sent to facet-geth using:
engine_forkchoiceUpdatedV2
engine_newPayloadV2
engine_getPayloadV2
Gas Mechanics
Sequential Gas Buying
Unlike standard EVM chains:
Each transaction attempts to buy gas sequentially
If insufficient gas remains in block, transaction fails
Next transaction attempts to buy gas
Block gas limit is not pre-validated
FCT Minting Rules
For contract-initiated transactions:
Contract cannot increase its own FCT balance
Excess minted FCT transfers to L1 transaction origin
Prevents contracts from self-funding
Base Fee Only
No priority fee mechanism
All transactions pay current base fee
Simplifies gas pricing model
State Transition Differences
Invalid Block Handling
facet-geth modifications ensure:
Invalid blocks cannot be submitted by facet-node
Pre-check errors affect individual transactions, not entire block
Chain continues processing subsequent transactions
Gas Limit Validation
Standard EVM: sum(tx.gas_limit) <= block.gas_limit
Facet: Each transaction validated independently during execution
Data Structures
Source Hash Computation
source_hash = l1_tx_hash
L1 Info Transaction Format
{
type: 0x7D,
to: L1_INFO_PRECOMPILE,
data: abi.encode(
block_number,
block_timestamp,
base_fee,
block_hash,
sequence_number
...fct_fields
)
}
Validation Process
Deterministic Derivation
Chain state can be validated by:
Fetching L1 data from any Ethereum RPC
Running facet-node derivation logic
Comparing resulting state roots
Required L1 Data
Block headers (number, timestamp, hash)
Transaction receipts (status, logs)
Transaction calldata
Consensus Rules
All nodes following these rules will:
Derive identical state from same L1 data
Produce identical block sequences
Calculate identical state roots
Implementation References
facet-node: Consensus client implementation
facet-geth: Execution client (op-geth fork)
Optimism Specs: Deposit transaction specification
Last updated