> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whitechain.io/llms.txt
> Use this file to discover all available pages before exploring further.

# How the migration works

> The freeze and dump procedure that turns the legacy Whitechain L1 state into the L2 Mainnet genesis: the phases, the three allocations, and the tooling that performs each step.

The legacy Whitechain L1 does not stop and restart empty. Its full state is exported at a chosen block and written into the genesis block of the L2. Every account, contract and storage slot then exists on the L2 from block 0, at the same address. This page describes how that is done and which tool performs each step.

<Note>
  Migration applies to Mainnet only, and it has not happened yet. The legacy L1 mainnet, chain ID `1875`, is live and producing blocks. Whitechain Sepolia started from its own genesis and carries no legacy state. If you are a user or an integrator asking what you have to do, read [Migration to L2](/learn/general/migration) instead: this page is the mechanics, not the checklist.
</Note>

## The strategy

The migration is a freeze and dump. L1 block production stops at a snapshot block. The full state trie at that block is exported, the export is transformed into an OP Stack genesis allocation, and the L2 boots from it.

| Carried into the L2 genesis                               | Not carried                       |
| --------------------------------------------------------- | --------------------------------- |
| Native WBT balances, to the wei                           | Transaction history               |
| Contract bytecode                                         | Event logs and receipts           |
| Contract storage, so ERC-20 and NFT balances come with it | Pending mempool transactions      |
| Account nonces                                            | Block headers of the legacy chain |
| Addresses, unchanged                                      |                                   |

Final state migrates. History does not, because the L2 starts a new chain whose block 0 holds that state rather than replaying the blocks that produced it.

## The three allocations

Three files in this procedure are all called an allocation at some point, and confusing them is the most common way to produce a chain that will not boot. They are kept in separate files with distinct names.

| File                | What it is                                                                                                                                    | What it is not                                                            |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `genesis.base.json` | Output of `op-deployer inspect genesis`. Contains the OP Stack predeploys, the preinstalls, and the pre-funded allocations for the new chain. | Bootable as the migrated chain. It holds none of the legacy state.        |
| `state_dump.json`   | Output of `geth dump` at the snapshot block. JSON Lines: a header line carrying the state root, then one account per line.                    | Readable by `op-node`. The format is different in every field.            |
| `alloc.json`        | The two above merged into a single OP Stack allocation map.                                                                                   | A genesis file. It is an allocation map, and `op-node` turns it into one. |

The final `genesis.json` and `rollup.json` are emitted together by one `op-node genesis l2` run. They are a pair: `op-node` recomputes the L2 block 0 hash from the allocation and writes it into `rollup.json`, so a `rollup.json` from an earlier run paired with a later `genesis.json` fails at boot with a genesis hash mismatch.

## Why the merge step needs its own tool

`op-node genesis l2 --l2-allocs` reads its input as an OP Stack allocations document. Each key is an address and each value is that account, with `balance` and `nonce` as hex quantities, `code` as `0x` prefixed bytes, and every storage key and value a full 32 byte `0x` prefixed hash. It also replaces the allocation rather than merging into it, so the predeploys have to be inside the file it is given.

A `geth dump` satisfies neither requirement.

| Field                     | `geth dump` produces                                            | `op-node` expects                               |
| ------------------------- | --------------------------------------------------------------- | ----------------------------------------------- |
| Document shape            | JSON Lines, one account per line, plus a state root header line | A single JSON object mapping address to account |
| `balance`                 | Decimal string                                                  | Hex quantity                                    |
| `nonce`                   | JSON number                                                     | Hex quantity                                    |
| `storage` keys and values | Unpadded hex, no `0x` prefix                                    | Full 32 bytes, `0x` prefixed                    |
| Predeploys                | Absent                                                          | Required to be present                          |

Upstream OP Stack tooling ships no converter for this. `scripts/dump-to-alloc.mjs` in the migration repository does exactly those two jobs, the format conversion and the merge, in a single streaming pass. Only the base allocation is held in memory, so peak memory does not grow with the size of the dump. Everything before and after that step is stock `op-deployer` and `op-node`.

## Address collisions

An address defined by both the base allocation and the legacy state dump is a collision, and the dump account wins. The one exception is a dump account that is itself empty and gets pruned, which leaves the base entry standing. Two groups are exposed, and they carry different risk.

