> ## 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.

# Verify the migration

> Check the migrated state yourself: reconcile every account, token and NFT between the legacy Whitechain L1 at the snapshot block and the L2, using the published reconciliation tooling.

The migration is checkable by anyone, not only by the team that runs it. Every account that existed on the legacy L1 at the snapshot block has a counterpart on the L2, and the comparison is a matter of reading both chains over JSON-RPC and diffing the result. The tooling that does it is published, so you can reproduce the check rather than take the outcome on trust.

<Note>
  Migration applies to Mainnet only and has not happened yet, so there is nothing to reconcile today. This page describes the procedure and the tooling. Whitechain Sepolia started from its own genesis, so it carries no legacy state.
</Note>

## What you need

| Requirement                                                                                                                 | Why                                                                                                                                                                               |
| --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A legacy L1 archive RPC that still serves state at the snapshot block, with `debug_accountRange` and `eth_getProof` enabled | The account walk reads L1 state as it stood when production stopped                                                                                                               |
| An L2 RPC with `eth_getProof` enabled                                                                                       | Code hashes and storage hashes are compared through proofs on both sides. `eth_getProof` is in the standard `eth` namespace, so any RPC serves it. Only the L1 side needs `debug` |
| The snapshot block, as a hex tag                                                                                            | The last legacy L1 block. Published with the migration record                                                                                                                     |
| A pinned L2 block                                                                                                           | Pin it, or a moving head produces false mismatches                                                                                                                                |
| A contract inventory                                                                                                        | The tokens, NFTs and contracts you want reconciled by name                                                                                                                        |

```bash theme={null}
export L1_RPC=<L1_ARCHIVE_RPC>
export L2_RPC=<L2_RPC>
export L1_BLOCK=<SNAPSHOT_BLOCK_HEX>
export L2_BLOCK=<L2_BLOCK_HEX>
```

