Configuration Reference
Configuration values prefixed YOUR_ are placeholders for your own deployment. For the public testnet use the contract reference; never mix networks or environments.
Complete reference for all bridge configuration options.
Environment Variables
Multi-Chain EVM Configuration
The bridge supports multiple EVM chains. Environment variables follow the format {CHAIN_NAME}_RPC_URL and {CHAIN_NAME}_LOCKER.
| Variable | Required | Default | Description |
|---|---|---|---|
SEPOLIA_RPC_URL |
Yes | - | Sepolia RPC endpoint |
SEPOLIA_BEACON_URL |
Yes | - | Sepolia beacon API |
SEPOLIA_LOCKER |
Yes | - | Sepolia locker contract address |
BASE_SEPOLIA_RPC_URL |
No | - | Base Sepolia RPC |
BASE_SEPOLIA_LOCKER |
No | - | Base Sepolia locker address |
# Sepolia (Primary)
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_KEY
SEPOLIA_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com
SEPOLIA_LOCKER=YOUR_SEPOLIA_LOCKER
# Base Sepolia (Optional)
BASE_SEPOLIA_RPC_URL=https://base-sepolia.infura.io/v3/YOUR_KEY
BASE_SEPOLIA_LOCKER=YOUR_CHAIN_LOCKER
Casper Network
| Variable | Required | Default | Description |
|---|---|---|---|
CASPER_NODE_URL |
Yes | - | Casper RPC endpoint |
CASPER_PUBLIC_NODE_URLS |
No | - | Comma-separated additional public/community Casper RPC endpoints. These count as the public source class for the verification diversity gate - a terminal verification verdict requires a public-class source (CSPR.cloud alone can never verify or reject an event). Configure at least one besides CASPER_NODE_URL for resilience. |
CASPER_CHAIN_NAME |
Yes | - | Chain name (casper-test) |
CSPR_CLOUD_RPC_URL |
No | CSPR.cloud host | CSPR.cloud RPC endpoint. Classified as the cloud source class - it can never satisfy the verification diversity gate on its own. |
CASPER_ETH_CLIENT |
Yes | - | EthClient contract hash |
CASPER_TOKEN_FACTORY |
Yes | - | TokenFactory contract hash |
CASPER_ERC7786_GATEWAY |
For exposure-control contracts | - | Casper ERC-7786 gateway package hash. Transfers to a V18+ locker, and to an exposure-control TokenFactory, travel only through the gateways; without it the relayer signs nothing for them |
CASPER_NODE_URL=https://node.testnet.casper.network
CASPER_CHAIN_NAME=casper-test
CASPER_ETH_CLIENT=YOUR_ETH_CLIENT_PACKAGE_HASH
CASPER_TOKEN_FACTORY=YOUR_TOKEN_FACTORY_PACKAGE_HASH
Relayer Keys
| Variable | Required | Default | Description |
|---|---|---|---|
RELAYER_PRIVATE_KEY |
Yes | - | Ethereum private key (0x...) |
CASPER_SECRET_KEY |
Yes | - | Path to Casper secret_key.pem |
RELAYER_PRIVATE_KEY=0x1234567890abcdef...
CASPER_SECRET_KEY=/path/to/secret_key.pem
P2P Network
| Variable | Required | Default | Description |
|---|---|---|---|
P2P_ENABLED |
No | true | Enable P2P networking |
P2P_PORT_EVM2CASPER |
No | 8080 | EVM→Casper P2P port |
P2P_PORT_CASPER2EVM |
No | 8180 | Casper→EVM P2P port |
P2P_PEERS |
No | - | Comma-separated peer addresses |
P2P_TLS_ENABLED |
No | true | Enable TLS for P2P |
P2P_ALLOW_PRIVATE_PEER_ENDPOINTS |
No | false | Allow peer endpoints resolving to private/loopback addresses (otherwise rejected by the SSRF guard). Enable only for local test meshes or VPC-internal fleets - peers remain gated by mTLS + on-chain authorization. |
P2P_TLS_CERT |
No | - | TLS certificate path |
P2P_TLS_KEY |
No | - | TLS private key path |
P2P_ENABLED=true
P2P_PORT_EVM2CASPER=8080
P2P_PORT_CASPER2EVM=8180
P2P_PEERS=10.0.1.10:8080,10.0.1.11:8080,10.0.1.12:8080
P2P_TLS_ENABLED=true
P2P_TLS_CERT=/path/to/cert.pem
P2P_TLS_KEY=/path/to/key.pem
Relayer Identity
| Variable | Required | Default | Description |
|---|---|---|---|
RELAYER_INDEX |
Yes | - | Relayer index (0-4) |
RELAYER_COUNT |
No | 5 | Total relayers |
RELAYER_THRESHOLD |
No | 3 | Required signatures. Must be ≥ 2 and a strict majority of the relayer set (threshold × 2 > count) - enforced on-chain (EVM V14+ / Casper EthClient); relayers log a SECURITY warning if an on-chain threshold is a minority. |
RELAYER_INDEX=0
RELAYER_COUNT=5
RELAYER_THRESHOLD=3
Fee & Minimum-Amount Safety Rails
The bridge fee is the economic brake on dust-lock relayer griefing: every lock forces all relayers to sign and pay destination-chain gas, so an unmetered lock path drains relayer gas wallets. Configuration interplay to keep in mind:
- Every EVM route needs a positive
minimumCommitmentAmount. The route policy enforces it (zero is refused when a route is configured), together with the route maximum and the domain's custody and representation ceilings. Size the minimum so that a dust commitment still pays for the relayers' work; with a zero bridge fee it is the only economic brake on lock spam. - Size the fee against destination gas, not just source-chain UX. The fee is flat; destination gas is variable. A 50 CSPR fee (~$0.25) against a several-dollar mainnet-Ethereum mint leaves a 10-20x asymmetry in an attacker's favor. Revisit fee levels when destination gas regimes change.
- Zero-fee mode rejects attached value: with fee 0,
lock/burnWrappedrevert on anymsg.value, and Casperburn/lock_nativerevert on any attached CSPR. Wallet integrations must not attach value when the fee reads 0. - Casper fee ceiling: fee setters reject values above 10,000 CSPR
(
FeeExceedsMaximum) so a fat-fingered fee can't brick the bridge. - Token remapping caveat (
update_native_token_mapping): re-pointing a CEP-18 mapping does not migrate the locked-total accounting recorded under the old token key. Reconcile totals manually after any remap (tooling tracked as a known limitation).
Submission Claims (Duplicate-Submission Prevention)
Relayers claim in-flight submissions over P2P so backup relayers don't duplicate
a transaction that is dispatched but not yet final. Holds are bounded: a claim
extends (never resets) the backup failover deadline, hard-capped at
CLAIMS_MAX_ATTEMPTS × CLAIMS_GRACE_SECONDS from the first claim.
| Variable | Required | Default | Description |
|---|---|---|---|
CLAIMS_ENABLED |
No | true | Disables transfer claim holds/advisories/parking; for proactive headers disables pending-transaction holds only, not cadence or election |
CLAIMS_GRACE_SECONDS |
No | 90 | Hold granted per claim, from its last renewal |
CLAIMS_RENEWAL_SECONDS |
No | 30 | Interval between claim renewals for our own in-flight txs |
CLAIMS_MAX_ATTEMPTS |
No | 2 | Distinct tx-hash attempts honored per event (caps total hold) |
CLAIMS_HASH_DEADLINE_SECONDS |
No | 15 | A hash-less claim lapses unless a tx hash arrives within this window |
Deterministic on-chain rejections (e.g. unregistered token) park the affected
event fleet-wide after independent on-chain verification, logged as
SUBMISSION_PERMANENT_FAILURE. Parked state is in-memory: after fixing the
root cause (e.g. registering the token), restart the relayers to resume.
CLAIMS_ENABLED=true
CLAIMS_GRACE_SECONDS=90
CLAIMS_MAX_ATTEMPTS=2
Proactive header freshness
| Variable | Required | Default | Description |
|---|---|---|---|
PROACTIVE_HEADER_INTERVAL_SECONDS |
No | 600 | Restart-applied interval shared by all enabled EVM chains and relayers |
Use positive whole decimal seconds only (no spaces, signs, fractions, exponent
notation or leading zeroes). Unsafe integer conversions fail startup. Match the
value across the fleet; mismatched coordination contexts are not adopted.
Ansible uses proactive_header_interval_seconds; Helm relayer environment maps
carry the same default. This setting does not change stranded-lock reconciliation.
A verified increase in EthClient latest height starts a local monotonic interval.
Transfers are not rate-limited and still require their own exact header. Backups
inherit a valid retained candidate, with 30-second selection and quorum rank waits.
Signature-request backoff does not delay an available quorum. Failed paid attempts
remain paced, and verified pending transactions can suppress dispatch after final
checks. Verified success for the exact active header stops further payment and
persists domain-bound occupied-height proof across restart, including interval,
authorized-set and threshold changes, even while the latest-height snapshot lags.
Successful outstanding local transactions retain proof before candidate selection.
If old signatures no longer satisfy the current quorum, or the current quorum
cannot be read during recovery, an unfolded local success additionally requires
exact destination-storage confirmation. While storage is missing, mismatched or
unavailable, that network's proactive publication pauses across restarts rather
than risking another payment. This wait is not capped by the pending hold budget
and is not disabled by CLAIMS_ENABLED=false. It ends on exact storage confirmation
or coherent destination progress reaching the height, never on a lookup timeout.
Peer hints cannot create this historical-recovery wait.
An occupied height is not paid for again, but an old header's proof never completes
a different same-height header. Signatures and old scheduling authority are not
restored after configuration changes; without terminal proof, a full new interval
and current quorum are required. Destination chain/package and source-chain changes
isolate old proof. Suppression retires only when a coherent watermark reaches its
height; later heights retain normal pacing. Missing/stale state, failed saves and
rollback to older software do not guarantee durable suppression.
Only a coherent higher watermark starts the next interval. Local rank clocks can
differ, so duplicate fees remain possible under network delay or partitions.
CLAIMS_ENABLED=false disables only proactive pending-transaction holds, not this
interval or election. Proactive holds are fixed at 90 seconds with a durable
180-second cycle budget, not renewable leases. Changing authorized membership
requires an operator-coordinated restart; a differing on-chain view pauses new
proactive signing and dispatch without changing transfer authorization behavior.
Proactive lifecycle logs distinguish scheduling from completed work. A null
candidateHeight means no candidate is currently selected, not a malformed
header. phase describes the local scheduling state, and activeCycleId is null
when no cycle is open. For retired work, read supersededHeight and
supersededCycleId; for verified completion, read completedHeight,
completedHeader, and transactionHash. completedCycleId may be null during
recovery when no matching active cycle remains. Repeated completion observations
for one transaction do not mean repeated payments.
Gas and Payments
| Variable | Required | Default | Description |
|---|---|---|---|
CASPER_STANDARD_PAYMENT_AMOUNT |
No | 45000000000 | Block header submission payment (motes); sized to measured max cost +5% |
CASPER_MINT_PAYMENT |
No | 46000000000 | Mint payment (motes); sized to measured max cost +5% (unused Casper 2.0 gas is refunded) |
CASPER_BURN_PAYMENT |
No | 2500000000 | Burn payment (motes) |
CASPER_STANDARD_PAYMENT_AMOUNT=45000000000 # 45 CSPR
CASPER_MINT_PAYMENT=46000000000 # 46 CSPR
CASPER_BURN_PAYMENT=2500000000 # 2.5 CSPR
Logging
| Variable | Required | Default | Description |
|---|---|---|---|
LOG_LEVEL |
No | info | Log level (debug, info, warn, error) |
LOG_FORMAT |
No | json | Log format (json, pretty) |
LOG_LEVEL=debug
LOG_FORMAT=json
Persisted-State Retention
| Variable | Required | Default | Description |
|---|---|---|---|
CASPER2EVM_PROCESSED_EVENTS_LIMIT |
No | 10000 | Max processed-event keys retained in Casper2Evm persisted state |
EVM2CASPER_PROCESSED_EVENTS_LIMIT |
No | 10000 | Max processed-event keys retained per chain in Evm2Casper persisted state |
Retention caps bound persisted-state growth. Events trimmed from local state remain protected by the on-chain processed-event guards, which are the authoritative replay defense; raise these limits if your deployment restarts frequently and you want a larger local dedup window.
CASPER2EVM_PROCESSED_EVENTS_LIMIT=10000
EVM2CASPER_PROCESSED_EVENTS_LIMIT=10000
Monitoring
| Variable | Required | Default | Description |
|---|---|---|---|
METRICS_ENABLED |
No | true | Enable Prometheus metrics |
METRICS_PORT |
No | 9080 | Metrics endpoint port |
METRICS_ENABLED=true
METRICS_PORT=9080
API Server
| Variable | Required | Default | Description |
|---|---|---|---|
API_ENABLED |
No | false | Enable API server |
API_PORT |
No | 3001 | API server port |
API_HOST |
No | 0.0.0.0 | API bind address |
API_ENABLED=true
API_PORT=3001
API_HOST=0.0.0.0
Chain Configuration
Multi-Chain Setup
For multi-chain deployments, configure each chain:
# Sepolia (Chain ID: 11155111)
CHAIN_SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/KEY
CHAIN_SEPOLIA_LOCKER=0x...
# Exposure-control contracts only: the chain's ERC-7786 gateway proxy (both directions)
CHAIN_SEPOLIA_GATEWAY=0x...
CHAIN_SEPOLIA_CONFIRMATIONS=64
CHAIN_SEPOLIA_FINALITY_MODE=finalized
# Base Sepolia (Chain ID: 84532)
CHAIN_BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
CHAIN_BASE_SEPOLIA_LOCKER=0x...
CHAIN_BASE_SEPOLIA_CONFIRMATIONS=64
CHAIN_BASE_SEPOLIA_FINALITY_MODE=finalized
Finality Settings
EVM source blocks must pass both the configured protocol-native block tag and the confirmation-depth floor. RPC errors or unsupported tags fail closed; the worker does not automatically fall back to depth-only processing.
| Variable | Default | Description |
|---|---|---|
FINALITY_CONFIRMATIONS |
64 | Global confirmation floor; each chain enforces the stricter of this value and its per-chain setting |
CHAIN_<CHAIN_NAME>_CONFIRMATIONS |
Chain default | Per-chain depth floor, for example CHAIN_SEPOLIA_CONFIRMATIONS |
CHAIN_<CHAIN_NAME>_FINALITY_MODE |
finalized |
Protocol tag to query: finalized or safe; depth-only is an explicit emergency compatibility mode |
FINALIZED_TAG_TTL_MS |
12000 | Per-chain cache lifetime for protocol-finality tag lookups, in milliseconds |
CHAIN_<CHAIN_NAME>_FINALITY_MAX_REGRESSION |
30 min of blocks (ceil(1800 / blockTime), e.g. 900 on Base, 150 on Ethereum) |
Deepest drop, in blocks, a fresh tag reading may show below the per-process high-water mark before the chain fails closed |
Concurrent lookups share one refresh per chain. Lookup failures remain fail-closed, without using expired cache data.
In finalized mode the worker keeps a per-chain, per-process high-water mark of
the tag height. On OP Stack chains the finalized tag is per-node derived state,
so a load-balanced RPC pool routinely answers a few hundred blocks below the
previous reading even though nothing was reorged. A finalized block cannot
un-finalize, so a lower reading within CHAIN_<CHAIN_NAME>_FINALITY_MAX_REGRESSION
is logged at warn (Finality tag reading below high-water mark - keeping mark)
and the ceiling stays at the mark. A drop deeper than the bound cannot be
explained by derivation lag: the worker logs at error, discards the mark, and
treats the chain as not-final until a later, distinct successful reading
re-bootstraps it. Concurrent lookups that share one RPC response all receive the
same decision. The mark lives only in process memory and resets on restart. The
confirmation-depth floor is applied on top and is unaffected.
safe mode keeps no high-water mark. The OP Stack safe head can be reorged by an
L1 reorg, so a lower safe reading is authoritative and lowers the ceiling.
Finality tag observation regressed warns when a valid block hash changes at the
same height. Finality tag reading below high-water mark - keeping mark warns when
the returned height decreases within the bound. Both are evidence of an inconsistent
RPC view, not proof of a reorg. The logs include both observations and a
credential-free provider vendor label; other means the vendor is not recognized.
Existing confirmation-depth requirements remain unchanged.
| Chain | Chain Default (global floor applies) | Block Time |
|---|---|---|
| Ethereum Sepolia | 6 | ~12s |
| Base Sepolia | 64 | ~2s |
| Polygon Amoy | 15 | ~2s |
Use depth-only only when an RPC serves neither finalized nor safe. The
worker logs a warning at startup whenever this emergency mode is configured.
Configuration File
Alternatively, use a config.json file:
{
"chains": {
"sepolia": {
"chainId": 11155111,
"rpcUrl": "https://sepolia.infura.io/v3/KEY",
"lockerAddress": "0x...",
"confirmations": 64
},
"base-sepolia": {
"chainId": 84532,
"rpcUrl": "https://sepolia.base.org",
"lockerAddress": "0x...",
"confirmations": 64
}
},
"casper": {
"nodeUrl": "https://node.testnet.casper.network",
"chainName": "casper-test",
"ethClient": "hash-...",
"tokenFactory": "hash-..."
},
"relayer": {
"index": 0,
"count": 5,
"threshold": 3
},
"p2p": {
"enabled": true,
"ports": {
"eth2casper": 8080,
"casper2eth": 8180
},
"tls": {
"enabled": true,
"cert": "/path/to/cert.pem",
"key": "/path/to/key.pem"
}
}
}
Load with:
bridge-cli --config /path/to/config.json start
Validation
Validate configuration before starting:
bridge-cli config validate
Output:
Configuration validation:
✓ Ethereum RPC responsive
✓ Base RPC responsive
✓ Casper RPC responsive
✓ Relayer key valid
✓ Casper key valid
✓ Contract addresses verified
✓ P2P configuration valid
✓ Data directory writable
Configuration is valid!
Example Configurations
Single Relayer (Development)
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/KEY
SEPOLIA_LOCKER=YOUR_SEPOLIA_LOCKER
CASPER_NODE_URL=https://node.testnet.casper.network
RELAYER_PRIVATE_KEY=0x...
CASPER_SECRET_KEY=./keys/secret_key.pem
P2P_ENABLED=false
LOG_LEVEL=debug
Multi-Relayer (Production)
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/KEY
SEPOLIA_LOCKER=YOUR_SEPOLIA_LOCKER
CASPER_NODE_URL=https://node.testnet.casper.network
RELAYER_PRIVATE_KEY=aws:secretsmanager:bridge/relayer/0/eth_key
CASPER_SECRET_KEY=/opt/bridge/keys/secret_key.pem
RELAYER_INDEX=0
RELAYER_COUNT=5
RELAYER_THRESHOLD=3
P2P_ENABLED=true
P2P_PEERS=10.0.1.11:8080,10.0.1.12:8080,10.0.1.13:8080,10.0.1.14:8080
P2P_TLS_ENABLED=true
LOG_LEVEL=info
METRICS_ENABLED=true
Note: Relayers use file-based JSON persistence in the
data/directory. No database configuration is required for relayer operation.