| Group                             | Addresses                                                                                                                  | Why it happens                                                                                                                                  | Risk                                                        |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| OP Stack predeploys               | `0x42000000000000000000000000000000000000xx` and `0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000`                              | Nobody holds a key for these, but anyone can send WBT to one on the legacy L1, which creates a codeless account that the dump then carries over | High. The predeploy loses its code and storage              |
| Preinstalls at ordinary addresses | Multicall3, Create2Deployer, DeterministicDeploymentProxy, Permit2, the Safe singletons, the ERC-4337 EntryPoints, CreateX | These deploy at the same address on every EVM chain, so a collision is expected rather than exceptional                                         | Low, if the bytecode is identical on both sides. Compare it |

`dump-to-alloc.mjs` reports every collision on stderr as it happens, repeats the full list at the end of the run, and states explicitly when there were none. The authoritative list of addresses at risk for a given deployment is the base allocation itself, read with `jq -r '.alloc | keys[]' genesis.base.json`.

Empty accounts, meaning zero balance, zero nonce, no code and no storage, are pruned by default, so account counts between the dump and the allocation differ by that number. `--no-prune-empty` keeps them.

## The phases

The L1 is read only from the moment production stops until the L2 is serving, so everything in phase 2 is time boxed.

| Phase            | Steps                                                                                                                                                                         | Reversible                 |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| Before migration | Confirm readiness, deploy the L1 contracts with `op-deployer apply`, generate the base genesis artifacts with `op-deployer inspect`                                           | Yes                        |
| Migration        | Freeze the L1 at the snapshot block, dump the state, build the merged allocation, regenerate `genesis.json` and `rollup.json`, generate the challenger prestates, boot the L2 | No, from the freeze onward |
| After migration  | Health checks, state reconciliation, functional QA, sign off                                                                                                                  | Verification only          |

What each step produces:

| Step                | Command                                                        | Output                                                                                                                                                                                                                             |
| ------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Deploy L1 contracts | `op-deployer apply`                                            | `state.json`, the record of every deployed L1 address                                                                                                                                                                              |
| Base artifacts      | `op-deployer inspect genesis`, `rollup`, `deploy-config`, `l1` | The base genesis, `rollup.json`, the deploy config, `l1-addresses.json`. The runbook writes the base genesis as `genesis.json` in the deployer workdir; this page calls it `genesis.base.json` to keep it apart from the final one |
| Freeze              | Stop the block producer                                        | The snapshot block number, hash and state root, recorded                                                                                                                                                                           |
| Dump                | `geth dump <block>` with `--cache.preimages`                   | `state_dump.json`, validated against the snapshot block state root                                                                                                                                                                 |
| Merge               | `dump-to-alloc.mjs`                                            | `alloc.json`, one entry per line                                                                                                                                                                                                   |
| Regenerate          | `op-node genesis l2 --l2-allocs`                               | Final `genesis.json` and `rollup.json`, as a pair                                                                                                                                                                                  |
| Prestates           | `make reproducible-prestate` in the Optimism monorepo          | The Cannon64 absolute prestate, which must match what is registered on L1 for the proposer game type                                                                                                                               |
| Boot                | Start the stack on empty volumes                               | A sequencer whose block 0 hash equals `genesis.l2.hash` in `rollup.json`                                                                                                                                                           |

Two details decide whether the boot succeeds. The L1 node that is dumped must be a full, non pruned node synced with `--cache.preimages`, stopped gracefully so the trie cache is flushed, and the dump header state root must equal the snapshot block state root. The execution client volumes must be empty before the first start, because booting on top of an old database is the most common cause of a genesis hash mismatch.

## Where the tooling lives

| Repository                                                                                                  | Contents                                                                                                                                    |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| [whitechain-labs/whitechain-state-migration](https://github.com/whitechain-labs/whitechain-state-migration) | The runbooks, `dump-to-alloc.mjs`, and the reconciliation tooling used afterwards                                                           |
| [whitechain-labs/whitechain-bootstrap](https://github.com/whitechain-labs/whitechain-bootstrap)             | The published genesis, rollup config and deployment record for each network. See [Network artifacts](/operate/run-a-node/network-artifacts) |

The migration repository carries two runbooks. The default one builds the genesis with `op-deployer`, `dump-to-alloc.mjs` and `op-node genesis l2`, and is used for any migration that carries real L1 state. The second uses upstream OP Stack tooling only, and applies when the allocation is small and hand authored rather than dumped from a chain.

## Related

* [Migration to L2](/learn/general/migration)
* [Verify the migration](/operate/migration/verify-the-migration)
* [Network artifacts](/operate/run-a-node/network-artifacts)
* [Node operators](/operate/run-a-node/overview)
* [Network reference](/learn/network/reference)