<Warning>
  Verification depends on a legacy L1 archive endpoint that answers at the snapshot block. That endpoint goes away at L1 decommissioning. Run the checks you care about, and export what you need, while the legacy read only endpoints are still online. See [L1 sunset stages](/learn/general/migration#l1-sunset-stages).
</Warning>

## The layers

Each layer gates the next. A failure at a lower number invalidates everything above it.

| Layer | Tool                         | What it establishes                                                        |
| ----- | ---------------------------- | -------------------------------------------------------------------------- |
| 0     | `cast`, plain JSON-RPC calls | The chain is alive and is the chain you think it is                        |
| 1     | `staterecon`                 | Every account matches on balance, nonce, code hash and storage hash        |
| 2     | `tokenrecon`                 | Every token and NFT matches on view function returns and raw storage slots |
| 3     | `soul-verification`          | The SoulDrop and Soul registry state matches                               |

A fourth tool, `tokensync`, ships alongside these. It backfills token metadata into the explorer database from the legacy explorer API. It changes explorer data rather than chain state, so it is a migration step, not a verification step, and a clean `tokensync` run proves nothing about the chain.

The published runbook, `docs/post-migration-verification.md`, numbers its layers differently: it carries `tokensync` as layer 4 and adds functional QA as layer 5. Functional QA is the acceptance testing that follows reconciliation, not a check on the migrated state, so it sits in the [migration phases](/operate/migration/how-migration-works#the-phases) rather than here.

## Layer 0: chain sanity

Run this first. It is quick and it fails loudly.

```bash theme={null}
cast chain-id --rpc-url "$L2_RPC"
```

```bash theme={null}
curl -s -X POST "$L2_RPC" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
```

Confirm that the chain ID is the published L2 value, that the block number advances at the configured block time, and that a test transaction is charged in WBT.

## Layer 1: account reconciliation

`staterecon` walks every account on the legacy L1 with `debug_accountRange`, page by page, and compares each one against the L2. Results stream to CSV as it goes, so it holds no full copy of the state in memory.

```bash theme={null}
cd staterecon
go build -o staterecon ./cmd/staterecon
```

```bash theme={null}
./staterecon \
  --l1-rpc "$L1_RPC" \
  --l2-rpc "$L2_RPC" \
  --l1-block "$L1_BLOCK" \
  --l2-block "$L2_BLOCK" \
  --output-dir ./out \
  --print-mismatches
```

Compared per account:

| Field          | Read from L1 with    | Read from L2 with         |
| -------------- | -------------------- | ------------------------- |
| `balance`      | `debug_accountRange` | `eth_getBalance`          |
| `nonce`        | `debug_accountRange` | `eth_getTransactionCount` |
| `code_hash`    | `eth_getProof`       | `eth_getProof`            |
| `storage_hash` | `eth_getProof`       | `eth_getProof`            |

Code hash and storage hash are compared for contract accounts only. The CSV carries `address`, `status`, `field`, `l1_value`, `l2_value` and `error` per row.

A clean run has zero rows with `status=mismatch` and zero rows with `status=error`. An `error` row is not a pass: it means the comparison did not happen, usually an RPC timeout, and it has to be rerun rather than waved through. Resume an interrupted walk with `--account-range-start`, and tune throughput with `--page-size`, `--batch-size`, `--rpc-batch-size` and `--rpc-timeout`.

Two expected differences are not findings. The OP Stack predeploys exist only on the L2 and have no L1 counterpart. Empty accounts pruned while the allocation was built are absent on the L2 by design, so the account count differs by that number.

## Layer 2: token and NFT reconciliation

`tokenrecon` calls view functions on both chains for the contracts you list, compares the raw return data, and additionally compares raw storage slots with `eth_getStorageAt`.

```bash theme={null}
cd staterecon
go build -o tokenrecon ./cmd/tokenrecon
cp config/config.example.yml config/config.yml
```

Fill in the config with the contracts you want checked:

```yaml config/config.yml theme={null}
config:
  erc20:
    - name: USDC
      address: 0x<token-address>
      addresses:
        - 0x<holder-address>
      storages:
        - 0x<storage-slot>
      rules:
        - general
        - balance
        - roles
        - codehash
        - storagehash
```

```bash theme={null}
./tokenrecon \
  --l1-rpc "$L1_RPC" \
  --l2-rpc "$L2_RPC" \
  --l1-block "$L1_BLOCK" \
  --l2-block "$L2_BLOCK" \
  --config config/config.yml \
  --output-dir ./token_out
```

What each rule covers:

| Rule          | Compares                                                                                                      |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| `general`     | Name, symbol, decimals, version, currency, `totalSupply`, `DOMAIN_SEPARATOR` and the authorization typehashes |
| `balance`     | `balanceOf` and `nonces` for each listed holder                                                               |
| `roles`       | Owner, blacklister, master minter, pauser, rescuer                                                            |
| `codehash`    | The `codeHash` returned by `eth_getProof`                                                                     |
| `storagehash` | The `storageHash` returned by `eth_getProof`                                                                  |

A clean run has zero `mismatch` and zero `error` rows, `totalSupply` equal on both chains for every token, and matching roles and owners. The check is only as complete as the inventory you give it, so a token absent from the config is a token nobody verified.

## Layer 3: SoulDrop and Soul registries

`soul-verification` is a set of RPC tests that diff the pre-migration deployment against the post-migration one, including storage layout checks, which is why it needs the contract sources compiled first.

```bash theme={null}
cd soul-verification
npm run compile
cp .env.example .env
```

```bash theme={null}
npx hardhat test test/migrationStateDiff.rpc.ts
```

Pin a block number on both sides so a moving head cannot produce a false diff. For a fast signal on a subset rather than the full run, use the sample variant:

```bash theme={null}
MIGRATION_SAMPLE_SOUL_IDS=1,42,100 npx hardhat test test/migrationStateDiffSample.rpc.ts
```

## What counts as verified

| Check           | Pass condition                                                                                                                                                                               |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Chain sanity    | Chain ID matches the published value, blocks advance, fees are charged in WBT                                                                                                                |
| Accounts        | Zero `mismatch` rows and zero `error` rows from `staterecon`                                                                                                                                 |
| Tokens and NFTs | Zero `mismatch` rows and zero `error` rows from `tokenrecon`, and `totalSupply` equal on both chains                                                                                         |
| Registries      | The full migration state diff passes with storage layout checks enabled                                                                                                                      |
| Record          | Snapshot block number, hash and state root, the dump and allocation checksums, the final `genesis.json` and `rollup.json` checksums, the L2 block 0 hash and the prestate hash, all archived |

The last row is what makes the rest reproducible later. The checksums of the published artifacts are verifiable independently at any time: see [Network artifacts](/operate/run-a-node/network-artifacts).

## Where the tooling lives

Both tools are in [whitechain-labs/whitechain-state-migration](https://github.com/whitechain-labs/whitechain-state-migration), under `staterecon/`, with the full verification runbook in `docs/post-migration-verification.md`. Building them needs Go 1.25 or later. `soul-verification` needs Node.js and the SoulDrop contract sources.

## Related

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