The Relay Oracle attests to the onchain facts that drive Relay Protocol settlement. This guide covers how it fits into the protocol and how to deploy and operate an external oracle outside Relay’s internal infrastructure.
Relay Oracle attests to onchain facts used in Relay Protocol settlement and serves as:
An operationally stateless oracle service.
A Dockerized oracle service that exposes an HTTP API.
A signer that produces authenticated messages and execution signatures.
A verifier that reads chain state from RPC endpoints and chain-specific APIs.
Its job is to inspect chain state, determine whether a specific protocol-relevant event happened, and sign the corresponding result for downstream settlement flows.
At a high level, the Relay Protocol needs reliable confirmation that key lifecycle steps actually happened onchain before downstream settlement actions are accepted.The oracle is responsible for attesting these core actions:
Deposits into depository contracts.
Withdrawals from depository contracts.
Solver fills.
Solver refunds.
Withdrawal-address flows used during withdrawal handling.
When a client asks the oracle to attest an event:
The oracle fetches the relevant chain configuration.
It reads chain state from the configured RPC endpoint or chain-specific service.
It builds a protocol message that describes what happened.
It signs the message with its configured signer.
For endpoints that produce execution payloads, it may also collect signatures from peer oracles.
The caller can then use those signatures in the wider settlement flow.
The oracle service can be run as a single instance or as multiple cooperating instances.
The oracle supports multiple VM families, with chain-specific attestation logic behind a unified API.The current VM attestor implementation supports:
ethereum-vm
solana-vm
bitcoin-vm
hyperliquid-vm
lighter-vm
tron-vm
Which chains are enabled is controlled by the selected environment config:
configs/chains.<ENVIRONMENT>.json
configs/chains.hub.<ENVIRONMENT>.json
These config files do not hardcode every endpoint. Instead, many values are placeholders that are resolved from environment variables at runtime.That means your real deployment depends on the quality of the RPC and service endpoints you provide.
Each running instance uses one configured signer.The oracle service supports two signing modes:
raw-private-key
aws-kms
The signer is used for:
Message signatures returned by attestation endpoints.
Execution signatures used in settlement flows.
If your instance is meant to participate in active production signing, coordinate signer onboarding with Relay before expecting its signatures to be used downstream.If you are just getting started, run the instance first in shadow mode: it signs requests and proves operational readiness, but its signatures are not yet relied on for quorum.
A single oracle can answer requests on its own, and the oracle service also supports peering across multiple oracle instances to collect signatures.When an incoming request sets requestPeerSignatures=true and PEERS is configured, the oracle starts its local attestation and every configured peer request concurrently.When ORACLE_SIGNERS is also configured, the oracle service returns early when a response group reaches the configured threshold of unique allowlisted signatures. The local oracle is not privileged on that path, so a quorum can be satisfied entirely by remote peers.The oracle service combines responses only when their attestation payloads, signing domains, and non-signature array order match. It skips responses that fail the endpoint schema.During aggregation, the oracle service excludes duplicate signatures, signatures from non-allowlisted signers when ORACLE_SIGNERS is configured, and signers missing from any signature array in a multi-message response.To enable quorum aggregation, define an allowlist of trusted signer addresses and an early-return signature target:
ORACLE_SIGNERS is a semicolon-separated allowlist of authorized signer addresses.
ORACLE_SIGNERS_THRESHOLD is the target number of unique allowlisted signatures for early return (defaults to 1). It does not prevent the oracle service from returning fewer signatures.
Each instance applies its own ORACLE_SIGNERS list, so operators are not required to configure identical lists. A signature counts only at instances that list its signer, and the signer authorized onchain decides which signatures settlement accepts.ORACLE_SIGNERS_THRESHOLD counts the local oracle’s signature along with peer signatures. A threshold of 1 can be met by the local oracle alone, so a 2-of-3 configuration uses 2.
ORACLE_SIGNERS_THRESHOLD controls early return but does not enforce quorum. During peer aggregation, Relay Oracle can return the strongest valid partial group with a success response when no group reaches the threshold, and logs the matched and required counts. Callers must verify the unique allowlisted signature count before relying on the response, and operators should alert on quorum-shortfall warnings.
For example, a 2-of-3 configuration looks like this:
The oracle service fails at startup when ORACLE_SIGNERS_THRESHOLD is not an integer or is less than 1.When ORACLE_SIGNERS is configured, the oracle service also fails at startup when:
ORACLE_SIGNERS_THRESHOLD exceeds the number of unique entries in ORACLE_SIGNERS.
Any entry in ORACLE_SIGNERS is not a valid nonzero address.
The local signer address is not included in ORACLE_SIGNERS.
When ORACLE_SIGNERS is omitted, the threshold is ignored for quorum purposes, so a threshold set without an allowlist doesn’t enforce anything. The oracle service logs a startup warning when a threshold is set without the allowlist.Without the allowlist, the oracle service waits for the local operation and every configured peer, bounded by PEER_REQUEST_TIMEOUT_MS, instead of completing early at a threshold. Only peer responses that match the local unsigned payload are aggregated, and the request fails if the local attestation fails.With an allowlist, a failed local attestation is logged but does not by itself fail the request. The oracle service can return a full or partial response using only allowlisted peer signatures. If the local attestation fails, the request fails only when no valid allowlisted group is available.Peer calls are best-effort attempts. If a peer times out, errors, fails response validation, or returns a payload that does not match any signable group, it is skipped and a warning is emitted.
GET /status/v1 returns the instance’s status, build identifiers (version, image, gitCommit), uptimeSeconds, lowercase oracleSigner address, peerCount, and quorum configuration. Unavailable information is null. peerCount includes all configured peers, including unreachable ones.status is ok when the signer lookup succeeds, or partial when it fails or exceeds five seconds. It describes the local signer check, not a test signature or overall peer health. Without a configured signer allowlist, quorum is { "signers": [], "threshold": null }.Build identifiers use the first available value in these sequences:
version: VERSION, build manifest version, package version, IMAGE_TAG, then the resolved gitCommit.
image: IMAGE_DIGEST, IMAGE_TAG, then the build manifest image.
gitCommit: GIT_COMMIT, then the build manifest commit.
GET /lives/v1?withReport=true uses the same version resolution.Add withPeers=true to /status/v1 or /chains/v1 to check every configured peer. The response includes one peers entry per peer, including failed checks. Without this option, the peers field is omitted.In /status/v1?withPeers=true, each peer entry contains the same status fields plus url, checkResult, latencyMs, retryCount, and httpStatus. Unavailable fields are null, including quorum when the peer’s configuration is unknown. Peer URLs omit credentials, query parameters, and fragments, and invalid URLs are omitted entirely.checkResult is success when a valid report is collected, or a failure reason such as timeout, authentication-error, or invalid-response. latencyMs measures the successful request, and httpStatus records the final request’s HTTP status when available. retryCount counts retries after transient failures, excluding fallback requests.For older peers, status checks fall back to /lives/v1?withReport=true, then /lives/v1. Legacy reports supply limited information, with their old version value treated as image metadata.In /chains/v1?withPeers=true, peer entries contain only url, checkResult, and chainDifferences, relative to the top-level local chains list:
local-only: the chain exists locally but not on the peer. Includes only id and status.
peer-mismatch: the chain exists on both sides. Includes id, status, and only changed peer values for vmType or depository. depository: null means the peer has no depository configured.
peer-only: the chain exists only on the peer. Includes id, status, vmType, and depository when present.
chainDifferences: [] means the chain sets match, while null means the comparison failed. A successful comparison can still report differences.Peer checks run concurrently, honor PEER_REQUEST_TIMEOUT_MS and PEER_RESPONSE_MAX_BYTES, retry transient failures, and stop when the caller disconnects. Status responses and chain responses with withPeers=true set cache-control: no-store.
If you operate a third-party oracle, you are expected to treat it as production infrastructure.Minimum security expectations:
Use a dedicated signing key that is not shared with any other oracle.
Prefer a hardened key-management approach. aws-kms is supported; if you use raw keys, store them in a real secret manager.
Enforce access control at an API gateway or reverse proxy when public access is not intended.
Use HTTPS for any publicly reachable deployment.
Use dedicated peer API keys to authenticate requests between oracle instances.
Restrict management access to the host platform and secret storage.
The oracle is stateless, but its signer is security-critical. If the signing key is compromised, the instance should be treated as compromised immediately.
You should run the oracle service on a platform that can run a Docker container and expose it over HTTP.For most third-party operators, Railway should be treated as the preferred starting point. It is the recommended default when you want the simplest path to running a single external oracle instance.Typical Docker-hosting options:
Railway
ECS
Kubernetes
Fly.io
A VM running Docker
You need:
A long-running Docker runtime.
Stable outbound access to all configured RPC and chain service endpoints.
Stable inbound access from callers and peer oracles.
Secure secret storage for signing material and API keys.
Because the oracle service is stateless, scaling horizontally is possible. In practice, the biggest operational constraints are uptime, RPC quality, and signer security.If you already operate production container infrastructure, another platform can be a valid choice. If not, default to Railway.
SIGNING_MODULE=raw-private-key is optional because raw-key signing is the default code path, but setting it explicitly is clearer for operators.You can also set:
HTTP_PORT is optional if your platform injects PORT. The server resolves ports in this order: HTTP_PORT, then PORT, then 3000.
API_KEYS defines the keys accepted on status, chains, and attestation routes. At least one key must be configured for the oracle to start.
PEER_REQUEST_TIMEOUT_MS defaults to 10000.
ORACLE_SIGNERS is the semicolon-separated allowlist used to count unique oracle signatures during quorum aggregation.
ORACLE_SIGNERS_THRESHOLD sets the target number of unique allowlisted signatures for early return. It defaults to 1, must be an integer of at least 1, and is validated against the size of ORACLE_SIGNERS when that allowlist is configured. It does not prevent a successful response with fewer signatures.
The chain config files reference environment variables dynamically. Your deployment is only as reliable as the endpoints and credentials you provide there.There are two categories to think about:
Standard RPC URLs for the chains the oracle reads.
Supplemental service variables for chains that need more than a plain RPC endpoint.
The oracle validates chain state in real time, so weak or rate-limited RPCs directly reduce reliability.
If a configured chain does not have a working RPC endpoint, any attestation that depends on that chain will fail.
You may also need additional chain RPC variables beyond the list above, depending on which chains remain enabled in your selected ENVIRONMENT. The chain config files are the source of truth for the full required set.
Some ecosystems expose critical settlement-related data more reliably through companion APIs than through a single generic RPC method.
Some providers require API credentials even when the underlying access pattern still looks like a chain read.
These variables improve reliability and coverage for chain-specific reads without changing the public API of the oracle itself.
You do not need to expose internal provider choices publicly. You only need to ensure the variables required by your chosen providers are present, valid, and managed like any other production secret.
Status, chains, and attestation routes require a valid x-api-key. Requests to these routes without a valid key return 401 Unauthorized.The health and documentation routes do not require an API key:
/
/documentation and its subpaths
/lives/v1, including withReport=true
Registered peers have no automatic exemption. Each peer request must carry a key accepted by the receiving oracle.API_KEYS uses this format:
API_KEYS=key-one:partner-a;key-two:partner-b
The right-hand label is stored as metadata for that key.
pass-through is also supported as a peer credential, which forwards the incoming x-api-key instead of a dedicated peer key. That is generally better suited to tightly controlled internal environments. For separate organizations, use dedicated peer keys.If Relay-managed oracles need to call your instance, the usual pattern is:
You expose a stable public HTTPS URL.
You create a dedicated incoming API key to authenticate peer requests.
Relay adds your URL and that key to the PEERS list used by its oracle instances.
The container entrypoint also loads secrets from /vault/secrets when present, which is useful for secret-injection systems.If your platform builds from source automatically, the important requirement is still the same: the oracle service should be deployed and run as the repository’s Docker image, with the required environment variables injected at runtime.
GET /status/v1 reporting status: "partial" and oracleSigner: null.
Request latency.
Error rate by endpoint.
Unexpected 401 Unauthorized responses.
Peer timeout, peer mismatch, and quorum-shortfall warnings.
Peer checkResult drift over time via GET /status/v1?withPeers=true and GET /chains/v1?withPeers=true.
RPC provider failures and rate limits.
Memory and CPU consumption.
Signer address correctness after deploys.
A healthy process can still be operationally unusable if its RPC dependencies are degraded, so RPC observability matters as much as container health.
Was this page helpful?
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Relay","colors":{"primary":"#4615C8","light":"#A7AAFF","dark":"#4615C8"},"logo":{"light":"https://mintcdn.com/unevenlabs/Gbv82V37Oid9F6eG/logo/relay-black.svg?fit=max\u0026auto=format\u0026n=Gbv82V37Oid9F6eG\u0026q=85\u0026s=7983e6e441ff77eeac7e17c03c2d0e8c","dark":"https://mintcdn.com/unevenlabs/Gbv82V37Oid9F6eG/logo/relay-white.svg?fit=max\u0026auto=format\u0026n=Gbv82V37Oid9F6eG\u0026q=85\u0026s=fbdcf916b96f42397c1470872ab80cda"},"favicon":"https://mintcdn.com/unevenlabs/f--GnNtyUvuqA2AW/favicon.png?fit=max\u0026auto=format\u0026n=f--GnNtyUvuqA2AW\u0026q=85\u0026s=d40a0a20b4bf062cea218d7eed38c576","api":{"openapi":["https://api.relay.link/documentation/json"],"examples":{"defaults":"required","languages":["curl","javascript","python"],"prefill":false},"mdx":{"server":["https://api.relay.link","https://api.testnets.relay.link"]}},"appearance":{"default":"light"},"navbar":{"links":[{"href":"https://support.relay.link/en/","label":"Support"},{"href":"https://forms.gle/XNeELYavjwoJwPEf7","label":"Get in Touch"}],"primary":{"type":"button","label":"Try Relay","href":"https://www.relay.link/"}},"footer":{"socials":{"twitter":"https://twitter.com/RelayProtocol"}},"integrations":{"intercom":{"appId":"b09ps4q8"}},"contextual":{"options":["copy","view","chatgpt","claude","perplexity","mcp","cursor","vscode"],"display":"header"},"styling":{"latex":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"unevenlabs","hasAudioNarration":false,"actualSubdomain":"unevenlabs","customDomains":["docs.relay.link"],"gitSource":{"type":"github","owner":"relayprotocol","repo":"relay-docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ae3af697ae675900f76a6ef","collectionId":"be28ab12-bcba-4839-9c2d-e9938fa2d546","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ae3af697ae675900f76a6ef"},"feedback":{"thumbs":true},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6ae3f64d50e0502dc9f49d6a","clientVersion":"0.0.4044","searchFilterCounts":[{"language":"default","tag":"API Reference","count":101},{"language":"default","tag":"RelayKit","count":48},{"language":"default","tag":"Relay Platform","count":29},{"language":"default","tag":"Relay Protocol","count":29},{"language":"default","tag":"Settlement","count":17},{"language":"default","tag":"Integration Guides","count":13},{"language":"default","tag":"Vaults","count":12},{"language":"default","tag":"Core Concepts","count":11},{"language":"default","tag":"SDK","count":11},{"language":"default","tag":"Features","count":9}]},"assistantConfig":{},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"@keyframes rotation{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}#navbar a[href*=\"/references\"]{padding-left:18px;padding-right:18px}.card-link{color:var(--tw-prose-links);text-decoration:none;font-weight:600;margin-top:10px;display:inline-block;margin-right:5px;border-bottom:1px solid transparent}.card-link:hover{border-bottom:1px solid rgb(var(--primary-light))}#ui-kit-overview-cards a>div>div:first-child{float:left;margin-right:12px}#ui-kit-overview-cards a>div h2{margin:0;display:inline}.relay-table{td:not(:first-child) {\n padding-left: 24px;\n }\n\n th:not(:first-child) {\n padding-left: 24px;\n }\n\n .chain-icon-light {\n display: block;\n }\n\n .chain-icon-dark {\n display: none;\n }}.dark .relay-table .chain-icon-light{display:none}.dark .relay-table .chain-icon-dark{display:block}#\\/references\\/api\\/advanced,#\\/references\\/api\\/core,#\\/references\\/api\\/utilities{pointer-events:none;font-weight:700}.loader{width:48px;height:48px;border:5px solid rgb(70 21 200);border-bottom-color:transparent;border-radius:50%;display:inline-block;box-sizing:border-box;animation:rotation 1s linear infinite}#body-amount [data-component-part=field-info-pill]:has(>span.text-gray-400),#body-destination-chain-id [data-component-part=field-info-pill]:has(>span.text-gray-400),#body-destination-currency [data-component-part=field-info-pill]:has(>span.text-gray-400),#body-origin-chain-id [data-component-part=field-info-pill]:has(>span.text-gray-400),#body-origin-currency [data-component-part=field-info-pill]:has(>span.text-gray-400),#body-trade-type [data-component-part=field-info-pill]:has(>span.text-gray-400),#body-user [data-component-part=field-info-pill]:has(>span.text-gray-400){display:none}#custom-vm-quicksets{display:flex;gap:10px;margin-top:10px;margin-left:16px;border-bottom:1px solid;border-color:rgb(var(--gray-200)/.7);padding-bottom:16px}#custom-vm-quicksets button{border-radius:4px;overflow:hidden;border:0;cursor:pointer;transition:opacity .2s ease;opacity:1}#custom-vm-quicksets button img{width:32px;height:32px}#custom-vm-quicksets button:hover,.homepage-card:hover .homepage-card-link{opacity:.8}#custom-vm-quicksets-intro{font-weight:600;margin-top:20px;margin-left:16px}#hyperliquid-button{background-color:#fff;padding:2px;max-height:32px}body:has(.homepage-bg) .nav-tabs-item[href=\"/what-is-relay\"]{color:rgb(75 85 99)!important;text-shadow:none!important}.dark body:has(.homepage-bg) .nav-tabs-item[href=\"/what-is-relay\"],body.dark:has(.homepage-bg) .nav-tabs-item[href=\"/what-is-relay\"]{color:rgb(156 163 175)!important}body:has(.homepage-bg) .nav-tabs-item[href=\"/what-is-relay\"]>div{background:0 0!important}.homepage-bg{width:100%;height:40vh;position:relative;background:var(--Violet-Light-9, #4615C8);pointer-events:none}.homepage-cards{display:flex;gap:24px;margin:40px 50px;justify-content:center}.homepage-card{min-height:200px;padding:24px;border-radius:12px;border:1px solid rgba(0,0,0,.1);background-color:#fff;text-decoration:none;color:inherit;display:flex;flex-direction:column;gap:12px;max-width:300px;transition:background-color .2s,border-color .2s}.dark .homepage-card{border:1px solid rgba(255,255,255,.1);background-color:#1a1a1a}.homepage-card h3{font-size:18px;font-weight:700;color:#000;margin:0 0 -8px}.dark .homepage-card h3{color:#fff}.homepage-card-description{margin:0 0 40px;font-size:14px;color:#666;line-height:1.5}.dark .homepage-card-description{color:#999}.homepage-card-link{font-size:14px;color:#4615c8;font-weight:500;margin-top:auto}.dark .homepage-card-link{color:#a78bfa}.relay-checkbox{appearance:none;-webkit-appearance:none;width:12px;height:12px;border:1px solid #c8ccff;border-radius:3px;background-color:#f7f8ff;cursor:pointer;position:relative;transition:all .15s ease;flex-shrink:0}.relay-checkbox:hover{border-color:#b4b8ff;background-color:#eff1ff}.relay-checkbox:focus{outline:0;border-color:#4615c8;box-shadow:0 0 0 2px rgba(70,21,200,.15)}.relay-checkbox:checked{background-color:#4615c8;border-color:#4615c8}.relay-checkbox:checked:hover{background-color:#3b00b4;border-color:#3b00b4}.relay-checkbox:checked::after{content:'';position:absolute;left:3px;top:0;width:4px;height:7px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}.relay-checkbox:disabled{cursor:not-allowed;opacity:.5;background-color:#eff1ff;border-color:#d7dbff}.dark .relay-checkbox{background-color:#141331;border-color:#4016b8}.dark .relay-checkbox:hover{border-color:#4c24d1;background-color:#216}.dark .relay-checkbox:focus{border-color:#5b2af9;box-shadow:0 0 0 2px rgba(91,42,249,.25)}.dark .relay-checkbox:checked{background-color:#4615c8;border-color:#4615c8}.dark .relay-checkbox:checked:hover{background-color:#3901aa;border-color:#3901aa}.dark .relay-checkbox:disabled{background-color:#216;border-color:#4016b8}.callout .prose>:first-child{margin-top:0}[data-cl-filter-bar]{position:sticky;top:var(--cl-sticky-top, 5rem);z-index:20;display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:12px 0;margin-bottom:20px;border-bottom:1px solid var(--gray-200)}.dark [data-cl-filter-bar]{border-bottom-color:rgb(55 65 81)}div:has(>[data-component-part=update-label]){top:calc(var(--scroll-mt, 5rem) + var(--cl-bar-height, 0px))}[data-cl-filter-bar] button,[data-component-part=update-tag]{cursor:pointer;padding:2px 8px;border:1px solid var(--gray-200);border-radius:9999px;transition:border-color .15s,color .15s,background-color .15s}[data-cl-filter-bar] button{font-size:.875rem;font-weight:500;line-height:1.5;color:rgb(107 114 128);background:0 0}[data-cl-filter-bar] button:hover,[data-cl-filter-bar] button[data-cl-active=true],[data-component-part=update-tag]:hover,[data-component-part=update-tag][data-cl-active=true]{border-color:rgb(var(--primary)/var(--tw-text-opacity, 1));color:rgb(var(--primary)/var(--tw-text-opacity, 1))}[data-cl-filter-bar] button[data-cl-active=true],[data-component-part=update-tag][data-cl-active=true]{background:rgb(var(--primary)/.08)}.dark [data-cl-filter-bar] button,.dark [data-component-part=update-tag]{border-color:rgb(55 65 81);color:rgb(156 163 175)}.dark [data-cl-filter-bar] button:hover,.dark [data-cl-filter-bar] button[data-cl-active=true],.dark [data-component-part=update-tag]:hover,.dark [data-component-part=update-tag][data-cl-active=true]{border-color:rgb(var(--primary-light));color:rgb(var(--primary-light))}.dark [data-cl-filter-bar] button[data-cl-active=true],.dark [data-component-part=update-tag][data-cl-active=true]{background:rgb(var(--primary-light)/.14)}"}],"page":{"pagePath":"references/protocol/guides/third-party-oracle","slug":"/references/protocol/guides/third-party-oracle","pageMetadata":{"title":"Operate a Third-Party Relay Oracle","description":"How to deploy and run a Relay Oracle outside Relay's internal infrastructure","sidebarTitle":"Third-Party Oracle","href":"/references/protocol/guides/third-party-oracle"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"How to deploy and run a Relay Oracle outside Relay’s internal infrastructure\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={code:`code`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,span:`span`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(Heading,{level:`2`,id:`overview`,children:`Overview`}),`\n`,_jsx(_components.p,{children:`The Relay Oracle attests to the onchain facts that drive Relay Protocol settlement. This guide covers how it fits into the protocol and how to deploy and operate an external oracle outside Relay’s internal infrastructure.`}),`\n`,_jsx(Heading,{level:`2`,id:`what-the-relay-oracle-is`,children:`What the Relay Oracle is`}),`\n`,_jsx(_components.p,{children:`Relay Oracle attests to onchain facts used in Relay Protocol settlement and serves as:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`An operationally stateless oracle service.`}),`\n`,_jsx(_components.li,{children:`A Dockerized oracle service that exposes an HTTP API.`}),`\n`,_jsx(_components.li,{children:`A signer that produces authenticated messages and execution signatures.`}),`\n`,_jsx(_components.li,{children:`A verifier that reads chain state from RPC endpoints and chain-specific APIs.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`Its job is to inspect chain state, determine whether a specific protocol-relevant event happened, and sign the corresponding result for downstream settlement flows.`}),`\n`,_jsx(Heading,{level:`2`,id:`how-the-oracle-fits-into-the-protocol`,children:`How the oracle fits into the protocol`}),`\n`,_jsx(_components.p,{children:`At a high level, the Relay Protocol needs reliable confirmation that key lifecycle steps actually happened onchain before downstream settlement actions are accepted.`}),`\n`,_jsx(_components.p,{children:`The oracle is responsible for attesting these core actions:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Deposits into depository contracts.`}),`\n`,_jsx(_components.li,{children:`Withdrawals from depository contracts.`}),`\n`,_jsx(_components.li,{children:`Solver fills.`}),`\n`,_jsx(_components.li,{children:`Solver refunds.`}),`\n`,_jsx(_components.li,{children:`Withdrawal-address flows used during withdrawal handling.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`When a client asks the oracle to attest an event:`}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsx(_components.li,{children:`The oracle fetches the relevant chain configuration.`}),`\n`,_jsx(_components.li,{children:`It reads chain state from the configured RPC endpoint or chain-specific service.`}),`\n`,_jsx(_components.li,{children:`It builds a protocol message that describes what happened.`}),`\n`,_jsx(_components.li,{children:`It signs the message with its configured signer.`}),`\n`,_jsx(_components.li,{children:`For endpoints that produce execution payloads, it may also collect signatures from peer oracles.`}),`\n`,_jsx(_components.li,{children:`The caller can then use those signatures in the wider settlement flow.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`The oracle service can be run as a single instance or as multiple cooperating instances.`}),`\n`,_jsx(Heading,{level:`2`,id:`what-the-oracle-service-actually-does`,children:`What the oracle service actually does`}),`\n`,_jsx(_components.p,{children:`The oracle service exposes HTTP endpoints for:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Health checks.`}),`\n`,_jsx(_components.li,{children:`Chain discovery.`}),`\n`,_jsx(_components.li,{children:`Status and observability.`}),`\n`,_jsx(_components.li,{children:`Attestation requests.`}),`\n`,_jsx(_components.li,{children:`Swagger-based API documentation.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`The main utility routes are:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`GET /lives/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`GET /status/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`GET /chains/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`GET /documentation`})}),`\n`]}),`\n`,_jsx(_components.p,{children:`The attestation routes are:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`POST /attestations/depository-deposits/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`POST /attestations/depository-withdrawals/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`POST /attestations/solver-fills/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`POST /attestations/solver-refunds/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`POST /attestations/withdrawal-initiation/v1`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`POST /attestations/withdrawal-initiated/v1`})}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`The built-in Swagger UI at `,_jsx(_components.code,{children:`/documentation`}),` is the best reference for exact request and response schemas.`]}),`\n`,_jsx(Heading,{level:`2`,id:`supported-chains-and-data-sources`,children:`Supported chains and data sources`}),`\n`,_jsx(_components.p,{children:`The oracle supports multiple VM families, with chain-specific attestation logic behind a unified API.`}),`\n`,_jsx(_components.p,{children:`The current VM attestor implementation supports:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`ethereum-vm`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`solana-vm`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`bitcoin-vm`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`hyperliquid-vm`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`lighter-vm`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`tron-vm`})}),`\n`]}),`\n`,_jsx(_components.p,{children:`Which chains are enabled is controlled by the selected environment config:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`configs/chains.\u003cENVIRONMENT>.json`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`configs/chains.hub.\u003cENVIRONMENT>.json`})}),`\n`]}),`\n`,_jsx(_components.p,{children:`These config files do not hardcode every endpoint. Instead, many values are placeholders that are resolved from environment variables at runtime.`}),`\n`,_jsx(_components.p,{children:`That means your real deployment depends on the quality of the RPC and service endpoints you provide.`}),`\n`,_jsx(Heading,{level:`2`,id:`signing-model`,children:`Signing model`}),`\n`,_jsx(_components.p,{children:`Each running instance uses one configured signer.`}),`\n`,_jsx(_components.p,{children:`The oracle service supports two signing modes:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`raw-private-key`})}),`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`aws-kms`})}),`\n`]}),`\n`,_jsx(_components.p,{children:`The signer is used for:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Message signatures returned by attestation endpoints.`}),`\n`,_jsx(_components.li,{children:`Execution signatures used in settlement flows.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`If your instance is meant to participate in active production signing, coordinate signer onboarding with Relay before expecting its signatures to be used downstream.`}),`\n`,_jsx(_components.p,{children:`If you are just getting started, run the instance first in shadow mode: it signs requests and proves operational readiness, but its signatures are not yet relied on for quorum.`}),`\n`,_jsx(Heading,{level:`2`,id:`peer-oracles-and-quorum`,children:`Peer oracles and quorum`}),`\n`,_jsx(_components.p,{children:`A single oracle can answer requests on its own, and the oracle service also supports peering across multiple oracle instances to collect signatures.`}),`\n`,_jsxs(_components.p,{children:[`When an incoming request sets `,_jsx(_components.code,{children:`requestPeerSignatures=true`}),` and `,_jsx(_components.code,{children:`PEERS`}),` is configured, the oracle starts its local attestation and every configured peer request concurrently.`]}),`\n`,_jsxs(_components.p,{children:[`When `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` is also configured, the oracle service returns early when a response group reaches the configured threshold of unique allowlisted signatures. The local oracle is not privileged on that path, so a quorum can be satisfied entirely by remote peers.`]}),`\n`,_jsx(_components.p,{children:`The oracle service combines responses only when their attestation payloads, signing domains, and non-signature array order match. It skips responses that fail the endpoint schema.`}),`\n`,_jsxs(_components.p,{children:[`During aggregation, the oracle service excludes duplicate signatures, signatures from non-allowlisted signers when `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` is configured, and signers missing from any signature array in a multi-message response.`]}),`\n`,_jsx(_components.p,{children:`To enable quorum aggregation, define an allowlist of trusted signer addresses and an early-return signature target:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` is a semicolon-separated allowlist of authorized signer addresses.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`ORACLE_SIGNERS_THRESHOLD`}),` is the target number of unique allowlisted signatures for early return (defaults to `,_jsx(_components.code,{children:`1`}),`). It does not prevent the oracle service from returning fewer signatures.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Each instance applies its own `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` list, so operators are not required to configure identical lists. A signature counts only at instances that list its signer, and the signer authorized onchain decides which signatures settlement accepts.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`ORACLE_SIGNERS_THRESHOLD`}),` counts the local oracle’s signature along with peer signatures. A threshold of `,_jsx(_components.code,{children:`1`}),` can be met by the local oracle alone, so a 2-of-3 configuration uses `,_jsx(_components.code,{children:`2`}),`.`]}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[_jsx(_components.code,{children:`ORACLE_SIGNERS_THRESHOLD`}),` controls early return but does not enforce quorum. During peer aggregation, Relay Oracle can return the strongest valid partial group with a success response when no group reaches the threshold, and logs the matched and required counts. Callers must verify the unique allowlisted signature count before relying on the response, and operators should alert on quorum-shortfall warnings.`]})}),`\n`,_jsx(_components.p,{children:`For example, a 2-of-3 configuration looks like this:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ORACLE_SIGNERS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`0x1111111111111111111111111111111111111111`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`0x2222222222222222222222222222222222222222`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`0x3333333333333333333333333333333333333333`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ORACLE_SIGNERS_THRESHOLD`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`2`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The oracle service fails at startup when `,_jsx(_components.code,{children:`ORACLE_SIGNERS_THRESHOLD`}),` is not an integer or is less than `,_jsx(_components.code,{children:`1`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`When `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` is configured, the oracle service also fails at startup when:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`ORACLE_SIGNERS_THRESHOLD`}),` exceeds the number of unique entries in `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Any entry in `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` is not a valid nonzero address.`]}),`\n`,_jsxs(_components.li,{children:[`The local signer address is not included in `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),`.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`When `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` is omitted, the threshold is ignored for quorum purposes, so a threshold set without an allowlist doesn’t enforce anything. The oracle service logs a startup warning when a threshold is set without the allowlist.`]}),`\n`,_jsxs(_components.p,{children:[`Without the allowlist, the oracle service waits for the local operation and every configured peer, bounded by `,_jsx(_components.code,{children:`PEER_REQUEST_TIMEOUT_MS`}),`, instead of completing early at a threshold. Only peer responses that match the local unsigned payload are aggregated, and the request fails if the local attestation fails.`]}),`\n`,_jsx(_components.p,{children:`With an allowlist, a failed local attestation is logged but does not by itself fail the request. The oracle service can return a full or partial response using only allowlisted peer signatures. If the local attestation fails, the request fails only when no valid allowlisted group is available.`}),`\n`,_jsx(_components.p,{children:`Peer calls are best-effort attempts. If a peer times out, errors, fails response validation, or returns a payload that does not match any signable group, it is skipped and a warning is emitted.`}),`\n`,_jsx(Heading,{level:`2`,id:`status-and-observability`,children:`Status and observability`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`GET /status/v1`}),` returns the instance’s `,_jsx(_components.code,{children:`status`}),`, build identifiers (`,_jsx(_components.code,{children:`version`}),`, `,_jsx(_components.code,{children:`image`}),`, `,_jsx(_components.code,{children:`gitCommit`}),`), `,_jsx(_components.code,{children:`uptimeSeconds`}),`, lowercase `,_jsx(_components.code,{children:`oracleSigner`}),` address, `,_jsx(_components.code,{children:`peerCount`}),`, and `,_jsx(_components.code,{children:`quorum`}),` configuration. Unavailable information is `,_jsx(_components.code,{children:`null`}),`. `,_jsx(_components.code,{children:`peerCount`}),` includes all configured peers, including unreachable ones.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`status`}),` is `,_jsx(_components.code,{children:`ok`}),` when the signer lookup succeeds, or `,_jsx(_components.code,{children:`partial`}),` when it fails or exceeds five seconds. It describes the local signer check, not a test signature or overall peer health. Without a configured signer allowlist, `,_jsx(_components.code,{children:`quorum`}),` is `,_jsx(_components.code,{children:`{ \"signers\": [], \"threshold\": null }`}),`.`]}),`\n`,_jsx(_components.p,{children:`Build identifiers use the first available value in these sequences:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`version`}),`: `,_jsx(_components.code,{children:`VERSION`}),`, build manifest version, package version, `,_jsx(_components.code,{children:`IMAGE_TAG`}),`, then the resolved `,_jsx(_components.code,{children:`gitCommit`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`image`}),`: `,_jsx(_components.code,{children:`IMAGE_DIGEST`}),`, `,_jsx(_components.code,{children:`IMAGE_TAG`}),`, then the build manifest image.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`gitCommit`}),`: `,_jsx(_components.code,{children:`GIT_COMMIT`}),`, then the build manifest commit.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`GET /lives/v1?withReport=true`}),` uses the same version resolution.`]}),`\n`,_jsxs(_components.p,{children:[`Add `,_jsx(_components.code,{children:`withPeers=true`}),` to `,_jsx(_components.code,{children:`/status/v1`}),` or `,_jsx(_components.code,{children:`/chains/v1`}),` to check every configured peer. The response includes one `,_jsx(_components.code,{children:`peers`}),` entry per peer, including failed checks. Without this option, the `,_jsx(_components.code,{children:`peers`}),` field is omitted.`]}),`\n`,_jsxs(_components.p,{children:[`In `,_jsx(_components.code,{children:`/status/v1?withPeers=true`}),`, each peer entry contains the same status fields plus `,_jsx(_components.code,{children:`url`}),`, `,_jsx(_components.code,{children:`checkResult`}),`, `,_jsx(_components.code,{children:`latencyMs`}),`, `,_jsx(_components.code,{children:`retryCount`}),`, and `,_jsx(_components.code,{children:`httpStatus`}),`. Unavailable fields are `,_jsx(_components.code,{children:`null`}),`, including `,_jsx(_components.code,{children:`quorum`}),` when the peer’s configuration is unknown. Peer URLs omit credentials, query parameters, and fragments, and invalid URLs are omitted entirely.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`checkResult`}),` is `,_jsx(_components.code,{children:`success`}),` when a valid report is collected, or a failure reason such as `,_jsx(_components.code,{children:`timeout`}),`, `,_jsx(_components.code,{children:`authentication-error`}),`, or `,_jsx(_components.code,{children:`invalid-response`}),`. `,_jsx(_components.code,{children:`latencyMs`}),` measures the successful request, and `,_jsx(_components.code,{children:`httpStatus`}),` records the final request’s HTTP status when available. `,_jsx(_components.code,{children:`retryCount`}),` counts retries after transient failures, excluding fallback requests.`]}),`\n`,_jsxs(_components.p,{children:[`For older peers, status checks fall back to `,_jsx(_components.code,{children:`/lives/v1?withReport=true`}),`, then `,_jsx(_components.code,{children:`/lives/v1`}),`. Legacy reports supply limited information, with their old `,_jsx(_components.code,{children:`version`}),` value treated as `,_jsx(_components.code,{children:`image`}),` metadata.`]}),`\n`,_jsxs(_components.p,{children:[`In `,_jsx(_components.code,{children:`/chains/v1?withPeers=true`}),`, peer entries contain only `,_jsx(_components.code,{children:`url`}),`, `,_jsx(_components.code,{children:`checkResult`}),`, and `,_jsx(_components.code,{children:`chainDifferences`}),`, relative to the top-level local `,_jsx(_components.code,{children:`chains`}),` list:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`local-only`}),`: the chain exists locally but not on the peer. Includes only `,_jsx(_components.code,{children:`id`}),` and `,_jsx(_components.code,{children:`status`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`peer-mismatch`}),`: the chain exists on both sides. Includes `,_jsx(_components.code,{children:`id`}),`, `,_jsx(_components.code,{children:`status`}),`, and only changed peer values for `,_jsx(_components.code,{children:`vmType`}),` or `,_jsx(_components.code,{children:`depository`}),`. `,_jsx(_components.code,{children:`depository: null`}),` means the peer has no depository configured.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`peer-only`}),`: the chain exists only on the peer. Includes `,_jsx(_components.code,{children:`id`}),`, `,_jsx(_components.code,{children:`status`}),`, `,_jsx(_components.code,{children:`vmType`}),`, and `,_jsx(_components.code,{children:`depository`}),` when present.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`chainDifferences: []`}),` means the chain sets match, while `,_jsx(_components.code,{children:`null`}),` means the comparison failed. A successful comparison can still report differences.`]}),`\n`,_jsxs(_components.p,{children:[`Peer checks run concurrently, honor `,_jsx(_components.code,{children:`PEER_REQUEST_TIMEOUT_MS`}),` and `,_jsx(_components.code,{children:`PEER_RESPONSE_MAX_BYTES`}),`, retry transient failures, and stop when the caller disconnects. Status responses and chain responses with `,_jsx(_components.code,{children:`withPeers=true`}),` set `,_jsx(_components.code,{children:`cache-control: no-store`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`security-and-trust-model`,children:`Security and trust model`}),`\n`,_jsx(_components.p,{children:`If you operate a third-party oracle, you are expected to treat it as production infrastructure.`}),`\n`,_jsx(_components.p,{children:`Minimum security expectations:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Use a dedicated signing key that is not shared with any other oracle.`}),`\n`,_jsxs(_components.li,{children:[`Prefer a hardened key-management approach. `,_jsx(_components.code,{children:`aws-kms`}),` is supported; if you use raw keys, store them in a real secret manager.`]}),`\n`,_jsx(_components.li,{children:`Enforce access control at an API gateway or reverse proxy when public access is not intended.`}),`\n`,_jsx(_components.li,{children:`Use HTTPS for any publicly reachable deployment.`}),`\n`,_jsx(_components.li,{children:`Use dedicated peer API keys to authenticate requests between oracle instances.`}),`\n`,_jsx(_components.li,{children:`Restrict management access to the host platform and secret storage.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`The oracle is stateless, but its signer is security-critical. If the signing key is compromised, the instance should be treated as compromised immediately.`}),`\n`,_jsx(Heading,{level:`2`,id:`infrastructure-requirements`,children:`Infrastructure requirements`}),`\n`,_jsx(_components.p,{children:`You should run the oracle service on a platform that can run a Docker container and expose it over HTTP.`}),`\n`,_jsx(_components.p,{children:`For most third-party operators, Railway should be treated as the preferred starting point. It is the recommended default when you want the simplest path to running a single external oracle instance.`}),`\n`,_jsx(_components.p,{children:`Typical Docker-hosting options:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Railway`}),`\n`,_jsx(_components.li,{children:`ECS`}),`\n`,_jsx(_components.li,{children:`Kubernetes`}),`\n`,_jsx(_components.li,{children:`Fly.io`}),`\n`,_jsx(_components.li,{children:`A VM running Docker`}),`\n`]}),`\n`,_jsx(_components.p,{children:`You need:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`A long-running Docker runtime.`}),`\n`,_jsx(_components.li,{children:`Stable outbound access to all configured RPC and chain service endpoints.`}),`\n`,_jsx(_components.li,{children:`Stable inbound access from callers and peer oracles.`}),`\n`,_jsx(_components.li,{children:`Secure secret storage for signing material and API keys.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`Because the oracle service is stateless, scaling horizontally is possible. In practice, the biggest operational constraints are uptime, RPC quality, and signer security.`}),`\n`,_jsx(_components.p,{children:`If you already operate production container infrastructure, another platform can be a valid choice. If not, default to Railway.`}),`\n`,_jsx(Heading,{level:`2`,id:`configuration-model`,children:`Configuration model`}),`\n`,_jsx(_components.p,{children:`The runtime configuration is mostly environment-variable driven.`}),`\n`,_jsx(_components.p,{children:`For a recommended raw-key mainnet deployment, define:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ENVIRONMENT`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`mainnets.prod`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ECDSA_PRIVATE_KEY`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`0x...`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`API_KEYS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`your-api-key:your-name`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`SIGNING_MODULE=raw-private-key`}),` is optional because raw-key signing is the default code path, but setting it explicitly is clearer for operators.`]}),`\n`,_jsx(_components.p,{children:`You can also set:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`5`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`5`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`HTTP_PORT`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`3000`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`PEERS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`https://peer-one.example.com`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`|`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`peer-api-key`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`https://peer-two.example.com`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`|`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`peer-api-key`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`PEER_REQUEST_TIMEOUT_MS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`10000`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ORACLE_SIGNERS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`0x1111111111111111111111111111111111111111`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`0x2222222222222222222222222222222222222222`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`0x3333333333333333333333333333333333333333`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ORACLE_SIGNERS_THRESHOLD`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`2`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Notes:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`HTTP_PORT`}),` is optional if your platform injects `,_jsx(_components.code,{children:`PORT`}),`. The server resolves ports in this order: `,_jsx(_components.code,{children:`HTTP_PORT`}),`, then `,_jsx(_components.code,{children:`PORT`}),`, then `,_jsx(_components.code,{children:`3000`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`API_KEYS`}),` defines the keys accepted on status, chains, and attestation routes. At least one key must be configured for the oracle to start.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`PEER_REQUEST_TIMEOUT_MS`}),` defaults to `,_jsx(_components.code,{children:`10000`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` is the semicolon-separated allowlist used to count unique oracle signatures during quorum aggregation.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`ORACLE_SIGNERS_THRESHOLD`}),` sets the target number of unique allowlisted signatures for early return. It defaults to `,_jsx(_components.code,{children:`1`}),`, must be an integer of at least `,_jsx(_components.code,{children:`1`}),`, and is validated against the size of `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` when that allowlist is configured. It does not prevent a successful response with fewer signatures.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`If you use AWS KMS instead of a raw key:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`SIGNING_MODULE`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`aws-kms`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`AWS_KMS_SIGNER_KEY_ID`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`...`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`AWS_KMS_SIGNER_KEY_REGION`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`...`})]}),`\n`]})})}),`\n`,_jsx(Heading,{level:`2`,id:`chain-rpc-and-additional-service-variables`,children:`Chain RPC and additional service variables`}),`\n`,_jsx(_components.p,{children:`The chain config files reference environment variables dynamically. Your deployment is only as reliable as the endpoints and credentials you provide there.`}),`\n`,_jsx(_components.p,{children:`There are two categories to think about:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Standard RPC URLs for the chains the oracle reads.`}),`\n`,_jsx(_components.li,{children:`Supplemental service variables for chains that need more than a plain RPC endpoint.`}),`\n`]}),`\n`,_jsx(Heading,{level:`3`,id:`core-rpc-variables`,children:`Core RPC variables`}),`\n`,_jsx(_components.p,{children:`For a mainnet deployment, the most important RPC variables to populate first are:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`8`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`8`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ETHEREUM_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`BASE_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`ARBITRUM_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`BNB_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`POLYGON_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`SOLANA_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`BITCOIN_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`HYPERLIQUID_RPC_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Why these matter:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`The oracle validates chain state in real time, so weak or rate-limited RPCs directly reduce reliability.`}),`\n`,_jsx(_components.li,{children:`If a configured chain does not have a working RPC endpoint, any attestation that depends on that chain will fail.`}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`You may also need additional chain RPC variables beyond the list above, depending on which chains remain enabled in your selected `,_jsx(_components.code,{children:`ENVIRONMENT`}),`. The chain config files are the source of truth for the full required set.`]}),`\n`,_jsx(Heading,{level:`3`,id:`supplemental-non-rpc-variables`,children:`Supplemental non-RPC variables`}),`\n`,_jsx(_components.p,{children:`Some chains and integrations need service-specific URLs or credentials in addition to a raw RPC endpoint. Common examples are:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`9`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`9`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`HYPERLIQUID_HUB_API_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`HYPERLIQUID_HUB_API_KEY`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`HYPERLIQUID_PROXY_API_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`HYPERLIQUID_PROXY_API_KEY`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`BITCOIN_ESPLORA_COMPATIBLE_API_URL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`BITCOIN_BLOCKSTREAM_CLIENT_ID`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`BITCOIN_BLOCKSTREAM_CLIENT_SECRET`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`BITCOIN_MAESTRO_API_KEY`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`LIGHTER_RPC_API_KEY`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Why these matter:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Some ecosystems expose critical settlement-related data more reliably through companion APIs than through a single generic RPC method.`}),`\n`,_jsx(_components.li,{children:`Some providers require API credentials even when the underlying access pattern still looks like a chain read.`}),`\n`,_jsx(_components.li,{children:`These variables improve reliability and coverage for chain-specific reads without changing the public API of the oracle itself.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`You do not need to expose internal provider choices publicly. You only need to ensure the variables required by your chosen providers are present, valid, and managed like any other production secret.`}),`\n`,_jsx(Heading,{level:`3`,id:`practical-guidance`,children:`Practical guidance`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Public RPC endpoints may work for lower-volume or non-critical traffic, but are typically less predictable.`}),`\n`,_jsx(_components.li,{children:`Hyperliquid and Bitcoin commonly need supplemental services, not just a single RPC URL.`}),`\n`,_jsx(_components.li,{children:`When adding or removing supported chains, recheck the required environment variables before deploying.`}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`authentication`,children:`Authentication`}),`\n`,_jsxs(_components.p,{children:[`Status, chains, and attestation routes require a valid `,_jsx(_components.code,{children:`x-api-key`}),`. Requests to these routes without a valid key return `,_jsx(_components.code,{children:`401 Unauthorized`}),`.`]}),`\n`,_jsx(_components.p,{children:`The health and documentation routes do not require an API key:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:_jsx(_components.code,{children:`/`})}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`/documentation`}),` and its subpaths`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`/lives/v1`}),`, including `,_jsx(_components.code,{children:`withReport=true`})]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Registered peers have no automatic exemption. Each peer request must carry a key accepted by the receiving oracle.`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`API_KEYS`}),` uses this format:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`API_KEYS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`key-one:partner-a`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`key-two:partner-b`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`The right-hand label is stored as metadata for that key.`}),`\n`,_jsx(Heading,{level:`2`,id:`peering-with-other-oracle-operators`,children:`Peering with other oracle operators`}),`\n`,_jsxs(_components.p,{children:[`Use `,_jsx(_components.code,{children:`PEERS`}),` to define other oracle instances this oracle should call when peer signatures are requested.`]}),`\n`,_jsx(_components.p,{children:`Format:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`PEERS`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`https://peer-a.example.com`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`|`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`peer-a-key`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`https://peer-b.example.com`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`|`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`peer-b-key`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Each entry is:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`A base URL.`}),`\n`,_jsxs(_components.li,{children:[`A per-peer API key, separated by `,_jsx(_components.code,{children:`|`}),`.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`pass-through`}),` is also supported as a peer credential, which forwards the incoming `,_jsx(_components.code,{children:`x-api-key`}),` instead of a dedicated peer key. That is generally better suited to tightly controlled internal environments. For separate organizations, use dedicated peer keys.`]}),`\n`,_jsx(_components.p,{children:`If Relay-managed oracles need to call your instance, the usual pattern is:`}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsx(_components.li,{children:`You expose a stable public HTTPS URL.`}),`\n`,_jsx(_components.li,{children:`You create a dedicated incoming API key to authenticate peer requests.`}),`\n`,_jsxs(_components.li,{children:[`Relay adds your URL and that key to the `,_jsx(_components.code,{children:`PEERS`}),` list used by its oracle instances.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`recommended-deployment-sequence`,children:`Recommended deployment sequence`}),`\n`,_jsx(_components.p,{children:`The safest way to bring up a new third-party oracle is:`}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsx(_components.li,{children:`Create a new signer dedicated to this oracle.`}),`\n`,_jsx(_components.li,{children:`Provision RPC and chain-service endpoints.`}),`\n`,_jsx(_components.li,{children:`Deploy the container on Railway first unless you already have an established alternative platform.`}),`\n`,_jsx(_components.li,{children:`Verify health and chain loading.`}),`\n`,_jsx(_components.li,{children:`Configure API keys for trusted callers and any additional edge access controls.`}),`\n`,_jsx(_components.li,{children:`Expose the oracle service over HTTPS.`}),`\n`,_jsx(_components.li,{children:`Configure peering and receive live requests in shadow mode.`}),`\n`,_jsx(_components.li,{children:`Run long enough to observe stability, latency, and RPC usage.`}),`\n`,_jsx(_components.li,{children:`Only then consider onboarding the signer for active participation.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`This order lets you validate the infrastructure and chain dependencies before your signer becomes operationally important.`}),`\n`,_jsx(Heading,{level:`2`,id:`running-the-oracle-service`,children:`Running the oracle service`}),`\n`,_jsx(_components.p,{children:`Build and run the oracle service as a Docker container:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`docker`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` build`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -t`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` relay-protocol-oracle`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` .`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`docker`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` run`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --rm`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -p`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 3000:3000`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --env-file`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` .env`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` relay-protocol-oracle`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The container entrypoint also loads secrets from `,_jsx(_components.code,{children:`/vault/secrets`}),` when present, which is useful for secret-injection systems.`]}),`\n`,_jsx(_components.p,{children:`If your platform builds from source automatically, the important requirement is still the same: the oracle service should be deployed and run as the repository’s Docker image, with the required environment variables injected at runtime.`}),`\n`,_jsx(Heading,{level:`2`,id:`first-run-verification-checklist`,children:`First-run verification checklist`}),`\n`,_jsxs(_components.p,{children:[`After deployment, verify the following. Include a valid `,_jsx(_components.code,{children:`x-api-key`}),` for status, chains, and attestation requests:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`GET /lives/v1`}),` returns `,_jsx(_components.code,{children:`{\"status\":\"ok\"}`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`GET /status/v1`}),` returns `,_jsx(_components.code,{children:`status: \"ok\"`}),`, the expected `,_jsx(_components.code,{children:`version`}),`, `,_jsx(_components.code,{children:`image`}),`, and `,_jsx(_components.code,{children:`gitCommit`}),` for the running build, and your local `,_jsx(_components.code,{children:`oracleSigner`}),` address.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`GET /chains/v1`}),` returns the chains expected for your selected `,_jsx(_components.code,{children:`ENVIRONMENT`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`GET /documentation`}),` loads the Swagger UI.`]}),`\n`,_jsx(_components.li,{children:`Your logs show the oracle service started successfully.`}),`\n`,_jsxs(_components.li,{children:[`Your logs show the signer address you expect, and that address matches one of the entries in `,_jsx(_components.code,{children:`ORACLE_SIGNERS`}),` if the allowlist is configured.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Then test at least one real attestation flow with a valid `,_jsx(_components.code,{children:`x-api-key`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`operational-monitoring`,children:`Operational monitoring`}),`\n`,_jsx(_components.p,{children:`At minimum, monitor:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Container restarts.`}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`GET /status/v1`}),` reporting `,_jsx(_components.code,{children:`status: \"partial\"`}),` and `,_jsx(_components.code,{children:`oracleSigner: null`}),`.`]}),`\n`,_jsx(_components.li,{children:`Request latency.`}),`\n`,_jsx(_components.li,{children:`Error rate by endpoint.`}),`\n`,_jsxs(_components.li,{children:[`Unexpected `,_jsx(_components.code,{children:`401 Unauthorized`}),` responses.`]}),`\n`,_jsx(_components.li,{children:`Peer timeout, peer mismatch, and quorum-shortfall warnings.`}),`\n`,_jsxs(_components.li,{children:[`Peer `,_jsx(_components.code,{children:`checkResult`}),` drift over time via `,_jsx(_components.code,{children:`GET /status/v1?withPeers=true`}),` and `,_jsx(_components.code,{children:`GET /chains/v1?withPeers=true`}),`.`]}),`\n`,_jsx(_components.li,{children:`RPC provider failures and rate limits.`}),`\n`,_jsx(_components.li,{children:`Memory and CPU consumption.`}),`\n`,_jsx(_components.li,{children:`Signer address correctness after deploys.`}),`\n`]}),`\n`,_jsx(_components.p,{children:`A healthy process can still be operationally unusable if its RPC dependencies are degraded, so RPC observability matters as much as container health.`})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Operate a Third-Party Relay Oracle","description":"How to deploy and run a Relay Oracle outside Relay's internal infrastructure","sidebarTitle":"Third-Party Oracle","href":"/references/protocol/guides/third-party-oracle"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Overview","slug":"overview","depth":2,"children":[]},{"title":"What the Relay Oracle is","slug":"what-the-relay-oracle-is","depth":2,"children":[]},{"title":"How the oracle fits into the protocol","slug":"how-the-oracle-fits-into-the-protocol","depth":2,"children":[]},{"title":"What the oracle service actually does","slug":"what-the-oracle-service-actually-does","depth":2,"children":[]},{"title":"Supported chains and data sources","slug":"supported-chains-and-data-sources","depth":2,"children":[]},{"title":"Signing model","slug":"signing-model","depth":2,"children":[]},{"title":"Peer oracles and quorum","slug":"peer-oracles-and-quorum","depth":2,"children":[]},{"title":"Status and observability","slug":"status-and-observability","depth":2,"children":[]},{"title":"Security and trust model","slug":"security-and-trust-model","depth":2,"children":[]},{"title":"Infrastructure requirements","slug":"infrastructure-requirements","depth":2,"children":[]},{"title":"Configuration model","slug":"configuration-model","depth":2,"children":[]},{"title":"Chain RPC and additional service variables","slug":"chain-rpc-and-additional-service-variables","depth":2,"children":[{"title":"Core RPC variables","slug":"core-rpc-variables","depth":3,"children":[]},{"title":"Supplemental non-RPC variables","slug":"supplemental-non-rpc-variables","depth":3,"children":[]},{"title":"Practical guidance","slug":"practical-guidance","depth":3,"children":[]}]},{"title":"Authentication","slug":"authentication","depth":2,"children":[]},{"title":"Peering with other oracle operators","slug":"peering-with-other-oracle-operators","depth":2,"children":[]},{"title":"Recommended deployment sequence","slug":"recommended-deployment-sequence","depth":2,"children":[]},{"title":"Running the oracle service","slug":"running-the-oracle-service","depth":2,"children":[]},{"title":"First-run verification checklist","slug":"first-run-verification-checklist","depth":2,"children":[]},{"title":"Operational monitoring","slug":"operational-monitoring","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":["/_mintlify/custom/unevenlabs/e205e0c83de02f5e/script.js","/_mintlify/custom/unevenlabs/90d7a6a8bc7d1a3b/content/intercom-opener.js","/_mintlify/custom/unevenlabs/a13c5467619df0df/iframe-resizer.js","/_mintlify/custom/unevenlabs/411e1fdad39a2d9e/posthog.js","/_mintlify/custom/unevenlabs/9cf2d871386c2976/playground-enhancer.js"],"subdomain":"unevenlabs","siteName":"Relay","title":"Operate a Third-Party Relay Oracle - Relay","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-08T20:02:12.622Z","originalFileLocation":"references/protocol/guides/third-party-oracle.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"light\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"How to deploy and run a Relay Oracle outside Relay\u0026#x27;s internal infrastructure\">\u003cmeta data-mint-head name=\"application-name\" content=\"Relay\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Operate a Third-Party Relay Oracle - Relay\">\u003cmeta data-mint-head property=\"og:description\" content=\"How to deploy and run a Relay Oracle outside Relay\u0026#x27;s internal infrastructure\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Relay\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.relay.link/references/protocol/guides/third-party-oracle\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://unevenlabs.mintlify.app/_mintlify/api/og/v2?division=Guides\u0026amp;title=Operate+a+Third-Party+Relay+Oracle\u0026amp;description=How+to+deploy+and+run+a+Relay+Oracle+outside+Relay%27s+internal+infrastructure\u0026amp;theme=bb8130bdebc4204a02b71ad8\u0026amp;signature=128064f7e58aa59d4964311fd41a3cc20686d4938f0f366d846274708c33f45a\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Operate a Third-Party Relay Oracle - Relay\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"How to deploy and run a Relay Oracle outside Relay\u0026#x27;s internal infrastructure\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://unevenlabs.mintlify.app/_mintlify/api/og/v2?division=Guides\u0026amp;title=Operate+a+Third-Party+Relay+Oracle\u0026amp;description=How+to+deploy+and+run+a+Relay+Oracle+outside+Relay%27s+internal+infrastructure\u0026amp;theme=bb8130bdebc4204a02b71ad8\u0026amp;signature=128064f7e58aa59d4964311fd41a3cc20686d4938f0f366d846274708c33f45a\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/unevenlabs/P-TdogRhaTE5DlJS/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Relay\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#4615C8\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.relay.link/references/protocol/guides/third-party-oracle\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/references/protocol/guides/third-party-oracle.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.relay.link/#organization\",\"name\":\"Relay\",\"url\":\"https://docs.relay.link\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/unevenlabs/Gbv82V37Oid9F6eG/logo/relay-black.svg?fit=max\u0026auto=format\u0026n=Gbv82V37Oid9F6eG\u0026q=85\u0026s=7983e6e441ff77eeac7e17c03c2d0e8c\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.relay.link#website\",\"name\":\"Relay\",\"url\":\"https://docs.relay.link\",\"publisher\":{\"@id\":\"https://docs.relay.link/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle#webpage\",\"url\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle\",\"name\":\"Operate a Third-Party Relay Oracle\",\"description\":\"How to deploy and run a Relay Oracle outside Relay's internal infrastructure\",\"dateModified\":\"2026-10-08T20:02:12.622Z\",\"isPartOf\":{\"@id\":\"https://docs.relay.link#website\"},\"breadcrumb\":{\"@id\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Settlement\",\"item\":\"https://docs.relay.link/references/protocol/overview\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Guides\",\"item\":\"https://docs.relay.link/references/protocol/guides/for-apps\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Operate a Third-Party Relay Oracle\",\"item\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle#article\",\"headline\":\"Operate a Third-Party Relay Oracle\",\"name\":\"Operate a Third-Party Relay Oracle\",\"description\":\"How to deploy and run a Relay Oracle outside Relay's internal infrastructure\",\"url\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle\",\"mainEntityOfPage\":{\"@id\":\"https://docs.relay.link/references/protocol/guides/third-party-oracle#webpage\"},\"dateModified\":\"2026-10-08T20:02:12.622Z\",\"publisher\":{\"@id\":\"https://docs.relay.link/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.relay.link#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Relay\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Relay","nav":{"global":null,"tabs":[{"tab":"Relay Platform","pages":[{"title":"Relay Platform","href":"/what-is-relay"}]},{"tab":"API Reference","pages":[{"title":"API Reference","href":"/references/api/overview"}]},{"tab":"RelayKit","pages":[{"title":"RelayKit","href":"/references/relay-kit/overview"}]},{"tab":"Relay Protocol","groups":[{"group":"Settlement","pages":[{"title":"Relay Protocol","sidebarTitle":"Overview","href":"/references/protocol/overview"},{"title":"How It Works","sidebarTitle":"How It Works","href":"/references/protocol/how-it-works"},{"group":"Components","pages":[{"title":"The Relay Chain","sidebarTitle":"Relay Chain","href":"/references/protocol/components/relay-chain"},{"title":"Depository","sidebarTitle":"Depository","href":"/references/protocol/components/depository"},{"title":"Deposit Addresses","sidebarTitle":"Deposit Addresses","href":"/references/protocol/components/deposit-addresses"},{"title":"Oracle","sidebarTitle":"Oracle","href":"/references/protocol/components/oracle"},{"title":"Hub","sidebarTitle":"Hub","href":"/references/protocol/components/hub"},{"title":"Allocator","sidebarTitle":"Allocator","href":"/references/protocol/components/allocator"},{"title":"Security Council","sidebarTitle":"Security Council","href":"/references/protocol/components/security-council"}]},{"title":"Security \u0026 Audits","sidebarTitle":"Security \u0026 Audits","href":"/references/protocol/security"},{"group":"Guides","pages":[{"title":"For Apps","sidebarTitle":"For Apps","href":"/references/protocol/guides/for-apps"},{"title":"Programmatic Withdrawals","sidebarTitle":"Withdrawals","href":"/references/protocol/guides/withdrawals"},{"title":"Withdrawing with a Safe","description":"Claim and withdraw Relay Hub funds when the recovery owner is a Safe multisig","sidebarTitle":"Safe Withdrawals","href":"/references/protocol/guides/safe-withdrawals"},{"title":"Operate a Third-Party Relay Oracle","sidebarTitle":"Third-Party Oracle","href":"/references/protocol/guides/third-party-oracle"}]},{"group":"Contracts","pages":[{"title":"Addresses","description":"Contract addresses for the Relay Settlement Protocol","sidebarTitle":"Addresses","href":"/references/protocol/addresses"},{"title":"EVM Depository Reference","sidebarTitle":"EVM Depository Reference","href":"/references/protocol/contracts/evm-depository"},{"title":"SVM Depository Reference","sidebarTitle":"SVM Depository Reference","href":"/references/protocol/contracts/solana-depository"}]}]},{"group":"Vaults","pages":[{"title":"Overview","sidebarTitle":"Overview","href":"/references/protocol/vaults/overview"},{"title":"Architecture","sidebarTitle":"Architecture","href":"/references/protocol/vaults/architecture"},{"title":null,"href":"/references/protocol/vaults/security"},{"title":"Relay Vaults Backend","sidebarTitle":"Backend","href":"/references/protocol/vaults/backend"},{"group":"Guides","pages":[{"title":"Bridging","sidebarTitle":"Bridging","href":"/references/protocol/vaults/guides/bridging"},{"title":"Interacting with Relay Vaults","sidebarTitle":"Interacting","href":"/references/protocol/vaults/guides/interacting"},{"title":"Withdrawing from Relay Vaults","sidebarTitle":"Withdrawing","href":"/references/protocol/vaults/guides/withdrawing"}]},{"group":"Contracts Reference","pages":[{"title":null,"href":"/references/protocol/vaults/contracts/RelayPoolFactory"},{"title":null,"href":"/references/protocol/vaults/contracts/RelayPool"},{"title":null,"href":"/references/protocol/vaults/contracts/RelayPoolNativeGateway"},{"title":null,"href":"/references/protocol/vaults/contracts/RelayBridgeFactory"},{"title":null,"href":"/references/protocol/vaults/contracts/RelayBridge"}]}]}]},{"tab":"Changelog","pages":[{"title":"Changelog","href":"/changelog"}]}]},"basePath":"","title":"Operate a Third-Party Relay Oracle - Relay","buildId":"dpl_DeHjVRGfFnhYQwHnfC2pjeXXv8h9","deploymentId":"dpl_DeHjVRGfFnhYQwHnfC2pjeXXv8h9","dynamics":[],"chunks":[]}