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.0.0.tar.gz (36.5 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.0.0-py3-none-any.whl (27.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: optimum_keysync-1.0.0.tar.gz
  • Upload date:
  • Size: 36.5 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.0.0.tar.gz
Algorithm Hash digest
SHA256 e9faec04f2fb426685329a9a41364e9c008588c40d0cb285cbb0e42721534974
MD5 4a0537265ef4d624cb10687785e860ab
BLAKE2b-256 9bce875566d57fe81184cbfc0d7bfa47bd32ee6932eae775e60a8f141af40a08

See more details on using hashes here.

Provenance

The following attestation bundles were made for optimum_keysync-1.0.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.0.0-py3-none-any.whl.

File metadata

  • Download URL: optimum_keysync-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 27.4 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.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 67407262198877da8b12d58994260592f2d10e4c70d4dcd0fdab57162dc2f677
MD5 d36bc20bd3014a51f5251ca3f0e1c0c4
BLAKE2b-256 0ed6114cbfe0b7c0bb7efed27da1de7d8a4bda70c035d4ee2db896707f209608

See more details on using hashes here.

Provenance

The following attestation bundles were made for optimum_keysync-1.0.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