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

# Exit Certificate Tool

> Chain-operator tool for exiting Agglayer: generate and settle a final certificate that moves every L2 balance back to L1, then serve claim proofs to users.

**Source code:** [github.com/agglayer/aggkit](https://github.com/agglayer/aggkit) (`tools/exit_certificate`, `tools/exit_certificate_claimer`)

## Overview

The Exit Certificate Tool decommissions an Agglayer-connected chain by producing one final certificate that moves every value-holding balance back to a destination network, then serves the claim proofs that end users need to withdraw those balances on L1.

It ships in AggKit as two standalone binaries:

| Binary                     | Role                                                                                                                                                                                                                                |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `exit_certificate`         | Operator CLI. Scans L2 state, builds the final `Certificate` with one `BridgeExit` per (address, token), signs it, and submits it to the Agglayer node.                                                                             |
| `exit_certificate_claimer` | HTTP service run after the certificate settles. Given a destination address it returns the bridge exits available for that address together with the merkle proofs and calldata needed to invoke `AgglayerBridge.claimAsset` on L1. |

The two binaries are built with `make build-exit_certificate` and `make build-exit_certificate_claimer` and are also included in the AggKit Docker image.

## When to use it

Use this tool to sunset an Agglayer-connected chain and return user balances to L1.

Prerequisites, verified by the tool before it does any work:

* The sequencer is stopped, and every pre-halt L2 to L1 bridge exit is already settled.
* The chain runs the Pessimistic Proof consensus. FEP chains are rejected.
* The Agglayer committee threshold for the chain is 1.
* The chain uses no custom gas token.
* The L2 RPC endpoint is an archive node that exposes `debug_accountRange`.
* Anvil is available on the operator's machine when shadow-fork verification is enabled (the default).

## Two-phase workflow

The CLI is deliberately split into a generation phase and a submission phase so that the signed certificate can be reviewed before it is sent to Agglayer.

<Steps>
  <Step title="Generate and sign">
    ```bash theme={null}
    ./target/exit_certificate --config parameters.toml
    ```

    Runs the full pipeline `CHECK → 0 → A → B → C → D → E → F → G → H → I → SIGN` and writes `exit-certificate-signed.json`. `SUBMIT` and `WAIT` are intentionally excluded from the default step set.
  </Step>

  <Step title="Submit and wait">
    ```bash theme={null}
    ./target/exit_certificate --config parameters.toml --step submit
    ./target/exit_certificate --config parameters.toml --step wait
    ```

    `submit` sends the signed certificate to the Agglayer node over gRPC. `wait` polls the node until the certificate is `Settled` or `InError`, then confirms settlement on L1 by matching the `VerifyBatchesTrustedAggregator` and `UpdateL1InfoTree(V2)` events.
  </Step>

  <Step title="Serve claim proofs">
    Start `exit_certificate_claimer` behind an HTTP endpoint that end users can query. For each destination address it returns the bridge exits that belong to the address together with the merkle proofs, global index, exit roots, and leaf fields required to call `AgglayerBridge.claimAsset` on L1. The `claim-asset.sh`, `claim-all.sh`, and `list-bridges.sh` helper scripts (and a docker-compose file) live next to the binary.
  </Step>
</Steps>

`--step` also accepts single step names, comma-separated lists, and ranges (`a-c`, `g-`, `0-wait`). Every step persists its result to the output directory and reads its inputs from files written by earlier steps, so any step can be re-run in isolation.

## Pipeline steps

| Step       | What it does                                                                                                                                                                                                                                            |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CHECK**  | Verifies prerequisites: Anvil available (shadow-fork mode), L1 RPC reachable, bridge addresses and network ID validated onchain, chain is PP, committee threshold = 1, no custom gas token, no unsettled L2 bridge exits.                               |
| **0**      | Resolves `targetBlock` and builds the Locked Balance Table (LBT) from `NewWrappedToken` events and per-token `totalSupply` calls.                                                                                                                       |
| **A**      | Collects every value-holding address using a state-trie dump (`debug_accountRange`) merged with per-token `Transfer` event logs.                                                                                                                        |
| **B**      | Classifies EOAs and contracts, fetches ETH and wrapped-token balances per EOA, detects ERC-20 contracts, and decomposes extra-token holders.                                                                                                            |
| **C**      | Computes smart-contract-locked value per token as `LBT.totalSupply − EOA accumulated`. The native token is measured directly from contract balances by default.                                                                                         |
| **D**      | Assembles the draft `Certificate`, with one `BridgeExit` per (EOA, token) plus one per token for the smart-contract-locked value routed to `exitAddress`. Set `options.skipSCLockedValue` to omit the SC-locked entries.                                |
| **E**      | Detects unclaimed L1 to L2 deposits on L1 and adds them to the certificate. Optional cross-check against the AggKit Bridge Service REST API.                                                                                                            |
| **F**      | Verifies token balances against the Agglayer node (`admin_getTokenBalance`) as a three-way check: LBT == Agglayer == certificate. Falls back to LBT versus certificate offline. Supports proportional capping on mismatch.                              |
| **G**      | Computes `NewLocalExitRoot`. `G1` syncs the full L2 bridge history into a lite database. `G2` builds the exit tree and, by default, replays every exit on an Anvil shadow-fork of the real bridge contract and verifies the result against `getRoot()`. |
| **H**      | Fetches `PreviousLocalExitRoot` (the settled LER) from the Agglayer node over gRPC and cross-checks it against Step G.                                                                                                                                  |
| **I**      | Assembles the final certificate (`NewLER`, `PrevLER`, `L1InfoTreeLeafCount` read from L1) and writes `exit-certificate-final.json`.                                                                                                                     |
| **SIGN**   | Signs the certificate with the configured signer and writes `exit-certificate-signed.json`.                                                                                                                                                             |
| **SUBMIT** | Explicit only. Verifies no pending certificate exists, captures the current L1 block, and submits the signed certificate to the Agglayer node over gRPC.                                                                                                |
| **WAIT**   | Explicit only. Polls the Agglayer node until the certificate is `Settled` or `InError`, then confirms L1 settlement events.                                                                                                                             |

## Configuration

`exit_certificate` reads a standalone configuration file. Both TOML and JSON are accepted; the format is chosen from the file extension. It does not consume the main AggKit configuration.

Minimum viable `parameters.toml`:

```toml theme={null}
l2RpcUrl = "https://your-l2-rpc.example.com"
l1RpcUrl = "https://your-l1-rpc.example.com"

l2BridgeAddress = "0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe"
l2NetworkId    = 1

# Finality keyword ("LatestBlock", "FinalizedBlock", …, with optional offset
# "LatestBlock/-10"), or a decimal or hex block number.
targetBlock = "LatestBlock"

# Receives smart-contract-locked funds on the destination network. Must not be
# the zero address and the operator must control its key. A multisig is
# strongly recommended. Not required when options.skipSCLockedValue = true.
exitAddress        = "0x...your-multisig..."
destinationNetwork = 0

# L1 contracts: aggchain base (Step CHECK) and PolygonZkEVMGlobalExitRootV2 (Step I).
sovereignRollupAddr     = "0x..."
l1GlobalExitRootAddress = "0x..."

[options]
outputDir        = "./output"
blockRange       = 10000
concurrencyLimit = 20
rpcBatchSize     = 200

# Agglayer admin RPC (Step F). Optional bearer token for IAP-protected endpoints.
agglayerAdminURL   = "https://admin-agglayer.example.com"
agglayerAdminToken = ""

# Agglayer gRPC (Steps H, SUBMIT, WAIT).
[options.agglayerClient.GRPC]
URL    = "agglayer.example.com:50051"
UseTLS = true

# Same signer format as aggsender's AggsenderPrivateKey.
[signerConfig]
Method   = "local"
Path     = "keystore.json"
Password = "..."
```

Fully annotated templates live next to the source at `tools/exit_certificate/parameters.toml.example` and `tools/exit_certificate/parameters.json.example`. Pre-filled configurations for known networks live under `tools/exit_certificate/config-examples/` (for example `zkevm-cardona.toml` and `zkevm-mainnet.toml`); the operator only needs to fill in the RPC URLs, `exitAddress`, Agglayer endpoints, and signer.

The claimer service has its own configuration template at `tools/exit_certificate_claimer/service/config.toml.example`.

## Signer

`signerConfig` uses the same shape as the AggSender signer, so any signer supported by AggKit is accepted, including local keystore files and cloud KMS backends. See the aggsender documentation for the full field list.

## Output files

Each step writes its result to `options.outputDir`. The main artifacts are:

| File                           | Written by | Purpose                                                          |
| ------------------------------ | ---------- | ---------------------------------------------------------------- |
| `exit-certificate-final.json`  | Step I     | The unsigned, final certificate.                                 |
| `exit-certificate-signed.json` | `SIGN`     | The signed certificate that `SUBMIT` sends to the Agglayer node. |

Intermediate files persist between runs, which is what allows any step to be resumed independently.

## Notes

* Step A requires an archive L2 node that exposes `debug_accountRange`.
* Anvil is only needed in shadow-fork mode (`options.verifyNewLocalExitRootUsingShadowFork = true`, the default). Setting it to `false` skips the extra verification and removes the Anvil requirement.
* Only Pessimistic Proof chains are supported. FEP chains are rejected by Step CHECK.
* The Agglayer committee threshold must be 1.
* The sequencer must be stopped, with every pre-halt L2 to L1 exit already settled, before the CHECK step will pass.
* `parameters.json` / `parameters.toml` and the output directory are git-ignored in the AggKit repository so that operator inputs and intermediate artifacts stay local.
