# Overview

## What is an Unstoppable Rollup?

An **Unstoppable Rollup** is a rollup that cannot be interrupted without compromising the L1. No exit windows, no security councils, no admin keys.

Ethereum rollups secure tens of billions in user assets, yet almost all can be halted or censored by a handful of admin keys. Even the most "decentralized" rollups today can be stopped by fewer than 20 people.

Facet is the only rollup that cannot be stopped. If Ethereum is running, Facet continues running. Period.

### The Problem with Traditional Rollups

Today's rollups have a fundamental vulnerability—they can be stopped by a small group of people:

* **Disable the Sequencer** - A single multisig transaction can halt block production
* **Overwhelm Forced Inclusion** - Gas limits drop 99%, creating functional stoppage
* **Disable Forced Inclusion Entirely** - Contract upgrades can remove censorship resistance

These aren't theoretical. In 2024 alone, Linea halted block production, Blast blocked force-inclusion, and Soneium censored specific tokens.

## The Facet Solution

Facet eliminates all forms of admin control through four fundamental architectural decisions:

1. **Immutable Sequencing** - Transactions go directly to an immutable L1 address
2. **Native Gas Token** - FCT is minted algorithmically, no bridge admins
3. **ZK Fault Proofs** - Immutable proof system with no overrides
4. **Fork-Based Upgrades** - Changes only through user choice

### Fork-Based Upgrades: True User Sovereignty

Traditional rollups force upgrades on users—15 signatures change the protocol, and everyone must accept it or exit. Facet upgrades work like Ethereum L1: through forks and social consensus.

**How It Works:**

1. Deploy new system with updated rules
2. Users decide which version to follow
3. Old version continues running forever

No one can force you to upgrade. Ever.

Just like Ethereum Classic still exists alongside Ethereum, multiple Facet versions can coexist based on user choice.

## Why This Matters

**For Users:**

* Your assets are safe—no admin can freeze withdrawals
* Guaranteed inclusion—if Ethereum runs, your transaction goes through
* True ownership—no one can take away your ability to transact

**For Developers:**

* Build without permission—deploy whatever you want
* No admin approval needed
* Design with version flexibility for true decentralization

**For Ethereum:**

* A rollup that actually inherits L1's security properties
* No compromise on decentralization
* Proof that unstoppable rollups are possible

## Learn More

