force-inclusion demonstrates Arbitrum's censorship resistance mechanism end-to-end. It deploys a fresh Orbit rollup with a short force inclusion delay (90 seconds), deposits ETH via the delayed inbox, waits for the delay window to pass, and then force includes the deposit — all without a running sequencer.
Arbitrum's Sequencer normally orders transactions. But what if the sequencer goes offline or starts censoring? Arbitrum guarantees that any message sent to the delayed inbox on the parent chain can be force included into the chain's inbox after a time delay, bypassing the sequencer entirely.
This tutorial runs the full cycle in a single command:
- Deploy a new Orbit rollup with
maxTimeVariation.delaySeconds = 90(instead of the default 24 hours) - Deposit ETH via
Inbox.depositEth()on the parent chain (goes into the delayed inbox) - Force include the deposit by calling
SequencerInbox.forceInclusion()after the delay window passes - (Optional) Start a fullnode (no sequencer) to verify the deposit appears on the child chain
- Node.js 18+
- A deployer account with ETH on the parent chain (Arbitrum Sepolia or a custom parent chain)
- Docker (only needed for the
--with-nodeoption)
Copy the sample env file and fill in your values:
cp .env-sample .envRequired variables:
| Variable | Description |
|---|---|
DEPLOYER_PRIVATE_KEY |
Private key of the deployer (must have ETH on the parent chain) |
PARENT_CHAIN_RPC |
RPC URL of the parent chain |
Optional variables:
| Variable | Description |
|---|---|
PARENT_CHAIN_ID |
Parent chain ID (defaults to Arbitrum Sepolia 421614) |
BATCH_POSTER_PRIVATE_KEY |
Batch poster key (auto-generated if not set) |
VALIDATOR_PRIVATE_KEY |
Validator key (auto-generated if not set) |
Run steps 1–3 (deploy, deposit, force include):
yarn testRun all 4 steps including fullnode verification (requires Docker):
yarn test:withNodeThe script uses @arbitrum/chain-sdk (Orbit SDK) to deploy a new rollup. The key configuration is sequencerInboxMaxTimeVariation, which controls how long a delayed message must wait before it can be force included:
sequencerInboxMaxTimeVariation: {
delayBlocks: 6n,
futureBlocks: 12n,
delaySeconds: 90n,
futureSeconds: 3600n,
}ETH is deposited using @arbitrum/sdk's EthBridger.deposit(). Under the hood, this calls Inbox.depositEth() on the parent chain, which routes through bridge.enqueueDelayedMessage(). Without a running sequencer, this message stays in the delayed inbox.
After the delay window passes (90 seconds + 6 blocks), InboxTools.forceInclude() calls SequencerInbox.forceInclusion() on the parent chain. This emits the same SequencerBatchDelivered event as a normal sequencer batch, making the deposit part of the canonical chain.
When --with-node is passed, the script starts a Nitro fullnode via Docker with the sequencer disabled (node.sequencer = false, execution.forwarding-target = "null"). The fullnode reads from the parent chain and processes the force-included batch, allowing you to verify that the deposited ETH appears on the child chain.
- Send a signed transaction from the parent chain — demonstrates sending transactions via the delayed inbox with a running sequencer