Demo

P2P routing

Sending an eIOU to someone you're not directly contacts with — or to someone you are, but with whom you don't have enough credit. The protocol finds a route through a mutual contact and the eIOU hops along it. Walked through with three nodes: Alice (port 80), Bob (port 8080), and a fresh Carol (port 8081).

What this demo is actually showing

P2P routing happens under the hood — in normal use you just send to an address and the wallet picks a route, hops the eIOU, and settles fees without surfacing any of it. This walkthrough exists so you can see the protocol's mechanics step by step, in particular how relay fees flow when a payment passes through someone else.

Spin up a third node

By now you have Alice running on port 80 and Bob on port 8080 from the earlier setup. Add a third node, Carol, on port 8081 — same image, different name, different host port.

docker run -d --restart unless-stopped --name Carol -p 8081:80 -v Carol-mysql-data:/var/lib/mysql -v Carol-config:/etc/eiou/config -v Carol-plugins:/etc/eiou/plugins -v Carol-plugin-scratch:/var/lib/eiou/plugin-scratch -v Carol-backups:/var/lib/eiou/backups -v Carol-backup-locks:/var/lib/eiou/backup-locks -v Carol-ssl-cert:/var/lib/eiou/ssl eiou/eiou:latest

Open http://localhost:8081 in a separate browser window. First-run sign-in is the same as Alice and Bob — capture Carol's seed phrase and auth code from the /dev/shm/ file the container drops on first boot, or run docker exec -it Carol eiou info --show-auth to re-print the auth code at any time, then sign in. See Set up Docker environment for the full first-run walkthrough if you need a refresher.

If Alice or Bob aren't running

If their containers merely stopped, preserve their identities and contact history with docker start Alice Bob. If you deleted a container, recreate it with the same seven Alice- or Bob-prefixed volume mounts from the setup walkthrough. Never attach one node's volumes to another node.

Carol's Dashboard after sign-in

Carol's Dashboard — Wallet Information panel with Total Balance, Total Fee Earnings, and Total Available Credit all at 0.00 VWL; her .onion address and public key visible; Status Active

Connect Bob & Carol

Repeat the contact flow you already did for Alice ↔ Bob, but between Bob and Carol. From Carol's Dashboard, copy her onion address. Switch to Bob's wallet, open the Contacts tab, click New Contact, paste Carol's address, fill in Display Name + credit limit + fee + currency, submit. On Carol's wallet, accept the pending request from the Pending Contact Requests banner.

  • Currency matters — pick a currency Bob ↔ Carol both have credit lines in. The next step needs Alice ↔ Bob and Bob ↔ Carol to share the same currency for the route to resolve.
  • Credit limit on Bob's side toward Carol determines how much Bob is willing to relay through this link before the route runs dry.
  • Carol can accept on different fee / credit terms than Bob proposed via the per-currency Apply modal — same as the original Alice / Bob walkthrough.

Don't add Carol on Alice's wallet. That's the whole point of this section — Alice has Bob, Bob has Carol, but Alice and Carol have no direct contact relationship. The next step is what happens when Alice tries to send Carol money anyway.

Bob's Contacts list with Alice and Carol both accepted

Bob's Contacts tab — Alice and Carol both listed with status Accepted, balance and credit columns populated for each
Refresher on the contact flowAdd & manage contacts

Multi-hop send

On Alice's wallet, switch to the Payment tab and use the New eIOU form. Paste Carol's onion address into Recipient Address — Carol isn't in Alice's contacts, so the autocomplete won't suggest her. Because there's no direct contact link, the wallet automatically goes into P2P routing: it searches Alice's contacts for a path to Carol and finds Alice → Bob → Carol. Bob earns his per-contact relay fee for forwarding the eIOU.

  • Recipient Address — paste Carol's .onion. The form goes into ad-hoc P2P mode since Carol isn't a saved contact.
  • Address Type — Tor (default for the demo).
  • Amount — anything that fits in Alice's available credit on the resolved route (her remaining credit with Bob, with Bob's relay fee on top). The form rejects sends that exceed the route's capacity.
  • Currency — the protocol resolves the route automatically, looking for any path from Alice to Carol that supports the chosen currency end-to-end. In this three-node demo the only available path is Alice ↔ Bob ↔ Carol, so the currency happens to need to be one that exists on both of Bob's credit lines; on a real network with more contacts, any other path that carries the currency would do.
  • Description — short memo, end-to-end between Alice and Carol only. Relays don't see it.

