Skip to main content

Mass exit operations

This section explains how to trigger the mass exit process and enable users to withdraw their funds using account state snapshots.

When a mass exit is triggered, standard deposits and withdrawals are immediately blocked, and the bridge enters mass exit mode. Users must then obtain account state proofs from the Chromia side as of the last known valid block, and submit them to the EVM contract to withdraw their funds.

Step 1: Triggering mass exit

The bridge owner can trigger the mass exit.

Basic form (for trusted validator set):

function triggerMassExit(
bytes memory blockHeader, // The block header from the most recent valid block
bytes[] memory sigs, // Validator signatures
address[] memory signers, // Validator public keys
Data.ExtraProofData memory extraProof // Extra proof data containing the Merkle root hash of the account state snapshot tree
)

If the validator contract has been hijacked, use historical validators to verify the block header:

function triggerMassExitWithHistoricalValidators(
bytes memory blockHeader, // The block header from the most recent valid block
bytes[] memory sigs, // Validator signatures
address[] memory signers, // Validator public keys
Data.ExtraProofData memory extraProof, // Extra proof data containing the Merkle root hash of the account state snapshot tree
address[] memory historicalValidators // Historical validator addresses used to verify the block header
)
  • Ensure that the blockHeader corresponds to a valid block no older than 3 days.
  • The signatures must align with the validator set at the time of that block.

✅ Once triggered, deposits and standard withdrawals are disabled.
✅ Snapshot-based withdrawal becomes the only supported method.

Step 2: Withdrawing funds using account state snapshots

Retrieve account state proof

Each account may use multiple state slots, corresponding to different networks, bridge contracts, protocols, etc. To get the account state proof, a user must first retrieve the relevant state slot IDs:

val state_slot_ids = hbridge.get_state_slot_ids_for_address(
beneficiary, // The beneficiary's address
network_id // The EVM network ID
);

Then, account state proof can be retrieved using the following query:

chr query -brid $BRIDGE get_account_state_merkle_proof \
-- blockHeight=$MASS_EXIT_BLOCK_HEIGHT \
accountNumber=$ACCOUNT_STATE_SLOT_ID

Replace $MASS_EXIT_BLOCK_HEIGHT with the mass exit block height, and $ACCOUNT_STATE_SLOT_ID with the specific account state slot ID.

Submit account state proof on EVM

Submit the snapshot proof on the EVM to withdraw funds.

function withdrawBySnapshot(
bytes calldata snapshot, // Snapshot data of the account state
Data.Proof memory stateProof // Merkle proof for the snapshot
)

This function allows users to withdraw their balances from the bridge based on the snapshot state.

Step 3: Complete in-progress withdrawals

If a user initiated a withdrawal before the mass exit, they can complete it by following these steps:

Get withdrawal by transaction + index

Retrieve the specific withdrawal details using the transaction RID and operation index.

val withdrawal = hbridge.get_erc20_withdrawal_by_tx(
tx_rid, // Rell transaction RID
op_index // Operation index within that transaction
);

Get event proof

Obtain the Merkle proof for the withdrawal event.

chr query -brid $BRIDGE get_event_merkle_proof \
-- eventHash=$WITHDRAWAL_EVENT_HASH

Replace $WITHDRAWAL_EVENT_HASH with the hash of the withdrawal event.

Retrieve withdrawal state slot IDs

Retrieve the state slot IDs associated with the beneficiary's withdrawals.

val slot_ids = hbridge.get_withdrawal_state_slot_ids_for_address(
beneficiary, // The beneficiary's address
network_id // The EVM network ID
);

Obtain account state proof again

To get the account state proof, use the following query.

chr query -brid $BRIDGE get_account_state_merkle_proof \
-- blockHeight=$MASS_EXIT_BLOCK_HEIGHT \
accountNumber=$ACCOUNT_STATE_SLOT_ID

Complete the withdrawal

To complete the withdrawal initiated before the mass exit, use the completeWithdrawalBySnapshot function:

function completeWithdrawalBySnapshot(
bytes calldata _stateRecord, // Account state data; contains header and a list of withdrawal event hashes
uint64 n, // Index of the withdrawal hash
bytes memory _event, // Withdrawal event
Data.Proof memory stateProof // Snapshot proof
)

Step 4: Emergency withdraw period

After triggering a mass exit, users have 90 days to submit snapshot proofs for their balances. If a balance doesn't have an associated account state snapshot—whether due to user inaction or misconfiguration on the Rell side—the snapshot-based withdrawal will fail. Following the 90-day period, the bridge owner can reclaim and withdraw those funds.

Step 5: Pausing the bridge

Any validator can temporarily pause the bridge if they detect unusual behavior. This action immediately freezes deposits and withdrawals, allowing time to investigate potential issues and decide whether a mass exit is necessary. The bridge owner can later unpause the bridge if the anomaly turns out to be a false positive.