For AI agents: an LLM-friendly Markdown version of every page is available by appending .md to its URL or by sending an Accept: text/markdown request header. The full documentation index is at https://www.ankr.com/docs/llms.txt
Skip to main content

Ankr Confidential RPC

In Ankr Confidential RPC mode every request and every answer is encrypted end to end between your application and one attested blockchain node. The Ankr gateway still authenticates, routes, rate-limits and bills each call. It sees the API key, the client IP address, the chain, the node, sizes, status and timing, not what was asked or answered. Every answer is still signed inside the enclave and checked by your SDK, exactly as in open mode.

Same endpoint, same API key, same JSON-RPC: you switch on one option.

:::info Demo phase Ankr Confidential RPC is live in demo phase on Ankr's production network and rolling out across Verifiable RPC chains. During the demo phase the node's own error logs, which can include the method and error data of a failed call, can still leave the confidential VM; this is closed before the first customer uses production. We enable Verifiable RPC for your API key on the chains you need; Ankr Confidential RPC is one SDK option on top. Customer production follows per agreement: contact the Ankr team and tell us which chains you need first. :::

How a call travels​

  1. Discover and attest. The client requests an attestation from your chain's vRPC route with a fresh nonce. The Ankr gateway picks a node of that chain your API key may use and returns its attestation unchanged, plus vRPC-NodeId. The client verifies the TDX quote exactly as in open mode.
  2. Pin. The client pins that node for later calls. The attested signing key is also the node's encryption key, so there is no extra key exchange.
  3. Seal. The client builds a normal HTTP request (method, path, content-type, accept, body; for JSON-RPC a POST / with the JSON body or a batch) and encrypts it to the pinned node, with a fresh ephemeral key per call.
  4. Relay. The gateway sends the call to exactly the pinned node, with no fallback, and passes the encrypted body through byte for byte. The sidecar decrypts it inside the enclave, calls the node, and returns the answer encrypted for this request and signed.
  5. Verify and open. The client checks the signature, checks that the signer is the pinned node, and only then decrypts.

Every failure is a typed error. If the answer blames the node (a failed verification or decryption, an unexpected response, a refusal other than 413 or 429), the client drops the pin and the next call attests again, possibly a different node. The SDK never retries the failed call itself.

What each party sees​

PartySeesDoes not see
The network between you and AnkrTLS onlyAnything inside TLS
Ankr gateway and edge, Ankr logs and telemetryThat a confidential call happened and when; the API key; the client IP address; the chain; the pinned node; request and response sizes; the HTTP status; timing; the number of calls in a batchMethod, params, path, query, inner headers, request and response bodies
Operator of the node's host machineEncrypted traffic to the confidential VM; its resource usageThe plaintext inside the VM, protected by Intel TDX, and the node's logs, as long as log export from the VM is off
Sidecar and node inside the VMThe full request and response, which they need to execute the callYour identity and outer headers; they never reach the node

Enable it​

The chain id is required in this mode: the client never asks the node for eth_chainId in clear.

ethers v6​

import { FetchRequest } from "ethers";
import { VrpcProvider } from "@w3tech.io/vrpc-ethers";

const req = new FetchRequest("https://rpc.ankr.com/arbitrum_vrpc");
req.setHeader("x-api-key", process.env.ANKR_API_KEY!);