The send form on Alice's Payment tab

Alice's New eIOU form — Recipient Address pasted with Carol's .onion (no autocomplete match), Amount 14.00, Currency VWL, Description 'Refund for purchase INV000001', Best Fee Route toggle off; Send eIOU button ready
Route confirmation (optional) — click to expand

In short: if Auto-Accept P2P Transactions is disabled, every P2P send needs confirmation — once a route is found and a fee is calculated (or several routes, if Best Fee Route is enabled, for Alice to pick from), the wallet asks before committing. The screenshots below walk through what that looks like.

1. The toggle lives under Settings → Section: Feature Toggles → Transactions. Flipped to Disabled here so the rest of the flow becomes visible.

The Auto-Accept P2P Transactions feature toggle in Wallet Settings — Transactions group, shown Disabled with the help text 'Automatically send P2P transactions when a route is found. When disabled, you'll be asked to approve each transaction before sending.'

2. When Alice submits the send, an In-Progress Transactions panel appears at the top of her Activity tab with the recipient, amount, route fee and total cost, plus Accept / Reject buttons. The Route Fee is always a single total — the sender never sees a per-relayer breakdown, and the protocol divides that total across the hops internally. Here the route is one hop long, so the total happens to coincide with what Bob earns; on a longer route it would still be one number, just spread across more relayers behind the scenes.

Activity tab with an In-Progress Transactions panel — Sending to Carol's onion prefix, status 'Awaiting your approval', To: Carol's onion, Amount 14.00 VWL, Route Fee 0.00 VWL, Total Cost 14.00 VWL, with Accept and Reject buttons

3. Clicking Accept brings up a final small "Approve transaction?" modal as a last go-ahead before the eIOU is committed.

Small Approve transaction? confirmation modal — 'Are you sure you want to approve and send this transaction?' with Cancel and Send buttons

Three perspectives on the same transaction

The eIOU shows up in all three Activity tabs — one transaction, three views. Each wallet fires a toast notification when the hop lands, but the Activity list itself isn't force-refreshed, so you may need to reload the page (or switch tabs and back) to see the new row.

Alice's Activity

An outgoing P2P send debited ≈14.00 VWL: the 14.00 going to the end recipient plus a tiny routing fee for the hop (0.0014 VWL, which rounds to 0.00 in the display). The row's counterparty is Bob (Alice's direct neighbour on the route), but the Origin / Dest. column is filled with Carol's onion: as the sender, Alice knows exactly where the eIOU is going, and the column reflects the actual final recipient, not the next hop.

Alice's Activity tab — Recent Transactions list with a P2P row to Bob, ≈−14.00 VWL, description 'Refund for purchase INV000001'; below it a Direct row to Bob from earlier and the original Contact request rows

Bob's Activity

Two rows that net out to his relay fee of ≈0.00 VWL (0.0014 at full precision): a P2P credit of ≈+14.00 from his upstream neighbour (Alice) and a P2P debit of −14.00 to his downstream neighbour (Carol). On a longer route those would be other relays rather than the endpoints. Both relay rows leave the Origin / Dest. column blank — the protocol doesn't tell Bob where the eIOU originally came from or where it's ultimately going.

Bob's Activity tab — two new P2P rows from the relay: Carol −14.00 VWL and Alice ≈+14.00 VWL, plus the earlier Direct payment for dinner from Alice and the Contact request rows

Carol's Activity

An incoming P2P receive of +14.00 VWL, no fee charged to her. The row's counterparty is Bob (Carol's direct neighbour), but the Origin / Dest. column is filled with Alice's onion. That's the design, not a side effect of route length: when an eIOU lands, the wallet confirms the origin directly with the original sender on a back-channel that bypasses every relay along the way. Carol — and only Carol — learns that this came from Alice; Bob is never told.

Carol's Activity tab — a single P2P row with counterparty Bob, +14.00 VWL, description 'Refund for purchase INV000001', origin column showing Alice's onion-address prefix

Bob's Dashboard picks up the relay fee

