Peer-to-peer video streaming with per-minute Cashu micropayments.
Satstreamr connects a streamer and a viewer directly over WebRTC. Before the stream starts, the viewer's wallet pre-splits its proofs into exact per-cycle denominations. Every few seconds, the viewer's browser picks one pre-split token from the pool and sends it over the WebRTC data channel. The streamer redeems it with the Cashu mint and acknowledges receipt. If the viewer's pool runs out, the session ends automatically.
Lightning is used only at the edges: to fund the viewer's wallet (deposit) and to cash out the streamer's accumulated proofs (withdrawal). The payment loop itself never touches the Lightning Network.
The preSplitProofs step runs after the viewer accepts the rate
and before the WebRTC handshake completes. It ensures the viewer's wallet
holds proofs in denominations that exactly match the agreed per-interval amount,
so each payment cycle sends a single clean token with no change.
Completing a Lightning payment during a live session would require a round-trip through the routing network for every interval — adding hundreds of milliseconds of variable latency and introducing routing failure modes. Cashu tokens settle with a single HTTP request to the mint instead.
| Property | Lightning (per interval) | Cashu (per interval) |
|---|---|---|
| Settlement latency | ✗ hundreds of ms, variable | ✓ tens of ms, constant (HTTP roundtrip to mint) |
| Routing failure risk | ✗ Yes — path may fail | ✓ No routing involved |
| Fee predictability | ✗ Routing fees vary with route selection and liquidity availability | ✓ Mint fees are known up front, no routing variability |
| Custodial risk | ✓ Non-custodial | ✗ Mint custodies proofs |
Cashu tokens are bearer instruments: whoever holds the proofs can redeem them. That property lets the viewer encode a payment as a binary blob and push it over a WebRTC data channel — the same channel carrying video metadata. The streamer redeems with the mint and sends an ack; no other coordination is needed.
Both streamer and viewer must use the same Cashu mint. The mint is a trusted third party: it issues proofs and settles swaps. If the mint is dishonest or goes offline, accumulated proofs cannot be redeemed. For self-hosted deployments (e.g. on Umbrel), the operator runs their own mint, which limits external custodial risk.
When the streamer receives a token, the code first calls the mint's NUT-07 state-check endpoint to confirm the proofs are unspent before attempting a NUT-03 swap. This catches replayed tokens without needing a local spent-proof database.
The data channel is encrypted by DTLS (mandatory in WebRTC). Tokens are base64-encoded Cashu strings, so a passive observer would need to break DTLS to read them. The signaling server (used only for SDP offer/answer exchange) never sees token data.
A malicious streamer could take tokens and not stream video — there is no atomicity guarantee between payment and content delivery. A malicious viewer could send double-spent tokens; the NUT-07 check catches this but requires the mint to be reachable. Satstreamr assumes both parties are known to each other and the session stakes are low.
Frontend — Static HTML/TypeScript app (Vite + Vanilla TS). All wallet logic runs in the browser; there is no backend holding funds.
Signaling server — Minimal WebSocket server for SDP and ICE candidate exchange. Stateless with respect to payments.
Cashu mint — Standard Nutshell-compatible mint. Handles Lightning deposits (NUT-04), proof swaps (NUT-03), state checks (NUT-07), and Lightning withdrawals (NUT-05).
WebRTC — Browser-native peer connection. Video/audio over media tracks; token payments over a separate ordered data channel.
Viewer funds a Cashu wallet via Lightning. During a session, the viewer's browser sends Cashu tokens to the streamer's browser over a WebRTC data channel every N seconds. The streamer swaps each token with the mint for fresh proofs and sends an ack. At session end, the streamer cashes out via Lightning.