DEV Community

Cover image for Relay wallets — what adding hops actually changes when someone traces a transfer
d joker
d joker

Posted on Edited on

Relay wallets — what adding hops actually changes when someone traces a transfer

Every transfer you make is a permanent public record of one sentence: this address paid that address. That's fine when the sentence is boring. It stops being fine the moment one of the two addresses is one people already know.

Concretely: you have a main wallet. You also have a wallet that's going to be public — the one you deploy a token from, the one that shows up on a project page, the one you hand to a collaborator to pay them, the one you use so your personal address never touches a contract you don't trust. The moment you fund it from your main wallet, that public wallet carries your main wallet's address with it, forever, in a form anyone can read. And it's not only funding: every direct payment you make from an address you care about teaches the recipient which address you care about.

Private Transfers (/wallet/distribute) is the tool we built for that single sentence. Here's how it works, what it costs, and what it doesn't do.

The idea, in one paragraph

Instead of paying the destination from your wallet, the money passes through a short chain of throwaway wallets we call relays: your wallet → relay 1 → relay 2 → … → destination. Each relay signs for itself, and there's a random pause before every hop after the first. Nothing is left sitting in the relays when it's over.

The record that used to be one line is now a route, and the last leg of it never involves your wallet. Being exact about what that buys you: the route is still walkable. Start at the destination, look at what paid into it, and you've found the last relay; look at what paid into that one, and you've found the relay before it. Since every relay empties itself, there's barely any branching to guess through — it's one lookup per hop, walked backwards, until it lands on your address.

So the thing that changed is not whether the answer exists. It's that the answer went from one glance at a deposit address to a handful of deliberate steps someone has to decide to take, and the number of steps is the number of relays you picked. The tool says it plainly, in its own words: "There's no such thing as true anonymity on-chain; this just makes tracing much more expensive. You still have to be careful about patterns like timing and amounts." If you want a tool that promises more than that, this isn't it.

Before you start

  • The private key of the paying wallet. The field shows it in plaintext on purpose — password-masked inputs turned out to break pasting on phones — and it displays the address that key derives, so you can see which wallet you're about to spend from before anything is signed. It never leaves your browser and it's never saved into the form draft.
  • The destination address and the amount.
  • Somewhere to save one seed phrase. This matters more than it looks; step 1 explains why.
  • Costs: 0.0001 of that chain's native coin per relay, plus gas on every transaction in the route. Do the first one on the BNB testnet — it's one click in the chain switcher.

Step 1 · Generate relay wallets

Pick Number of relays. The field's own guidance: one to five is usually enough, up to ten for a larger amount; twenty is the hard cap. Each extra relay is one more step someone has to walk backwards, and one more round of gas and waiting for you — past a few, you're paying for legwork nobody was going to do.

Press Generate relay wallets and the tool shows you one 12-word mnemonic. Every relay in the batch is derived from it, in order.

That phrase is the entire backup. The relays' private keys are computed from it on the fly and we don't store a single byte of them, which is why the screen says what it says: "It exists only on this computer. If caches are cleared or you switch machines, this phrase is the only way to recover the relay addresses and get the money out — so save it before you continue (a password manager or a piece of paper both work)." There's a CSV export next to it (relay-wallets-N.csv), because "I copied the keys and then closed the tab" has happened to everyone.

The I've saved the mnemonic checkbox is the gate to step 2. We'd rather nag you once than have a relay sitting on money nobody can sign for. If you're picking up an old run, "Already have a relay wallet mnemonic? Restore it here" rebuilds the same set of addresses.

Step 2 · Send funds

Four things to fill: Private key of the paying wallet, Destination address, Send amount, and the two delay boxes.

The amount is what enters the route, not what arrives. The relays pay their own gas out of the money they're holding, so the destination receives the amount minus one transfer's worth of gas per hop. The plan box states it before you press anything:

5 relays means 5 hops, so gas is paid on 7 transactions: two from the sending wallet (service fee + first hop) and one from each relay wallet.

If the amount can't cover the hop gas you get "The send amount doesn't cover the relay gas", or, if it's tiny, "The amount doesn't even cover gas for the relay hops — nothing would arrive." Nothing is sent and nothing is charged. Send everything is the other mode: it empties the paying wallet, so no change is left behind anywhere on the route.

The delay boxes are "How long to wait between hops (seconds)" and "to (seconds)": each hop waits a random number drawn from that range. The default is 0 to 1 second. Filling 0 isn't a bug either — every hop waits for the previous one to be mined before it signs, so even at 0 the transactions land in different blocks. If the point is that this shouldn't look like one action, put the range in minutes rather than seconds.

