The ENS demo, pulled apart
A normal ENS lookup (name to address to IPFS website), built with ethers.js, except every network request goes through the Nym mixnet instead of leaving over your normal connection. The Ethereum RPC node and the IPFS gateway see the IPR exit’s IP, not yours, and your ISP cannot see which names or sites you reach.
The swap
ethers v6 exposes FetchRequest.getUrlFunc as a settable property, so you replace its HTTP transport with a function that calls mixFetch. To ethers it looks like an ordinary fetch; to the mixnet, ethers looks like any other caller.
import { JsonRpcProvider, FetchRequest } from 'ethers';
import { mixFetch } from '@nymproject/mix-fetch';
function buildProvider(rpcUrl: string): JsonRpcProvider {
const base = new FetchRequest(rpcUrl);
// Route every JSON-RPC call through mixFetch.
base.getUrlFunc = async (req) => {
const res = await mixFetch(req.url, {
method: req.method,
headers: req.headers,
body: (req.body ?? undefined) as BodyInit | undefined,
});
return {
statusCode: res.status,
statusMessage: res.statusText,
headers: Object.fromEntries(res.headers),
body: new Uint8Array(await res.arrayBuffer()),
};
};
// The real adapter also runs a DecompressionStream over each response
// (mixFetch does not decompress gzip); see the caveat below.
// staticNetwork skips the eth_chainId probe: one mixnet round trip saved.
return new JsonRpcProvider(base, 'mainnet', { staticNetwork: true });
}One caveat: browser fetch decompresses gzip transparently and mixFetch does not, so the demo adds a DecompressionStream step after each response (Cloudflare gzips RPC replies).
The full demo, with decompression and per-call logging, is in components/demos/ens in the nym monorepo.
The lookup is three steps
- Resolve address.
provider.resolveName(name)reads the ENS registry’sresolver(node), then callsresolver.addr(node)for the Ethereum address. - Get contenthash. One more
eth_call:resolver.contenthash(node), decoded to anipfs://URI. - Fetch from IPFS. A plain HTTPS GET to a gateway with the CID.
Each step is an Ethereum call or an HTTPS GET over the same tunnel.
What to expect
- The first request is the slow one. Connecting builds the mixnet client and handshakes with the IPR; no TCP or TLS yet. The first request to a host then runs a TCP and TLS handshake carried as IP packets over the mixnet (several sequential round trips). smolmix keeps that connection warm and reuses it, so later requests to the same host are much quicker. A long pause is handshakes in flight, not a hang.
- You will not see the tunnelled requests in DevTools. The RPC and IPFS requests never touch the browser’s
fetch. They leave the worker as encrypted packets over a single WebSocket to the entry gateway, which is the one connection the Network tab shows. The exception is Verify IP routing, which deliberately makes one direct clearnet call toipinfo.iofor comparison. - Rate limiting. Public IPFS gateways and Ethereum RPCs rate-limit shared IP addresses. If requests start failing with 403, 429, or connection errors, the exit IP is likely flagged: tick Use random IPR and reload for a fresh exit.
Live: ENS over the mixnet
Loading the demo…