Appearance
Server and Crank
The bridge server is the operational loop that keeps authority acknowledgements moving. It is not an optional dashboard backend — it is core bridge infrastructure. If the server is down, no authority votes are submitted and all transfers stall after the deposit step.
What the server does
The server runs an HTTP service and an in-process crank loop within a single binary.
At runtime it is responsible for:
- Polling the source chain's
DepositTrackerfor new deposits - Polling the destination chain's
WithdrawTrackerfor finalized results - Submitting
ack_depositon the destination chain when unacknowledged deposits are found - Submitting
ack_withdrawon the source chain when unacknowledged results are found - Discovering and submitting
cleanup_depositfor finalized entries that have not yet been cleaned up - Retrying failed actions with bounded backoff
- Exposing health, metrics, and admin endpoints for operators
Environment variables
HTTP configuration
| Variable | Required | Default | Purpose |
|---|---|---|---|
BRIDGE_BIND_ADDRESS | No | 0.0.0.0:$PORT or 0.0.0.0:8080 | Explicit bind address and port |
PORT | No | 8080 | Port fallback (Cloud Run sets this automatically) |
BRIDGE_AUTH_USERNAME | No | — | Basic auth username for admin endpoints |
BRIDGE_AUTH_PASSWORD | No | — | Basic auth password for admin endpoints |
BRIDGE_AUTH_USERNAME and BRIDGE_AUTH_PASSWORD must either both be set or both be unset. If set, they protect the admin endpoints (/kill, /crank/once).
Runtime configuration
The crank loop activates when any of these variables is present. Once one is set, the full set is required — a partial configuration is a startup error.
| Variable | Type | Purpose |
|---|---|---|
BRIDGE_SOURCE_RPC_URL | URL | RPC endpoint for the source chain |
BRIDGE_TARGET_RPC_URL | URL | RPC endpoint for the destination chain |
BRIDGE_SOURCE_CHAIN_ID | u16 | Numeric chain ID of the source chain |
BRIDGE_TARGET_CHAIN_ID | u16 | Numeric chain ID of the destination chain |
BRIDGE_AUTHORITY_KEYPAIR | Path | Filesystem path to the authority signer keypair JSON |
Crank tuning
| Variable | Type | Default | Constraints | Purpose |
|---|---|---|---|---|
BRIDGE_POLL_INTERVAL_SECS | u64 | 15 | — | Seconds between crank poll cycles |
BRIDGE_CRANK_MAX_ATTEMPTS | u32 | 3 | Minimum 1 | Maximum retry attempts per failed action |
BRIDGE_CRANK_RETRY_BACKOFF_MS | u64 | 1000 | — | Base backoff in milliseconds between retries |
Bridge-specific
The crank loop holds a run lock (Mutex) that prevents concurrent executions. Even if the poll interval is short, a long-running cycle will not overlap with the next. This is deliberate — concurrent authority submissions for the same entries would waste transactions and complicate error handling.
HTTP endpoints
Public endpoints
GET /health
Returns a JSON BridgeHealthSnapshot:
json
{
"status": "ok",
"service": "bridge-server",
"crank": {
"enabled": true,
"last_run_at": 1714000000,
"last_success_at": 1714000000,
"last_error": null,
"last_action": "ack_deposit",
"total_actions_submitted": 42
}
}| Field | Type | Meaning |
|---|---|---|
enabled | bool | Whether runtime config is present and the crank is active |
last_run_at | u64 or null | Unix timestamp of the most recent crank cycle |
last_success_at | u64 or null | Unix timestamp of the most recent successful action |
last_error | string or null | Error message from the most recent failure |
last_action | string or null | Description of the most recent action taken |
total_actions_submitted | u64 | Cumulative count of all actions submitted since startup |
This is the first endpoint to check when diagnosing bridge issues. A stale last_run_at means the crank loop has stopped. A persistent last_error means the loop is running but failing.
GET /metrics
Prometheus-format metrics including counters and histograms for crank runs, retries, per-action submissions, and durations. Suitable for scraping into Prometheus/Grafana.
Admin endpoints
These require basic auth when BRIDGE_AUTH_USERNAME and BRIDGE_AUTH_PASSWORD are configured.
POST /kill
Terminates the server process. Use for emergency shutdown or controlled rotation.
POST /crank/once
Triggers a single crank cycle immediately, bypassing the poll interval. Returns a CrankReportResponse:
json
{
"ack_deposit_attempted": true,
"ack_withdraw_attempted": false,
"cleanup_attempted": 3,
"cleanup_completed": 2
}This is the primary manual intervention tool. If the automated loop is stuck or you need to force a specific cycle, hit this endpoint rather than restarting the service.
Crank cycle details
Each poll cycle executes the following steps in order:
- Check for pending
ack_deposit— compare sourceDepositTracker.entry_countagainst destinationWithdrawTracker.entry_count. - Submit
ack_depositif unacknowledged entries exist. - Check for pending
ack_withdraw— compare destination finalized entries against sourceDepositTrackerstate. - Submit
ack_withdrawif unacknowledged results exist. - Discover cleanup candidates — scan for deposit entries that are
Finalizedbut havecleanup_completed == false. - Submit
cleanup_depositfor each candidate.
Each action that fails is retried up to BRIDGE_CRANK_MAX_ATTEMPTS times with BRIDGE_CRANK_RETRY_BACKOFF_MS backoff between attempts. If all attempts fail, the error is recorded in the health snapshot and the crank moves on to the next action in the cycle.
Recommended Cloud Run deployment
| Setting | Value | Rationale |
|---|---|---|
min-instances | 1 | Always-on; cold starts would stall bridge operations |
max-instances | 1 | Prevents duplicate authority submissions from concurrent instances |
concurrency | 1 | One request at a time; prevents admin endpoint races |
cpu | 1 | Sufficient for RPC polling and transaction submission |
memory | 1Gi | Headroom for tracker account deserialization |
| Execution environment | Gen2 | Required for always-on CPU allocation outside of request handling |
| Ingress | IAM-protected | Admin endpoints should not be publicly accessible |
Zink recommendation
Do not run multiple instances for the same authority signer. Duplicate submissions waste compute and fees, and concurrent authority votes on the same entries create confusing on-chain state. The single-instance, single-concurrency model is a deliberate safety constraint, not a scaling limitation.
Keypair mounting
Mount the authority keypair from Secret Manager as a file. The conventional path is:
/secrets/authority.jsonSet the environment variable to match:
BRIDGE_AUTHORITY_KEYPAIR=/secrets/authority.jsonDo not bake the keypair into the container image. Do not pass it as a plaintext environment variable. Secret Manager mounting ensures the key is available at runtime without persisting it in image layers or environment metadata.