One design decision worth being honest about: the service fee is not routed through the relays. Your wallet pays it directly to a platform address, as its own visible transaction. Routing the fee through the relays would mean pushing your money through them a second time for no benefit — but it does mean there is one direct outgoing line from your wallet on every run, and you should count it when you're weighing what this route buys you.

Press Start sending.

Step 3 · View progress

Status comes from on-chain balances, not from a task log in your browser. A hop counts as done when the money has actually moved, and there's a Last checked timestamp plus a Refresh progress button, because a screen that quietly claims to be current is worse than one that admits it hasn't looked.

Which means: closing the browser is not a rollback. Anything already broadcast is on the chain and won't come back. Paste the relay mnemonic later, hit Refresh progress, and you'll see exactly which stop the money is sitting at.

"Closing the browser is fine — what's already been sent isn't recalled. Paste the relay mnemonic back in later, click "Refresh progress", and you'll see exactly where the money stopped so you can resume."

Keep going (hop N) resumes the route and completed hops are skipped rather than re-sent. Two switches beside it are off by default, and both are traps if you flip them without a reason:

  • "Also resend anything already broadcast and waiting to be mined (risks duplicate transfers)" — for a genuinely stuck transaction; otherwise you may pay twice.
  • "Clear relay dust (send whatever is left in the relays back to the paying wallet)" — sending relay money back to your main wallet is the fastest link you can create, and it undoes the exercise. The hint says so: "This re-creates the link between the relays and the paying wallet, undoing some of the separation — which is why it's off by default."

One status word deserves an explanation, because it cost us an internal argument. A hop reads Not sent only when that batch never started. Once it has started, a cell with a hash reads Broadcast, and a cell with no hash reads No local record rather than "not sent". After you clear a cache or change machines you genuinely don't know whether that transfer went out — and telling you "not sent" would push you to pay for it again. The Export list button gives you the same balance-derived table as CSV.

A worked example

BNB Chain, 5 relays, 2 BNB entering the route, at roughly 1 gwei:

Service fee 5 × 0.0001 = 0.0005 BNB, paid by your wallet on top
Your wallet's own gas 2 transfers ≈ 0.000042 BNB, on top
Relay hop gas 5 transfers ≈ 0.000105 BNB, taken out of the money in the route
Destination receives ≈ 1.999895 BNB
Balance your wallet needs ≈ 2.000542 BNB
Transactions 7

A side effect of that gas arithmetic is worth knowing: each relay forwards everything it holds minus its own gas, so the amount at each stop is slightly different. The number leaving relay 2 isn't the number leaving relay 3, which means matching the two ends of a route by amount alone doesn't work the way people expect it to.

How you undo your own work

The route is only as good as the habits around it. Five ways people hand the whole thing back:

  1. Sending from the destination back to your paying wallet. One transaction, and the two ends are joined permanently.
  2. Paying your main wallet into the destination later for anything at all — a top-up, a refund, a "just this once". The same line, just slower.
  3. Moving the money the moment it lands. The tool's own hint: "when you move the money, leave a few days and a few transactions in between."
  4. Reusing one batch of relays for a second transfer. Now the two payments share a route, and a shared route is easier to cluster than a fresh set of wallets.
  5. Sweeping dust back to the source, which is why that switch starts off.

What this doesn't do

Be precise here, because overpromising on this topic is both dishonest and the fastest way to get hurt.

Anyone can walk the route, and the first hop is the easiest part of it. The chain records that your address paid a freshly created wallet, and every hop after that is an equally public transfer. Walking it backwards is mechanical: the destination's incoming payment names relay N, relay N's incoming payment names relay N−1, and so on until it reaches you. What a route actually buys you is that nobody doing the casual version of this — glancing at one deposit address, one level deep — ends up sitting on your main wallet.

Think of it as a price: hops × (one lookup plus one judgement) is what the answer costs someone. A direct payment prices it at one glance. That's the whole reason the relay count matters — one relay is barely a speed bump, five is someone deciding to sit down and work, and twenty is a job.

It's not a mixer. No funds are pooled with strangers, nothing crosses a trust boundary, and every hop is an ordinary transfer signed in your own browser, visible forever. A competent analyst with a reason to look can still cluster freshly created wallets by gas patterns, timing and value — this raises the cost of that work, it doesn't make the work impossible.

It doesn't answer anyone entitled to ask. If a regulator, an exchange or a court asks where funds came from, a relay route doesn't change the answer, and it shouldn't. Anyone trying to obscure money from authorities with a lawful claim to see it should not read this as a recommendation. We build and document this for people learning how these systems behave and for testing, and everyone is responsible for the law where they actually are.

The tool is at gtokentool.com/en/wallet/distribute. If you run it on a chain we test less often and a number looks wrong, tell me — with fifteen chains live, your transaction is a better test than ours.

Top comments (0)