Integration Guide: Anonymizing Networks

View on GitHub →

anon-rpc lets an anonymizing network — a mixnet, onion router, RPC privacy relay — offer its transport to every anon-rpc wallet at once, with no per-wallet SDK integrations. The mechanism: the network ships its client as a worker bundle, a single file identified by the keccak256 hash of its bytes; that hash is published on-chain in a specifier contract the network owns; and wallets run the bundle in a sandbox, granting it a small capability API. The trust a wallet extends is narrow — bounded to the RPC path by the sandbox — and pinned to bytes anyone can audit.

This guide walks through providing that: authoring a conforming worker, publishing its specifier, and hosting the bytes. Three deliverables:

  1. a conforming worker bundle (your network client, one file);
  2. a specifier contract pinning its hash and suggesting where to fetch it;
  3. hosting for the bundle bytes (any static host; or over KPS itself).

1. Author the worker

Start by copying impl/passthrough-worker — a complete, minimal, conforming worker. It deliberately copies the worker-facing types (spec-types.ts) rather than importing them: your worker is a standalone artifact, buildable with no dependency on any harness.

The shape of every worker is an accept loop:

declare const anonRpcWorker: AnonRpcWorkerApi; // your entire platform

(async () => {
  // Set up your network client here (dial entry nodes via anonRpcWorker.kps,
  // read anonRpcWorker.config, load state from anonRpcWorker.storage…).

  anonRpcWorker.signalReady(); // or signalFailed({ code, message }) — see below

  for (;;) {
    const call = await anonRpcWorker.acceptCall();
    if (call.kind !== "fetch") continue; // future call kinds: ignore unknown
    call.respond(handle(call.url, call.requestInit));
  }
})();

The harness queues inbound calls in order, with backpressure, buffering them until the worker accepts — a slow startup loses nothing. A call leaves the queue only by being delivered, aborted by the host, or failed back to the host if the worker dies; none are silently discarded.

Your platform: the capability API

Everything your code gets is on the global anonRpcWorker (SPEC §7):

Conformance (SPEC §3.2)

Build: your bytes are your identity

Bundle to a single standalone file (the template uses esbuild, IIFE, no external imports). The exact bytes are what workerHash() pins — build deterministically, commit the toolchain, and publish the source so anyone can rebuild and verify the hash. That reproducibility is your users' audit trail.

Test against the reference harness before publishing: point the live demo at your specifier (next step) on a local chain, or adapt the repo's e2e (impl/test/run-e2e.mjs), which boots workers against mock specifiers.

2. Publish the specifier

The on-chain surface anon-rpc requires is deliberately tiny (SPEC §4):

interface IWorkerSpecifier {
  // keccak256 hash of the canonical worker bundle bytes.
  function workerHash() external view returns (bytes32);
  // Suggested locations from which the bundle MAY be retrieved.
  function workerResolvers() external view returns (string[] memory);
}

Any contract exposing those two views is a specifier — and implementing your own is squarely in your wheelhouse. The hash is what wallets pin; how it changes is your governance design space: a multisig or timelock behind the setter, DAO-voted upgrades, integration with on-chain machinery your network already has. Whatever update policy your users already trust in you can govern which code they run.

For a ready-made starting point, impl/specifier has WorkerSpecifier.sol, a deliberately simple single-owner model:

It comes with a publish script:

cd impl/specifier
# .env: RPC_URL, PRIVATE_KEY, RESOLVER_URLS, … (see its README for all knobs)
node publish-worker.mjs

The script rebuilds the bundle, prints its hash, verifies each resolver actually serves the pinned bytes before spending gas, deploys, reads the contract back, and (with ETHERSCAN_API_KEY) verifies the source. Deployment is ~1.1M gas. RESOLVER_URLS accepts https: URLs and kps: resolver strings; --github also publishes the bundle to a content-addressed branch of your repo and adds its raw URL as a resolver.

3. Host the bundle

Resolvers are advisory — any bytes matching the hash are accepted, from anywhere — so hosting is low-stakes and you should list several:

Checklist

Reference