For a comprehensive technical deep dive, read the [Facet Whitepaper](https://facet.org/whitepaper).

For a visual overview of how Facet components fit together, see the [Architecture Overview](/introduction/architecture-overview).

## Key Repositories

* facet-node: <https://github.com/0xFacet/facet-node> — Consensus/derivation client
* facet-geth: <https://github.com/0xFacet/facet-geth> — Execution client
* zk-fault-proofs: <https://github.com/0xFacet/zk-fault-proofs> — Rollup.sol + proposer/challenger
* facet-kona: <https://github.com/0xFacet/facet-kona> — Rust STF/derivation
* facet-sdk: <https://github.com/0xFacet/facet-sdk> — TypeScript SDK
* facet-sol: <https://github.com/0xFacet/facet-sol> — Foundry helpers
* FIPs: <https://github.com/0xFacet/FIPs> — Governance proposals
* FCT metrics: <https://fct.fyi/> (repo: <https://github.com/0xFacet/fct-fyi>)

Full list and descriptions: [Important Repositories](/reference/important-repositories).


# Architecture Overview

Below is a simplified picture of how Facet components fit together, followed by a more detailed Mermaid diagram.

The simplified flow:

* L1: Users submit Facet payloads to inbox EOA `0xface7` (see Sending Transactions). `Rollup.sol` (ZK Fault Proofs) lives on L1.
* L2: `facet-node` derives Facet blocks from L1 (see Chain State Derivation) and sends them to facet-geth which holds state.
* Proposer/Challenger: read from `facet-geth` and interact with `Rollup.sol` (with ZK proofs) (see Proposing and Challenging).
* Bridges/Apps on L1: read canonical roots from `Rollup.sol` to verify withdrawals (see Building Bridges).

{% @mermaid/diagram content="flowchart TB
subgraph L2 \[Facet L2]
FNode\[Facet Node]
FGeth\[Facet Geth]
FNode -->|Facet blocks| FGeth
end

subgraph L1 \[Ethereum L1]
Inbox\[Inbox 0xface7]
Rollup\[Rollup.sol]
end

U\[Users / Apps]
P\[Proposer]
C\[Challenger]
B\[Bridges / L1 Apps]

%% Core flow
Inbox -->|inbox txs| FNode
U -->|read state| FGeth

%% Proof system
FGeth --> P
FGeth --> C
P -->|propose roots| Rollup
C -->|challenge / prove| Rollup

%% Bridge reads
Rollup -->|canonical roots| B" %}

See also

* Immutable Sequencing → [Sending Transactions](/immutable-sequencing/sending-transactions)
* Reference → [Chain State Derivation](/reference/chain-state-derivation)
* Proof System & Bridging → [ZK Fault Proofs](/proof-system-and-bridging/zk-fault-proofs)
* Proof System & Bridging → [Building Bridges](/proof-system-and-bridging/building-bridges)


# Comparison with Other Rollups

## The Fundamental Difference: Stoppability

All rollups today except Facet can be stopped by a small group of people:

| Rollup                                                           | Who Can Stop It      |
| ---------------------------------------------------------------- | -------------------- |
| **Optimism, Arbitrum, Base, zkSync, Polygon zkEVM, Taiko, etc.** | <20 multisig signers |
| **Facet**                                                        | No one               |

This single fact drives every other architectural decision.

## Critical Control Points

| Component             | Traditional Rollups             | Facet                             |
| --------------------- | ------------------------------- | --------------------------------- |
| **Can be halted by**  | <20 keys                        | No one                            |
| **Forced inclusion**  | Can be disabled                 | Always on                         |
| **Gas token**         | Admin-controlled bridge         | Native & permissionless           |
| **Protocol control**  | Smart contracts with admin keys | No contracts control the protocol |
| **Emergency powers**  | Guardians can intervene         | No emergency mechanism exists     |
| **Protocol upgrades** | Forced by admins                | User choice via forks             |

Traditional rollups have smart contracts that control the protocol. These contracts have admin keys. Facet has no controlling contracts—just an immutable L1 address.

## Proof Systems: Best of Both Worlds

Traditional rollups chose one extreme. Facet combines the best of both worlds.

| Approach                    | How It Works                         | Weaknesses                                                   |
| --------------------------- | ------------------------------------ | ------------------------------------------------------------ |
| **Optimistic (Arbitrum)**   | Assume honesty, 7-day disputes       | Slow resolution, requires watchers, complex escalating bonds |
| **Validity/ZK (zkSync)**    | Prove every transaction              | Expensive even when everyone's honest                        |
| **ZK Fault Proofs (Facet)** | Optimistic normally, ZK for disputes | None—combines both strengths                                 |

Facet's approach:

* **Normal operation:** No proofs needed (like optimistic)
* **Disputes:** Single ZK proof resolves instantly (like validity)
* **Result:** Free when honest, fast when disputed

## Time to Finality

Facet is a based rollup.

| Stage                        | Traditional Non-Based Rollups      | Facet                               |
| ---------------------------- | ---------------------------------- | ----------------------------------- |
| **User submits transaction** | Goes to centralized sequencer      | Goes to Ethereum (`0xface7`)        |
| **Soft confirmation**        | \~2 seconds from sequencer         | N/A - no sequencer                  |
| **Posted to L1**             | Minutes to hours later\*           | Immediate (it IS an L1 transaction) |
| **Actually final**           | When L1 tx with batch is finalized | \~15 minutes (Ethereum finality)    |

\*Sequencers batch transactions to save costs, creating delays

## Real Examples of Rollups Being Stopped

* **Linea:** Stopped chain to freeze hacker funds
* **Blast:** Disabled forced inclusion to censor addresses
* **Soneium:** Blocked memecoin trading

## The Exit Window Myth

"Stage 2" rollups claim safety through exit windows, but three problems make them ineffective:

**1. Prohibitive exit costs**

* Mass exit from any major L2 would cost hundreds of millions in L1 gas
* Attackers can trigger exits cheaply while users pay the price

**2. Locked assets can't exit**

* Vesting contracts, staking pools, and DeFi positions have their own timeframes
* A 30-day exit window doesn't help if your assets are locked for 90 days

**3. L2-native assets have nowhere to go**

* Tokens created on L2 have no L1 representation
* Complex financial positions can't be "moved" to L1

Result: Exit windows create a false sense of security while leaving most users vulnerable.

## Summary

Traditional rollups optimize for the happy path—when sequencers are honest, admins are benevolent, and no one needs to exit.

Facet optimizes for reality—where companies get subpoenaed, multisigs get compromised, and infrastructure must work without trust.

The choice is simple: Accept admin control or build on truly unstoppable infrastructure.


# Fork-Based Upgrades

## The Problem

Traditional rollups can force upgrades on users:

* 15 signatures change the protocol for everyone
* Users must accept or exit within a time window
* Exit costs can reach hundreds of millions in gas fees
* Many can't exit (locked DeFi positions, L2-native assets)

## The Solution: Opt-In Changes

Facet makes all upgrades voluntary. No one can change the rules without your consent.

**How it works:**

1. Deploy new node software with updated rules
2. Deploy a new instance of the `Rollup.sol` contract with different parameters (see below)
3. That's it—you have a new version of Facet

Users, applications, and bridges choose which version to use. Old versions keeps running forever.

## The Trade-Off

Without central coordination, you risk:

* **More versions** - Different groups might use different forks
* **Fragmentation risk** - Liquidity and users spread across versions
* **No unified roadmap** - Evolution through competition, not committee

This is the price of true decentralization. When admins have the most power, at least everyone uses the same version.

## Summary

Traditional rollups: "Upgrade or leave" Facet: "Stay or upgrade—your choice"

No forced changes. No exit deadlines. No permission needed.

This is how Bitcoin works. This is how Ethereum works. And this is how truly unstoppable systems must work—through individual choice, not collective force.


# Introduction

## What is Immutable Sequencing?

Immutable sequencing means Facet's transaction ordering is determined entirely by Ethereum L1, with no intermediaries. Users send transactions to an immutable address, and Facet blocks mirror the exact order that Ethereum includes them.

## How Sequencing Works

### 1. User Submits Transaction

Users send Ethereum transactions containing Facet transaction data to:

```
0x00000000000000000000000000000000000face7
```

### 2. L1 Inclusion

Ethereum validators include these transactions in L1 blocks, just like any other Ethereum transaction. The transaction can face the same censorship risks as any L1 transaction, but benefits from Ethereum's censorship resistance.

### 3. Facet Block Construction

Facet nodes:

* Monitor every Ethereum block
* Extract all transactions sent to `0xface7`
* Construct Facet blocks using the **exact same order** as they appear in the L1 block
* Produce Facet blocks on a fixed 12s cadence. When an Ethereum block exists for a slot, that slot’s Facet block includes those inbox transactions. When Ethereum skips a slot (no L1 block), Facet still produces a filler block for that slot with no inbox transactions.

### 4. Guaranteed Inclusion

If your transaction makes it into Ethereum block 20,000,000, it **will** appear in the Facet block for that same 12s slot (the block whose L1 attributes reference that L1 block). No sequencer or admin can change this ordering.

## Transaction Ordering Rules

**Within each Facet block:**

1. Transactions are ordered exactly as they appear in the corresponding L1 block
2. If L1 block has transactions at positions 42, 87, and 203 going to `0xface7`, they appear in that exact order in the Facet block
3. No reordering, no prioritization, no MEV extraction by sequencers

**Key Properties:**

* **Deterministic**: Given an L1 block, anyone can derive the exact Facet block that corresponds to its slot; additional filler blocks may exist for slots without an L1 block
* **Immutable**: The inbox address is an EOA, not a contract—no upgrade mechanism exists
* **Permissionless**: Anyone who can get a transaction into L1 can get a transaction into Facet

## Why EOA Instead of Smart Contract?

Traditional rollups use smart contracts for their inbox, which enables:

* Disabling forced inclusion via upgrade
* Changing gas metering parameters
* Adding censorship logic

Facet uses `0x00..00face7`, an EOA with no known private key:

* Cannot be upgraded
* Cannot implement logic
* Cannot discriminate between transactions
* Will accept transactions forever

## Comparison

| Aspect                    | Traditional Rollups          | Facet                                           |
| ------------------------- | ---------------------------- | ----------------------------------------------- |
| **Transaction ordering**  | Sequencer decides            | L1 block order                                  |
| **Inbox type**            | Smart contract (upgradeable) | EOA (immutable)                                 |
| **MEV extraction**        | Sequencer captures value     | Goes to L1 validators                           |
| **Censorship resistance** | Depends on sequencer/admins  | Same as Ethereum                                |
| **Block time**            | 1-2 seconds                  | 12 seconds (slots; filler blocks when L1 skips) |

## Trade-offs

By inheriting L1's sequencing:

* **Slower blocks**: 12 seconds instead of 2
* **Higher submission costs**: Full L1 transaction fees
* **No soft confirmations**: Must wait for L1 inclusion

In exchange:

* **Truly unstoppable**: If Ethereum lives, Facet lives
* **No sequencer MEV**: Value flows to L1 validators
* **Perfect L1 alignment**: State transitions match L1 blocks


# Sending Transactions

A unique advantage of Facet is that users post Facet transactions directly on Ethereum, so you do not need to "bridge in" value before using Facet, and you do not have to change your wallet's connection away from Ethereum.

<figure><img src="/files/v41jiz89bLzBoQRsEM8q" alt=""><figcaption><p>Metamask's Network Selector</p></figcaption></figure>

## Basic Transactions

Applications (Apps) on Facet abstract the complexity involved with operating on the rollup by constructing regular Ethereum "envelope" transactions for users to sign. Here's the process:

{% stepper %}
{% step %}
**User Connects to an App**

User connects their preferred Ethereum wallet to a Facet App in the same way they would with any Ethereum App.
{% endstep %}

{% step %}
**App Constructs a Transaction**

When the user initiates a transaction (e.g. a swap on a DEX), the App creates a regular Ethereum transaction for the user to sign. This transaction is configured to send "TO" the Facet inbox address:

```
0x00000000000000000000000000000000000face7
```

{% endstep %}

{% step %}
**App Encodes the Facet Payload**

The App encodes the user's Facet intent as a "payload" within the transaction's calldata (e.g., swap *X* amount of wETH for wUSDC). The Dapp presents this transaction for the user to review/sign.

<img src="/files/2ZdlnHHnlOVJ9VUWtUys" alt="" data-size="original">

{% hint style="info" %}
**Note:** The plain English payload shown above is a simplified representation. The actual payload will be encoded in hexadecimal format, which is necessary for it to be processed on the blockchain. Learn more in [Facet Transaction Structure](/immutable-sequencing/transaction-spec).
{% endhint %}
{% endstep %}

{% step %}
**User Signs the Transaction**

The transaction is approved/signed by the user via the wallet interface, the same user experience as an Ethereum App. Since this transaction is sent on Ethereum, the user pays gas using L1 ether. Fees are lower than calling L1 contracts, but remain sensitive to L1 congestion and the size of the calldata payload sent to the Facet inbox address.
{% endstep %}

{% step %}
**Transaction Execution**

Upon signing, the transaction is sent to the Ethereum network. Once the transaction is secured in an L1 block through Ethereum's native consensus, the Facet payload is processed by Facet nodes, updating Facet state accordingly. The L1 envelope transaction must set ETH `value = 0`; the Facet payload may specify an L2 `value` (in FCT) that transfers along with the call on Facet.
{% endstep %}

{% step %}
**Apps Read State**

Apps (in this case, a DEX) query the latest state from a Facet node, reflecting the user's reduced wETH balance and increased wUSDC balance.
{% endstep %}
{% endstepper %}


# Facet Transaction Spec

## Overview

Facet transactions are the primary mechanism for interacting with the Facet protocol. They follow a format similar to EIP-1559 Ethereum transactions and Optimism's Deposit transactions, with specific adaptations for the Facet ecosystem.

## Transaction Format

Facet transactions use the EIP-2718 transaction type `0x46` (70 in base 10) and contain the following fields:

| Field        | Description                                                                                                                                                                                                  |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `chain_id`   | <p>Distinguishes the payload as being relevant only to Facet<br>• Mainnet: <code>1027303</code> (<code>0xface7</code> in hex)<br>• Sepolia testnet: <code>16436858</code> (<code>0xface7a</code> in hex)</p> |
| `to`         | L2 recipient's address (EOA or L2 contract). Blank for contract creations                                                                                                                                    |
| `value`      | Amount of FCT being transferred in the transaction                                                                                                                                                           |
| `gas_limit`  | The gas limit of the L2 transaction                                                                                                                                                                          |
| `data`       | EVM input data (ABI encoded contract call or contract creation code)                                                                                                                                         |
| `mine_boost` | Optional appended data that increases payload size to increase FCT mining (see below)                                                                                                                        |

**Important**: Facet transactions have no signature field. The "from" address is inferred from the properties of the Ethereum transaction in which they are delivered.

### Serialization Format

Facet transactions must be serialized in this specific format for Facet nodes to process them:

```
0x46 ++ rlp_encode([chain_id, to, value, gas_limit, data, mine_boost])
```

## Fee Model

Facet uses a base-fee-only pricing model on L2. There is no per-transaction priority fee and no `max_fee_per_gas` field in Facet transactions. All transactions pay the current L2 base fee computed by the protocol.

## Execution on L2 (Deposit Type 0x7D)

Facet payloads are executed on L2 as deposit transactions of type `0x7D` (125 decimal). The execution-layer transaction includes fields like `sourceHash`, `from`, optional `mint`, `value` (FCT), `gas`, `isSystemTransaction`, and `data`. For hashing, the typed-transaction hash treats `mint` as zero/nil so the mint amount does not affect the transaction hash. See Reference → Chain State Derivation for full field definitions and hashing rules.

## Transaction Submission Methods

There are two methods to submit Facet transactions to the network:

### Method 1: From EOA via Calldata

EOAs (Externally Owned Accounts) can create Facet transactions by sending a successful Ethereum transaction (receipt status = 1) to the Facet inbox address:

```
0x00000000000000000000000000000000000face7
```

This L1 transaction acts as an "envelope" that carries the RLP-encoded Facet transaction as a payload within its calldata. The signer of the Ethereum envelope transaction determines the "from" address on the Facet transaction.

Note on ETH value: The L1 envelope transaction to the inbox address must set `value = 0`. Never send ETH to `0xface7`. Any L2 transfer amount should be expressed via the Facet payload’s `value` field, which is denominated in FCT and applied on L2 during execution.

#### Implementation Example (TypeScript/Viem)

```typescript
const transactionData = [
  toHex(l2ChainId),
  to ?? "0x",
  value ? toHex(value) : "0x",
  gasLimit ? toHex(gasLimit) : "0x",
  data ?? "0x",
  mineBoost ?? "0x"
];

const encodedTransaction = concatHex([toHex(70), toRlp(transactionData)]);

const l1Transaction = {
  account: l1WalletClient.account,
  to: "0x00000000000000000000000000000000000face7",
  value: 0n,
  data: encodedTransaction,
  chain: l1WalletClient.chain,
};

await l1WalletClient.sendTransaction(l1Transaction);
```

### Method 2: From L1 Smart Contract via Event Logs

L1 Smart Contracts can create Facet transactions by emitting events with the following specification:

#### Event Format Requirements

**Topic Requirements:**

* **Exactly one topic**: The event MUST have exactly one topic
* **Topic value**: `0x00000000000000000000000000000000000000000000000000000000000face7`. This is `bytes32(uint256(0xface7))` - the number `0xface7` cast to uint256, then to bytes32.

**Data Requirements:**

* **Payload location**: The entire Facet transaction payload MUST be in the event's `data` field
* **Payload format**: Same RLP-encoded transaction format as calldata method (`0x46` + RLP)
* **No indexed parameters**: The event MUST NOT have any indexed parameters beyond the single topic

**One Transaction Per Ethereum Transaction Rule:**

* **Single Facet transaction**: Each Ethereum transaction can contain at most ONE valid Facet transaction
* **Multiple candidates**: If an Ethereum transaction contains multiple events with the Facet signature, only the first valid Facet transaction is processed
* **Processing order**: Calldata is checked before events. Within events, they are processed in log index order
* **Failure semantics**: Subsequent Facet transaction candidates in the same Ethereum transaction are silently ignored, not treated as errors

#### Implementation Example (Solidity)

```solidity
import { LibRLP } from "lib/solady/src/utils/LibRLP.sol";

contract FacetSender {
    using LibRLP for LibRLP.List;

    bytes32 constant facetEventSignature = 0x00000000000000000000000000000000000000000000000000000000000face7;
    uint8 constant facetTxType = 0x46;

    function sendFacetTransaction(
        uint256 chainId,
        bytes memory to,
        uint256 value,
        uint256 gasLimit,
        bytes memory data,
        bytes memory mineBoost
    ) internal {
        LibRLP.List memory list;

        list.p(chainId);
        list.p(to);
        list.p(value);
        list.p(gasLimit);
        list.p(data);
        list.p(mineBoost);

        bytes memory payload = abi.encodePacked(facetTxType, list.encode());
        
        assembly {
            log1(add(payload, 32), mload(payload), facetEventSignature)
        }
    }
}
```

## Key Differences Between Methods

| Aspect          | EOA (Calldata)                        | Smart Contract (Event Log)                                                                                                                   |
| --------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Sender Type     | Externally Owned Account              | L1 Smart Contract                                                                                                                            |
| Delivery Method | Transaction calldata to inbox address | Event emission with Facet signature                                                                                                          |
| From Address    | EOA that signed the L1 transaction    | The [aliased address](https://docs.facet.org/reference/chain-state-derivation#address-aliasing) of the smart contract that emitted the event |
| Use Case        | Direct user interactions              | Programmatic/bridged transactions                                                                                                            |

## Best Practices

1. **Chain ID Verification**: Always ensure you're using the correct chain ID for your target network (mainnet vs testnet)
2. **Gas Estimation**: Properly estimate gas limits to ensure transaction success
3. **Error Handling**: Verify that L1 transactions have a successful receipt status before considering the Facet transaction submitted
4. **Mine Boost**: Use the optional `mine_boost` field strategically to increase FCT mining rewards when needed

## Security Considerations

* The Facet inbox address (`0x00000000000000000000000000000000000face7`) has no known private key, ensuring it cannot be controlled by any party
* Always validate transaction parameters before submission
* Monitor L1 transaction success to ensure proper Facet transaction delivery


# Basic Transaction Flow

This guide explains the technical flow of Facet transactions. For conceptual understanding of immutable sequencing, see [Immutable Sequencing](/immutable-sequencing/introduction).

## Transaction Lifecycle

<figure><img src="/files/kiHePexbMIuW7kvZF1IY" alt=""><figcaption></figcaption></figure>

### 1. Transaction Creation

Users create Facet transactions by:

* Constructing an RLP-encoded Facet transaction
* Wrapping it in an Ethereum L1 transaction
* Sending to the Facet inbox address: `0x00000000000000000000000000000000000face7`

See [Facet Transactions](/immutable-sequencing/transaction-spec) for detailed formatting.

### 2. L1 Submission

The Ethereum transaction containing the Facet payload:

* Gets included in the L1 mempool
* Is sequenced by Ethereum validators
* Becomes part of L1 block history

### 3. Node Processing

Facet nodes:

* Monitor L1 for transactions to the inbox address
* Extract Facet payloads from calldata
* Also monitor for contract-emitted events with Facet topic

### 4. State Computation

Using the ordered transactions from L1:

* Facet nodes execute transactions through facet-geth
* State is computed deterministically
* Results are stored locally by each node

### 5. State Queries

Applications and users:

* Connect to Facet RPC endpoints
* Query current state using standard Ethereum RPC methods
* Receive responses from facet-geth

## Technical Implementation

### Creating Transactions

* [Transaction Specification](/immutable-sequencing/transaction-spec) - Transaction formats and encoding

### Running Infrastructure

* [Running a Facet Node](/guides/running-a-facet-node) - Node setup guide
* [Chain State Derivation](/reference/chain-state-derivation) - How state is computed

### Integration Points

* [Network Information](/reference/network-information) - Query endpoints
* [Facet TypeScript SDK](/developer-tools/facet-typescript-sdk) - Developer tools


# Introduction

## What is FCT?

FCT (Facet Token) is Facet Protocol's native gas token, an essential component of the protocol's architecture that ensures the network remains truly unstoppable and permissionless. Unlike traditional rollups that rely on bridged ETH for gas payments, Facet uses its own native token that is algorithmically minted when users burn ETH on Ethereum L1.

## The Problem with Canonical Bridges

Traditional rollups have "canonical bridges" - official protocol bridges that users MUST trust. Why? Because typical rollups use bridged ETH as their gas token they need to provide a bridge so users can buy gas.

But typical canonical bridges are fundamentally insecure. Users expect to deposit and withdraw assets at any time in the future, regardless of what forks happen on the rollup. Therefore, these bridges are typically upgradeable to track the latest fork. This upgrade ability creates a security issue where rollup admins can:

* Devalue the gas token by printing an unlimited amount
* Prevent deposits
* Otherwise tamper with user funds

Facet has no canonical bridge because we don't use a bridged token as our gas token. Instead, we use the **Facet Compute Token (FCT)**, a Facet-native token.

## How FCT Works

Facet eliminates these vulnerabilities through a simple mechanism:

1. **Burn ETH on L1** - Users send Facet transactions to Ethereum, paying standard gas fees
2. **Mint FCT on L2** - FCT is algorithmically minted proportional to the ETH burned for transaction data
3. **Use FCT for Gas** - The minted FCT pays for Facet computation, with remaining balance available for future use

This process requires no bridge and no admin intervention - it's purely algorithmic and unstoppable.

## Key Benefits

### True Permissionlessness

If you can send an Ethereum transaction, you can get FCT. No gatekeepers, no permission required.

### No Admin Control

There are no special privileges, no pre-mine, and no ability for anyone to manipulate the gas token supply.

### Unstoppable Access

Since FCT minting is tied directly to L1 gas burning, no one can prevent users from obtaining gas tokens for the network.

### Fair Launch

Everyone starts at zero. The only way to get FCT is to use the network, ensuring fair distribution based on actual usage.

### Deterministic Supply

FCT has a predictable, capped total supply determined algorithmically - no arbitrary minting or inflation surprises.

## Bridges Still Exist - Just No Privileged One

Removing the canonical bridge doesn't mean Facet has no bridges. Instead, it enables an open bridge market where:

* Anyone can deploy a bridge for any asset
* Multiple bridges can serve the same assets
* No bridge has special protocol privileges
* Users choose bridges based on security preferences

The same L1 asset might have multiple L2 versions from different bridges (e.g., "USDC-A" and "USDC-B"), similar to how Ethereum has USDC, USDT, and DAI as different stablecoin representations.

Without a canonical bridge forcing users through one chokepoint, the market decides which bridges succeed based on security, reliability, and user trust.

***

By using a native gas token instead of bridged ETH, Facet ensures that no administrator can control access to the network. This is the foundation of Facet's unstoppability - removing the most critical admin control point that exists in every other rollup.


# FCT Issuance Formula

This section provides a detailed explanation of the formula that governs the issuance of FCT, intended for technical readers who want to understand issuance on a block-by-block basis.

To see the current mint rate, issuance progress, and other real-time FCT metrics, visit [fct.fyi](https://fct.fyi/).

## Overview

FCT issuance is designed to provide:

* **Predictable ownership** through a deterministic total supply cap
* **Stable issuance** via dynamic rate adjustments every 500 blocks
* **Protection from gas spikes** by minting based on ETH burned, not gas units

## Basic minting formula

FCT is minted proportionally to the ETH burned for transaction calldata:

$$
\text{FCT minted} = \text{ETH burned for data} \times \text{mint rate}
$$

Where:

* **ETH burned for data** = Data Gas × Block Base Fee (Data Gas is defined in the [Calldata gas calculation](#calldata-gas-calculation) section)
* **Mint rate** = Dynamically adjusted rate (FCT-wei per ETH-wei)

The *mint rate* is denominated in **FCT-wei per ETH-wei burned**.

**Important**: Data Gas only includes the gas for the transaction's calldata/event data - not the total gas used by the transaction.

## Total supply cap

The total supply of FCT is:

* **1,646,951,661 FCT** (human-readable)
* **1,646,951,661,163,841,381,479,607,357** (in FCT-wei, with 18 decimals)

The protocol enforces this cap through an issuance-based halving schedule.

### Halving schedule

Halvings are intended to occur every 5,256,000 blocks. However, the actual halvings are triggered by issuance thresholds, not time:

* **1st Halving**: 50% of total supply minted
* **2nd Halving**: 75% of total supply minted (50% + 25%)
* **Subsequent**: Each adds half of remaining supply

Each halving reduces the per-period issuance target by 50%.

**Example**: If the current per-period target is 100,000 FCT, after the next halving it becomes 50,000 FCT. This ensures issuance slows down over time as the total supply approaches its cap.

## Dynamic rate adjustment

### What is a period?

A **period** is a span of time during which the FCT mint rate remains constant. When a period ends, the protocol recalculates the rate based on how much FCT was actually minted versus the target.

Periods can end *during* a transaction. If a transaction's minting would exceed the period's target, the period ends mid-transaction:

* The portion of ETH burned before hitting the target mints at the current period's rate
* The remaining portion mints at the new period's rate

The same applies to halvings - they can occur within a single transaction.

This creates a self-balancing system: if too much FCT is minted, rates go down; if too little is minted, rates go up.

### Adjustment periods

A period ends when **either**:

1. 500 L2 blocks elapse, or
2. The period's FCT target is hit

This dual-threshold system ensures rapid response to demand changes while maintaining predictable issuance.

### Rate calculation

The rate adjusts based on which threshold triggered the period end.

**Variable glossary:**

* `old_rate`, `new_rate`: FCT-wei per ETH-wei burned
* `target`: per-period FCT target (e.g., 100,000 FCT)
* `minted`: actual FCT minted in period
* `blocks_elapsed`: blocks in period when target hit

#### Under-issuance (500 blocks reached first)

When the period times out before hitting the target:

```
if 500-block timeout:
    if minted == 0:
        new_rate = min(old_rate * 4, MAX_MINT_RATE)
    else:
        new_rate = old_rate × min(target / minted, 4)
    new_rate = clamp(new_rate, MIN_MINT_RATE, MAX_MINT_RATE)
```

Capped at 4× the old rate to prevent extreme spikes.

#### Over-issuance (Target reached first)

When the target is minted in fewer than 500 blocks:

```
if target hit first:
    new_rate = old_rate × max(blocks_elapsed / 500, 0.25)
    new_rate = clamp(new_rate, MIN_MINT_RATE, MAX_MINT_RATE)
```

Capped at 0.25× the old rate to prevent extreme drops.

### Examples

Assuming a target of 100,000 FCT per period and old\_rate = 1,000,000,000,000,000 (FCT-wei/ETH-wei):

1. **Moderate under-issuance**: 80,000 FCT minted in 500 blocks
   * new\_rate = old\_rate × 1.25
2. **Severe under-issuance**: 25,000 FCT minted in 500 blocks
   * new\_rate = old\_rate × 4.0 (capped)
3. **Moderate over-issuance**: Target hit in 450 blocks
   * new\_rate = old\_rate × 0.9
4. **Severe over-issuance**: Target hit in 50 blocks
   * new\_rate = old\_rate × 0.25 (capped)

## Calldata gas calculation

Following Ethereum's EIP-7623 gas pricing:

$$
\text{Data Gas} = (\text{Zero Bytes} \times 10) + (\text{Non-Zero Bytes} \times 40)
$$

## L1 contract-initiated transactions

Facet transactions created by L1 smart contracts use event data instead of calldata:

$$
\text{Event Data Gas} = \text{Data Bytes} \times 8
$$

This gas is multiplied by the block's base fee to get ETH burned, then by the mint rate to get FCT minted (for contract-initiated Facet tx only).

## Protocol constants

| Constant                          | Value                     | Description           |
| --------------------------------- | ------------------------- | --------------------- |
| `ADJUSTMENT_PERIOD_TARGET_LENGTH` | 500 blocks                | Period length target  |
| `MAX_RATE_ADJUSTMENT_UP_FACTOR`   | 4×                        | Maximum rate increase |
| `MAX_RATE_ADJUSTMENT_DOWN_FACTOR` | 0.25×                     | Maximum rate decrease |
| `MAX_MINT_RATE`                   | 2\*\*128 - 1 ≈ 3.4 × 10³⁸ | Global maximum rate   |
| `MIN_MINT_RATE`                   | 1                         | Global minimum rate   |

## State variables

The protocol tracks:

* `fct_mint_rate`: Current issuance rate (FCT-wei/ETH-wei)
* `current_period_start_block`: L2 block when period began
* `current_period_fct_minted`: FCT minted so far this period
* `total_fct_minted`: Cumulative supply

These values update with each L1 block in `setL1BlockValuesEcotone()`.

## Fixed-point arithmetic

All fractional calculations use 18-decimal fixed-point arithmetic and truncate toward zero. This ensures deterministic results across all implementations.

## Appendix: Halving schedule

Assuming the protocol reaches each halving on schedule (yearly):

| Year | Halving | Supply Minted (% of Total) | Cumulative Supply (% of Total) |
| ---- | ------- | -------------------------- | ------------------------------ |
| 1    | -       | 50%                        | 50%                            |
| 2    | 1st     | 25%                        | 75%                            |
| 3    | 2nd     | 12.5%                      | 87.5%                          |
| 4    | 3rd     | 6.25%                      | 93.75%                         |
| 5    | 4th     | 3.125%                     | 96.875%                        |
| 6    | 5th     | 1.5625%                    | 98.4375%                       |

The total supply asymptotically approaches but never exceeds the cap.


# ZK Fault Proofs

## Best of Both Worlds

For a visual summary of how state derivation and proofs fit together, see the [Architecture Overview](/introduction/architecture-overview).

Traditional rollups forced a choice between two extremes:

* **Optimistic**: Cheap when everyone's honest, but slow multi-round disputes
* **Validity/ZK**: Instant finality, but expensive even when no disputes

Facet's ZK Fault Proofs combine the best of both:

* **Optimistic by default**: No proofs needed unless disputed (cheap normal operation)
* **ZK for disputes**: Single transaction resolves any dispute (no multi-round games)
* **No admin overrides**: Completely immutable system

## No Training Wheels Philosophy

Traditional rollups have "training wheels" - admin controls that can pause, upgrade, or override the proof system. Facet takes a different approach:

**The protocol has no training wheels:**

* [One immutable contract: `Rollup.sol`](https://github.com/0xFacet/zk-fault-proofs/blob/facet/contracts/src/Rollup.sol)
* Cannot be paused, upgraded, or overridden
* No guardians, security councils, or admin keys

**Bridges bring their own training wheels:**

* Individual bridges can add safety features
* Users choose bridges based on their risk tolerance
* Competition determines the right balance

This philosophy recognizes that security preferences vary. Some users want maximum decentralization, others want safety nets. The protocol stays neutral while the market decides.

## Anyone Can Deploy

The proof system has no special smart contract status. This means:

* **Equal access**: Anyone can deploy their own Rollup.sol
* **Fork freedom**: Change the state transition function by deploying a new version
* **No gatekeepers**: Your proof system is as valid as the "official" one
* **Market competition**: Better implementations can win adoption
* **Multiple valid contracts**: For any given Facet fork, multiple Rollup.sol contracts can exist and should all produce the same results if functioning correctly

This is possible because Facet's core protocol doesn't depend on any smart contract - just the immutable L1 address `0xface7`.

### Deployed Instance and Proposer Whitelisting

The Rollup.sol contract deployed at `0x686E7d01C7BFCB563721333A007699F154C04eb4` includes proposer whitelisting, but this **only affects timing, not liveness or censorship resistance**:

* **Whitelisted proposers**: Can submit optimistic proposals immediately
* **Anyone**: Can submit validity proofs at any time (no whitelist)
* **Fallback mechanism**: After `FALLBACK_TIMEOUT_SECS` (14 days) of inactivity, anyone can propose optimistically

This means:

1. The whitelist cannot prevent valid state from being posted
2. Validity proofs always bypass the whitelist entirely
3. The fallback ensures liveness even if all whitelisted proposers disappear

To check current parameters:

```solidity
// Read whitelist status
rollup.whitelist(address) // returns (bool isWhitelisted, uint8 role)

// Read timing parameters
rollup.FALLBACK_TIMEOUT_SECS() // 1209600 (14 days)
rollup.MAX_CHALLENGE_SECS()    // 259200 (3 days)
rollup.MAX_PROVE_SECS()        // 432000 (5 days)
```

## The Rollup Contract

Here's how `Rollup.sol` implements the ZK Fault Proof system:

### Dual-Track Design

```solidity
/// @title Rollup
/// @notice Dual-track ZK fault validity proof system
/// @dev Supports two tracks:
/// @dev 1. Fault proofs: Optimistic proposals that can be challenged (low cost)
/// @dev 2. Validity proofs: Direct ZK proofs that bypass challenges (instant finality)
```

### Immutable Parameters

All critical parameters are immutable - no admin can change them:

```solidity
uint256 public immutable MAX_CHALLENGE_SECS;    // Challenge window
uint256 public immutable MAX_PROVE_SECS;        // Proof deadline
uint256 public immutable CHALLENGER_BOND;       // ETH required to challenge
uint256 public immutable PROPOSER_BOND;         // ETH required to propose
ISP1Verifier public immutable VERIFIER;         // ZK proof verifier
bytes32 public immutable ROLLUP_CONFIG_HASH;    // Chain configuration
```

### Fault Proof Flow

1. **Propose** - Submit a state root with bond:

```solidity
function submitProposal(
    bytes32 root,
    uint128 l2BlockNumber,
    uint32  parentId
) external payable returns (uint256 proposalId)
```

2. **Challenge** - Dispute a proposal with bond:

```solidity
function challengeProposal(uint256 id) external payable
```

3. **Prove** - Submit ZK proof to defend:

```solidity
function proveProposal(
    uint256 id, 
    uint256 l1BlockNumber,
    bytes calldata proof
) public
```

4. **Resolve** - Determine winner based on proofs:

```solidity
function resolveProposal(uint256 id) public
```

### Validity Proof Fast Track

Skip the optimistic flow entirely with a direct proof:

```solidity
function proveBlock(
    uint128 l2BlockNumber,
    bytes32 root,
    uint256 l1BlockNumber,
    bytes calldata proof
) external {
    // Creates, proves, and resolves atomically
    // No bonds required - pure validity proof
}
```

**The Power of Validity Proofs:** A single validity proof doesn't just skip the optimistic flow—it acts as a tool that can invalidate multiple incorrect proposals simultaneously. If several optimistic games target the same root, one validity proof settles them all, making it the ultimate defense against incorrect claims.

That is:

* Invalid parents invalidate all children (cascading)
* Validity proofs override all conflicting proposals

## Integration with Bridges

Bridges integrate with the proof system to enable trust-minimized withdrawals. The key integration points are:

1. **Reading canonical state**: Bridges query `Rollup.sol` for accepted state roots
2. **Verifying withdrawals**: Using merkle proofs against the canonical state
3. **Handling immutability**: Working with a system that cannot be paused or overridden

For complete bridge implementation details, see [Building Bridges](/proof-system-and-bridging/building-bridges).

## Key Takeaways

1. **Immutable proof system** - No admin keys, no upgrades, no pauses
2. **Bring your own safety** - Bridges add training wheels, not the protocol
3. **Open competition** - Anyone can deploy proof systems and bridges
4. **Single-round disputes** - ZK proofs resolve conflicts in one transaction
5. **Trustless verification** - Cryptographic proofs, not committees, determine truth

This design ensures that Facet remains unstoppable while allowing innovation and user choice in how assets are bridged.

## Reference Implementations

* ZK Fault Proofs (contracts + services): <https://github.com/0xFacet/zk-fault-proofs>
* Rust STF & Derivation (used for proofs):
  * FacetKona (Kona fork): <https://github.com/0xFacet/facet-kona>
  * FacetREVM (REVM fork): <https://github.com/0xFacet/facet-revm>
  * FacetOpAlloy (supporting components): <https://github.com/0xFacet/facet-op-alloy>


# Building Bridges

## Overview

For the high-level flow of how Facet derives state and how canonical roots are exposed on L1 for bridges, see the [Architecture Overview](/introduction/architecture-overview).

Facet has no canonical bridge, but anyone can build and deploy a bridge. This guide covers the complete process from understanding bridge mechanics to deploying production-ready contracts.

## How Bridging Works

### Architecture Overview

Bridges on Facet consist of two main components:

1. **L1 Contract**: Handles deposits and withdrawals on Ethereum
2. **L2 Contract**: Manages the L2 token representation

### Depositing Assets (L1 → Facet)

{% stepper %}
{% step %}
**Submit Deposit**

User deposits assets (e.g., USDC, WETH) into an L1 bridge contract.
{% endstep %}

{% step %}
**Bridge Emits Event**

The bridge contract emits an event with the Facet event signature:

```
0x00000000000000000000000000000000000000000000000000000000000face7
```

This event contains the deposit details in RLP-encoded format.
{% endstep %}

{% step %}
**Facet Nodes Process**

Facet nodes monitor L1 for these events and convert them into L2 transactions from the L1 bridge contract to the L2 bridge contract.
{% endstep %}

{% step %}
**Assets Available on Facet**

The L2 bridge contract credits the user's L2 account with the bridged assets when it receives the L2 transaction.
{% endstep %}
{% endstepper %}

### Withdrawing Assets (Facet → L1)

Withdrawals use Facet's ZK Fault Proof system:

{% stepper %}
{% step %}
**Initialize Withdrawal**

User burns L2 assets through the bridge's L2 contract, which calls `L2ToL1MessagePasser` predeploy.
{% endstep %}

{% step %}
**Wait for State Root**

The withdrawal is included in a Facet state root. Proposers post roots to `Rollup.sol`.
{% endstep %}

{% step %}
**Prove Withdrawal**

Once the root is accepted, user submits a proof that their withdrawal exists.
{% endstep %}

{% step %}
**Claim Assets**

After any bridge-specific delays, user claims assets from the L1 bridge contract.
{% endstep %}
{% endstepper %}

## Implementation Guide

This section walks through the reference ETH bridge implementation in zk-fault-proofs. Source:

* L1: <https://github.com/0xFacet/zk-fault-proofs/blob/facet/contracts/src/L1Bridge.sol>
* L2: <https://github.com/0xFacet/zk-fault-proofs/blob/facet/contracts/src/L2Bridge.sol>

### 1) L1Bridge: Deposits, Proofs, Finalization

Key capabilities:

* Deposits ETH to L2 with replay support if L2 blocks are full
* Optional training wheels (pause, withdrawal delay, root blacklist)
* Fork handling via `setRollup` (owner-controlled or renounced)

Core deposit data and send path:

```solidity
// Deposit parameters recorded for replay
struct DepositTransaction {
    uint256 nonce;   // unique deposit id
    address to;      // L2 recipient
    uint256 amount;  // ETH amount in wei
}

// Store deposit hash and send to L2 via 0xface7
function initiateDeposit(address to) public payable returns (uint256 nonce) {
    nonce = ++depositNonce;
    DepositTransaction memory d = DepositTransaction({ nonce: nonce, to: to, amount: msg.value });
    depositHashes[nonce] = _hashDeposit(d);

    bytes memory data = abi.encodeWithSelector(L2Bridge.finalizeDeposit.selector, d);
    LibFacet.sendFacetTransaction({ to: l2Bridge, gasLimit: 500_000, data: data });
}

// Retry with identical parameters if the previous attempt failed due to full blocks
function replayDeposit(DepositTransaction calldata d) external {
    require(depositHashes[d.nonce] == _hashDeposit(d), "param mismatch");
    bytes memory data = abi.encodeWithSelector(L2Bridge.finalizeDeposit.selector, d);
    LibFacet.sendFacetTransaction({ to: l2Bridge, gasLimit: 500_000, data: data });
}
```

EOA convenience path:

```solidity
// EOA-only direct deposit to self (supports EIP-7702 delegated EOAs)
receive() external payable {
    if (!EOA.isSenderEOA()) revert OnlyCanDepositWithoutTo();
    initiateDeposit(msg.sender);
}
```

Training wheels and fork handling:

```solidity
// Update Rollup contract used for proofs (owner-controlled)
function setRollup(address _rollup) external onlyOwner { /* ... */ }

// Pause/unpause and safety delay
function pause() external onlyOwner { _pause(); }
function unpause() external onlyOwner { _unpause(); }
function setWithdrawalDelay(uint256 secs) external onlyOwner { /* ... */ }

// Blacklist a specific root (by hash) if needed
function setRootBlacklisted(bytes32 root, bool blacklisted) external onlyOwner { /* ... */ }
```

Withdrawal verification and finalization:

```solidity
// Prove a withdrawal exists in canonical L2 state
function proveWithdrawal(
    address to,
    uint256 amount,
    uint256 nonce,
    uint256 proposalId,
    Types.OutputRootProof calldata rootProof,
    bytes[] calldata withdrawalProof
) external {
    bytes32 withdrawalHash = _hashWithdrawal(to, amount, nonce);
    require(!finalized[withdrawalHash], "already finalized");

    // Ensure the proposal is canonical
    if (!rollup.proposalIsCanonical(proposalId)) revert ProposalNotCanonical();
    Rollup.Proposal memory prop = rollup.getProposal(proposalId);

    // Optional safety check
    if (rootBlacklisted[prop.rootClaim]) revert RootBlacklisted();

    // Root and storage proof verification
    if (prop.rootClaim != Hashing.hashOutputRootProof(rootProof)) revert InvalidOutputRoot();
    bytes32 storageKey = keccak256(abi.encode(withdrawalHash, uint256(0))); // slot 0
    bool ok = SecureMerkleTrie.verifyInclusionProof({
        _key: abi.encode(storageKey),
        _value: hex"01",
        _proof: withdrawalProof,
        _root: rootProof.messagePasserStorageRoot
    });
    if (!ok) revert InvalidWithdrawalProof();

    proven[withdrawalHash][rollup] = ProvenWithdrawal({ proposalId: uint32(proposalId), provenAt: uint32(block.timestamp) });
}

// Finalize after delay and safety checks
function finalizeWithdrawal(address to, uint256 amount, uint256 nonce) external {
    bytes32 withdrawalHash = _hashWithdrawal(to, amount, nonce);
    ProvenWithdrawal memory info = proven[withdrawalHash][rollup];
    if (info.provenAt == 0) revert WithdrawalNotProven();
    if (block.timestamp <= info.provenAt + withdrawalDelay) revert WithdrawalDelayNotMet();
    Rollup.Proposal memory prop = rollup.getProposal(info.proposalId);
    if (rootBlacklisted[prop.rootClaim]) revert RootBlacklisted();
    finalized[withdrawalHash] = true;
    to.forceSafeTransferETH(amount, SafeTransferLib.GAS_STIPEND_NO_STORAGE_WRITES);
}
```

Trust model note: With ownership renounced, the bridge becomes human-free but permanently locked to one Rollup contract (fork). With ownership active, the operator can switch Rollup to follow new rules—introducing operator trust but preserving flexibility.

### 2) L2Bridge: Finalize Deposits, Initiate Withdrawals

Core responsibilities:

* Finalize deposits from L1 with nonce-based replay protection
* Initiate withdrawals to L1 by burning and calling the message passer

```solidity
// Only the aliased L1 bridge can finalize deposits
modifier onlyL1Bridge() {
    if (msg.sender != AddressAliasHelper.applyL1ToL2Alias(l1Bridge)) revert UnauthorizedBridge();
    _;
}

// Finalize deposit exactly once per nonce
function finalizeDeposit(L1Bridge.DepositTransaction calldata d) external onlyL1Bridge {
    if (finalizedDeposits[d.nonce]) revert DepositAlreadyFinalized();
    finalizedDeposits[d.nonce] = true;
    _mint(d.to, d.amount); // wrap ETH on L2
}

// Burn on L2 and send withdrawal message to L1
function initiateWithdrawal(address to, uint256 amount) external {
    if (amount == 0) revert InvalidWithdrawalAmount();
    _burn(msg.sender, amount);
    bytes memory data = abi.encode(to, amount);
    MESSAGE_PASSER.initiateWithdrawal(l1Bridge, 0, data);
}
```

### 3) Putting It Together

End-to-end flow:

* Deposit (L1 → L2): Users send ETH to `L1Bridge.initiateDeposit`. The bridge records the deposit (nonce + hash) and sends a Facet transaction to `L2Bridge.finalizeDeposit`. If an L2 block is full, `replayDeposit` can resend the same deposit (same nonce/params).
* Withdraw (L2 → L1): Users call `L2Bridge.initiateWithdrawal` to burn wrapped ETH and create a message in `L2ToL1MessagePasser`. On L1, users prove the withdrawal via `L1Bridge.proveWithdrawal` using the canonical root in `Rollup.sol` and finalize after any delay.

Security & safety gears:

* Replay-safe deposits (nonce + hash) and single-use finalize on L2
* Optional pause, withdrawal delay, and root blacklist
* Fork handling via `setRollup` (trust vs flexibility)


# Running a Facet Node

## Introduction

To run a Facet node you will need two pieces of software: [facet-node](https://github.com/0xFacet/facet-node) and [facet-geth](https://github.com/0xFacet/facet-geth). This architecture follows the Consensus/Execution split on the Ethereum L1, with `facet-node` acting as the consensus client and `facet-geth` as the execution layer.

facet-geth is a fork of Optimism's op-geth. facet-node is modeled after op-node, though it isn't a fork.

#### How it works

1. `facet-node` connects to an L1 Ethereum RPC server and monitors each L1 block for Facet transactions—i.e., transactions whose "to: address is `address(0xface7)` or event logs whose first topic is `bytes32(uint256(0xface7))`.
2. `facet-node` extracts the Facet transaction information and combines it with other data to form a transaction payload that `facet-geth` can understand (this is called a "Deposit Transaction").
3. `facet-node` constructs a Facet block with these Deposit transactions and sends the block to `facet-geth` using the [engine API](https://github.com/ethereum/execution-apis/tree/main/src/engine). This is the same API Ethereum consensus clients use to tell the execution layer about new blocks.

`facet-node` is stateless. All data required to operate `facet-node` is stored in `facet-geth.`

### Facet Genesis State

Facet's genesis block includes state from its initial pre-EVM implementation, \[legacy] Facet. This state is deterministically generated by executing the latest Facet logic against historical \[legacy] Facet transactions. From the perspective of running a Facet node all you need is the final genesis.json file which is included in the `facet-geth` repository.

### Run a Node with Docker Compose

Ensure you have Docker or [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed.

Clone `facet-node`:

```
git clone https://github.com/0xFacet/facet-node
cd facet-node/docker-compose
mv .env.sample .env
```

Now edit `.env`. The defaults should be fine but you need to add your own `L1_RPC_URL`.

Finally, start the node:

```
docker compose up
```

And that's it!

### Run a Node without Docker

* Clone `facet-node`:

  ```
  git clone https://github.com/0xFacet/facet-node
  ```

  and `facet-geth`:

  ```
  git clone https://github.com/0xFacet/facet-geth
  ```
* Now `cd facet-node`
* Install Ruby Version Manager (RVM) if not already installed:

  ```
  \curl -sSL https://get.rvm.io | bash -s stable
  ```

  If you encounter GPG issues, run:

  ```
  gpg2 --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDBd
  ```
* Install Ruby 3.3.4:

  ```
  rvm install 3.3.4
  ```

  On macOS, if you encounter OpenSSL issues:

  ```
  rvm install 3.3.4 --with-openssl-dir=$(brew --prefix openssl@1.1)
  ```
* Run `rvm use 3.3.4`
* Run `bundle install`
* Set up Foundry (make sure you install it first):

  ```
  cd contracts && forge soldeer install --recursive-deps && forge build && cd ..
  ```
* Set up environment variables. First copy the .sample.env files to .env.

  ```
  cp .sample.env .env 
  ```

  Now edit the files. Here's what the variables are for:

<table><thead><tr><th width="290">Variable</th><th>Description</th><th width="164">Default/Example</th><th>Purpose</th></tr></thead><tbody><tr><td>L1_NETWORK</td><td>Ethereum network to derive blocks from</td><td>"sepolia" or "mainnet"</td><td>Determines which Ethereum network Facet will sync from</td></tr><tr><td>L1_GENESIS_BLOCK</td><td>Starting L1 block number</td><td>21373000</td><td>Block number to start syncing from. Mainnet genesis: 21373000, Sepolia genesis: 7201200</td></tr><tr><td>LOCAL_GETH_DIR</td><td>Path to local geth directory</td><td>"/path/to/geth_dir"</td><td>Location where facet-geth is cloned</td></tr><tr><td>GETH_RPC_URL</td><td>Authenticated RPC endpoint</td><td>"http://127.0.0.1:8551"</td><td>Used for authenticated connections to facet-geth</td></tr><tr><td>NON_AUTH_GETH_RPC_URL</td><td>Non-authenticated RPC endpoint</td><td>"http://127.0.0.1:8545"</td><td>Used for non-authenticated connections to facet-geth</td></tr><tr><td>GETH_DISCOVERY_PORT</td><td>P2P discovery port</td><td>"30303"</td><td>Port used by facet-geth for peer discovery</td></tr><tr><td>BLOCK_IMPORT_BATCH_SIZE</td><td>Number of blocks per batch</td><td>"5"</td><td>Controls how many simultaneous RPC requests are made</td></tr><tr><td>L1_RPC_URL</td><td>Ethereum RPC endpoint</td><td>"https://eth_rpc_url"</td><td>Source of L1 block data</td></tr><tr><td>JWT_SECRET</td><td>Authentication secret</td><td>"0x0101..."</td><td>Must match value in /tmp/jwtsecret for auth</td></tr><tr><td>MIGRATION_MODE</td><td>Enable migration features</td><td>"false"</td><td>Only needed when creating genesis file</td></tr><tr><td>FACET_V1_VM_DATABASE_URL</td><td>Legacy database connection</td><td>""</td><td>Only needed when creating genesis file</td></tr></tbody></table>

* Put a JWT\_SECRET in `/tmp/jwtsecret` on your local machine:

  ```
  openssl rand -hex 32 | awk '{print "0x"$1}' > /tmp/jwtsecret
  ```
* Run the tests to ensure everything works.

  ```
  rspec && cd contracts && forge test -vv && cd ..
  ```

### Using `facet-geth`

To use `facet-geth` to process blocks instead of just in a test:

1. From the `facet-node` directory, generate the geth initialization command.

   <pre><code><strong>bundle exec rake geth:init_command
   </strong></code></pre>
2. Copy the command, cd back into `facet-geth`, and run it. Note, this command will restart the chain from genesis, deleting any blocks you've already derived. If you want to just restart geth without doing this you should only run the /build/bin/geth command.
3. Finally, cd back into `facet-node` and start deriving Facet blocks from L1 blocks:

   ```
   bundle exec clockwork config/derive_facet_blocks.rb
   ```

You should now have `facet-node` and `facet-geth` set up and running!

### Accessing Facet Data

You can use the geth's normal RPC API to get information about Facet blocks and transactions. For example `eth_getBlockByNumber`. You can also query the chain directly from the geth console which the above command launches. For example `eth.getBlock(1)`.

From the perspective of querying data, Facet will behave identically to any other EVM chain.


# Proposing and Challenging

This guide explains how to participate in Facet's ZK Fault Proof system by running a proposer or challenger.

## Overview

Facet's proof system allows:

* **Validity proofs**: Anyone can submit direct ZK proofs (permissionless)
* **Fault proofs**: Whitelisted proposers submit optimistic proposals
* **Challenges**: Anyone can challenge incorrect proposals
* **Single-round resolution**: ZK proofs resolve disputes instantly

The system uses optimistic proposals by default, with a fallback window where anyone can propose if whitelisted proposers are silent.

## How It Works

```
Propose → Challenge? → Prove? → Resolve
```

1. **Proposer** submits a state root with ETH bond
2. **Anyone** can challenge within the window
3. **ZK proof** resolves the dispute in one transaction
4. **Winner** takes both bonds

## Prerequisites

### Clone the Repository

```bash
git clone https://github.com/0xFacet/zk-fault-proofs
cd zk-fault-proofs
```

### Succinct Prover Network

The system uses Succinct's Prover Network to generate ZK proofs. You'll need:

* An account funded on the Prover Network
* The private key for this account in your configuration

## Running a Proposer

### 1. Create Configuration

Create `.env.proposer` file in the root of the repository:

```bash
# OP Succinct Proposer Service Configuration
# Copy this file to .env.proposer and fill in your actual values

# ===== RPC ENDPOINTS =====
# L1 RPC endpoint for reading Ethereum mainnet/testnet data
L1_RPC=https://your-l1-rpc-endpoint.com

# L2 RPC endpoint for reading OP Stack rollup data
L2_RPC=https://mainnet.facet.org

# L2 node RPC endpoint for accessing op-node specific APIs
L2_NODE_RPC=https://your-l2-node-rpc-endpoint.com

# L1 beacon chain RPC for accessing consensus layer data
L1_BEACON_RPC=https://your-beacon-rpc-endpoint.com

# ===== CONTRACT ADDRESSES =====
# Address of the deployed Rollup.sol contract on L1
ROLLUP_ADDRESS=0x0000000000000000000000000000000000000000

# ===== PRIVATE KEYS =====
# Private key for the proposer account (must be whitelisted on Rollup.sol)
# WARNING: Never commit real private keys to version control
PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000001

# Private key for SP1 network operations (proof generation)
NETWORK_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000002

# Interval for generating range proofs (in L2 blocks)
# Smaller values = more frequent proofs but higher costs
RANGE_PROOF_INTERVAL=1000

# Enable safe database fallback for better reliability
# Recommended: true for production
SAFE_DB_FALLBACK=true

# Mock mode for testing - uses mock proofs instead of real SP1 proofs
# MUST be false for production
MOCK_MODE=false

# ===== SP1 PROOF GENERATION =====
# Maximum cycles allowed for SP1 proof generation
# Higher values allow larger proofs but cost more
SP1_CYCLE_LIMIT=40000000000

# Timeout for SP1 proof generation (in seconds)
# Increase if experiencing timeouts on large proofs
SP1_TIMEOUT=48000
```

To run an `L2_NODE_RPC`:

```bash
git clone https://github.com/0xFacet/facet-optimism
cd facet-optimism
./init_node.sh
```

Repo: <https://github.com/0xFacet/facet-optimism> — This is a read-only fork of op-node used to expose auxiliary RPCs (e.g., sync status, output-at-clock) that aren’t yet implemented in `facet-node`. It’s generally only required for proposers/challengers, not for normal users.

You will need the following in your env:

```bash
OP_NODE_L2_ENGINE_RPC
OP_NODE_L1_ETH_RPC
```

### 2. Start the Proposer Service

```bash
cargo run --bin proposer --release
```

### 3. How Proposing Works

The proposer service:

1. Monitors the anchor block on Rollup.sol
2. Runs Facet node to compute new state
3. Submits proposal with bond when needed
4. Uses Prover Network to generate ZK proof if challenged

## Running a Challenger

### 1. Create Configuration

Create `.env.challenger` file in the root of the repository:

```bash
# Ethereum RPC endpoints
L1_RPC="https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY"
L2_RPC="http://localhost:9545"  # Your Facet node

# Contract address
ROLLUP_ADDRESS="0x..."

# Challenger account
CHALLENGER_PRIVATE_KEY="0x..."
```

### 2. Start the Challenger Service

```bash
cargo run --bin challenger --release
```

### 3. How Challenging Works

The challenger service:

1. Monitors new proposals on-chain
2. Validates each proposal independently
3. Challenges incorrect proposals
4. Claims bonds when successful

## Manual Interaction

### Query System Configuration

All parameters are immutable and can be queried from the contract.

**Timing parameters:**

* `MAX_CHALLENGE_SECS` - Challenge window duration
* `MAX_PROVE_SECS` - Proof deadline after challenge
* `FALLBACK_TIMEOUT_SECS` - When anyone can propose

**Bond requirements:**

* `PROPOSER_BOND` - ETH required to propose
* `CHALLENGER_BOND` - ETH required to challenge

**L2 configuration:**

* `PROPOSAL_INTERVAL` - Blocks between proposals
* `L2_BLOCK_TIME` - Seconds per L2 block
* `L2_START_TIMESTAMP` - L2 genesis timestamp

**Proof system:**

* `VERIFIER` - SP1 verifier contract
* `ROLLUP_CONFIG_HASH` - Chain configuration
* `AGG_VKEY` - Aggregation verification key
* `RANGE_VKEY_COMMITMENT` - Range proof commitment

Example query:

```bash
cast call $ROLLUP_ADDRESS "PROPOSER_BOND()"
```

### Submit a Proposal

```bash
# Get required bond amount first
BOND=$(cast call $ROLLUP_ADDRESS "PROPOSER_BOND()")

# Submit proposal
cast send $ROLLUP_ADDRESS "submitProposal(bytes32,uint128,uint32)" \
  $STATE_ROOT $BLOCK_NUMBER $PARENT_ID \
  --value $BOND \
  --private-key $PROPOSER_PRIVATE_KEY
```

### Challenge a Proposal

```bash
# Get required bond amount
BOND=$(cast call $ROLLUP_ADDRESS "CHALLENGER_BOND()")

# Challenge
cast send $ROLLUP_ADDRESS "challengeProposal(uint256)" \
  $PROPOSAL_ID \
  --value $BOND \
  --private-key $CHALLENGER_PRIVATE_KEY
```

### Submit a Validity Proof (Permissionless)

Anyone can submit a validity proof directly:

```bash
cast send $ROLLUP_ADDRESS "proveBlock(uint128,bytes32,uint256,bytes)" \
  $L2_BLOCK_NUMBER $STATE_ROOT $L1_BLOCK_NUMBER $PROOF_BYTES
```

## Monitoring Your Activity

### Check Proposal Status

```bash
cast call $ROLLUP_ADDRESS "getProposal(uint256)" $PROPOSAL_ID
```

### View Credit Balance

```bash
cast call $ROLLUP_ADDRESS "credit(address)" $YOUR_ADDRESS
```

### Claim Credits

```bash
cast send $ROLLUP_ADDRESS "claimCredit(address)" $YOUR_ADDRESS
```

## Economic Model

### Bonds

* Check current bond amounts using the contract
* Both proposer and challenger post equal bonds
* Bonds are returned to the winner

### Resolution Rules

* **Valid proposal**: Proposer gets both bonds
* **Successful challenge**: Challenger gets both bonds
* **Invalid parent**: Proposal automatically fails

### Profitability Considerations

* L1 gas costs for transactions
* Proof generation costs on Prover Network
* Competition from other participants
* Risk assessment for challenges

## Summary

To participate in Facet's proof system:

1. **Clone the repository** and set up your environment
2. **Configure services** with `.env.proposer` or `.env.challenger`
3. **Fund accounts** on both Ethereum and Prover Network
4. **Run services** to automatically propose or challenge
5. **Monitor and claim** rewards from successful participation

The system ensures honest participation is profitable while maintaining complete immutability - no admin keys can interfere with the propose-challenge-prove cycle.


# Facet TypeScript SDK

Facet SDK is a high-level TypeScript library for creating Facet transactions and calling Facet contracts.

See the `README` here:

{% embed url="<https://github.com/0xFacet/facet-sdk>" %}


# Facet Sol Foundry

`facet-sol` enables you to create Facet transactions in a Foundry environment:

{% embed url="<https://github.com/0xFacet/facet-sol>" %}

Install with `forge install 0xFacet/facet-sol`.

To send Facet transactions from a smart contract, first import `LibFacet`:

<pre class="language-solidity"><code class="lang-solidity"><strong>import { LibFacet } from "lib/facet-sol/src/utils/LibFacet.sol";
</strong></code></pre>

Now you can send transactions like this:

```solidity
LibFacet.sendFacetTransaction({
    value: 0,
    gasLimit: 500_000,
    data: type(SimpleStorage).creationCode
});
```

However you can't use LibFacet directly to send transactions from within a Foundry script. This is because Foundry scripts are run as EOAs and EOAs send Facet transactions using L1 transactions, not events.

facet-sol has a separate library for creating Facet transactions from within foundry scripts, FacetScript. Here's an example:

```solidity
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { FacetScript } from "lib/facet-sol/src/foundry-utils/FacetScript.sol";
import { Example } from "lib/facet-sol/src/Example.sol";
import {Script, console} from "forge-std/Script.sol";

contract DeployExample is FacetScript {
    function setUp() public override {
        super.setUp();
    }

    function run() public broadcast {
        address deployAddress = nextL2Address();
        console.log("Contract will be deployed at:", deployAddress);
        
        sendFacetTransactionFoundry({
            gasLimit: 5_000_000,
            data: abi.encodePacked(
                type(Example).creationCode,
                abi.encode(123, "hello!")
            )
        });
        
        bytes memory setNumberCalldata = abi.encodeWithSelector(
            Example.setNumber.selector,
            123
        );
        
        sendFacetTransactionFoundry({
            to: deployAddress,
            gasLimit: 5_000_000,
            data: setNumberCalldata
        });
    }
}

```

To run this you would use a command like:

{% code overflow="wrap" %}

```sh
L2_RPC="https://sepolia.facet.org" forge script 'script/DeployExample.s.sol' --rpc-url "https://...l1 rpc..." --private-key 0x1234 --broadcast
```

{% endcode %}


# Network Information

**Chain IDs:**

* Mainnet: `1027303` (`0xface7` in hex)
* Sepolia: `16436858` (`0xface7a` in hex)

**Block Parameters:**

* Block time: 12 seconds (same as Ethereum)
* Gas limit: 200 million gas per block

**Block Explorers:**

* Mainnet: <https://explorer.facet.org>
* Sepolia: <https://sepolia.explorer.facet.org>

**RPC Endpoints:**

* Mainnet: <https://mainnet.facet.org/>
* Sepolia: <https://sepolia.facet.org/>

These endpoints support all read-only Ethereum JSON-RPC methods:

* `eth_estimateGas` - Estimate gas for transactions
* `eth_getBalance` - Query FCT balances
* `eth_getBlockByNumber` - Retrieve block data
* `eth_call` - Execute read-only contract calls
* And all other standard read methods

**Sending Transactions:** Facet transactions are sent through Ethereum L1. See [Sending Transactions](/immutable-sequencing/sending-transactions) for details.

**Pre-deployed Contracts:** See [Genesis Contracts](/reference/genesis-contracts) for the full list of contracts available at genesis.


# 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](/introduction/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.

<figure><img src="/files/7XViqguzZeY6JlE2vDgZ" alt=""><figcaption><p>Facet architecture follows Ethereum's consensus/execution split</p></figcaption></figure>

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 calldata
* `eth_getBlockReceipts`: Retrieves logs and transaction status

### 2. Facet Transaction Detection

Valid Facet transactions are identified by:

* **Calldata transactions**: `to` address equals `address(0xface7)` with receipt status = 1
* **Event 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 to `Mint` 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:

1. Each transaction attempts to buy gas sequentially
2. If insufficient gas remains in block, transaction fails
3. Next transaction attempts to buy gas
4. 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:

1. Fetching L1 data from any Ethereum RPC
2. Running facet-node derivation logic
3. 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](https://github.com/0xFacet/facet-node): Consensus client implementation
* [facet-geth](https://github.com/0xFacet/facet-geth): Execution client (op-geth fork)
* [Optimism Specs](https://specs.optimism.io/): Deposit transaction specification


# Genesis Contracts

## Facet Genesis Contracts

Facet launches with a curated set of core protocol and utility contracts deployed at genesis. By making these available from the very first block, the network ensures essential functionality—like bridging, account abstraction, and common utilities—are ready for use without additional deployments.

### 1. Protocol Predeploys

Protocol predeploys are core system contracts located at deterministic addresses in the `0x420` namespace. They form the backbone of Facet's environment and provide critical functionalities.

**Active Protocol Predeploys:**

* **WFCT** (`0x4200000000000000000000000000000000000006`): Canonical Wrapped FCT token contract.
* **L1Block** (`0x4200000000000000000000000000000000000015`): Exposes L1 block information (e.g., number, timestamp) to L2.
* **L2ToL1MessagePasser** (`0x4200000000000000000000000000000000000016`): Facilitates message passing from L2 back to L1.
* **ProxyAdmin** (`0x4200000000000000000000000000000000000018`): Administers upgrades for proxied protocol contracts.
* **SchemaRegistry** (`0x4200000000000000000000000000000000000020`): Registers attestation schemas, enabling standardized verification logic.
* **EAS** (`0x4200000000000000000000000000000000000021`): Ethereum Attestation Service, providing native attestation capabilities on L2.

### 2. Utility Preinstalls

Utility preinstalls are non-protocol helper contracts deployed at known addresses, commonly used across EVM chains. They offer a variety of tools and primitives, ensuring immediate compatibility with widely used frameworks and standards.

**Included Preinstalls:**

* **MultiCall3** (`0xcA11bde05977b3631167028862bE2a173976CA11`): Batch multiple calls into a single aggregate call.
* **Create2Deployer** (`0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2`): Canonical CREATE2 deployer for deterministic addresses.
* **Safe Contracts**:
  * **Safe v1.3.0** (`0x69f4D1788e39c87893C980c06EdF4b7f686e2938`)
  * **SafeL2 v1.3.0** (`0xfb1bffC9d739B8D520DaF37dF666da4C687191EA`)
  * **MultiSendCallOnly v1.3.0** (`0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B`)
  * **MultiSend v1.3.0** (`0x998739BFdAAdde7C933B942a68053933098f9EDa`)
* **Safe Factory** (`0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7`): Deploys new Safe instances.
* **Deterministic Deployment Proxy** (`0x4e59b44847b379578588920cA78FbF26c0B4956C`): Standard proxy enabling deterministic deployments.
* **Permit2** (`0x000000000022D473030F116dDEE9F6B43aC78BA3`): Uniswap’s extended token permit system, chain-aware and standardized.
* **Account Abstraction Components**:
  * **EntryPoint v0.6.0** (`0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`)
  * **EntryPoint v0.7.0** (`0x0000000071727De22E5E9d8BAf0edAc6f37da032`)
  * **SenderCreator v0.6.0** (`0x7fc98430eAEdbb6070B35B39D798725049088348`)
  * **SenderCreator v0.7.0** (`0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C`)
* **BeaconBlockRoots** (`0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02`): Provides beacon block roots (EIP-4788).
* **BeaconBlockRootsSender** (`0x0B799C86a49DEeb90402691F1041aa3AF2d3C875`): Sender account for EIP-4788 transactions.


# Ecosystem

## Facet Apps

At the launch of Facet Protocol, the Facet team has deployed a set of foundational applications that showcase the capabilities of Facet Protocol. These apps aim to offer users and developers a firsthand experience with Facet's seamless integration with Ethereum, as well as the potential for more innovative, decentralized solutions.

### Live Applications

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="https://facetswap.com/"><mark style="color:purple;"><strong>FacetSWAP</strong></mark></a></td><td>FacetSwap is a decentralized exchange built on Facet, designed to take advantage of the protocol's scalability while maintaining the security of Ethereum. With a user-friendly interface and low fees, FacetSwap enables fast, trust-minimized transactions, all while benefiting from Facet's unique architecture that keeps transactions efficient and secure.</td><td></td></tr><tr><td><a href="https://beamr.xyz"><mark style="color:purple;"><strong>Beamr</strong></mark></a></td><td>Beamr lets you collect NFTs from Twitter. Fund your Beamr wallet with mainnet ETH, then reply to any tweet created in the last 48 hours that contains an image with "@BeamrBot mint me &#x3C;N>" to collect editions. The tweet's author receives 100% of the revenue. You can also transfer NFTs by replying with "@BeamrBot transfer &#x3C;N> to &#x3C;ENS, address, or Twitter username>" or visit beamr.xyz to view your collection, list items for sale, or make transfers.</td><td></td></tr><tr><td><a href="https://facetnft.com/"><mark style="color:purple;"><strong>FacetNFT</strong></mark></a></td><td>FacetNFT offers a dedicated platform for trading, and managing NFTs directly on Facet. With its streamlined design, FacetNFT allows collectors to engage with NFTs in a cost-effective and transparent way. Facet's scalability ensures low gas fees, making NFT transactions more affordable, while keeping the security and decentralization of the Ethereum ecosystem intact.</td><td></td></tr><tr><td><a href="https://facet.dev"><mark style="color:purple;"><strong>FacetDEV</strong></mark></a></td><td>FacetDEV is an integrated development environment (IDE) built for Solidity developers, designed to enhance productivity and streamline smart contract workflows. With support for external imports, multi-file contracts, and built-in contract deployment and verification tools, FacetDEV offers a seamless experience for building on Facet.</td><td></td></tr></tbody></table>

### Build Your Own

These initial applications are just the beginning. Facet Protocol empowers developers and users alike to build, deploy, and innovate with their own applications. Because Facet is EVM-compatible, developers can easily copy-paste Solidity code from existing mainnet applications and deploy it directly on Facet with minimal adjustments. This seamless compatibility means anyone can deploy an application on Facet, paving the way for a growing ecosystem of decentralized solutions that are fast, secure, and accessible.


# Security Audits

## ZK Fault Proofs Audit (2025)

{% file src="/files/ec7iXp7O32kfui8LgY4J" %}

## Protocol Audit (2024)

As part of Facet's commitment to transparency, security, and decentralization, we have partnered with [Zellic](https://zellic.io/), a leading blockchain security firm, to conduct a comprehensive audit of Facet Protocol. These audits ensure that the Facet codebase is rigorously tested for vulnerabilities and adheres to the highest security standards prior to mainnet release.

Below, you will find the final audit reports produced by Zellic, detailing their findings and assessments. Each report reflects a different aspect of Facet Protocol's security:

## Reports

### [Facet Node](https://github.com/Zellic/publications/blob/master/Facet%20Node%20-%20Zellic%20Audit%20Report.pdf)

> A specialized adaptation of the standard Ethereum node, designed to facilitate the execution of Facet’s off-chain compute. It is responsible for detecting Facet transactions, extracting and delivering the payloads to Facet geth for execution, and securely storing the resulting state to be queried by Facet users and dApps. Facet node also issues and consumes Facet Compute Token (FCT), the protocol’s native gas token.

### [Facet Geth](https://github.com/Zellic/publications/blob/master/Facet%20Geth%20-%20Zellic%20Audit%20Report.pdf)

> A forked version of OP Stack’s go-ethereum (geth), specifically adapted to process Facet transactions. It is responsible for executing Facet state transitions in a deterministic manner, ensuring that Facet nodes can derive and maintain consistent state from Ethereum’s transaction history.


# Important Repositories

A curated list of the core Facet repositories, their roles, and where they fit in the architecture. Use this page as a quick directory and jump-off point.

## Core Derivation (L2 Clients)

* facet-node — Consensus/derivation client
  * URL: <https://github.com/0xFacet/facet-node>
  * Purpose: Monitors Ethereum L1 for Facet inbox payloads, derives Facet blocks, and communicates with the execution client via the Engine API. Stateless; data stored in facet-geth.
* facet-geth — Execution client
  * URL: <https://github.com/0xFacet/facet-geth>
  * Purpose: Executes Facet blocks and maintains chain state. Forked from OP Stack’s op-geth with Facet-specific semantics.

## Fault Proof System (ZK Fault Proofs)

* zk-fault-proofs — On-chain contracts + services
  * URL: <https://github.com/0xFacet/zk-fault-proofs>
  * Purpose: Rollup.sol + proposer/challenger services for ZK fault proofs. Manages proposals, challenges, and proof verification.
* facet-kona — Rust STF / derivation (Kona fork)
  * URL: <https://github.com/0xFacet/facet-kona>
  * Purpose: Rust implementation of Facet’s state transition function and derivation logic, used by the ZK fault proof pipeline.
* facet-revm — Rust EVM (REVM fork)
  * URL: <https://github.com/0xFacet/facet-revm>
  * Purpose: Fork of REVM tailored for Facet’s execution semantics within the Rust STF.
* facet-op-alloy — OP/Alloy components
  * URL: <https://github.com/0xFacet/facet-op-alloy>
  * Purpose: Supporting Rust components used by facet-kona for OP/Alloy compatibility.

## OP Node Fork (Auxiliary RPC)

* facet-optimism — Read-only op-node fork
  * URL: <https://github.com/0xFacet/facet-optimism>
  * Purpose: Provides auxiliary RPCs (e.g., sync status, output-at-clock) not yet implemented in facet-node. Read-only in Facet; used by proposers/challengers.

## Developer Tools

* facet-sdk — TypeScript SDK
  * URL: <https://github.com/0xFacet/facet-sdk>
  * Purpose: High-level TypeScript utilities to construct and submit Facet transactions from web and server apps.
* facet-sol — Foundry library
  * URL: <https://github.com/0xFacet/facet-sol>
  * Purpose: Foundry helpers to emit Facet transactions from contracts and Foundry scripts.

## Governance & Dashboards

* FIPs — Facet Improvement Proposals
  * URL: <https://github.com/0xFacet/FIPs>
  * Purpose: Governance proposals and design discussions for protocol evolution via forks and social consensus.
* fct-fyi — FCT metrics site
  * URL: <https://github.com/0xFacet/fct-fyi>
  * Site: <https://fct.fyi/>
  * Purpose: Public dashboard for FCT mint rate, issuance progress, and real-time metrics.

## Where They’re Referenced

* Running a node: Guides → Running a Facet Node (facet-node, facet-geth)
* Proposer/Challenger: Guides → Proposing and Challenging (zk-fault-proofs, facet-optimism)
* Proofs design: Proof System & Bridging → ZK Fault Proofs (zk-fault-proofs, facet-kona, facet-revm, facet-op-alloy)
* Developer experience: Developer Tools (facet-sdk, facet-sol)
* Token metrics: Native Gas Token → FCT Issuance Calculation (fct.fyi)
* Governance: Reference → Important Repositories (FIPs)


# FAQs

## General

<details>

<summary><mark style="color:purple;">How does Facet differ from traditional Layer 2 solutions?</mark></summary>

Facet operates as an unstoppable rollup, fundamentally different from traditional L2s in two key ways:

1. **No centralized sequencer**: Traditional L2s use centralized sequencers that can censor transactions or be shut down. Facet transactions go directly to Ethereum's `0xface7` address, where they're sequenced by Ethereum validators - making censorship impossible.
2. **No privileged bridge**: Traditional L2s depend on upgradeable bridge contracts controlled by small multisigs. Facet has no canonical bridge and uses a native gas token (FCT), eliminating this critical vulnerability.

These features make Facet truly unstoppable - it runs as long as Ethereum runs, with no admin keys that can halt the system.

</details>

<details>

<summary><mark style="color:purple;">Why does Facet use ZK Fault Proofs instead of full ZK proofs?</mark></summary>

Facet uses ZK Fault Proofs because they provide the best of both worlds:

**Efficiency in the common case:** When there are no disputes (the normal situation), no proofs need to be generated or verified. This makes the system free to operate in the happy path, unlike full ZK rollups that must prove every state transition.

**Fast dispute resolution:** When a dispute does occur, it's resolved in a single transaction using a ZK proof. This avoids the complex multi-round games and escalating bonds of traditional interactive fault proofs.

**No admin overrides:** The entire system is immutable with no pause functions or guardian overrides, ensuring true unstoppability.

This approach aligns with Facet's philosophy: optimize for the common case (honest behavior) while maintaining strong security guarantees when disputes arise.

</details>

## Facet Compute Token (FCT)

<details>

<summary><mark style="color:purple;">What is FCT and how do I get it?</mark></summary>

FCT (Facet Compute Token) is Facet's native gas token. It's the only way to pay for transactions on Facet.

You acquire FCT by burning ETH when sending Facet transactions to Ethereum. The process is simple:

1. Send a Facet transaction to `0xface7` on Ethereum
2. FCT is automatically minted proportional to the ETH burned for transaction data
3. Use that FCT to pay for gas on Facet

This burn-to-mint mechanism ensures FCT has no admin control - no multisig can freeze, mint, or confiscate your gas tokens. It's as permissionless as Ethereum itself.

</details>

<details>

<summary><mark style="color:purple;">How is FCT different from bridged ETH?</mark></summary>

Traditional L2s use bridged ETH for gas, which creates critical vulnerabilities:

* Bridge admins can freeze the bridge, stopping the entire rollup
* Upgrades can change token behavior without warning
* Your ability to use the network depends on bridge operators

FCT eliminates these risks. Since it's minted algorithmically based on ETH burning, there's no bridge to freeze, no admin keys to compromise, and no dependency on any third party. Your ability to get gas tokens and use Facet is truly unstoppable.

</details>

## Security

<details>

<summary><mark style="color:purple;">What happens if someone reports incorrect Facet state?</mark></summary>

Facet's ZK Fault Proofs system ensures that incorrect state claims are quickly and definitively resolved:

1. **Optimistic by default**: Node operators report state updates without proofs (efficient in the common case)
2. **Single-transaction disputes**: If someone challenges a state report, the dispute is resolved in one Ethereum transaction using a ZK proof
3. **Verifiable computation**: The ZK proof mathematically guarantees the correct state computation

Additionally:

* **You can verify it yourself** by running a Facet node
* **Transactions include conditions** (like slippage limits) that prevent losses from bad data
* **No trust required**: Since the proof system is immutable and on-chain, anyone can trigger a dispute

The canonical source of truth is always the deterministic computation over Ethereum data, enforced by cryptographic proofs when needed.

</details>

<details>

<summary><mark style="color:purple;">How does Facet guarantee it can't be stopped?</mark></summary>

Facet's unstoppability comes from its architecture:

1. **Immutable inbox**: The `0xface7` address has no known private key and no smart contract code - it can never be changed or disabled
2. **No admin keys**: No multisig controls any part of the core protocol
3. **Native gas token**: FCT can't be frozen by bridge admins since there is no bridge
4. **Fork-based upgrades**: Protocol changes require deploying a new system, not upgrading the existing one

As long as Ethereum is running and you can send transactions to it, Facet will continue operating. No group of people, no matter how powerful, can stop it.

</details>


# Community Resources

Welcome to the Facet community! Whether you're a developer, user, or simply interested in learning more about Facet Protocol, our community is a place to connect, share knowledge, and contribute to the evolution of decentralized applications. Here’s where you can get involved:

<table><thead><tr><th width="156.33333333333331">Resource</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://x.com/0xfacet"><strong>X (Twitter)</strong></a></td><td>Stay up-to-date with the latest announcements, insights, and updates from the Facet team by following us on X.</td></tr><tr><td><a href="https://discord.gg/facet"><strong>Discord</strong></a></td><td>Join the conversation on Discord, where our community gathers to discuss everything from development tips to protocol updates.</td></tr><tr><td><a href="https://github.com/0xFacet"><strong>GitHub</strong></a></td><td>For developers interested in contributing to Facet Protocol or building on it, our GitHub repository is the place to access the source code, view open issues, and collaborate on new features.</td></tr></tbody></table>


# Brand Kit

Learn to use the foundational elements of the Facet brand, from logo, to colors and typography.

***

## The Logo

The Facet logo is comprised of two elements: the icon and the wordmark.

<figure><img src="/files/vaZG7mhE5kfZNtoONk4M" alt=""><figcaption></figcaption></figure>

The logo blueprint is carefully constructed. When utilizing it, please refrain from using two separate icon and wordmark files; instead use the full logo file.

<figure><img src="/files/1BC4dcZJkWv0EIgut74l" alt=""><figcaption></figcaption></figure>

### Downloadable Assets

Download the logo or icon below via "right-click, save."

<table><thead><tr><th width="111">Asset</th><th>SVG (vector)</th><th>PNG (black)</th><th>PNG (white)</th></tr></thead><tbody><tr><td>Logo</td><td><img src="/files/0ntWf82VOtugaqXCq9jK" alt="" data-size="original"></td><td><img src="/files/5Y8xIFIWAOn9JSpvSqXW" alt="" data-size="original"></td><td><img src="/files/PHJQwWEC49Ar3JNdkuOS" alt="" data-size="original"></td></tr><tr><td>Icon</td><td><img src="/files/3WQr7egPHWwMxkbpTfNr" alt="" data-size="original"></td><td><img src="/files/UBtDyaoNtavrR7XkvncW" alt="" data-size="original"></td><td><img src="/files/7jvLqKEAgrAf10iAfhfH" alt="" data-size="original"></td></tr></tbody></table>

## Colors

Facet's primary color scheme revolves around black, white, and Facet Purple. We use dark and light greys alongside black dots to create contrast. Ethscription Green stands out as a secondary color, bringing a vibrant touch to the palette.

<figure><img src="/files/8euWtDRge8LE77piDRbe" alt=""><figcaption></figcaption></figure>

| Color              | HEX Value | Usage     |
| ------------------ | --------- | --------- |
| Black              | #000000   | Primary   |
| Dark Grey          | #3A3E4A   | Primary   |
| Light Grey         | #E2E3DF   | Primary   |
| White              | #FFFFFF   | Primary   |
| Facet Purple       | #3F19D9   | Primary   |
| Ethscription Green | #C3FF00   | Secondary |

### Additional Colors

If you need additional colors for graphs or visualizations, use the tints below. Keep in mind, these are intended as tertiary alternatives to the primary/secondary colors above and should be used sparingly.

<figure><img src="/files/qt4PiVZPK8jYtEiokumY" alt=""><figcaption></figcaption></figure>

| Black   | Dark Grey | Facet Purple | ETHS Green |
| ------- | --------- | ------------ | ---------- |
| #4D4D4D | #757880   | #795EE4      | #DBFF66    |
| #808080 | #9C9EA4   | #9F8CEC      | #E7FF99    |
| #CCCCCC | #D8D8D8   | #C5BAF4      | #EDFFB2    |

## Typography

Facet places a high value on clarity, readability, and a modern aesthetic in our documentation. To achieve this, we have chosen the **Roobert** font family for our text.

* [Roobert Semibold](https://displaay.net/typeface/roobert-collection/roobert-semimono/) - This style is utilized for major headings and titles. It provides a bold and prominent look, making it perfect for catching the reader's attention and clearly delineating sections.
* [Roobert](https://displaay.net/typeface/roobert-collection/roobert/) - This style is used for body text. Its regular weight and ample size ensure readability and comfort for extended reading, making it ideal for paragraphs and detailed explanations.
* [Roobert Mono](https://displaay.net/typeface/roobert-collection/roobert-mono/) - This monospaced version of Roobert is used for code snippets and technical information. The fixed-width nature of monospaced fonts helps in maintaining the alignment and clarity of code, making it easy to read and understand.

Font sizes and tracking are detailed in the Brand Guide. If a font isn't supported, please use the following fallback typefaces:

* **Inter** to replace Roobert
* **IBM Plex Mono** to replace Roobert Mono

<figure><img src="/files/HvPCmOTJjAlEEOI20hrX" alt=""><figcaption></figcaption></figure>

## Detailed Brand Guide

For a comprehensive guide on the use of the Facet brand, please refer to the Facet Brand Guide below:

{% file src="/files/ldJX9FrUz5EnJBtZIcCi" %}

<figure><img src="/files/lsUhiMBkMaEvFFZziAs5" alt=""><figcaption></figcaption></figure>