const provider = new VrpcProvider(req, 42161, { confidential: true });
await provider.confidentialReady(); // optional: attest at start-up
console.log(provider.getConfidentialInfo()); // { nodeId, pubkey, composeHash, attestedAt }
console.log(await provider.getBlockNumber());
  • Headers, credentials and the timeout come from the FetchRequest.
  • The provider switches CCIP-Read off, because it would send call data in plaintext to a gateway the contract names. Do not turn it back on. A call that reverts with OffchainLookup fails with CALL_EXCEPTION, and ENS names with off-chain resolvers do not resolve. ENS avatar and IPFS metadata fetches are not RPC calls and still go out in plaintext.
  • Do not put a confidential provider into a FallbackProvider with plain providers: it sends calls to the others in plaintext. Even with confidential providers only, FallbackProvider can run the same call on more than one node.
  • Concurrent calls are batched into one encrypted request. Keep batchMaxCount at 1000 or less (the node's batch limit; the ethers default is 100) and batchMaxSize at its default.

viem​

import { createPublicClient } from "viem";
import { arbitrum } from "viem/chains";
import { vrpcHttp } from "@w3tech.io/vrpc-viem";

const client = createPublicClient({
chain: arbitrum, // required in this mode
transport: vrpcHttp("https://rpc.ankr.com/arbitrum_vrpc", {
confidential: true,
headers: { "x-api-key": process.env.ANKR_API_KEY! },
}),
ccipRead: false, // keep every request encrypted
});

await client.transport.ready(); // attest at start-up
console.log(await client.getBlockNumber());
  • Set ccipRead: false on the client; the transport cannot change a client option.
  • Call ready() at start-up: otherwise the first call includes the attestation and counts against viem's 10-second timeout.
  • Do not use fallback([...]) with a plain http transport. If you use fallback at all, set retryCount: 0.
  • vrpcHttp does not batch: each call is one encrypted request.

core: any HTTP request​

import { ConfidentialClient } from "@w3tech.io/vrpc-core/confidential";

const client = new ConfidentialClient({
url: "https://rpc.ankr.com/arbitrum_vrpc",
chainId: "42161",
headers: { "x-api-key": process.env.ANKR_API_KEY! },
});

const res = await client.request({
method: "POST",
path: "/",
contentType: "application/json",
accept: "application/json",
body: new TextEncoder().encode(
JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_blockNumber", params: [] }),
),
});
console.log(res.status, new TextDecoder().decode(res.body));

request() returns the node's status, contentType and body. Only content-type and accept travel inside the encrypted request, together with the method, path, query and body; headers go to the gateway only.

Any language: vrpc-proxy​

npx @w3tech.io/vrpc-proxy --confidential --upstream https://rpc.ankr.com/arbitrum_vrpc --chain-id 42161 --api-key "$ANKR_API_KEY"
  • The proxy attests one node at start-up, seals every client request (JSON-RPC, a batch or any REST path) to it and relays the decrypted answer.
  • The connection between your application and the proxy carries plaintext. Run the proxy next to the application and protect that connection.
  • Only the configured API key goes upstream; a client-supplied x-api-key is ignored in this mode. One proxy pins one node; run more proxies to scale.

Behavior to know​

  • Pinning. The first call, or ready(), attests and pins one node. The pin lives for one hour by default (pubkeyCacheTtlMs) or until a failure that blames the node; the next call then attests again and may pick another node. An abort, a failure before sending, TransportFailed, StaleTimestamp and a 413 or 429 refusal keep the pin.
  • No retries. The SDK never resends a call, never fails over and never follows a redirect. Retrying is your decision.
  • Did it run? Every error carries executed: "no" (the gateway or node did not run this attempt) or "unknown" (check state before resending a state-changing call). mayHaveExecuted(err) answers it for any value. "no" is the gateway's unsigned word about this attempt, and an edge retry of an earlier attempt may still have run. An aborted call rejects with your signal.reason and has no executed. A call without a verifiable answer throws an UnavailableError: AttestationUnavailable, RequestRejected, UnexpectedResponse, TransportFailed or InternalError.
  • Broken or hostile path. A VerificationError: BadSignature, ConfidentialSignerMismatch, ConfidentialDecryptFailed, MalformedConfidentialResponse, plus the open-mode classes. The pin is already dropped (except on StaleTimestamp).
  • Node answers are returned. ConfidentialClient returns any inner status as the response, including the sidecar's own application/problem+json errors, such as urn:vrpc:error:batch-too-large for a batch over 1000 calls. ethers and viem turn an inner non-2xx answer into their usual errors: SERVER_ERROR in ethers, HttpRequestError or RpcRequestError in viem.
  • A call can run twice. The Ankr edge can resend a failed request to the same pinned node, and your client cannot see it. Check state before resending a state-changing call.
  • No content-based filtering. Because Ankr cannot read the calls, Ankr-side method or contract policies do not apply to them; only the node's own configuration and the batch limit do.

Errors by origin​

Where the error comes fromFormExamplesDid the node run the call
Ankr gateway and edgePlaintext, unsigned401/403 key, 429 rate limit, 413 size, 404 unknown node, 502 node unreachable or timeoutNo for 4xx; unknown for 502
Sidecar, before decryptionPlaintext, unsigned400 ohttp-key (one answer for any key, suite or decryption failure), 413, 405No
Sidecar after decryption, and the node itselfEncrypted and signed; outer status mirrors the inner one400 bad request, 413 batch too large, 415, 502 upstream, 504 upstream timeout; any answer of the nodePer the inner status

Limits​

Except for the request body size, each limit fails first at the component closest to the node, so wherever possible you get a signed, encrypted error instead of a gateway error.

LimitValueWhat you get
Calls in one JSON-RPC batch1000Inner 413 urn:vrpc:error:batch-too-large; no call in the batch runs
Request body through the gateway5 MiB + 64 KiBRequestRejected (413)
Request body at the node8 MiBRequestRejected (413); the gateway limit is reached first
Inner header section64 KiBInvalidInnerRequest, nothing sent
Response at the node128 MiBInner 502 urn:vrpc:error:upstream
Response through the gateway160 MiBUnexpectedResponse (502)
Response read by the SDK192 MiBTransportFailed (too-large)
Node call at the sidecar25 sInner 504 urn:vrpc:error:upstream-timeout
Node call at the gateway30 sUnexpectedResponse (502)
Attestation60 sAttestationUnavailable (timeout)

Routes​

The SDK and the proxy build these from your vRPC route. You need them only to write your own client.

Key in the path
attestation GET /<chain>_vrpc/<key>/attestation?nonce=<hex>
call POST /<chain>_vrpc/<key>/confidential?node_id=<vRPC-NodeId>

Key in an x-api-key header
attestation GET /<chain>_vrpc/attestation?nonce=<hex>
call POST /<chain>_vrpc/confidential?node_id=<vRPC-NodeId>

Calls carry Content-Type: message/ohttp-req; answers are message/ohttp-res. Adding ?node_id= to an attestation request re-attests a node you already know. The host is rpc.ankr.com. The byte formats are in Ankr Confidential RPC cryptography.

Known limitations​

  • A cut answer looks like tampering. If the node connection drops or the gateway timeout fires mid-answer, the client receives a shorter, well-formed encrypted message and reports BadSignature, although the node may have run the call.
  • Browsers on other origins. The public endpoint does not expose the vRPC-* headers to other origins, so a page on another origin cannot read vRPC-NodeId and gets AttestationUnavailable (node-id-missing). Server-side and Node.js use is the supported path.
  • Redirects in browsers. A REST node's redirect answer reaches a browser as an opaque redirect, and the SDK reports UnexpectedResponse.
  • Chains not enabled yet. On a chain where Ankr Confidential RPC is not served yet, the call fails with RequestRejected (404). Ask us which chains are enabled for your key.
  • Your own fetch can follow redirects. A custom getUrlFunc (ethers), fetchFn (viem) or a fetch built on XMLHttpRequest (React Native, whatwg-fetch) can follow a redirect, which resends the encrypted request and your API key to its target. Such a function must not follow redirects or retry. In viem, a per-call retryCount you pass to client.request resends the call.
  • The gateway cannot fail over on a node error. An inner error such as a lagging node's header not found is encrypted, so the gateway cannot see it and route around it.

What Ankr Confidential RPC does not promise​

  • Not anonymity. Ankr still sees the API key, the client IP address, the chain, when calls happen, how many and how large. Who you are stays known to Ankr; what you ask does not.
  • The node reads the request. It has to, to execute it. Confidentiality towards the people who operate the machine rests on Intel TDX, with the same hardware trust and the same acknowledged physical-attack limits as open vRPC. See the Trust model.
  • No forward secrecy. Traffic is encrypted to the node's long-term attested key. If that key were ever extracted from the enclave, recorded traffic to that node could be decrypted. Any node software version Ankr approves for that node can derive the same key.
  • Not front-running protection by itself. A transaction is private on its way to the node; once the node broadcasts it, it is as public as any transaction on that chain.
  • A call can run twice. If the network retries a failed request, the node may execute it again. The SDK tells you when this is possible.
  • No content-based filtering by Ankr. Only the node's own configuration applies to an encrypted call.