An oracle service for the ANYONE Protocol that links relay operators to their on-chain identity and registers them with the Operator Registry AO process (smart contract).
The controller continuously discovers ANYONE relays, validates that each one is claimed by an operator's EVM address, verifies operator hardware, and submits operator certificates to the Operator Registry process so that operators can claim their relays and earn rewards. It also publishes relay/validation metrics to Arweave for permanent, public record.
Relay operators advertise their ANYONE (EVM) address in their relay's
contact field using the pattern @anon:<0x-evm-address>. The controller polls
Onionoo relay details, finds
these relays, runs them through validation and verification, and registers the
valid ones on-chain.
Work runs as a chain of BullMQ flows, triggered immediately on startup and re-queued on a 1-hour interval. Only the elected cluster leader (see Clustering) enqueues and processes the pipeline.
tasks-queue: validate ──► validation-flow ──► tasks-queue: verify ──► verification-flow
-
fetch-relays (
validation-queue)- Fetches relay details from
ONIONOO_DETAILS_URI. - Keeps only relays whose
contactcontains the@anon:pattern with a valid EVM address, and that are not inBANNED_FINGERPRINTS. - Resolves each relay's geolocation to an H3 cell (resolution 4) using fingerprint→coordinate data from the ANYONE API.
- Upserts the relay data into MongoDB (transient working storage) and returns the matching fingerprints.
- Fetches relay details from
-
validate-relays (
validation-queue)- Re-reads relays in batches, extracts and validates the operator's EVM
address (
any1_address) from thecontactstring (checksummed viaethers), and stores it back on the relay record.
- Re-reads relays in batches, extracts and validates the operator's EVM
address (
-
verify-relays (
verification-queue)- Reads the current Operator Registry state from the AO process
(
View-State) to skip already-claimable / already-verified relays. - For each new relay, runs hardware verification
where applicable, then submits valid relays in chunks of 100 via the
Admin-Submit-Operator-CertificatesAO message.
- Reads the current Operator Registry state from the AO process
(
-
confirm-verification / persist-verification (
verification-queue)- Aggregates per-relay results, computes validation stats and an H3 hex-map of relay coverage, and uploads them to Arweave via the ArDrive Turbo bundler.
- Persists a
VerificationDatarecord to MongoDB and cleans up the transient relay records. - If the stats upload fails, a
recover-persist-verificationjob retries the upload up to 3 times.
Some relays run on dedicated ANYONE hardware and submit a hardware_info proof.
The controller verifies these against:
- RELAYUP NFT ownership — confirms the operator address owns the claimed NFT
ID on the
RELAY_UP_NFT_CONTRACT_ADDRESScontract (with a backup RPC provider for resilience). - Known-device serial proofs — verifies a secp256r1 (P-256) signature over
the device's node ID / serials / fingerprint / address for devices imported
into the
known_devicescollection.
Each unique device (by ATEC serial) can only be verified once. Verified hardware
is recorded in MongoDB, and failures are stored for diagnostics. Successfully
hardware-verified relays are submitted to the Operator Registry smart-contract with hw: true flag.
Device-certificate verification (validating a device cert against a Vault PKI issuer) is also available.
| Dependency | Purpose |
|---|---|
| MongoDB | Transient relay working storage + verification/hardware records |
| Redis | Backing store for BullMQ queues (standalone or Sentinel) |
| Consul | Distributed leader election across instances |
| Vault | PKI issuer lookup for device-certificate verification |
| Onionoo | Source of relay details |
| ANYONE API | Fingerprint → geolocation map (/fingerprint-map) |
| EVM RPC | RELAYUP NFT ownership checks (primary + backup providers) |
| AO (aoconnect) | Reads/writes the Operator Registry process |
| ArDrive Turbo | Bundles and uploads metrics/stats to Arweave |
src/
main.ts App bootstrap (Nest + Winston logging)
app.module.ts Root module: Mongo, BullMQ/Redis, schedule wiring
cluster/ Consul leader election + multi-thread workers
tasks/ BullMQ queues, flows, and processors (pipeline orchestration)
validation/ Onionoo fetch, relay filtering, EVM-address extraction
verification/ Operator registry diff, hardware verification, persistence
operator-registry/ AO process client (View-State, submit certificates)
hardware-verification/ (within verification/) NFT + serial-proof checks
bundling/ ArDrive Turbo uploads to Arweave
geo-ip/ Fingerprint → H3 cell lookups via ANYONE API
evm-provider/ Resilient EVM JSON-RPC providers
vault/ Vault PKI issuer lookups
util/ AO messaging, signing, arbundles-lite, helpers
operations/ Nomad job specs (stage/live + Redis Sentinel)
- Node.js (LTS) and npm
- Reachable MongoDB and Redis instances (point
MONGO_URI/ Redis vars at them) - A
.envfile (loaded automatically via@nestjs/config)
When IS_LIVE is not 'true', the service runs in single-node mode (no Consul
required), obliterates the queues on startup, and skips all on-chain writes and
Arweave uploads — making it safe for local runs.
npm installnpm run start # development
npm run start:dev # watch mode
npm run start:prod # production (runs dist/main)npm run test # unit tests
npm run test:e2e # e2e tests
npm run test:cov # coveragenpm run lint
npm run formatAll configuration is provided via environment variables.
| Variable | Required | Description |
|---|---|---|
IS_LIVE |
yes | 'true' enables on-chain writes, Arweave uploads, and Consul clustering. Anything else runs in safe single-node mode and clears queues on startup. |
DO_CLEAN |
no | 'true' obliterates all queues on startup (leader only). |
PORT |
no | HTTP listen port (default 3000). |
CPU_COUNT |
no | Number of worker threads to fork (capped at host CPU count). |
IS_LOCAL_LEADER |
internal | Set per-worker by the clustering layer; do not set manually. |
| Variable | Required | Description |
|---|---|---|
MONGO_URI |
yes | MongoDB connection string. |
| Variable | Required | Description |
|---|---|---|
REDIS_MODE |
no | standalone (default) or sentinel. |
REDIS_HOSTNAME |
standalone | Redis host. |
REDIS_PORT |
standalone | Redis port. |
REDIS_MASTER_NAME |
sentinel | Sentinel master name. |
REDIS_SENTINEL_1_HOST / _PORT |
sentinel | Sentinel node 1. |
REDIS_SENTINEL_2_HOST / _PORT |
sentinel | Sentinel node 2. |
REDIS_SENTINEL_3_HOST / _PORT |
sentinel | Sentinel node 3. |
| Variable | Required | Description |
|---|---|---|
CONSUL_HOST |
live | Consul host. If unset, the service runs single-node. |
CONSUL_PORT |
live | Consul port. |
CONSUL_SERVICE_NAME |
live | Service name used for the leader-election KV key. |
CONSUL_TOKEN_CONTROLLER_CLUSTER |
live | Consul ACL token. |
| Variable | Required | Description |
|---|---|---|
ONIONOO_DETAILS_URI |
yes | URL of the Onionoo relay-details endpoint. |
DETAILS_URI_AUTH |
no | Authorization header value for the details endpoint. |
ONIONOO_REQUEST_TIMEOUT |
no | HTTP request timeout. |
ONIONOO_REQUEST_MAX_REDIRECTS |
no | Max HTTP redirects. |
BANNED_FINGERPRINTS |
no | Comma-separated relay fingerprints to exclude. |
| Variable | Required | Description |
|---|---|---|
ANYONE_API_URL |
yes | Base URL of the ANYONE API; /fingerprint-map is appended. |
ANYONE_API_CACHE_TTL |
no | Cache TTL in ms for the fingerprint map (default 1h). |
| Variable | Required | Description |
|---|---|---|
OPERATOR_REGISTRY_PROCESS_ID |
yes | AO process ID of the Operator Registry. |
OPERATOR_REGISTRY_CONTROLLER_KEY |
yes | EVM private key used to sign AO messages. Secret. |
CU_URL |
live | AO Compute Unit URL. |
GATEWAY_URL |
live | Arweave gateway URL for AO. |
GRAPHQL_URL |
live | Arweave GraphQL URL for AO. |
| Variable | Required | Description |
|---|---|---|
RELAY_UP_NFT_CONTRACT_ADDRESS |
yes | RELAYUP NFT contract address. |
EVM_MAINNET_PRIMARY_JSON_RPC |
yes | Primary EVM JSON-RPC endpoint. |
EVM_MAINNET_SECONDARY_JSON_RPC |
yes | Backup EVM JSON-RPC endpoint. |
VAULT_ADDR |
no | Vault address (PKI issuer lookups). |
VAULT_TOKEN |
no | Vault token. |
VAULT_TOKEN_PERIOD |
no | Vault token period (default 4h). |
VAULT_TOKEN_POLICIES |
no | Comma-separated Vault policies (default pki-hardware-reader). |
| Variable | Required | Description |
|---|---|---|
BUNDLER_CONTROLLER_KEY |
yes | EVM private key used to sign bundled uploads. Secret. |
BUNDLER_NODE |
yes | Turbo upload-service URL. |
BUNDLER_GATEWAY |
yes | Arweave gateway URL. |
BUNDLER_NETWORK |
yes | Bundler network identifier. |
In live mode the service forks one worker per CPU_COUNT and uses Consul to
elect a single cross-instance leader. Only the leader enqueues the immediate
startup job and the recurring 1-hour validation. Leadership is held via a Consul
session (15s TTL, renewed every 10s) over the KV key
clusters/<CONSUL_SERVICE_NAME>/leader; if the leader dies, its session is
deleted and another instance acquires the lock. When Consul is unavailable or
the service is not live, it runs as a single-node leader.
The service is built into a container image (see Dockerfile) by
the GitHub Actions workflow in
.github/workflows/build-and-publish-image.yml,
and deployed on a HashiCorp Nomad stack alongside Consul and Vault.
Nomad job specifications live in operations/:
| File | Purpose |
|---|---|
operator-registry-controller-stage.hcl |
Staging deployment |
operator-registry-controller-live.hcl |
Production deployment |
operator-registry-controller-redis-sentinel-stage.hcl |
Staging Redis Sentinel |
operator-registry-controller-redis-sentinel-live.hcl |
Production Redis Sentinel |
These job specs source secrets (controller keys, Consul/Vault tokens, RPC API keys) from Vault and inject the environment variables documented above.