A private transaction still starts with a question. Before a wallet can sign anything, it asks a node for the chain ID, the balance, the nonce, and a gas estimate, and it asks again whenever it needs to know what changed. Each question is a JSON-RPC request to a server the user doesn’t run, and each one carries the user’s IP address and the moment it was sent. The keys stay local. The questions don’t.

Two different secrets

Chain privacy and network privacy protect different things, and the first gets nearly all the attention. A shielded pool or a private transaction hides what moved on the ledger. It doesn’t hide which machine asked about which address, or when. An RPC provider that sees one IP query the same five addresses every morning has tied them to one person without reading a single transaction. The ISP in between sees the timing even when it can’t see the content.

On a public chain the picture is plainer. Addresses, balances, contract calls, and the transaction graph are visible to everyone, and hiding the IP that asked about an address does nothing to hide the address. What it hides is the link between that address and a person on a particular connection. That link is usually the one that matters. Fixing one layer doesn’t fix the other, and the network layer belongs to nobody by default: the chain doesn’t own it, the wallet rarely thinks about it, and the user never sees it.

The fallback is the failure

Most network leaks in a wallet aren’t attacks. They’re helpfulness. A proxy is flaky, someone adds a branch that retries over plain HTTPS, and the user stops seeing errors. The privacy layer works until the day it’s under load, then quietly steps aside. Those are exactly the days it was for.

So privacy has to fail closed. If the private path is down, the request doesn’t go. An error the user can see is better than a success that hands their IP to the provider. In this build that principle comes down to an if statement with no else that reaches the network: when the Nym proxy fails its health check, the transport raises an error, and the code contains no other path to try.

One door to the network

A rule like that only holds if nothing can route around it. The design gives the network to exactly one layer. The wallet core owns accounts, transaction building, and signing. The transport owns every connection. Wallet code never opens a socket; it hands a JSON-RPC request to the transport and gets an answer back. The README calls this “the missing seam between a wallet core and a privacy network,” and the seam is the point. A boundary with one door can be checked.

Checking it is the other half of the idea. Policy only covers code that goes through the door, so tests need a way to catch code that doesn’t. A leak monitor replaces the socket’s connect call for the duration of a test and records every outbound connection to anything other than the local proxy. A wallet that grows a stray direct call fails its test suite instead of failing its user.

The experiment

Private Wallet Transport is a Python package, version 0.1.0, built in five commits between May 11 and May 13, 2026. It isn’t a wallet, a VPN, or a chain privacy protocol. Its shape:

  • A PrivacyTransport interface with two backends: direct HTTPS for development, and a transport that sends requests through a local Nym SOCKS5 proxy into the mixnet.
  • A policy that allowlists 19 Ethereum RPC methods and optional endpoints, requires a private transport by default, and refuses to build a wallet service around a non-private transport unless direct mode is explicitly allowed. A misconfigured wallet fails at startup, not on its first request.
  • A lifecycle manager that finds and starts the Nym client, checks its health every 10 seconds, and restarts it up to three times before giving up.
  • Read-only adapters for a Logos Basecamp community module and an OpenZeppelin Governor, written as Python classes.

The suite has 70 tests. On Windows, 68 pass; the two failures check a POSIX executable bit that Windows doesn’t set. Every transport test mocks the HTTP client and every lifecycle test mocks the subprocess, so the repo records no run against a real Nym client. Nothing loads the adapters into a running Basecamp. There is no signing code, because signing belongs to a wallet core that lives elsewhere.

What it doesn’t answer yet

Cost is unmeasured. Each call crosses the mixnet twice, once for the health check and once for the request, and the repo has no latency numbers to say what that costs.

The leak monitor sees TCP connections opened from Python. Name lookups go through the system resolver and never touch it, so DNS, which the README names as a target, isn’t covered.

The defaults disagree. The library requires privacy unless told otherwise, while the command-line tools don’t require it unless told to. With no fallback path the practical difference is one error type, but a privacy default should be the same everywhere. The allowlist commented as read-only also includes eth_sendRawTransaction, which a wallet needs eventually and which means the default policy permits writes.

The boundary sits in the right place. Whether people will pay a mixnet round trip for every balance check is the open question.