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 your Optimum API key and tell it which validators you manage.
The simplest input is a JSON file of records (no beacon node needed); see
examples/indices.json for the format:
export KEYSYNC_API_KEY=ovi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export KEYSYNC_INDICES_FILE=/path/to/your-validators.json
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).
Prefer to track validators by BLS pubkey or bare index? Point keysync at a beacon node and it resolves the rest:
export KEYSYNC_BEACON_URL=http://your-beacon-node:5052
export KEYSYNC_PUBKEYS_FILE=/path/to/validators.pubkeys # one BLS pubkey per line
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
- Resolves your
operator_idfrom the API key viaGET /api/v1/meon optimum-auth (or usesKEYSYNC_OPERATOR_IDif set). - Resolves the desired validator set from your input files, querying the beacon
node's
/eth/v1/beacon/states/head/validatorsendpoint as needed (pubkey to index, or index to pubkey). - Lists your currently-assigned validators from the console API and computes the add / remove delta.
- 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:
- Bump
versioninpyproject.tomland merge tomain. - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file optimum_keysync-1.1.1.tar.gz.
File metadata
- Download URL: optimum_keysync-1.1.1.tar.gz
- Upload date:
- Size: 41.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43b2d1f02cef3308b171b0e1e6ef19e744e6eafe2b5fc600d3cf81bace9fc6d7
|
|
| MD5 |
a72d6437fb2b013e1b74dd6977a25868
|
|
| BLAKE2b-256 |
cf19671f26e4d4b92277dc3dbdd412b9cf0fdcb98bd358b78210ecc2cb0f7b16
|
Provenance
The following attestation bundles were made for optimum_keysync-1.1.1.tar.gz:
Publisher:
release.yml on getoptimum/optimum-keysync
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optimum_keysync-1.1.1.tar.gz -
Subject digest:
43b2d1f02cef3308b171b0e1e6ef19e744e6eafe2b5fc600d3cf81bace9fc6d7 - Sigstore transparency entry: 1958001704
- Sigstore integration time:
-
Permalink:
getoptimum/optimum-keysync@b11e61b08ee00fefdc4552575cb4a2f60188f462 -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/getoptimum
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b11e61b08ee00fefdc4552575cb4a2f60188f462 -
Trigger Event:
release
-
Statement type:
File details
Details for the file optimum_keysync-1.1.1-py3-none-any.whl.
File metadata
- Download URL: optimum_keysync-1.1.1-py3-none-any.whl
- Upload date:
- Size: 30.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bef8901c4e7baac47991ef611cfa554fd27b721cfeb85db294ae0b65a33f3830
|
|
| MD5 |
e89ca55fe33d752a478422bea9952061
|
|
| BLAKE2b-256 |
80c1194d76b8601f388525ffef435f8cec28625f31dd56800c5c4111c5348417
|
Provenance
The following attestation bundles were made for optimum_keysync-1.1.1-py3-none-any.whl:
Publisher:
release.yml on getoptimum/optimum-keysync
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optimum_keysync-1.1.1-py3-none-any.whl -
Subject digest:
bef8901c4e7baac47991ef611cfa554fd27b721cfeb85db294ae0b65a33f3830 - Sigstore transparency entry: 1958001989
- Sigstore integration time:
-
Permalink:
getoptimum/optimum-keysync@b11e61b08ee00fefdc4552575cb4a2f60188f462 -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/getoptimum
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b11e61b08ee00fefdc4552575cb4a2f60188f462 -
Trigger Event:
release
-
Statement type: