Admin Commands

Administrative commands for managing bridge contracts. These commands require private keys and submit transactions.

Usage

bridge-cli admin <command> [options]

View all admin commands:

bridge-cli admin --help

Ethereum Commands

register-token

Register an ERC-20 token on MultiSigLocker.

bridge-cli admin register-token \
 --erc20 <address> \
 --casper-hash <hash> \
 --network <network>

Options:

Option Required Description
--erc20 <address> Yes ERC-20 token address
--casper-hash <hash> Yes Casper CEP-18 contract hash
-n, --network <network> No EVM network (defaults to config)

Example:

bridge-cli admin register-token \
 --erc20 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 \
 --casper-hash hash-62d62d5cb43d095b9096ecfe3a3f2bd4d2a1df4020cd9cda94594edb77fedf38 \
 --network sepolia

update-token-mapping

Update the Casper hash for an already registered ERC-20 token.

bridge-cli admin update-token-mapping \
 --erc20 <address> \
 --casper-hash <hash> \
 --network <network>

deploy-wrapped-token

Deploy a wrapped ERC-20 token on Ethereum for a Casper-native CEP-18 token.

bridge-cli admin deploy-wrapped-token \
 --casper-hash <hash> \
 --name <name> \
 --symbol <symbol> \
 --decimals <decimals> \
 --network <network>

Example:

bridge-cli admin deploy-wrapped-token \
 --casper-hash hash-abc123... \
 --name "Wrapped CSPR Test Token" \
 --symbol "wTEST" \
 --decimals 9 \
 --network sepolia

replace-wrapped-token

Replace an existing zero-supply Casper-native wrapper with the V13 bridge-only-burn implementation. This recovery command is intended for testnet wrappers deployed before the bridge-only-burn hardening; it does not migrate balances and refuses to run unless the locker is fully paused and the old wrapper's total supply is exactly zero.

bridge-cli admin replace-wrapped-token \
 --casper-hash hash-<64-hex-character-CEP-18-hash> \
 --expected-old-wrapper 0x<approved-legacy-wrapper> \
 --network sepolia

The command is hard-blocked outside Sepolia, Base Sepolia, and Polygon Amoy. It binds the operation to the exact approved old-wrapper address and validates that the wrapper still exposes the legacy direct-burn API, plus V13, admin ownership, full pause, zero supply, mappings, and enumeration before submission. After confirmation it verifies the replacement event, bytecode, both mappings, enumeration, immutable bridge/hash values, and that direct burn(uint256) plus non-bridge burnFrom(address,uint256) calls revert while locker-authorized zero-amount burnFrom succeeds. Use --yes only for controlled non-interactive testnet operations.

After replacement, retire the old reverse mapping and install the new Casper TokenFactory mapping for the same EVM chain ID while the locker remains fully paused:

bridge-cli admin casper-unregister-wrapped-token \
 --wrapped <old-wrapper> \
 --chain-id <testnet-chain-id> \
 --expected-cep18 hash-<64-hex-character-CEP-18-hash>

bridge-cli admin casper-update-native-token-mapping \
 --cep18 hash-<64-hex-character-CEP-18-hash> \
 --chain-id <testnet-chain-id> \
 --wrapped <new-wrapper>

# Exact read-back: old reverse mapping must be absent; both new directions must agree.
bridge-cli casper TokenFactory get_native_token <old-wrapper> <testnet-chain-id>
# Expected: (not registered as wrapped token)
bridge-cli casper TokenFactory get_wrapped_token hash-<CEP-18-hash> <testnet-chain-id>
# Expected: <new-wrapper>
bridge-cli casper TokenFactory get_native_token <new-wrapper> <testnet-chain-id>
# Expected: <CEP-18-hash>

Keep the locker fully paused until those three reads and relayer configuration resolve the replacement. The unregister command itself requires casper-test, an approved EVM testnet chain ID, and an exact live reverse-mapping match; a stale or absent mapping aborts before confirmation.

This procedure is authorized for one designated zero-supply Casper-native wrapper on testnet only; never use it for mainnet or a second/repeated replacement.

Record a real round trip with the production CLI after unpausing under the approved procedure:

# Capture Casper source and EVM destination balances before each operation.
bridge-cli admin lock-native --cep18 hash-<CEP-18-hash> --amount <amount> \
 --recipient <evm-recipient> --network <testnet-network>
# Assert NativeTokenLocked identifies the CEP-18, recipient, amount, nonce, and chain ID.
# Assert the EVM balance and totalSupply of <new-wrapper> each increase by <amount>.

bridge-cli admin burn-wrapped --wrapped <new-wrapper> --amount <amount> \
 --recipient <casper-recipient> --network <testnet-network>
# Assert exactly one WrappedBurned identifies <new-wrapper>, recipient type/hash,
# amount, nonce, and chain ID. Assert EVM balance/supply decrease by <amount> and
# the Casper CEP-18 balance increases by <amount> after unlock.

Preserve both source transaction IDs, both destination transaction/deploy IDs, exact decoded event fields, and all four before/after balance and supply values. The old wrapper remains deployed but is no longer recognized by the locker.


lock-tokens

Lock ERC-20 tokens on EVM chain to mint CEP-18 on Casper.

bridge-cli admin lock-tokens \
 --erc20 <address> \
 --route <route-id> \
 --amount <amount> \
 --recipient <casper-hash> \
 --network <network> \
  [--skip-fee]

Options:

Option Required Description
--erc20 <address> Yes ERC-20 token address; must be the route's origin asset
--route <route-id> On exposure-control lockers The lock-mint route (32-byte id)
--amount <amount> Yes Amount in smallest unit (wei)
--recipient <hash> Yes Casper recipient (account-hash-xxx or hash-xxx)
-n, --network <network> No Target EVM network
--skip-fee No Skip bridge fee (testing)

On an exposure-control locker the command reads the route's policy, the domain's ceilings and the pause flags first, and stops with the contract's reason (AmountOutOfRange, CustodyCeilingExceeded, RouteAllocationExceeded, RepresentationCeilingExceeded, CommitmentsNotActive, CommitmentsPaused) before sending anything. The contract admits the amount the locker actually receives, so when the requested amount is refused only by an upper bound (a token that charges a transfer fee delivers less), the command asks the contract with a simulated call and proceeds if it admits the transfer. On success it prints the transfer id, the route's source sequence and the gateway sendId. Deployed V17 lockers take the legacy token-based lock without --route.

Example:

bridge-cli admin lock-tokens \
 --erc20 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 \
 --route 0x4f5e...7ea8 \
 --amount 100000000 \
 --recipient account-hash-abc123... \
 --network sepolia

update-relayer-set

Update the relayer set on MultiSigLocker.

bridge-cli admin update-relayer-set \
 --relayers <addr1,addr2,...> \
 --threshold <n>

Example:

bridge-cli admin update-relayer-set \
 --relayers 0xabc...,0xdef...,0x123...,0x456...,0x789... \
 --threshold 3

get-relayer-set

Query current relayer configuration from MultiSigLocker.

# Single network
bridge-cli admin get-relayer-set --network sepolia

# All configured networks
bridge-cli admin get-relayer-set --all

Source limits

On exposure-control lockers, minimum and maximum commitment amounts are part of each route's policy, set with configurePolicy by the governance Safe. set-min-lock-amount and set-token-min-lock refuse with that path. They still apply to deployed V17 lockers:

bridge-cli admin set-min-lock-amount --amount <token-units> --network <network>
bridge-cli admin set-token-min-lock --token <address> --amount <decimal> --network <network>

Casper Commands

deploy-token

Deploy/register an ERC-20 token mapping on TokenFactory.

bridge-cli admin deploy-token \
 --erc20 <address> \
 --name <name> \
 --symbol <symbol> \
 --decimals <decimals> \
  [--skip-decimals-check]

