Documentation
¶
Index ¶
- Constants
- func MakeBridgeExit(originNetwork uint32, originTokenAddress common.Address, destNetwork uint32, ...) *agglayertypes.BridgeExit
- func Run(c *cli.Context) error
- func RunStepI(ctx context.Context, cfg *Config, certificate *agglayertypes.Certificate, ...) error
- func RunStepSign(ctx context.Context, cfg *Config, cert *agglayertypes.Certificate) (*agglayertypes.Certificate, error)
- type AccumulatedBalance
- type CertificateEntry
- type Config
- type DetectedERC20
- type DiscardedERC20
- type EOABalance
- type EOATokenBalance
- type ERC20Holder
- type ERC20HolderBreakdown
- type FailedBridgeExit
- type HolderBridge
- type L1Deposit
- type L1InfoTreeUpdate
- type L1InfoTreeV2Update
- type LBTEntry
- type LegacyToken
- type Options
- type RPCCall
- type RPCExecutionError
- type SCLockedValue
- type Step0Result
- type StepAResult
- type StepB1Result
- type StepB2Result
- type StepB3Result
- type StepBResult
- type StepCResult
- type StepCheckResult
- type StepDResult
- type StepEResult
- type StepFResult
- type StepG1Result
- type StepGResult
- type StepHResult
- type StepSubmitResult
- type StepWaitResult
- type TokenBalanceCheck
- type WrappedToken
- type WrappedTokenBalance
Constants ¶
const ( // CapModeNone forbids capping: Step F fails if any bridge exit would have to be trimmed. CapModeNone = "none" // CapModeByAppearance allocates each token's cap budget to its exits in appearance order. CapModeByAppearance = "appearance" // CapModeByAmount allocates each token's cap budget to its smallest-amount exits first, so the // largest-amount exits are the first to be capped/dropped. CapModeByAmount = "amount" )
Cap modes for Options.CapMode (how Step F trims exits when capping a certificate).
const ( // BridgeServiceTypeAggkit selects the aggkit bridge service API (/bridge/v1/bridges). BridgeServiceTypeAggkit = "aggkit" // BridgeServiceTypeZkevm selects the zkevm-bridge-service API (/pending-bridges). BridgeServiceTypeZkevm = "zkevm" )
Variables ¶
This section is empty.
Functions ¶
func MakeBridgeExit ¶
func MakeBridgeExit( originNetwork uint32, originTokenAddress common.Address, destNetwork uint32, destAddress common.Address, amount *big.Int, ) *agglayertypes.BridgeExit
MakeBridgeExit creates a BridgeExit for an asset transfer. Exported for tests.
func RunStepI ¶
func RunStepI( ctx context.Context, cfg *Config, certificate *agglayertypes.Certificate, gResult *StepGResult, hResult *StepHResult, ) error
RunStepI assembles the final certificate by applying the NewLocalExitRoot from Step G, the PreviousLocalExitRoot from Step H, and the L1InfoTreeLeafCount from L1.
func RunStepSign ¶
func RunStepSign( ctx context.Context, cfg *Config, cert *agglayertypes.Certificate, ) (*agglayertypes.Certificate, error)
RunStepSign signs the certificate with the configured keystore and sets AggchainData to an AggchainDataMultisig containing the ECDSA signature.
Types ¶
type AccumulatedBalance ¶
type AccumulatedBalance struct {
WrappedTokenAddress common.Address `json:"wrappedTokenAddress"`
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress common.Address `json:"originTokenAddress"`
TotalBalance string `json:"totalBalance"`
}
AccumulatedBalance holds the total balance across all EOAs for a single token.
type CertificateEntry ¶
type CertificateEntry struct {
DestinationNetwork uint32 `json:"destinationNetwork"`
DestinationAddress string `json:"destinationAddress"`
Amount string `json:"amount"`
}
CertificateEntry is one bridge exit entry for a given token, used in mismatch reports.
type Config ¶
type Config struct {
L2RPCURL string `json:"l2RpcUrl"`
L1RPCURL string `json:"l1RpcUrl"`
L2BridgeAddress common.Address `json:"l2BridgeAddress"`
L1BridgeAddress common.Address `json:"l1BridgeAddress"`
L2NetworkID uint32 `json:"l2NetworkId"`
TargetBlock aggkittypes.BlockNumberFinality `json:"targetBlock"`
ExitAddress common.Address `json:"exitAddress"`
DestinationNetwork uint32 `json:"destinationNetwork"`
SovereignRollupAddr common.Address `json:"sovereignRollupAddr"`
// L1GlobalExitRootAddress is the address of the PolygonZkEVMGlobalExitRootV2 contract on L1.
// Required for Step I to fetch the L1InfoTreeLeafCount from UpdateL1InfoTreeV2 events.
L1GlobalExitRootAddress common.Address `json:"l1GlobalExitRootAddress"`
// RollupManagerAddress is the optional address of the PolygonRollupManager (AgglayerManager)
// contract on L1. Used by Step WAIT to confirm the certificate was settled on L1 by scanning for
// the VerifyBatchesTrustedAggregator event matching the rollupID and the certificate's exit root.
// When unset it is resolved on-chain from SovereignRollupAddr.rollupManager() (PolygonConsensusBase).
RollupManagerAddress common.Address `json:"rollupManagerAddress"`
Options Options `json:"options"`
SignerConfig signertypes.SignerConfig `json:"-"`
// ConfigPath is the path the config was loaded from, and ConfigSHA256 is the
// hex sha256 of the exact on-disk bytes that produced this Config. Both are set
// by LoadConfig and used by the startup traceability banner. Not serialized.
ConfigPath string `json:"-"`
ConfigSHA256 string `json:"-"`
}
Config holds all parameters required by the exit certificate tool.
func LoadConfig ¶
LoadConfig reads and validates the config file. The format is selected by file extension: ".toml" is parsed as TOML, anything else (".json" or no extension) as JSON.
type DetectedERC20 ¶
type DetectedERC20 struct {
Address common.Address `json:"address"`
Name string `json:"name,omitempty"`
Symbol string `json:"symbol,omitempty"`
TotalSupply string `json:"totalSupply"`
WrappedTokenBalances []WrappedTokenBalance `json:"wrappedTokenBalances"`
}
DetectedERC20 holds an ERC-20 contract that holds at least one tracked wrapped token.
type DiscardedERC20 ¶
type DiscardedERC20 struct {
Address common.Address `json:"address"`
Name string `json:"name,omitempty"`
Symbol string `json:"symbol,omitempty"`
TotalSupply string `json:"totalSupply"`
}
DiscardedERC20 is an ERC-20 contract that holds none of the tracked wrapped tokens.
type EOABalance ¶
type EOABalance struct {
Address common.Address `json:"address"`
ETHBalance string `json:"ethBalance"`
Tokens []EOATokenBalance `json:"tokens"`
}
EOABalance holds all non-zero balances for a single EOA address.
type EOATokenBalance ¶
type EOATokenBalance struct {
WrappedTokenAddress common.Address `json:"wrappedTokenAddress"`
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress common.Address `json:"originTokenAddress"`
Balance string `json:"balance"`
}
EOATokenBalance records a single token balance for an EOA.
type ERC20Holder ¶
ERC20Holder is an (address, balance) pair produced by Step B2.
type ERC20HolderBreakdown ¶
type ERC20HolderBreakdown struct {
Address common.Address `json:"address"`
Holders []ERC20Holder `json:"holders"`
// Detected is the collateral info from Step B2: which tracked wrapped tokens this
// contract holds, plus its name/symbol/totalSupply. Nil when the contract was not
// present in the B2 detected list (e.g. it holds no tracked wrapped tokens).
Detected *DetectedERC20 `json:"detected,omitempty"`
}
ERC20HolderBreakdown holds the full holder decomposition for a single ERC-20 contract produced by Step B3.
type FailedBridgeExit ¶
type FailedBridgeExit struct {
Index int `json:"index"`
Error string `json:"error"`
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress string `json:"originTokenAddress"`
DestinationNetwork uint32 `json:"destinationNetwork"`
DestinationAddress string `json:"destinationAddress"`
Amount string `json:"amount"`
IsNative bool `json:"isNative"`
L2TokenAddress string `json:"l2TokenAddress"`
}
FailedBridgeExit records the bridge exit whose replay aborted Step G, persisted to step-g-failed-exit.json so the offending exit can be inspected after the run fails.
type HolderBridge ¶
type HolderBridge struct {
VaultAddress common.Address `json:"vaultAddress"`
WrappedTokenAddress common.Address `json:"wrappedTokenAddress"`
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress common.Address `json:"originTokenAddress"`
HolderAddress common.Address `json:"holderAddress"`
Amount string `json:"amount"`
}
HolderBridge is an individual bridge exit for a holder of an ERC-20 vault/staking contract, representing their proportional share of the tracked wrapped tokens locked inside that contract. Produced by Step C from the Step B3 breakdown data.
type L1Deposit ¶
type L1Deposit struct {
LeafType uint8 `json:"leafType"`
OriginNetwork uint32 `json:"originNetwork"`
OriginAddress common.Address `json:"originAddress"`
DestinationNetwork uint32 `json:"destinationNetwork"`
DestinationAddress common.Address `json:"destinationAddress"`
Amount *big.Int `json:"amount"`
Metadata []byte `json:"metadata"`
DepositCount uint32 `json:"depositCount"`
BlockNumber uint64 `json:"blockNumber"`
TxHash common.Hash `json:"txHash"`
}
L1Deposit represents an L1 bridge deposit targeting the L2 chain.
type L1InfoTreeUpdate ¶
type L1InfoTreeUpdate struct {
MainnetExitRoot common.Hash `json:"mainnetExitRoot"`
RollupExitRoot common.Hash `json:"rollupExitRoot"`
TxHash common.Hash `json:"txHash"`
}
L1InfoTreeUpdate captures an UpdateL1InfoTree(bytes32 indexed mainnetExitRoot, bytes32 indexed rollupExitRoot) event from the L1 GlobalExitRoot contract.
type L1InfoTreeV2Update ¶
type L1InfoTreeV2Update struct {
CurrentL1InfoRoot common.Hash `json:"currentL1InfoRoot"`
LeafCount uint32 `json:"leafCount"`
Blockhash common.Hash `json:"blockhash"`
MinTimestamp uint64 `json:"minTimestamp"`
TxHash common.Hash `json:"txHash"`
}
L1InfoTreeV2Update captures an UpdateL1InfoTreeV2(bytes32 currentL1InfoRoot, uint32 indexed leafCount, uint256 blockhash, uint64 minTimestamp) event from the L1 GlobalExitRoot contract.
type LBTEntry ¶
type LBTEntry struct {
WrappedTokenAddress common.Address `json:"wrappedTokenAddress"`
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress common.Address `json:"originTokenAddress"`
Balance string `json:"balance"`
// LegacyTokens holds previous wrapped addresses (replaced via SetSovereignTokenAddress)
// and their totalSupply at the target block. Populated only when an override was applied.
LegacyTokens []LegacyToken `json:"legacyTokens,omitempty"`
}
LBTEntry is a single entry from the Local Balance Tree file exported by the getLBT tool.
func LoadLBTEntries ¶
LoadLBTEntries reads the full LBT JSON file.
type LegacyToken ¶
LegacyToken records a wrapped token address that was replaced by a SetSovereignTokenAddress override, along with its totalSupply at the target block.
type Options ¶
type Options struct {
BlockRange int `json:"blockRange"`
ConcurrencyLimit int `json:"concurrencyLimit"`
RPCBatchSize int `json:"rpcBatchSize"`
RPCDelayMs int `json:"rpcDelayMs"`
OutputDir string `json:"outputDir"`
L1StartBlock uint64 `json:"l1StartBlock"`
// L1EndBlock is an optional L1 cutoff block for the L1 reads. When set (>0), Step E scans L1 for
// unclaimed deposits only up to this block (and filters the bridge service cross-check
// accordingly) and Step I starts its backward UpdateL1InfoTreeV2 scan from it, so L1 deposits
// submitted after the L2 snapshot cannot block the pipeline (AET-03). Pick a block at or after
// the moment the sequencer was stopped. 0 (the default) means no cutoff: the current latest L1
// block is used (previous behaviour).
L1EndBlock uint64 `json:"l1EndBlock"`
L2StartBlock uint64 `json:"l2StartBlock"`
AgglayerAdminURL string `json:"agglayerAdminURL"`
// AgglayerAdminToken is an optional Bearer token for authenticating requests to agglayerAdminURL.
// Required when the admin endpoint is protected by Google Cloud IAP.
// Obtain it with: gcloud auth print-identity-token --impersonate-service-account=<SA>
// --audiences=<AUDIENCE> --include-email
AgglayerAdminToken string `json:"agglayerAdminToken"`
AgglayerClient agglayer.ClientConfig `json:"agglayerClient"`
// UseAgglayerAdminToStepFCheck, when true (the default), runs Step F: it queries the agglayer
// admin API (admin_getTokenBalance) and verifies the per-token balances against the certificate
// and LBT. When false, Step F is skipped entirely (no agglayer admin query, no balance check).
UseAgglayerAdminToStepFCheck bool `json:"useAgglayerAdminToStepFCheck"`
// IgnoreGenesisBalance, when true, suppresses the abort that fires when any EOA or contract has a
// non-zero ETH balance at block 0 (a genesis preload that would inflate the exit certificate
// totals): the check still runs and warns, but the run continues. Defaults to false (abort); set
// to true only for Kurtosis or test environments.
IgnoreGenesisBalance bool `json:"ignoreGenesisBalance"`
// NativeSCLockedFromContracts, when true (the default), computes the native-token SC-locked value
// in Step C from the actual ETH balances held by contract accounts (summed, excluding the L2
// bridge) rather than from LBT − EOA_accumulated. That formula underflows on chains with a native
// genesis premint, clamping to 0 and silently dropping contract-held ETH. Set to false to fall
// back to the LBT − EOA derivation for the native token.
NativeSCLockedFromContracts bool `json:"nativeSCLockedFromContracts"`
// SkipSCLockedValue, when true, excludes the SC-locked funds from the certificate: Step D does not
// create the per-token bridge exits that send each PendingSCLockedBalance (Step C) to exitAddress —
// those funds are intentionally left behind. exitAddress is then no longer required. Step C still
// runs (its output records what is left behind), and Step F discounts the omitted per-token amounts
// from the LBT/agglayer budgets so the balance check still requires strict equality. Holder-bridge
// exits (vault redistribution to real holders) are unaffected. Defaults to false.
SkipSCLockedValue bool `json:"skipSCLockedValue"`
// IgnoreBalanceMismatch suppresses the error returned by Step F when token balances
// do not match. Set to true only when investigating discrepancies without blocking the pipeline.
IgnoreBalanceMismatch bool `json:"ignoreBalanceMismatch"`
// IgnoreUnclaimed skips adding unclaimed L1→L2 deposits to the certificate in Step E.
// The step still detects and warns about any unclaimed deposits, but the certificate is left unchanged.
IgnoreUnclaimed bool `json:"ignoreUnclaimed"`
// ExtraERC20Contracts is an optional list of ERC-20 contract addresses whose token holders
// are decomposed in Step B3. Step A includes these contracts in its Transfer-log scan so even
// passive holders (no ETH/nonce/code) are discovered; Step B3 then queries each contract with
// balanceOf for every EOA address collected in Step A.
ExtraERC20Contracts []common.Address `json:"extraErc20Contracts,omitempty"`
// BridgeServiceURL is the base URL of the bridge service REST API.
// When set, Step E queries the bridge service for pending bridges targeting this L2 and returns an
// error if any unclaimed deposits are found.
// Aggkit example: "http://127.0.0.1:32970"
// zkevm example: "http://127.0.0.1:33019"
BridgeServiceURL string `json:"bridgeServiceURL"`
// BridgeServiceType selects the bridge service API flavour: "aggkit" (default) or "zkevm".
BridgeServiceType string `json:"bridgeServiceType"`
// IgnoreUnsupportedL2Events, when true, makes the Step G lite syncer log a warning
// and continue instead of aborting when it sees an L2 event that would invalidate a
// BridgeEvent-only reconstruction (SetSovereignTokenAddress, MigrateLegacyToken,
// RemoveLegacySovereignTokenAddress, BackwardLET, ForwardLET). The computed NewLocalExitRoot may
// then be incorrect; enable only to inspect such a chain knowingly. Defaults to false.
IgnoreUnsupportedL2Events bool `json:"ignoreUnsupportedL2Events"`
// IgnoreLERMismatch, when true, downgrades the AET-11 unsettled-bridge-exits
// verification — Step CHECK check 9, the Step 0 guard and Step H's LER cross-check — from an
// abort to a warning: the pipeline proceeds even when the L2 bridge's LER at the target block
// does not match the agglayer's settled LER. The resulting certificate chains from a
// PrevLocalExitRoot that does not cover every emitted bridge exit, so the agglayer will most
// likely reject it — enable only to inspect such a snapshot knowingly. It does not affect
// Step H's pending-certificate guard. Defaults to false.
IgnoreLERMismatch bool `json:"ignoreLERMismatch"`
// VerifyNewLocalExitRootUsingShadowFork, when true (the default), makes Step G2 spin up the Anvil
// shadow-fork, replay every bridge exit against the real bridge contract, and verify the computed
// NewLocalExitRoot against the contract's getRoot(). When false, Step G2 computes the
// NewLocalExitRoot purely off-chain from the lite exit tree (Step G1's genesis→fork bridges plus
// the certificate's bridge exits) without launching Anvil — much faster, but it trusts the
// off-chain leaf encoding (notably each exit's metadata) rather than verifying it on-chain.
VerifyNewLocalExitRootUsingShadowFork bool `json:"verifyNewLocalExitRootUsingShadowFork"`
// CapMode selects how bridge exits are trimmed when Step F needs to cap a certificate whose token
// totals exceed the allowed budget. "none" (the default) forbids capping entirely: if any exit
// would have to be trimmed, Step F fails instead. "amount" allocates each token's budget to its
// smallest-amount exits first, so the largest holders are the first to be capped/dropped once the
// budget runs out. "appearance" allocates to its exits in the order they appear, capping/dropping
// the ones that no longer fit. In both trimming modes the surviving exits are emitted in their
// original order.
CapMode string `json:"capMode"`
// GenesisPrefundETHWei is an optional amount of native token (in Wei, as a decimal string) that was
// pre-funded at genesis. Those funds sit in accounts — and therefore in the certificate's bridge
// exits — without a matching agglayer deposit, so Step F subtracts this value from the native-token
// certificate sum before comparing it against the agglayer balance and the LBT (which only count
// genuinely bridged funds), logging the certificate total, the pre-fund and the difference. The
// pre-fund has no agglayer collateral and can never be bridged out: even when the checks match,
// Step F produces a capped certificate trimming the native exits to min(agglayer, LBT) — this
// requires a trimming CapMode ("amount" or "appearance"; the default "none" fails instead). The
// Step 0 LBT and Step C SC-locked totals are untouched. Step B verifies the declared value against
// the detected genesis ETH preload total. Empty means 0. Typical testnet value:
// 100000 ETH = "100000000000000000000000".
GenesisPrefundETHWei string `json:"genesisPrefundETHWei"`
}
Options holds tuning parameters for RPC parallelism and output.
type RPCExecutionError ¶
RPCExecutionError is returned by singleRPC when the node returns an RPC-level error. Data holds the raw hex-encoded revert payload (e.g. ABI-encoded custom error).
func (*RPCExecutionError) Error ¶
func (e *RPCExecutionError) Error() string
type SCLockedValue ¶
type SCLockedValue struct {
WrappedTokenAddress common.Address `json:"wrappedTokenAddress"`
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress common.Address `json:"originTokenAddress"`
LBTBalance string `json:"lbtBalance"`
EOAAccumulated string `json:"eoaAccumulated"`
// ERC20HoldersCovered is the portion of SC-locked value distributed as individual
// bridge exits to holders of ERC-20 vault contracts (from Step B3 breakdowns).
// Empty when no breakdown applies to this token.
ERC20HoldersCovered string `json:"erc20HoldersCovered,omitempty"`
// TotalSCLockedBalance is the gross value locked in smart contracts: LBT - EOA.
// It includes both the portion covered by ERC-20 holder bridges and the remainder.
TotalSCLockedBalance string `json:"totalSCLockedBalance"`
// PendingSCLockedBalance is the net SC-locked value that requires a bridge exit to
// exitAddress: TotalSCLockedBalance − ERC20HoldersCovered.
PendingSCLockedBalance string `json:"pendingSCLockedBalance"`
}
SCLockedValue holds the computed smart-contract-locked value for a single token.
type Step0Result ¶
type Step0Result struct {
TargetBlock uint64 `json:"targetBlock"`
Entries []LBTEntry `json:"entries"`
}
Step0Result holds the output of Step 0 (LBT generation).
type StepAResult ¶
type StepAResult struct {
Addresses []common.Address `json:"addresses"`
WrappedTokens []WrappedToken `json:"-"`
}
StepAResult holds the output of Step A (address discovery via state dump + Transfer logs).
func RunStepA ¶
func RunStepA( ctx context.Context, cfg *Config, targetBlock uint64, wrappedTokens []WrappedToken, ) (*StepAResult, error)
RunStepA collects every value-holding address at targetBlock without replaying the full transaction history via debug_traceTransaction.
It always combines two cheap sources and merges them, each covering the other's blind spot:
- a state-trie dump at targetBlock (debug_accountRange) — every account with non-zero balance/nonce/code (all native-ETH holders and every contract), and
- Transfer event logs (eth_getLogs) per wrapped token and per extra ERC-20 contract (cfg.Options.ExtraERC20Contracts) — every token holder, including token-only EOAs that never appear in a state dump or a trace (an ERC-20 transfer only mutates the token contract's storage, so the recipient account itself is never "touched"). Extra ERC-20 holders must be discovered here so Step B3 can probe their balances.
type StepB1Result ¶
type StepB1Result struct {
EOABalances []EOABalance `json:"eoaBalances"`
Accumulated []AccumulatedBalance `json:"accumulated"`
ContractAddresses []common.Address `json:"contractAddresses"`
}
StepB1Result holds the output produced exclusively by Step B1 (address classification and balance fetching). It does not include the ERC-20 detection data added by Step B2.
func RunStepB1 ¶
func RunStepB1(ctx context.Context, cfg *Config, targetBlock uint64, stepA *StepAResult) (*StepB1Result, error)
RunStepB1 classifies addresses as EOA vs contract, then collects ETH and wrapped token balances at targetBlock for all EOAs.
type StepB2Result ¶
type StepB2Result struct {
// DetectedERC20s are contracts that hold at least one tracked wrapped token.
DetectedERC20s []DetectedERC20 `json:"detectedErc20s"`
// DiscardedERC20s are contracts that responded to totalSupply() but hold none
// of the tracked wrapped tokens and are therefore irrelevant to the certificate.
DiscardedERC20s []DiscardedERC20 `json:"discardedErc20s,omitempty"`
}
StepB2Result holds the output of Step B2.
func RunStepB2 ¶
func RunStepB2( ctx context.Context, cfg *Config, targetBlock uint64, contractAddrs, eoaAddrs []common.Address, wrappedTokens []WrappedToken, ) (*StepB2Result, error)
RunStepB2 probes the contract addresses from Step B1 for the ERC-20 interface. For each contract that responds to totalSupply() with a non-zero value it checks whether it holds any of the tracked wrapped tokens:
- holds at least one → DetectedERC20 (relevant to the certificate)
- holds none → DiscardedERC20 (no tracked value locked inside)
RPC execution errors on totalSupply() calls are silently treated as "not ERC-20".
type StepB3Result ¶
type StepB3Result struct {
Breakdowns []ERC20HolderBreakdown `json:"breakdowns"`
}
StepB3Result holds the output of Step B3 (extra ERC-20 holder decomposition).
func RunStepB3 ¶
func RunStepB3( ctx context.Context, cfg *Config, targetBlock uint64, eoaAddrs []common.Address, b2Result *StepB2Result, ) (*StepB3Result, error)
RunStepB3 fetches the per-EOA token balance for each contract listed in cfg.Options.ExtraERC20Contracts. For each address, balanceOf is called for every EOA collected in Step A. Collateral info (tracked wrapped tokens held) is attached from the B2 detected list when available.
type StepBResult ¶
type StepBResult struct {
EOABalances []EOABalance `json:"eoaBalances"`
Accumulated []AccumulatedBalance `json:"accumulated"`
ContractAddresses []common.Address `json:"contractAddresses"`
DetectedERC20s []DetectedERC20 `json:"detectedErc20s,omitempty"`
DiscardedERC20s []DiscardedERC20 `json:"discardedErc20s,omitempty"`
ERC20HolderBreakdowns []ERC20HolderBreakdown `json:"erc20HolderBreakdowns,omitempty"`
// NativeContractLocked, when non-empty, is the total native ETH (wei, decimal) held by contract
// accounts (bridge excluded). Set only when options.nativeSCLockedFromContracts is enabled; Step C
// uses it as the native token's SC-locked value instead of the LBT − EOA formula.
NativeContractLocked string `json:"-"`
}
StepBResult holds the combined output of Step B (B1 + B2 + B3).
func RunStepB ¶
func RunStepB(ctx context.Context, cfg *Config, targetBlock uint64, stepA *StepAResult) (*StepBResult, error)
RunStepB runs Step B1, B2, and B3 and returns the combined result. B1 classifies addresses and collects balances; B2 detects ERC-20 contracts; B3 fetches holder breakdowns for the contracts listed in ExtraERC20Contracts.
type StepCResult ¶
type StepCResult struct {
SCLockedValues []SCLockedValue `json:"scLockedValues"`
// HolderBridges are individual bridge exits for holders of ERC-20 vault contracts
// whose breakdowns were provided by Step B3. These replace what would otherwise be a
// single SC-locked exit to exitAddress for the portion of value they cover.
HolderBridges []HolderBridge `json:"holderBridges,omitempty"`
}
StepCResult holds the output of Step C.
func RunStepC ¶
func RunStepC(lbtEntries []LBTEntry, stepB *StepBResult) (*StepCResult, error)
RunStepC computes the value locked in smart contracts for each token.
Formula: SC_locked = LBT_totalSupply − accumulated_EOA_balances
When ERC20HolderBreakdowns are present (from Step B3), the portion of each token held by a vault/staking contract is distributed proportionally to its holders as individual HolderBridge exits instead of a single exit to exitAddress. The corresponding SC_locked value is reduced by the amount distributed.
type StepCheckResult ¶
type StepCheckResult struct {
AnvilInstalled bool `json:"anvilInstalled"`
BridgeNetworkID uint32 `json:"bridgeNetworkID"`
// L1BridgeAddressStatus records the l1BridgeAddress verification outcome: "ok" when networkID()
// on the configured address returns 0 (the L1/mainnet network), "invalid (networkID()=N)" when
// it hosts a non-L1 bridge, "error" when the call fails, "unchecked" when the L1 RPC is unavailable.
L1BridgeAddressStatus string `json:"l1BridgeAddressStatus"`
// RollupManagerBridgeAddress is the canonical L1 bridge address published by the RollupManager
// (bridgeAddress()), recorded so a failed l1BridgeAddress check shows the correct value to use.
RollupManagerBridgeAddress string `json:"rollupManagerBridgeAddress,omitempty"`
NetworkType string `json:"networkType"`
Threshold uint64 `json:"threshold"`
SignerCount int `json:"signerCount"`
Signers []string `json:"signers,omitempty"`
GasTokenAddress string `json:"gasTokenAddress,omitempty"`
GasTokenNetwork uint32 `json:"gasTokenNetwork,omitempty"`
WETHToken string `json:"wethToken,omitempty"`
// UnsettledExitsStatus records the AET-11 unsettled-bridge-exits check outcome: "ok" when the
// L2 bridge's LER at the target block equals the agglayer's settled LER, "unsettled exits at
// block N" on mismatch, "error" when a query failed, "unchecked" when the agglayer gRPC URL is
// not configured.
UnsettledExitsStatus string `json:"unsettledExitsStatus,omitempty"`
// SettledLER / L2BridgeLER are the two roots the AET-11 check compared: the agglayer's last
// settled LER and the L2 bridge's getRoot() at the resolved target block.
SettledLER string `json:"settledLER,omitempty"`
L2BridgeLER string `json:"l2BridgeLER,omitempty"`
}
StepCheckResult holds the output of Step CHECK (prerequisite verification).
func RunStepCheck ¶
func RunStepCheck(ctx context.Context, cfg *Config) (*StepCheckResult, error)
RunStepCheck verifies prerequisites before running the pipeline:
- Anvil is installed ($PATH) — only required (counted as a failure) when Step G2 will use the shadow-fork (options.verifyNewLocalExitRootUsingShadowFork=true); otherwise its absence is just logged.
- L1 RPC is set and reachable.
- l1BridgeAddress is the L1 bridge: networkID() on it must return 0 (the L1/mainnet network).
- L2 network ID matches the bridge contract.
- sovereignRollupAddr is set.
- Network type is PP (FEP is not supported).
- Multisig threshold is 1, and l1BridgeAddress matches both aggchainbase.bridgeAddress() and the canonical rollupManager.bridgeAddress().
- No custom gas token is configured on the L2 bridge.
- No unsettled L2 bridge exits at the target block (AET-11): the L2 bridge's local exit root at the resolved target block must equal the agglayer's last settled LER, otherwise the certificate cannot be generated from that snapshot and Step H would abort after the expensive scan/replay phases.
All checks run regardless of individual failures. Returns a combined error listing every failed check.
type StepDResult ¶
type StepDResult struct {
Certificate *agglayertypes.Certificate `json:"certificate"`
}
StepDResult holds the output of Step D.
func RunStepD ¶
func RunStepD(cfg *Config, stepB *StepBResult, stepC *StepCResult) (*StepDResult, error)
RunStepD builds the exit certificate from EOA balances (Step B) and SC-locked values (Step C).
Creates BridgeExit entries for:
- Every (EOA, token) pair with a non-zero balance
- Every holder of an ERC-20 vault/staking contract (from Step C HolderBridges)
- Every token with remaining SC-locked value, directed to exitAddress — omitted entirely when options.skipSCLockedValue is true (those funds are intentionally left behind; Step F discounts them from the LBT/agglayer budgets)
type StepEResult ¶
type StepEResult struct {
// UnclaimedBridges are unclaimed L1→L2 deposits with leaf_type=asset that were added
// to the certificate as bridge exits and imported bridge exits.
UnclaimedBridges []L1Deposit `json:"unclaimedBridges"`
// UnclaimedMessages are unclaimed L1→L2 deposits with leaf_type=message. These are
// logged as warnings but NOT added to the certificate (messages are not transferable assets).
UnclaimedMessages []L1Deposit `json:"unclaimedMessages,omitempty"`
FinalCertificate *agglayertypes.Certificate `json:"finalCertificate"`
}
StepEResult holds the output of Step E.
func RunStepE ¶
func RunStepE( ctx context.Context, cfg *Config, certificate *agglayertypes.Certificate, ) (*StepEResult, error)
RunStepE finds unclaimed L1→L2 bridge deposits and reports them.
Approach:
- Scan L1 bridge for BridgeEvent where destinationNetwork == L2 networkId
- For each deposit, call isClaimed(depositCount, 0) on the L2 bridge contract
- Message deposits (leaf_type=1) are saved separately and never added to the certificate.
- Asset deposits (leaf_type=0): if none, the certificate is passed through unchanged. If ignoreUnclaimed=true, detected deposits are logged but the certificate is unchanged. If ignoreUnclaimed=false and any assets are found, the step errors (Merkle proofs not yet implemented).
type StepFResult ¶
type StepFResult struct {
AllMatch bool `json:"allMatch,omitempty"`
TokenBalances json.RawMessage `json:"tokenBalances,omitempty"`
Checks []TokenBalanceCheck `json:"checks,omitempty"`
// CappedCertificate is set when mismatches were found and ignoreBalanceMismatch=true.
// Bridge exits are trimmed so their per-token sum equals min(agglayer, lbt); the allocation
// order is controlled by Options.CapMode (see capCertificateExits).
CappedCertificate *agglayertypes.Certificate `json:"cappedCertificate,omitempty"`
}
StepFResult holds the output of Step F (agglayer token balance check).
func RunStepF ¶
func RunStepF( ctx context.Context, cfg *Config, certificate *agglayertypes.Certificate, lbtEntries []LBTEntry, scLockedValues []SCLockedValue, ) (*StepFResult, error)
RunStepF verifies the certificate's per-token bridge-exit sums.
When useAgglayerAdminToStepFCheck is true (the default) it queries the agglayer admin API for token balances and performs a three-way comparison: LBT (Step 0 total supplies) == agglayer balance == sum of certificate bridge exits. agglayerAdminURL is required. lbtEntries may be nil, in which case it falls back to a two-way agglayer-vs-certificate comparison.
When useAgglayerAdminToStepFCheck is false it skips the agglayer admin query and instead runs an offline two-way comparison of the LBT (Step 0) totals against the certificate bridge-exit sums (see runStepFOfflineLBT). When no LBT data is available there is nothing to compare and the step is skipped.
scLockedValues carries the Step C per-token SC-locked amounts. It is only used when options.skipSCLockedValue is true: those amounts were intentionally omitted from the certificate by Step D, so they are discounted from the LBT/agglayer amounts before comparing (see discountSkippedSCLocked). With the option disabled the values are ignored.
type StepG1Result ¶
type StepG1Result struct {
// ShadowForkBlock is the L2 block Step G2 forks at — the resolved targetBlock up to which Step G1
// lite-synced the bridge history.
ShadowForkBlock uint64 `json:"shadowForkBlock"`
}
StepG1Result holds the output of Step G1: the L2 block at which Step G2 spins up its Anvil shadow-fork. Step G1 lite-syncs the L2 bridge history from genesis up to that block into the lite DB Step G2 reuses.
func RunStepG1 ¶
RunStepG1 persists the L2 bridge history Step G2 needs and resolves the block Step G2 forks at.
It syncs every L2 bridge from genesis up to targetBlock against the real L2 (cfg.L2RPCURL) with the lite bridge syncer, persisting them (no tree yet) so Step G2 can insert the replayed bridges on top and build the whole exit tree once. The full-history scan runs against the fast real L2 rather than the slow Anvil fork. The shadow-fork block is exactly the resolved targetBlock (the lite syncer fetches that range, no overshoot), so Anvil forks there aligned to the contract's state at that block.
type StepGResult ¶
type StepGResult struct {
// InitialLocalExitRoot is the LER read from the bridge contract at targetBlock,
// before any bridge exits from the certificate are replayed.
InitialLocalExitRoot common.Hash `json:"initialLocalExitRoot"`
NewLocalExitRoot common.Hash `json:"newLocalExitRoot"`
BridgeExitCount uint64 `json:"bridgeExitCount"`
// BridgeExitMetadata holds each bridge exit's raw leaf metadata, in the same order as
// Certificate.BridgeExits (in shadow-fork mode it is verified against the Metadata field of the
// BridgeEvent the replay emitted for the exit). Step I applies these values to each
// BridgeExit.Metadata before finalising the certificate.
BridgeExitMetadata [][]byte `json:"bridgeExitMetadata,omitempty"`
}
StepGResult holds the output of Step G (NewLocalExitRoot calculation).
func RunStepG2 ¶
func RunStepG2( ctx context.Context, cfg *Config, forkBlock uint64, certificate *agglayertypes.Certificate, lbtEntries []LBTEntry, ) (*StepGResult, error)
RunStepG2 computes Certificate.NewLocalExitRoot and the per-exit metadata.
In both modes the certificate's bridge exits keep their incoming order — deterministic since Steps D/E/F are — and the NewLocalExitRoot is the off-chain lite tree root built from the exits in that order (the order agglayer rebuilds the LER from), so the same on-chain state always yields the same certificate.
- By default (options.verifyNewLocalExitRootUsingShadowFork is true — see defaultOptions) it spins up the Anvil shadow-fork, replays every exit against the real bridge contract, and recovers the on-chain metadata. The replay's tx ordering (and thus each exit's deposit count) is non-deterministic, so the contract's getRoot() is used only as a verification anchor: a lite tree built from the exits sorted by the replayed deposit counts must reproduce it, proving the off-chain leaf encoding matches the real exit tree before that same encoding is trusted for the certificate-order root.
- When the option is false it skips Anvil and takes the off-chain lite tree root directly (trusting the off-chain leaf encoding — nothing to verify against).
forkBlock is the block resolved by Step G1. lbtEntries (Step 0 output) is used only by the shadow-fork path as a wrapped-token lookup so getTokenWrappedAddress RPC calls are avoided.
type StepHResult ¶
type StepHResult struct {
PreviousLocalExitRoot common.Hash `json:"previousLocalExitRoot"`
// Height is the certificate height to use for the exit certificate (settled_height + 1,
// or 0 if no certificate has been settled yet).
Height uint64 `json:"height"`
}
StepHResult holds the output of Step H (PreviousLocalExitRoot and next height from agglayer).
func RunStepH ¶
func RunStepH(ctx context.Context, cfg *Config, gResult *StepGResult) (*StepHResult, error)
RunStepH fetches the PreviousLocalExitRoot for the L2 network from the agglayer by calling GetNetworkInfo and reading the SettledLER field. gResult is the output of Step G; when provided, its InitialLocalExitRoot is compared against the agglayer's settled LER and an error is returned on mismatch.
type StepSubmitResult ¶
type StepSubmitResult struct {
CertificateHash common.Hash `json:"certificateHash"`
// L1LatestBlockBeforeSubmittingCertificate is the latest L1 block number
// captured right before the certificate was sent to the agglayer. It marks
// the L1 starting point from which to look for the block where the agglayer
// settles this certificate on L1 (e.g. for the exit certificate claimer).
L1LatestBlockBeforeSubmittingCertificate uint64 `json:"l1LatestBlockBeforeSubmittingCertificate"`
}
StepSubmitResult holds the output of the SUBMIT step.
func RunStepSubmit ¶
func RunStepSubmit(ctx context.Context, cfg *Config, cert *agglayertypes.Certificate) (*StepSubmitResult, error)
RunStepSubmit sends the signed certificate to the agglayer via gRPC and returns the certificate hash assigned by the agglayer. Requires options.agglayerClient.grpc.url.
type StepWaitResult ¶
type StepWaitResult struct {
CertificateHash common.Hash `json:"certificateHash"`
FinalStatus agglayertypes.CertificateStatus `json:"finalStatus"`
SettlementTxHash *common.Hash `json:"settlementTxHash,omitempty"`
ElapsedSeconds float64 `json:"elapsedSeconds"`
// VerifyBatchesL1Block and VerifyBatchesTxHash record where on L1 the RollupManager emitted
// the VerifyBatchesTrustedAggregator event matching this certificate's rollupID and exit root
// (the L1 block where the agglayer settled the certificate). Set only when rollupManagerAddress
// is configured and the event was found.
VerifyBatchesL1Block uint64 `json:"verifyBatchesL1Block,omitempty"`
VerifyBatchesTxHash *common.Hash `json:"verifyBatchesTxHash,omitempty"`
// UpdateL1InfoTree and UpdateL1InfoTreeV2 are the last respective events emitted by the L1
// GlobalExitRoot contract in VerifyBatchesL1Block (the L1 info tree update that accompanies the
// certificate's settlement on L1).
UpdateL1InfoTree *L1InfoTreeUpdate `json:"updateL1InfoTree,omitempty"`
UpdateL1InfoTreeV2 *L1InfoTreeV2Update `json:"updateL1InfoTreeV2,omitempty"`
}
StepWaitResult holds the outcome of the WAIT step.
func RunStepWait ¶
func RunStepWait(ctx context.Context, cfg *Config, submitResult *StepSubmitResult) (*StepWaitResult, error)
RunStepWait waits for the submitted certificate to reach a final state. It polls the agglayer for the certificate header by hash with GetCertificateHeader — which always returns the current status — until it is Settled (success) or InError (error).
Requires options.agglayerClient.grpc.url.
type TokenBalanceCheck ¶
type TokenBalanceCheck struct {
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress string `json:"originTokenAddress"`
LBTAmount string `json:"lbtAmount,omitempty"`
CertificateAmount string `json:"certificateAmount"`
AgglayerAmount string `json:"agglayerAmount"`
Match bool `json:"match"`
CertificateEntries []CertificateEntry `json:"certificateEntries,omitempty"`
// SkippedSCLockedAmount is the SC-locked amount intentionally omitted from the certificate
// (options.skipSCLockedValue) and discounted from this token's LBT/agglayer amounts before the
// comparison (see discountSkippedSCLocked). Empty when no discount was applied.
SkippedSCLockedAmount string `json:"skippedSCLockedAmount,omitempty"`
// RemainingBalance is the cap budget for this token: min(LBT, agglayer), minus the omitted
// SC-locked amount when options.skipSCLockedValue discounts it.
// Not persisted to JSON; used internally by capCertificateExits.
RemainingBalance *big.Int `json:"-"`
}
TokenBalanceCheck holds the three-way comparison between Step 0 (LBT), the certificate bridge exits, and the agglayer state for one token. LBTAmount is empty when LBT data was not available.
type WrappedToken ¶
type WrappedToken struct {
WrappedTokenAddress common.Address `json:"wrappedTokenAddress"`
OriginNetwork uint32 `json:"originNetwork"`
OriginTokenAddress common.Address `json:"originTokenAddress"`
}
WrappedToken describes a wrapped token deployed on L2 by the bridge contract.
func LBTEntriesToWrappedTokens ¶
func LBTEntriesToWrappedTokens(entries []LBTEntry) []WrappedToken
LBTEntriesToWrappedTokens extracts the wrapped token list from LBT entries, filtering out entries with a zero wrappedTokenAddress (native token entry).
func LoadLBTWrappedTokens ¶
func LoadLBTWrappedTokens(lbtFilePath string) ([]WrappedToken, error)
LoadLBTWrappedTokens reads the LBT JSON file and returns only non-zero-address tokens.
type WrappedTokenBalance ¶
type WrappedTokenBalance struct {
Token WrappedToken `json:"token"`
Balance string `json:"balance"`
}
WrappedTokenBalance is the balance of a tracked wrapped token held by an ERC-20 contract.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
scripts
|
|
|
agglayer_status
command
Command agglayer_status prints the status and height of the latest agglayer certificate for an L2 network, using the same agglayer gRPC client as the exit_certificate tool.
|
Command agglayer_status prints the status and height of the latest agglayer certificate for an L2 network, using the same agglayer gRPC client as the exit_certificate tool. |