How the rail works

Four actors, one invariant. A supporter stakes once; every metered platform event settles on-chain to the creator — payee balance += rate × units.

The four actors

1. The payer — @universal-paywall/agent

The supporter (a viewer, a listener, or an AI agent acting for one) stakes USDC in their own StakeVault and grants the facilitator a bounded, revocable policy: a spending cap and an expiry. Nothing is custodied — funds stay in the vault until a valid, in-policy charge settles them. Revoke the grant and spending stops.

2. The platform event

The platform keeps doing what it already does: PeerTube counts a view, Owncast sees a viewer join and part, Navidrome scrobbles a play, Jellyfin reports a playback stop, Immich serves a shared-link photo. The platform is never forked — the rail listens at a boundary it already exposes.

3. The integration — @universal-paywall/integrations

A small adapter — a sidecar, a published plugin, a reverse proxy, or an external provider — translates that platform-native event into one call: reporter.report({ payerKey, creatorKey, amount, ref }). Identity resolvers map platform ids to wallets (for music, live MusicBrainz: recording_mbid → artist_mbid → wallet). Unknown ids are metered-and-skipped — nobody is ever mischarged.

4. The facilitator — @universal-paywall/facilitator

The facilitator batches metered charges and calls StakeVault.settle on-chain. Settlement is feeless per event — no processor cut, no rent in the rail — and only charges within the payer's granted cap and expiry can settle.

Permissionless attachment — the six patterns

The core principle: attach at a boundary the platform already exposes — its config, its outbound events, its request path, its plugin loader, its external-data slots, or the client. The platform's source is never modified.

#PatternIdeaProven on
1 Config redirect The platform already speaks a protocol to some upstream; we implement that protocol and the operator redirects one config value at us. The platform can't tell. Navidrome (ND_LISTENBRAINZ_BASEURL), Subsonic
2 Event sidecar The platform emits webhooks/notifications; we register our endpoint for the events that matter. Owncast (admin webhook), Jellyfin (official Webhook plugin), RSSHub (crawler boundary)
3 Reverse proxy Sit transparently in the request path; meter the requests whose serving is the billable event. Immich (shared-link resolves)
4 Published plugin Where the platform has a plugin loader, ship a published plugin into that sanctioned slot — still not a source edit. PeerTube (action:api.video.viewed)
5 External provider The platform fetches an external data source; we run that source. Mastodon (DONATION_CAMPAIGNS_URL)
6 Payer-side adaptor Nothing on the platform at all — pay on the user's behalf from the client. Browser extension (MV3)

Full write-up: the six permissionless attachment patterns — including why a core PR to the platform is the wrong shape for this (upstreams merge donation pointers, but reject per-user payment plumbing; the durable integration is one you ship yourself at a boundary the platform already exposes).

Tips vs. paywall — a mode, not a headline

The same rail runs two modes, and tip mode is the default framing:

Creators on community platforms overwhelmingly asked for support mechanisms, not gates — so the integrations default to metering support, and gating is opt-in.

Credibility: how each integration is proven

Every platform climbs the same four-layer ladder before its "tested" badge turns green — an integration is "done" only when its layer-4 money loop settles on-chain:

LayerWhat it proves
L1 Unitthe adapter maps a platform event to the right charge arguments
L2 HTTP contractthe running sidecar answers real platform-shaped HTTP correctly (status, body, headers)
L3 Real instancea real Docker'd platform instance, wired per its recipe, actually calls the sidecar on a real event
L4 On-chain money loopstake + grant → event → charge → batch → settle → the payee's balance increased by exactly rate × units

The per-platform matrix lives in the repo: testing-plan.md.