Before submitting deploy_token, the CLI verifies --decimals against the source ERC-20's live on-chain decimals(). A mismatch aborts the deploy - a wrong decimals value would cause systematic over/under-crediting on every future mint for that mapping, and it cannot be corrected after registration. Use --skip-decimals-check only for nonstandard tokens without a decimals() view or when the source RPC is intentionally unavailable.


get-cep18

Get the CEP-18 address for a registered ERC-20 token.

bridge-cli admin get-cep18 \
 --erc20 <address> \
 --chain <chainId>

Example:

bridge-cli admin get-cep18 \
 --erc20 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 \
 --chain 11155111

burn-tokens

Burn CEP-18 tokens on Casper to unlock ERC-20 on EVM.

bridge-cli admin burn-tokens \
 --route <route-id> \
 --amount <amount> \
 --recipient <evm-address> \
  [--cep18 <hash>] [--chain <chain-id>] [--skip-fee]

On an exposure-control TokenFactory --route names the burn-unlock route. The command reads the route first: it burns the domain's representation token toward the route's chain, and shows both before submitting. --cep18 and --chain are optional checks; if either disagrees with the route, or the route is unknown or cannot be read, the command stops before sending anything. The bridge fee must be exact. Pre-exposure TokenFactory deployments take the legacy token-based burn with --cep18, --chain (Sepolia by default) and no --route.

Example:

bridge-cli admin burn-tokens \
 --route 0x9b1c...04e2 \
 --amount 100000000 \
 --recipient 0x1234567890abcdef1234567890abcdef12345678

Casper-Native Token Commands

lock-native

Lock CEP-18 tokens on Casper to mint wrapped ERC-20 on EVM.

bridge-cli admin lock-native \
 --route <route-id> \
 --amount <amount> \
 --recipient <evm-address> \
  [--cep18 <hash>] [--chain <chain-id>] [--skip-fee]

On an exposure-control TokenFactory --route names the lock-mintWrapped route. The command reads the route first, shows the asset it locks and the chain it targets, approves TokenFactory on that asset, then commits with the exact bridge fee. --cep18 and --chain are optional checks; if either disagrees with the route, or the route is unknown or cannot be read, the command stops before approving or sending anything. Route bounds, ceilings, status and pause refusals come back with their contract names (AmountOutOfRange, CustodyCeilingExceeded, RouteAllocationExceeded, RepresentationCeilingExceeded, CommitmentsNotActive, CommitmentsPaused). Pre-exposure TokenFactory deployments take the legacy token-based lock with --cep18, --chain (Sepolia by default) and no --route.


burn-wrapped

Burn wrapped ERC-20 tokens on EVM to unlock CEP-18 on Casper.

bridge-cli admin burn-wrapped \
 --wrapped <address> \
 --route <route-id> \
 --amount <amount> \
 --recipient <casper-hash> \
  [--skip-fee]

On an exposure-control locker --route names the burnWrapped-unlock route and --wrapped must be the domain's representation token. The command checks the route's status (ACTIVE or DEPOSIT_PAUSED), bounds and pause flags first, then burns and prints the transfer id. Deployed V17 lockers take the legacy burn without --route.


inbound-status

Show an inbound transfer's status on an exposure-control locker, as the locker's transferStatus reports it from current conditions: unknown, unconfigured, paused, held, queued_waiting_delay, queued_waiting_capacity, queued_behind_head, queued_ready, recovery_held, executed or terminal. A held transfer shows the condition holding it now; once that has cleared it says so and shows the reason stored at the hold. The output also shows the domain's queue, its release limiter and any recovery shortfall.

bridge-cli admin inbound-status --transfer <transfer-id> [--network <network>]

complete-queued-release

Complete the head of a backing domain's release queue. Anyone may call it; it charges no bridge fee.

bridge-cli admin complete-queued-release --domain <backing-domain-id> [--network <network>]

