Skip to main content

Reconcile customer-owned Ethereum validator indices against Optimum's validator registry (console API). Cron-friendly CLI that authenticates with an ovi_live_* operator API key, diffs the desired set against the registry, and applies the delta.

Project description

optimum-keysync

Reconcile a customer's Ethereum validator set against Optimum's validator registry. A one-shot CLI, safe to run on a cron / systemd timer / k8s CronJob: --dry-run by default, --apply to write.

Quickstart

pip install optimum-keysync

Point keysync at the Optimum APIs and tell it which validators you own. The fastest path is a file of BLS pubkeys plus a beacon node (keysync looks up the indices for you):

export KEYSYNC_API_KEY=ovi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export KEYSYNC_BEACON_URL=http://localhost:5052
export KEYSYNC_PUBKEYS_FILE=/etc/optimum/validators.pubkeys   # one pubkey per line

keysync diff           # preview the planned changes, no writes
keysync sync --apply   # apply them

KEYSYNC_API_URL, KEYSYNC_AUTH_URL, KEYSYNC_NETWORK, and KEYSYNC_CHAIN_ID default to the public Optimum endpoints and Ethereum mainnet, so the snippet above omits them. Set them only to override, e.g. a testnet (KEYSYNC_NETWORK=hoodi with its KEYSYNC_CHAIN_ID).

Get KEYSYNC_API_KEY (an ovi_live_* key) from the Optimum partners dashboard. That is the only secret; everything else is non-sensitive config. Re-runs are idempotent, so a steady state reports nothing to do.

Telling keysync which validators you own

Pick whichever list you already have. All three may be combined.

Source Env var Format Beacon node
BLS pubkeys KEYSYNC_PUBKEYS_FILE one pubkey per line required (resolves the index)
Validator indices KEYSYNC_INDEX_LIST_FILE one index per line required (resolves the pubkey)
Pre-computed records KEYSYNC_INDICES_FILE JSON of {validator_index, chain_id, validator_key} not used

KEYSYNC_INDICES_FILE is the no-lookup option for air-gapped setups, and it wins over a beacon-resolved hit for the same (validator_index, chain_id). See examples/ for a sample of each format.

Configuration

All settings come from environment variables, each with a matching flag override (the flag wins).

Env var Required Purpose
KEYSYNC_API_URL no Optimum console API base URL (validator endpoints). Default: https://console.getoptimum.io
KEYSYNC_AUTH_URL no Auth API base URL (used for GET /api/v1/me). Default: https://auth.getoptimum.io
KEYSYNC_API_KEY yes ovi_live_* from the partners dashboard
KEYSYNC_NETWORK no mainnet, hoodi, etc. Default: mainnet
KEYSYNC_CHAIN_ID no Corresponding chain ID string. Default: 0x1
KEYSYNC_BEACON_URL conditional Required when using a pubkeys or index-list file
KEYSYNC_PUBKEYS_FILE conditional One BLS pubkey per line; beacon resolves the index
KEYSYNC_INDEX_LIST_FILE conditional One validator index per line; beacon resolves the pubkey
KEYSYNC_INDICES_FILE conditional Pre-computed JSON records; no lookup
KEYSYNC_OPERATOR_ID no Auto-resolved via GET /api/v1/me. Set to skip the lookup or pin the scope.
KEYSYNC_LOG_FORMAT no console (default) or json

Commands

keysync diff                  # print the planned add/remove delta, no writes
keysync sync [--apply]        # reconcile; --dry-run by default, --apply to write
keysync show                  # print currently-assigned validators

keysync sync also takes --dry-run (forces no-write, wins over --apply), --max-deletes N (default 10), and --log-format json.

--max-deletes is the guardrail against a misconfigured input wiping out an operator's assignments: keysync refuses to unassign more than N validators in a single run. Bump it when a large exit is expected.

What it does

  1. Resolves your operator_id from the API key via GET /api/v1/me on optimum-auth (or uses KEYSYNC_OPERATOR_ID if set).
  2. Resolves the desired validator set from your input files, querying the beacon node's /eth/v1/beacon/states/head/validators endpoint as needed (pubkey to index, or index to pubkey).
  3. Lists your currently-assigned validators from the console API and computes the add / remove delta.
  4. Under --apply, registers new keys, assigns them, and unassigns anything that left the desired set. Each stage is idempotent server-side.

The ovi_live_* key is the Bearer on every console API call (no JWT exchange, no token caching), so revoking it at the dashboard takes effect on the next run.

Exit codes

Wire alerts on these:

Code Meaning
0 success
2 auth / identity failure
3 console API failure
4 configuration error
5 --max-deletes guardrail tripped
6 beacon node failure

Operating notes

  • Beacon node: must allow unauthenticated reads on /eth/v1/beacon/states/head/validators (Lighthouse: --http).
  • Retries: HTTP calls retry with exponential backoff on 5xx / 429 / network errors only. Other 4xx fails fast, so a misconfiguration does not retry into a rate limit.

Examples

examples/ has a pubkeys file, an index list, and an indices JSON, plus systemd timer and Kubernetes CronJob templates.

Development

pip install -e '.[dev]'
pytest

Tests are hermetic: respx stubs every outbound HTTP call, so no live API or beacon access is required.

Releasing

Publishing to PyPI is automated via .github/workflows/release.yml, which runs when a GitHub Release is published and uploads with PyPI Trusted Publishing (OIDC) — no token is stored in the repo. To cut a release:

  1. Bump version in pyproject.toml and merge to main.
  2. Publish a GitHub Release tagged v<version> (e.g. v1.0.0).

The workflow verifies the tag matches the package version, builds the sdist and wheel, and publishes. One-time setup: register the trusted publisher on PyPI (project optimum-keysync, workflow release.yml, environment pypi).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

optimum_keysync-1.1.0.tar.gz (40.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

optimum_keysync-1.1.0-py3-none-any.whl (29.8 kB view details)

Uploaded Python 3

File details

Details for the file optimum_keysync-1.1.0.tar.gz.

File metadata

  • Download URL: optimum_keysync-1.1.0.tar.gz
  • Upload date:
  • Size: 40.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for optimum_keysync-1.1.0.tar.gz
Algorithm Hash digest
SHA256 0aa3ad8394a9fee0e67bd3fcc3ca7dee07f96af456829cfcb5949e5e54674917
MD5 72325f3477f034c55c3ce7a6125ba17b
BLAKE2b-256 3ea1a97183ce881c46ada7bebf1b50945fce6ba691824bb4a2558ec3d38fe6dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for optimum_keysync-1.1.0.tar.gz:

Publisher: release.yml on getoptimum/optimum-keysync

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file optimum_keysync-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: optimum_keysync-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 29.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for optimum_keysync-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e6950cfa814937aa6988115d708ca3cbb9ad5f4daaf7fcb2093a6054d03684f5
MD5 42e85109923d8e2d0d399edd333de113
BLAKE2b-256 650ef4a436cdb488f9f5ba57dba576478949fd844dab4c6c93fb7472e7480362

See more details on using hashes here.

Provenance

The following attestation bundles were made for optimum_keysync-1.1.0-py3-none-any.whl:

Publisher: release.yml on getoptimum/optimum-keysync

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page