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

Verifiable RPC Quickstart

Adding Verifiable RPC is a one-line change. Swap your ethers or viem client for the vRPC drop-in, and every existing call keeps working, now verified fail-closed over the exact node-signed bytes before the data reaches your code.

:::info Access Verifiable RPC is provisioned through Ankr sales as part of an Enterprise plan. Ankr gives you a vRPC-enabled endpoint for your chains; there is no public self-serve endpoint yet. Contact the Ankr team to get one, then follow the steps below. :::

Install

The packages are published to npm under the @w3tech.io scope. The adapters declare ethers and viem as peer dependencies, so installing one never pulls in the other. Install only the adapter you use:

# ethers users
npm install @w3tech.io/vrpc-ethers ethers

# viem users
npm install @w3tech.io/vrpc-viem viem

If you are building your own adapter or verifying responses captured outside a normal client call path, install the transport-agnostic engine directly:

npm install @w3tech.io/vrpc-core

The one-line swap

You pass the single vRPC-enabled endpoint URL that Ankr provisioned for you (for example https://rpc.ankr.com/arbitrum). The SDK derives the verifiable RPC route and the attestation route from it, so there is no second base URL to configure.

ethers v6

// Before:
// import { JsonRpcProvider } from "ethers";
// const provider = new JsonRpcProvider(url);

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

const provider = new VrpcProvider(url, chainId);

VrpcProvider extends JsonRpcProvider. Everything downstream, getBalance, call, getBlock, getLogs, contract reads, polling, is unchanged; the only difference is that the raw response bytes are verified before they are parsed.

viem

// Before:
// import { http, createPublicClient } from "viem";
// const client = createPublicClient({ transport: http(url) });

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

const client = createPublicClient({
chain: arbitrum,
transport: vrpcHttp(url),
});

vrpcHttp(url) is a custom transport that substitutes for http(url). Every viem action (getBalance, readContract, getLogs, getBlock, estimateGas, getTransactionReceipt, sendRawTransaction, and so on) funnels through its single verifying request.

The chain id is optional on both adapters. If you omit it (new VrpcProvider(url), or a viem client with no chain), the SDK derives it from a signed eth_chainId response on first use and verifies that signature self-consistently, so the derived value is still cryptographically attested by the node. A tampered or unsigned eth_chainId fails fast.

Passing the chain id explicitly is still strongly recommended. For ethers, pass it positionally: new VrpcProvider(url, chainId). For viem, set chain on the client (the chain id comes from chain.id). Doing so:

  • Pins your expected chain, catching a wrong-node or wrong-URL misconfiguration where you would otherwise verify genuine data from the wrong chain, and
  • Skips the bootstrap round-trip.

The adapters accept number | bigint | string. EVM chain ids can be passed as numbers; non-EVM chains pass the exact configured string (for example TON's "-239").

Verify it yourself

If you want to verify a response outside of a normal ethers or viem call, for example in an audit script or an off-chain pipeline, use verifyResponse from @w3tech.io/vrpc-core. It is the transport-agnostic verification seam: hand it the content-decoded request bytes, the response bytes, and the response headers, and it rebuilds the signed pre-image, checks the Ed25519 signature, and enforces the replay window, all fail-closed.

import { verifyResponse, VerificationError } from "@w3tech.io/vrpc-core";

try {
const pair = await verifyResponse(requestBytes, responseBytes, responseHeaders, {
chainId: "1", // must match the exact string the sidecar signs with
});
// pair.responseBytes — verified bytes, exactly as signed
} catch (err) {
if (err instanceof VerificationError) {
console.error(err.kind, err.message); // typed, fail-closed
}
throw err;
}

Because it is byte-level, verifyResponse verifies any signed HTTP exchange, JSON-RPC POSTs and path-based REST responses alike. For a GET, the request body is empty, so pass an empty Uint8Array.

Next steps