Why There Is No Tor Hidden-Service Wallet (2026 Test)
A wallet is an outbound client; a hidden service is an inbound endpoint. We stood up a real onion service, measured the first fetch at 27.8 s and 20 more at 1.08-2.78 s, and checked Monero's own source for where onion actually belongs in a self-custody stack.
Ask why there is no Tor hidden-service wallet and the answer sits in the direction of the connection. A hidden service exists so that other people can dial an address you control. A wallet never accepts a call: it scans chain data, signs locally, broadcasts a transaction to a node, and waits. All of that is outbound. Wrapping a wallet in an onion address therefore publishes an endpoint nobody needed, while the traffic that actually matters, your wallet's session with a node, stays exactly where it was. We stood up a real onion service today to put numbers on what that wrapper costs. (Is Tor safe for swaps?)
The 56 characters are a public key, not a domain. An onion v3 address decodes to 35 bytes: a 32-byte Ed25519 public key, a 2-byte checksum and the version byte 0x03. The checksum is SHA3-256(".onion checksum" || pubkey || 0x03) truncated to two bytes. We started a service on 127.0.0.1:8099, read back 2b6ff4u2...24qd.onion, and recomputed the whole string from the key: 35 bytes, checksum c39e, version 3, match. That is the entire trust model. No certificate authority, no DNS, no registrar, no exit node. If the address you typed was derived from the key, you are talking to the key holder, and nobody in the path can substitute a different endpoint without changing the string you see.
What the wrapper costs, measured. A 26-byte file behind that service. The first fetch from a fresh client took 27.81 seconds, which is descriptor lookup plus introduction and rendezvous setup. The next 20 fetches: 1.075 s minimum, 1.694 s median, 1.675 s mean, 2.776 s maximum to first byte. The same file directly on loopback: 0.61 ms median, so roughly 2,800 times slower. A 708,251-byte payload took 6.88 s end to end, 102,940 bytes per second. Tor's own circuit-status showed the client leg at 3 relays and the service leg at 4, so seven relays for one request where a clearnet fetch through an exit uses three. (Is Tor safe for swaps?)
Why the wallet is the wrong half to wrap. A wallet's network needs are narrow: read chain data, learn the fee, broadcast one signed transaction. All three are requests it initiates. Putting an onion in front of wallet-rpc publishes an RPC that can sign and spend, and the only default barrier is a generated 128-bit password printed at startup unless you pass --disable-rpc-login. An onion address is a bearer capability, not an access control: the string in a config file, a screenshot or a chat message is enough. And the wallet gains nothing, because it never needed to accept a connection in the first place. If you have ever recovered a wallet from a seed on a machine that was online, you have already run the client half of this design; there was no onion in it. (how Monero fees work)
Where onion actually belongs, according to Monero's source. monerod exposes --anonymous-inbound with the example "x.onion,127.0.0.1:18083,100", and refuses to start unless a matching --tx-proxy such as tor,127.0.0.1:9050,100,disable_noise is also listed, because the node must be able to relay its own transactions through the anonymity network. That is the shape that makes sense: the node is a server, so it gets an inbound onion address; the wallet stays a client and reaches the node through a SOCKS proxy. The wallet RPC code says the same thing from the other side: when a daemon proxy is set, an .onion or .i2p host counts as strong verification, and the error text spells it out: "SSL or proxy is enabled but no strong verification was configured... or use a .onion or .i2p address".
What it hides, and what it does not. It hides the pair (your IP, the endpoint) from each other and removes the exit relay, which is exactly the right fix when exits are blocked or hostile. It does not hide the amount, the payout address, the timing, or the fact that a swap happened; those live in the order and on the chain, and a hidden service cannot shorten a pool's log line. Latency is real and directional: 1.1-2.8 s per request here, 9.9-11.5 s for a full page on the site mirror, and tens of seconds on first contact. For a swap with a 30-minute deposit window and a 120-second quote TTL that is affordable; for anything that polls every few seconds it is not. Also budget for descriptor hygiene: an onion address that has appeared in a screenshot is an address you should rotate.
What that looks like on the swap side. The .onion mirror of this site served 69,252 bytes against 69,402 on clearnet, so it is the same build, and it answered with a 9.94 s time to first byte and 11.51 s total on a cold circuit; the clearnet page over the same Tor client was 1.98 s. The page ships the same inline analytics loader, but its first line is a guard: if the hostname ends in ".onion" it returns before creating the script, so on onion the tag never initialises. The rest is deliberately identical: the same order table, the same 30-minute deposit window, the same order link that functions as a capability token because there is no account to log into. Onion takes the exit node out of the path; it does not take the site out of the picture. (Is Tor safe for swaps?)
The short version. No hidden-service wallet exists because the wallet is the client half of the stack, and a hidden service is a server's answer to a server's problem. What you actually want is three separate things: a wallet that never listens, a node reachable over onion if you run one, and a site mirror that removes the exit node from the path. Today at 18:16 UTC the site quoted 100 USDT-TRC20 at 0.14812497 XMR (675.11 USDT per XMR), 1,000 USDT at 1.63675239 XMR (611.00 per XMR), 0.05 BTC at 6.42427381 XMR (128.48548 XMR per BTC) and 1 XMR at 578.713946 USDT, across a catalogue of 431 assets on 63 networks, 53 of them stablecoins. Rates move within seconds; the live quote on the page is the only one that counts. (XMR to USDT exit)