Stops with QueueEmpty or QueuedTransferNotReady (with the ready time) before sending. If the release limiter cannot cover the head yet, the transaction succeeds without paying out and keeps any debt repayment it made; run it again after refill. A head whose recipient the asset refuses moves to held, and the next entry becomes the head. A head marked terminal by a transfer-specific repair upgrade is skipped at once, whatever its ready time, and nothing is paid for it. The command reports only what its own mined transaction did, from that transaction's events (executed, held, skipped terminal), so a head another caller completed in the same block is never reported as this transaction's work. A transaction without one of those events returned because the release limiter could not cover the head it ran against; the command says so without naming a transfer, since later transactions in the block may already have moved the queue. Run inbound-status for the current state.


retry-held

Retry a held inbound transfer once its condition clears. Anyone may call it; it charges no bridge fee.

bridge-cli admin retry-held --transfer <transfer-id> [--network <network>]

Stops with TransferNotHeld, or with TransferStillHeld and the remaining reason, before sending. A backing release then executes or joins its domain's queue like a new arrival. A recipient refusal shows only at payout, so a refused release above the large-release threshold re-queues on retry even if the recipient still cannot receive; it is held again when it reaches the head, and the queue moves on.


restore-backing

Return backing to an EVM-origin domain after an emergency withdrawal created a recovery shortfall. Anyone may call it. The locker takes at most the shortfall and reduces it by the amount it actually received.

bridge-cli admin restore-backing --domain <backing-domain-id> --amount <raw-amount> [--network <network>]

Approve the locker for the asset first. Stops with NoRecoveryShortfall, or with the missing balance or allowance, before sending. Restoring backing never pays a transfer and never adds custody capacity. Releases held by the shortfall execute through retry-held and complete-queued-release once it is zero and governance has unpaused the domain.


casper-inbound-status

Show an inbound transfer's live state on an exposure-control TokenFactory, computed with the contract's own rules: unknown, unconfigured, paused, held (with the reason blocking it now, or that it can be retried), queued_behind_head, queued_waiting_delay, queued_waiting_capacity, queued_ready, recovery_held, executed or terminal. A pause, status or recovery shortfall outranks the queue states. Also prints the domain's queue and any recovery shortfall.

bridge-cli admin casper-inbound-status --transfer <transfer-id>

casper-complete-queued-release

Complete the head of a backing domain's release queue on Casper. Anyone may call it; it charges no bridge fee.

bridge-cli admin casper-complete-queued-release --domain <backing-domain-id>

Stops with QueueEmpty, QueuedTransferNotReady (with the ready time) or ReleaseNotExecutable (paused, not executing or a recovery shortfall) before sending. A head marked TERMINAL by a repair upgrade is sent whatever its ready time; the command then reports the skip, never a payout. Reports what its own deploy did, from that deploy's events: the release it executed, or that it paid nothing because the release limiter cannot cover the head yet (any debt repayment it made persists; run it again after refill). Another caller may complete the same queue at the same time, so the head shown afterwards is the current state, not this deploy's result. If the deploy's events cannot be read or verified, the command says so rather than guessing either way; check the transaction or run casper-inbound-status.


casper-retry-held

Retry a held inbound transfer on Casper once its condition clears. Anyone may call it; it charges no bridge fee.

bridge-cli admin casper-retry-held --transfer <transfer-id>

Stops with TransferNotHeld before sending, and the contract refuses with TransferStillHeld while the condition remains. A release then executes or joins its domain's queue like a new arrival; the command reports which, from its own deploy's events.


casper-restore-backing

Return backing to a Casper-origin domain after an emergency withdrawal created a recovery shortfall. Anyone may call it.

bridge-cli admin casper-restore-backing --domain <backing-domain-id> --amount <raw-amount>

Stops before sending when the domain is not a registered Casper-origin domain or has no shortfall. Approves TokenFactory on the domain's CEP-18 for at most the shortfall, then calls restore_backing and reports the amount received and the remaining shortfall from its own deploy's BackingRestored event. Restoring never pays a transfer and never adds custody capacity; held releases execute through casper-retry-held and casper-complete-queued-release once the shortfall is zero and governance has unpaused the domain.


Casper Contract Administration

casper-pause

Pause TokenFactory on Casper.

bridge-cli admin casper-pause

casper-unpause

