Chain Queries

A chain query is a one-shot, DON-backed, verifiable blockchain read. You submit a query to CRE Connect, the Chainlink DON executes it against a target block, and you receive a cryptographically signed result that you can verify end-to-end off-chain.

Queries are channel-scoped and asynchronous: creation returns 202 Accepted immediately, and the result arrives via polling or as a query.status event on the channel's event stream.

evm_call query kind

The only supported query kind today is evm_call: a read-only EVM eth_call against a specified block. The DON executes the call and returns the raw ABI-encoded return bytes. No state is modified on chain.

Block selection

Every query specifies a block selection that determines which block the call executes against:

SelectorDescription
latestResolve to the latest block before executing.
finalizedResolve to the finalized block before executing.
block_numberExecute against an explicit block number (decimal uint64).

The resolved block (block number, block hash, block timestamp) is included in the verifiable result, proving which block was actually read. This means you can verify not just what the DON read, but when it read it.

Query lifecycle

Queries move through a bounded state machine:

StateDescriptionTerminal?
acceptedQuery created and persisted; job enqueued for dispatch.No
sendingDispatch worker is actively sending to the CRE gateway.No
sentSuccessfully dispatched to CRE gateway; awaiting DON callback.No
completedDON returned a successful result with OCR proof.Yes
failedDON returned an error, or dispatch failed permanently.Yes
expiredTTL elapsed before a terminal callback arrived.Yes

The default TTL is 5 minutes. If no terminal callback arrives within this window, the query transitions to expired.

See Lifecycles for the full state diagram.

How results are delivered

There are two complementary paths:

  1. Poll the query resource: call GET /channels/{channel_id}/queries/{query_id} (or use client.Queries.Wait) until the query reaches a terminal status. This is the primary SDK path.
  2. Channel events: terminal query results are emitted as query.status events on the channel's event stream. Search or poll for them with client.Events.SearchEvents, filtered by type=query.status.

Both paths carry the same data: the verifiable_result, event_hash, and OCR proof.

The verifiable result

When a query reaches completed or failed, the result includes a base64-encoded verifiable_result string. Decoding it yields a ChainQueryVerifiableEvent envelope:

FieldDescription
serviceAlways "_crec".
nameAlways "ChainQuery".
chain_selectorThe chain the query was executed on.
timestampWhen the terminal result was produced.
data.query_idUUID of the query.
data.channel_idUUID of the owning channel.
data.query_kindThe query kind (evm_call).
data.targetThe EVM call target: from_address, contract_address, call_data.
data.block_selection.requestedThe original block selector you chose.
data.block_selection.resolvedThe concrete block metadata: block_number, block_hash, block_timestamp.
data.resultPresent on success: raw_return_data (0x-prefixed ABI-encoded bytes).
data.errorPresent on failure: code, message, and optional raw_revert_data.

Exactly one of data.result or data.error is present on a terminal result.

Verification

Terminal query.status events carry OCR proofs and can be verified with client.Events.VerifyQueryStatus. The verification algorithm is the same as for watcher.event and operation.status, with one difference: the event hash is computed as Keccak256(verifiable_result) instead of Keccak256(verifiable_event).

See Event Verification for the full algorithm.

Idempotency keys

Every query create requires an idempotency key. Keys are scoped to (org_id, channel_id, idempotency_key):

  • Same key, same request → the original query is returned (idempotent success).
  • Same key, different request409 Conflict with IDEMPOTENCY_KEY_MISMATCH.

Use a deterministic, unique-per-logical-request key (e.g. "balance-check-eth-2026-08-26-001") so that retries after network errors don't create duplicate queries.

Queries vs watchers

Both queries and watchers are channel-scoped, DON-backed chain reads, but they serve different purposes:

AspectQueriesWatchers
PurposeOne-shot on-demand readPersistent event subscription
ExecutionSingle call, returns a resultContinuous monitoring, emits events
Lifecycleaccepted → … → completed / failed / expired (TTL-bounded)pendingactivearchived (no TTL)
TTL5 minutes defaultNo expiry
Block selectionExplicit (latest / finalized / block_number)Confidence level (latest / safe / finalized)
IdempotencyRequired (idempotency_key)Not applicable
ResultSingle verifiable result with OCR proofStream of verifiable events with OCR proofs

A query asks "what is the value of X at block Y?" and gets a single signed answer. A watcher asks "tell me whenever event X happens" and receives a stream of signed events over time.

Error codes

When a query reaches failed, the data.error object in the verifiable result carries a machine-readable error code:

CodeMeaning
CRE_GATEWAY_REJECTEDCRE gateway rejected the query (e.g. 4xx).
CONTRACT_NOT_FOUNDThe target contract address does not exist on chain.
CALL_REVERTEDThe EVM call reverted; raw_revert_data contains revert bytes.
CHAIN_UNAVAILABLEThe target chain was unreachable during execution.
BLOCK_SELECTION_NOT_AVAILABLEThe requested block is not available.
CRE_WORKFLOW_FAILEDThe CRE chain-query workflow itself failed.
QUERY_EXPIREDQuery expired before a terminal callback arrived.
INTERNAL_ERRORUnexpected internal error.

See Error Handling for the full sentinel error catalog.

Get the latest Chainlink content straight to your inbox.