Click back to Bob's Dashboard. Total Fee Earnings is now ≈0.00 VWL (0.0014 at full precision), Bob's per-contact fee for being the middle hop. Total Balance and Total Available Credit also update, since the two halves of the relay shifted his credit lines with Alice and Carol.

Bob's Dashboard cards after the relay — Total Balance 64.45 VWL, Total Fee Earnings 0.00 VWL, Total Available Credit 2,264.45 VWL

Transaction details — what each side actually sees

Click any of the rows above to open the Transaction Details modal. The same eIOU is rendered very differently for sender, recipient, and relay — and one detail in particular only Alice and Carol can read.

Alice (sender)

Sees her own description, the immediate counterparty Bob, a P2P Routed badge, and a P2P Transaction Details panel with the End Recipient (Carol's onion), Amount to Recipient 14.00 VWL, and a Routing Fee of ≈0.00 VWL (0.0014 at full precision).

Alice's Transaction Details modal — ≈−14.00 VWL Sent, badges Completed / P2P Routed / Sent, To Bob with Bob's onion, Description 'Refund for purchase INV000001', Transaction ID, Previous Tx, Routing Hash; P2P Transaction Details panel with End Recipient (Carol's onion), Amount to Recipient 14.00 VWL, Routing Fee ≈0.00 VWL

The Routing Fee reads ≈ 0.00 VWL because Bob's relay fee is only 0.01% of the amount. Click the ≈ on any rounded value to reveal the full precision — here, 0.0014 VWL.

Full amount popover reading 0.00140000 VWL with the note 'Full precision to 8 decimal places (1e-8), shown regardless of your display-decimals setting.'

Bob (relay)

Gets two separate Transaction Details views, one per leg: an incoming ≈+14.00 from his upstream neighbour and an outgoing −14.00 to his downstream neighbour, both badged P2P Routed and Relay. Each panel only names the direct counterparty on that leg — no End Recipient, no origin field, just whoever Bob is talking to next. Neither view shows a description either — the memo is end-to-end between sender and final recipient. In this single-hop demo Bob's upstream and downstream neighbours happen to be Alice and Carol, so the addresses he sees coincide with the actual endpoints — but the panels still just label them as "from" and "to" on his leg, never as origin or final recipient. That coincidence comes from the route being one hop long, not from the protocol surfacing the endpoints.

Bob's Transaction Details modal for the incoming relay leg — +14.00 VWL Received, badges Completed / P2P Routed / Relay, From Alice with Alice's onion, no Description field shown, Transaction ID, Previous Tx, Routing Hash
Bob's Transaction Details modal for the outgoing relay leg — −14.00 VWL Sent, badges Completed / P2P Routed / Relay, To Carol with Carol's onion, no Description field shown, Transaction ID, Previous Tx, Routing Hash

Carol (recipient)

Sees the same description Alice typed, the immediate counterparty Bob, P2P Routed, and a P2P Transaction Details panel with End Recipient herself, Amount to Recipient 14.00 VWL, Routing Fee 0.00 VWL — the recipient pays nothing for routing.

Carol's Transaction Details modal — +14.00 VWL Received, badges Completed / P2P Routed / Received, From Bob with Bob's onion, Description 'Refund for purchase INV000001' (the same memo Alice typed), Transaction ID, Previous Tx, Routing Hash; P2P Transaction Details panel with End Recipient (Carol's onion), Amount to Recipient 14.00 VWL, Routing Fee 0.00 VWL

Each relay only ever sees its two immediate neighbours — the address it received from and the address it's forwarding to — plus the amount, the transaction id and the routing hash. The protocol never tells a relay who the original sender or final recipient is; in a single-hop route like this one those addresses happen to coincide with the relay's neighbours, but they're still just labelled as the previous hop and the next hop. The description never reaches a relay either: the memo is end-to-end between sender and final recipient, which is why Bob's two Transaction Details panels have no description field to show. Trust on each pairwise contact link is what makes that per-hop visibility acceptable.

The same multi-hop routing kicks in even when there is a direct contact link. If Alice and Carol were direct contacts but Alice didn't have enough credit with Carol to cover the send, her wallet would still hop through Bob. None of this is a button the user presses — it's the wallet's response to 'pay Carol' when there's no direct path or no credit on the direct path.

Continue toPayback methods