Unpause TokenFactory on Casper.

bridge-cli admin casper-unpause

casper-propose-admin / casper-accept-admin

Transfer admin on TokenFactory (two-step). The current admin proposes; the proposed admin accepts with their own key.

# Step 1: current admin key
bridge-cli admin casper-propose-admin --address <account-hash>

# Step 2: proposed admin key
bridge-cli admin casper-accept-admin

casper-register-token-mapping

Register a token mapping on TokenFactory.

bridge-cli admin casper-register-token-mapping \
 --erc20 <address> \
 --cep18 <hash> \
 --chain-id <chainId>

casper-update-native-token-mapping

Update native token mapping on TokenFactory.

bridge-cli admin casper-update-native-token-mapping \
 --cep18 <hash> \
 --chain-id <chainId> \
 --wrapped <address>

casper-set-eth-client

Set EthClient address on TokenFactory.

bridge-cli admin casper-set-eth-client --hash <contract-hash>

casper-set-erc20-locker

Set the ERC20Locker address on TokenFactory for a specific source EVM chain (per-chain mapping since v7 / BRIDGE-E2E-1).

bridge-cli admin casper-set-erc20-locker --chain-id <source-chain-id> --address <evm-address>

Both options are required. The contract entry point takes source_chain_id and erc20_locker.

Post-upgrade pitfall: an in-place upgrade_package to v7 leaves the erc20_lockers mapping empty (it is seeded only in the constructor path). Every mint from an unseeded chain reverts with Erc20LockerNotSet (60045). Seed each supported chain explicitly after upgrading.


Casper EthClient Administration

casper-add-relayer

Add a relayer to EthClient on Casper.

bridge-cli admin casper-add-relayer --address <eth-address>

casper-remove-relayer

Remove a relayer from EthClient on Casper.

bridge-cli admin casper-remove-relayer --address <eth-address>

casper-set-threshold

Set signature threshold on EthClient.

bridge-cli admin casper-set-threshold --threshold <n>

casper-propose-ethclient-admin / casper-accept-ethclient-admin

Transfer admin on EthClient (two-step - same propose/accept pattern as TokenFactory).

# Step 1: current admin key
bridge-cli admin casper-propose-ethclient-admin --address <account-hash>

# Step 2: proposed admin key
bridge-cli admin casper-accept-ethclient-admin

Relayer State Recovery

replay-lock

Re-queue a real, unminted EVM lock into a stopped relayer's persisted state so the normal 3-of-5 path mints it. For locks outside every automatic window (pruned logs, wiped PVC). Offline: edits the state file, no relayer endpoint involved.

bridge-cli admin replay-lock --chain 46630 --tx 0x… --dry-run   # verify only
bridge-cli admin replay-lock --chain 46630 --tx 0x… --yes       # write
Option Description
--chain <chainId> EVM source chain id (must be configured on this relayer)
--tx <hash> Lock transaction hash
--state-file <path> Explicit evm2casper-addr-<last8>.json; default derives from PERSISTENCE_DIR + the relayer key
--dry-run Run every check, write nothing
--yes Skip the "relayer is stopped" confirmation

Refuses (exit 2) when the receipt is missing or reverted, the tx did not call the configured locker, there is no single Locked log from it, the event's chain id disagrees, the lock is already pending, the block is incomplete, or Casper already minted it (TokenFactory.processed_events). A Casper RPC failure is an error (exit 1), never a pass. Full procedure for EKS: docs/runbooks/replay-lock.md.

Environment Requirements

Admin commands require private keys configured in .env:

# For EVM operations
ETH_PRIVATE_KEY=0x...

# For Casper operations
CASPER_PRIVATE_KEY_PATH=./keys/secret_key.pem

Common Errors

# Not admin
Error: Caller 0x... is not admin (0x...)

# Token not registered
Error: Token is not registered on MultiSigLocker

# Insufficient fee
Error: Insufficient bridge fee sent

# Missing key
Error: ETH_PRIVATE_KEY not configured in .env

results matching ""

    No results matching ""