Add pay2seed, paid2seed, pay2stream and paid2stream to the OpenSwarm family (#143)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run

* Add pay2seed to the OpenSwarm family: consent at upload and a paid seed market

OpenSwarm pays a seeder per verified piece served, and nothing pays anyone
to stay. An archive, a backup, a dataset waiting for its buyer or a
podcast's back catalogue earns nothing the month nobody downloads it, so
it dies the way every swarm always has. And nothing in BitTorrent says who
put a swarm there or whether they were allowed to, which is why a seeder
is presumed to be doing something wrong.

pay2seed is the member document for both halves. An attestation, signed
at upload with a fixed basis (own, licensed, open-license, public-domain,
personal) and a notice endpoint, is what a hub requires before it will
list anything; public claims get a claim window and a standing, and a
notice voids them. An offer escrows a budget at an ippay hub for a swarm,
public or private, to be held by M seeders for N days at a price per
GiB-month, bought over x402 exactly as a pass is. Seeders take leases,
prove each period by storage challenge or by a probe over the ordinary
wire, and are paid through the payee they already have. Public feeds ride
on ipdb; ipfile.pin on c0mpute is the same offer on the auction.

Also: the family table, stack diagram and registry rows; the ip seed
command group; PRD 0006; the protocol row on /openswarm.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5

* Split pay2seed into client and server halves, and add pay2stream and paid2stream

The rule is now in the names. pay2* is the client protocol: the side that
pays, over HTTPS, and plays. paid2* is the server protocol: the BitTorrent
side that earns. One hub implements both halves of a pair; a requester or
viewer implements only pay2*; a seeder, relay or gateway only paid2*.

pay2seed keeps consent, offers, the requester's market and notices.
paid2seed takes leases, storage challenges and probes over the wire,
GiB-month accrual and receipts, the seeder client, and the ipfile.pin
mapping.

pay2stream and paid2stream do the same for a live channel over iplive.
A broadcaster attests the channel (with the two rules that separate a
licensed rebroadcast from a stolen feed), buys relays by the hour, and
publishes listings; viewers buy tickets. Relays take leases and are
proven present by a verifier that pulls segments as a peer; a gateway is
a relay that also serves standard HLS, clear or sealed, with the M3U and
XMLTV pair every IPTV app asks for, so VLC, TiviMate, Kodi and a
television play a paid swarm with nothing installed. Ace Stream showed
BitTorrent can carry live TV to millions; this is that with consent,
payment and an open spec.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5

* pay2seed: encrypted by default, access as the product, a README in every swarm

The client encrypts by default and the hub never does: what the hub
manages is who may decrypt. A team is a named set of member keys with a
scope over the owner's swarms; the hub, as keeper, issues grants to
members when the owner is offline, with invitations that expire, roles,
an audit trail, and re-encryption on removal so the next version is
closed to whoever left. A few seats are free; above that the hub charges
per seat and per organisation, settled through the same pay plugins as
everything else. Seeding is priced at disk; access is where a hub earns,
and both sides earn: seeders rent disk, requesters sell access.

Public is not a fallback. Encryption off is an explicit act, and a
public swarm is attested, listed, kept and rendered exactly as a private
one is; the only difference is who can read it.

Every swarm on the market carries a README.md at its root, no
exceptions, and the attestation carries its Markdown and the hash of
the copy inside the swarm, so the hub renders it as the swarm's page
without a key. Relative links resolve into the swarm and are gated the
way the files are.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5

* pay2seed: 1 percent, ads on the free tier, and agents as sellers

Three things the specs did not say. The reference hub takes 1 percent of
any payment that crosses it, charged to whoever is paying and never
deducted from a seeder or a relay, so a quoted price is what the
publisher gets and a promised floor is what the seeder is paid.

Public swarms are free to fetch and free to list, and an advertisement on
the swarm README page is what pays for that. The ad is on the hub page
and nowhere else: never inside a swarm, never injected into a file, a
segment or a playlist, and never in the catalogue or the market API. A
requester who wants no ad buys a seat instead. A free-to-watch channel
works the same way.

And a requester is a key, not a person. An agent can attest what it made,
price access, sell tickets, take payment through its own payee and spend
what it earns keeping its own work online. The consent rules do not
soften because a machine signed them, and the reference hub asks an
agent public attestation to name a responsible operator key so somebody
is reachable when a notice arrives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5

* Fix CI: number the PRD requirements and advance the next-id assertion

Two checks the new PRD tripped, both by existing rather than by being
wrong.

The collection validator wants requirements as numbered R# entries and
0006 used a plain ordered list, so it reported OP-L-NO-REQUIREMENTS.
Rewritten as R1 to R7 with priorities, one capability per entry, and the
implementation tracking moved to a paragraph under them where it is not
pretending to be a requirement.

The MCP standards test asserts what the next free PRD id is, and its own
comment says that advances with every PRD added. Adding 0006 makes it
0007.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-09-05 19:12:45 -07:00 committed by GitHub
parent 4fed2681ec
commit be99e683bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1751 additions and 3 deletions

View file

@ -0,0 +1,276 @@
# paid2stream: The Server Protocol for Paid Live Streams
Status: 0.1 draft. Member of the [OpenSwarm](../openswarm.md) family.
Depends on the [core](./spec.md), [`iplive`](./iplive.md), [`ippay`](./ippay.md)
and [`paid2seed`](./paid2seed.md). Its client half is
[`pay2stream`](./pay2stream.md).
## 1. Scope
`paid2stream` is the side that gets paid to carry a live stream. It is
spoken by relays and gateways: peers on the `iplive` wire that take a
lease from a hub, stay online for a window, fan out segments, and, for a
gateway, turn the swarm into standard HLS for any player. It defines:
- **Leases** on a `pay2stream.offer`: one relay, one window, one price
per relay-hour.
- **Proof of relaying.** Presence proofs by a verifier that connects as a
peer and pulls segments, and a served-bytes ratio so a relay that is
online and idle is not paid the floor for nothing.
- **Settlement.** Relay-hour accrual, a gateway bonus, receipts, payout
through the `ippay` payee a relay already has for vouchers.
- **The gateway role**: the record, the HLS presentation in clear and
sealed modes, the M3U and EPG, gateway-bound vouchers, and what a
gateway must do when a channel is voided.
- **Regions and standing.**
Consent, offers, tickets and the viewer are [`pay2stream`](./pay2stream.md).
A hub implements both; a relay or gateway implements only this document.
### Non-goals
Deciding what a channel is. A relay sees a channel key, a basis, a
visibility and a price, and decides whether to carry it.
## 2. Terminology
- **Relay, viewer, channel, head, segment, part, epoch, origin.** As
`iplive` §2.
- **Gateway.** A relay that also speaks HTTP: presents the channel as HLS
and M3U, holds a grant, and signs vouchers on behalf of the HLS viewers
bound to it.
- **Relay-hour.** One relay, connected and serving, for one hour.
- **Period.** `offer.proof.everyMinutes` long.
## 3. Leases
A `paid2stream.lease` is a `paid2seed.lease` with `priceUsdPerRelayHour`
in place of the GiB-month price, `graceMinutes` (default 5) in place of
`graceHours`, a `window` copied from the offer, and a `gateway` boolean
set when the relay commits to §5.
`POST /relays` with `{ "offer", "relay", "regions", "gateway": bool,
"sig" }`, `sig` by the relay key over `"openswarm:paid2stream:lease:v1" ||
offer id`. `201` with the lease; `409` when no slot is free or the relay
already holds one for this window; `403` below the standing floor. A
relay MUST be online and announced in the channel's peer set within the
grace window, and holds at most one lease per channel window. When an
offer is voided every lease on it is `voided` and the hub pushes
`paid2stream.lease.voided`.
## 4. Proof and settlement
### 4.1 Presence
A leased relay is proven for a period when a verifier finds it, at its
announced address, serving the channel: the verifier connects as an
`iplive` peer, receives a `head` no older than two segments, requests one
segment at each rendition in `capacity.renditions`, and receives them
within one segment duration each. The verifier signs a
`paid2stream.proof` with `{ "lease", "period", "head", "segments",
"latencyMs" }`. A relay that is connected but answers `deny rate` to a
verifier fails the period.
For a gateway, the verifier also fetches the media playlist and one
segment over HTTP (§5.3) and records `hls: true`.
### 4.2 Served bytes
Vouchers a relay redeems for this channel during the window are the
second measure. A hub MAY require, per offer, a minimum ratio of served
bytes to pulled bytes for a period to count, so a relay that is present
and idle while others carry the viewers is not paid the floor for nothing.
The default ratio is 1.0 after the first two periods.
### 4.3 Accrual and receipts
```
earnedUsd = priceUsdPerRelayHour * (everyMinutes / 60) * (1 + gatewayBonusBps / 10000 if gateway)
```
per proven period, integer micro-USD, rounded down, credited to the
relay's `ippay` payee balance with a `paid2stream.receipt` shaped as
`paid2seed.receipt`. Two consecutive failed periods end the lease and
reopen the slot. Vouchers are separate and additive; the hub's fee is the
broadcaster's at purchase, never the relay's.
### 4.4 Regions and standing
A relay declares `regions` in its lease request, self-reported and
verified only in the sense that a verifier records the latency it saw.
Standing is `paid2seed` §6.3 with periods in place of days; a hub shows a
relay's median verified latency per region beside it.
## 5. Gateways and HLS
A gateway is what makes this usable by a person with a television. Any
relay MAY be one; a c0mpute node with `c0mpute:role:gateway` is expected
to be one; the reference hub runs one.
### 5.1 Record
```json
{
"openswarm": "0.1",
"type": "paid2stream.gateway",
"gateway": "ed25519:a41e…",
"base": "https://gw-eu1.example.net/openswarm",
"regions": ["eu"],
"modes": ["clear", "sealed"],
"lowLatency": true,
"hubs": ["https://bittorrented.com/api/openswarm"],
"createdAt": "2026-09-10T09:00:00.000Z",
"sigs": [{ "alg": "ed25519", "key": "ed25519:a41e…", "sig": "…" }]
}
```
Registered with `POST /gateways`; listed in `GET /gateways` and, per
channel, in the `gateways` array of `GET /streams/<id>`. An origin MAY
name preferred gateways in its channel record's `relays` block.
### 5.2 Presenting a pass
A gateway MUST accept the ticket three ways: `?pass=<base64url pass>` on
the URL, as a cookie, and as `Authorization: Bearer`. It verifies the
pass against the hub key and its scope (`ippay` §3.1), checks `expiresAt`,
and MUST rate-limit failed presentations by source address. A pass bought
against another gateway is valid here if the hub lists this gateway for
the channel; the pass is the hub's record.
### 5.3 HLS presentation
For a channel it relays, a gateway serves:
| Path | Body |
| --- | --- |
| `hls/<channel>/master.m3u8` | One `EXT-X-STREAM-INF` per rendition in the channel record, `CODECS` and `BANDWIDTH` from it, `EXT-X-INDEPENDENT-SEGMENTS`. |
| `hls/<channel>/<rendition>.m3u8` | A media playlist rebuilt from the latest signed head: `EXT-X-MAP` for the init segment, one `EXTINF` per segment in the DVR window, `EXT-X-DISCONTINUITY` at a key epoch boundary in `sealed` mode, `EXT-X-PROGRAM-DATE-TIME` from the head's timestamp. With `lowLatency`, `EXT-X-PART` entries from `iplive` parts and `EXT-X-SERVER-CONTROL` with `CAN-BLOCK-RELOAD=YES`. |
| `hls/<channel>/<rendition>/<seq>.m4s` | The segment. |
| `hls/<channel>/init/<rendition>.mp4` | The init segment. |
| `hls/<channel>/key/<epoch>` | `sealed` mode only: the 16-byte HLS key, to a pass holder. |
**`clear` mode.** The gateway decrypts each `iplive` segment with the
grant it holds and serves plaintext over TLS. Every IPTV player
understands it, and it is exactly as private as the TLS session and the
pass. A gateway MUST NOT serve `clear` for a channel whose record has
`keys.modes` without `gateway` in it; that is how an origin refuses to be
decrypted by anyone but viewers.
**`sealed` mode.** The gateway re-encrypts each segment under a
per-epoch AES-128-CBC key and writes
`EXT-X-KEY:METHOD=AES-128,URI="key/<epoch>"`, so the playlist is standard
HLS with standard encryption and the key endpoint is where the pass is
checked. Segments may then be cached by any HTTP cache in the path. The
re-encryption is the gateway's cost; the origin's content key never
leaves the `iplive` layer.
Both modes are one segment behind the peer edge; `lowLatency` narrows that
to one part.
### 5.4 The M3U and the EPG
`GET <base>/channels.m3u?pass=<pass>` returns `#EXTM3U` with one
`#EXTINF` per channel the pass is in scope for, carrying `tvg-id`,
`tvg-name`, `tvg-logo` and `group-title` from the channel record's
`title`, `logo` and `group` fields (added to `iplive.channel` as OPTIONAL
by this document), each pointing at that channel's `master.m3u8` with the
pass attached. `GET <base>/epg.xml?pass=` returns XMLTV built from the
`pay2stream.listing` entries of those channels. This pair is what every
IPTV app asks for on setup, and it is how a gateway looks, to a
television, like the provider it replaces.
### 5.5 Gateway-bound vouchers
A gateway signs vouchers to its upstream for what it pulls, as any relay
does, and signs vouchers to itself on behalf of each bound viewer for what
it serves them, bounded by that viewer's ticket `capUsd`. It MUST NOT
sign a voucher for bytes it did not serve; the hub compares signed bytes
against the segment count in the window, and the gateway's standing is
what it loses.
### 5.6 Stopping
On `pay2stream.channel.voided` a gateway MUST stop serving the channel
within one segment duration, answer its playlists with `410`, and drop
the channel from `channels.m3u`. A gateway that keeps serving a voided
channel is delisted and its standing zeroed.
## 6. Hub API, relay and gateway side
Relative to `pay2stream.base`. The client side is `pay2stream` §7.
| Method and path | Auth | Purpose |
| --- | --- | --- |
| `GET /streams?open&region` | none | Offers with free relay slots, and what each pays per hour. |
| `POST /relays` | relay signed | Take a lease (§3). |
| `GET /relays/<key>` | none | Standing, leases, median latency per region. |
| `GET /relays/<key>/leases?status` | none | What a relay resumes after a restart. |
| `POST /proofs` | verifier signed | Report a presence proof; returns the receipt. |
| `POST /gateways` | gateway signed | Register or update a gateway record. |
| `GET /passes/<id>` | none | Verify a presented ticket against the hub's record (`ippay` §5.2). |
| `POST /vouchers` | none | Redeem, as `ippay` §5.3; gateway-bound vouchers carry `boundBy`. |
| `POST /webhooks` | signed | Register a CloudEvents endpoint for a relay or gateway key. |
## 7. Relay and gateway client behaviour
1. Holds a relay key, registered as an `ippay` payee, stored as an
OpenCreds `key` item.
2. Polls `GET /streams?open` on its hubs, filtered by the operator's
policy: which `basis` values to carry, minimum price per hour,
regions, upload to keep in reserve.
3. Takes a lease, connects to the channel's peers using the offer's pass,
accepts up to `capacity.downstreamPerRelay` downstream peers, and
stays for the window.
4. As a gateway: holds a grant, serves §5.3 in at least `clear` mode,
§5.4, and the three pass presentations of §5.2.
5. Answers verifiers as ordinary peer traffic, and over HTTP for the
gateway probe.
6. On restart, resumes leases from `GET /relays/<key>/leases?status=proven`.
7. Stops on a void (§5.6). Shows the operator, per lease, the basis,
earned so far, viewers served, and the next period due.
## 8. Events
| `type` | Data | Subscriber |
| --- | --- | --- |
| `pay2stream.offer.listed`, `.active`, `.settled`, `.voided` | offer id | broadcaster, market followers |
| `paid2stream.lease.granted`, `.proven`, `.lapsed`, `.ended`, `.voided` | lease id, period, `earnedUsd` | relay, broadcaster |
| `pay2stream.channel.live`, `.ended`, `.voided` | channel key, gateways | relays, gateways, viewers with tickets |
| `pay2stream.listing.published` | listing id | market followers |
## 9. Security notes
- **Presence without service.** The served-bytes ratio (§4.2) is what
stops a relay from collecting the floor while doing nothing.
- **Gateway over-signing.** Bounded per viewer by `capUsd`, and in
aggregate by the hub's segment-count check and the gateway's standing.
- **Clear mode is clear.** A gateway is trusted with plaintext by the
origin's choice of `keys.modes`; an origin that does not want that
offers `sealed` only, or no gateways at all.
- **Relay regions are self-declared.** Only the verified latency beside
them is evidence.
## 10. Implementations
| Piece | Where | Status |
| --- | --- | --- |
| Reference hub, server side, and the hub's own gateway | `profullstack/media-streamer` (bittorrented.com), Live TV | planned; the IPTV resale rail (`/api/public/iptv/<slug>/stream`) already seals HLS manifests per session, the central version of §5.3 |
| Gateway on the storage network | `profullstack/c0mpute` `c0mpute:role:gateway`, DIP 0019 playlist rebuild | mapping; DIP 0019 already rebuilds playlists from swarmed segments |
| Relay client | `torlink` with the `iplive` extension | planned |
| Shared relay and gateway code | `@profullstack/pay2seed` (one package for the four protocols) | planned |
## 11. Conformance
A **hub** on this side: leases as §3, at least one presence proof per
period and pays only proven periods (§4), lists gateways per channel,
pushes `channel.voided` to relays and gateways.
A **relay**: `iplive` §12 as a relay, plus §7. A **gateway**: a relay,
plus §5.2, §5.3 in at least `clear` mode, §5.4, §5.5 and §5.6.
## 12. Version history
- 0.1 (2026-09-06): first draft, as the server half; leases per
relay-hour with a gateway bonus, presence proofs and the served-bytes
ratio, gateways with clear and sealed HLS, the M3U and EPG pair,
gateway-bound vouchers, stopping on a void.