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
- 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. - 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.
- Seal. The client builds a normal HTTP request (method, path,
content-type,accept, body; for JSON-RPC aPOST /with the JSON body or a batch) and encrypts it to the pinned node, with a fresh ephemeral key per call. - 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.
- 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
| Party | Sees | Does not see |
|---|---|---|
| The network between you and Ankr | TLS only | Anything inside TLS |
| Ankr gateway and edge, Ankr logs and telemetry | That 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 batch | Method, params, path, query, inner headers, request and response bodies |
| Operator of the node's host machine | Encrypted traffic to the confidential VM; its resource usage | The 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 VM | The full request and response, which they need to execute the call | Your 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
OffchainLookupfails withCALL_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
FallbackProviderwith plain providers: it sends calls to the others in plaintext. Even with confidential providers only,FallbackProvidercan run the same call on more than one node. - Concurrent calls are batched into one encrypted request. Keep
batchMaxCountat 1000 or less (the node's batch limit; the ethers default is 100) andbatchMaxSizeat 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: falseon 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 plainhttptransport. If you usefallbackat all, setretryCount: 0. vrpcHttpdoes 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-keyis 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,StaleTimestampand 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 yoursignal.reasonand has noexecuted. A call without a verifiable answer throws anUnavailableError:AttestationUnavailable,RequestRejected,UnexpectedResponse,TransportFailedorInternalError. - Broken or hostile path. A
VerificationError:BadSignature,ConfidentialSignerMismatch,ConfidentialDecryptFailed,MalformedConfidentialResponse, plus the open-mode classes. The pin is already dropped (except onStaleTimestamp). - Node answers are returned.
ConfidentialClientreturns any inner status as the response, including the sidecar's ownapplication/problem+jsonerrors, such asurn:vrpc:error:batch-too-largefor a batch over 1000 calls. ethers and viem turn an inner non-2xx answer into their usual errors:SERVER_ERRORin ethers,HttpRequestErrororRpcRequestErrorin 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 from | Form | Examples | Did the node run the call |
|---|---|---|---|
| Ankr gateway and edge | Plaintext, unsigned | 401/403 key, 429 rate limit, 413 size, 404 unknown node, 502 node unreachable or timeout | No for 4xx; unknown for 502 |
| Sidecar, before decryption | Plaintext, unsigned | 400 ohttp-key (one answer for any key, suite or decryption failure), 413, 405 | No |
| Sidecar after decryption, and the node itself | Encrypted and signed; outer status mirrors the inner one | 400 bad request, 413 batch too large, 415, 502 upstream, 504 upstream timeout; any answer of the node | Per 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.
| Limit | Value | What you get |
|---|---|---|
| Calls in one JSON-RPC batch | 1000 | Inner 413 urn:vrpc:error:batch-too-large; no call in the batch runs |
| Request body through the gateway | 5 MiB + 64 KiB | RequestRejected (413) |
| Request body at the node | 8 MiB | RequestRejected (413); the gateway limit is reached first |
| Inner header section | 64 KiB | InvalidInnerRequest, nothing sent |
| Response at the node | 128 MiB | Inner 502 urn:vrpc:error:upstream |
| Response through the gateway | 160 MiB | UnexpectedResponse (502) |
| Response read by the SDK | 192 MiB | TransportFailed (too-large) |
| Node call at the sidecar | 25 s | Inner 504 urn:vrpc:error:upstream-timeout |
| Node call at the gateway | 30 s | UnexpectedResponse (502) |
| Attestation | 60 s | AttestationUnavailable (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 readvRPC-NodeIdand getsAttestationUnavailable(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-callretryCountyou pass toclient.requestresends the call. - The gateway cannot fail over on a node error. An inner error such as a lagging node's
header not foundis 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.
Related
- Ankr Confidential RPC cryptography: the key, the encryption and the signature over ciphertext.
- Trust model: who is trusted for what, in both modes.
- What is verified: the checks behind every response.
- SDK core README, section "Ankr Confidential RPC mode": github.com/w3tech/verifiable-rpc-sdk.