logicsrc/docs/openswarm/paid2seed.md
Anthony Ettinger 12cdd34e6b 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
2026-09-06 00:53:20 +00:00

14 KiB

paid2seed: The Server Protocol for Paid Seeding

Status: 0.1 draft. Member of the OpenSwarm family. Depends on the core, ipfile and ippay. Its client half is pay2seed.

1. Scope

paid2seed is the side that gets paid. It is spoken by a seeder, a BitTorrent client with disk and uptime to sell, to a hub over HTTPS and to the swarm over the ordinary wire. It defines:

  • Leases. The hub's binding of one seeder to one pay2seed.offer for one period at one price.
  • Proof of seeding. Storage challenges answered from held pieces, and probes in which a verifier fetches pieces over BEP 3 or the ipfile extension. Both work on a vanilla torrent, which is what makes seeding a public swarm payable.
  • Settlement. GiB-month accrual per proven period, receipts, and payout through the ippay payee a seeder already has for vouchers.
  • The seeder client: how a torrent client polls a market, takes work, seeds it, proves it, and resumes after a restart. torlink's headless daemon is the reference.

Consent, offers, the requester's view and notices are pay2seed. A hub implements both. A seeder client implements only this document.

Non-goals

Anything about what the data is. A seeder sees an infohash or a file key, a size, a basis and a visibility, and decides whether to hold it. It is never asked to judge it and never able to read a private swarm.

2. Terminology

  • Seeder. An identity that holds a lease and serves the swarm.
  • Verifier. Whoever issues challenges and probes: the hub, a c0mpute verifier node, or the requester. The offer names who may.
  • Lease. One seeder's share of an offer (§3). Period. The unit of accrual and proof, offer.proof.everyHours long.
  • Other terms as pay2seed §2.

3. Leases

3.1 Record

{
  "openswarm": "0.1",
  "type": "paid2seed.lease",
  "hub": "ed25519:c9f1…",
  "offer": "sha256:1b7f…",
  "seeder": "ed25519:a41e…",
  "slot": 1,
  "priceUsdPerGibMonth": "0.150000",
  "startsAt": "2026-09-05T18:30:00.000Z",
  "endsAt": "2026-10-05T18:00:00.000Z",
  "graceHours": 24,
  "createdAt": "2026-09-05T18:30:00.000Z",
  "sigs": [{ "alg": "ed25519", "key": "ed25519:c9f1…", "sig": "…" }]
}
Field Rule
offer Record id of the pay2seed.offer.
seeder The seeder key. MUST be a registered ippay payee (§5.4).
slot 1-based, at most offer.seeders.max. One lease per seeder per offer.
priceUsdPerGibMonth Copied from the offer at lease time so a later edit never changes what was agreed.
graceHours Time from startsAt in which the seeder MUST pass its first proof, or the lease is abandoned and the slot reopens. Long enough to fetch the swarm.

A lease is fetching until its first proof, proven while its most recent period passed, lapsed when a period failed, abandoned after graceHours without a first proof, ended at endsAt, and voided with its offer.

3.2 Taking and losing a lease

POST /leases with { "offer": <id>, "seeder": <key>, "sig" }, sig by the seeder key over "openswarm:paid2seed:lease:v1" || offer id. The hub answers 201 with the lease, 409 when the offer has no free slot or this seeder already holds one, or 403 when the seeder's standing (§6.3) is below the offer's or hub's floor.

A hub SHOULD hand out slots in order of standing and MUST NOT hand out more than seeders.max. A seeder SHOULD NOT take a lease it cannot serve within graceHours; an abandoned lease counts against standing. When an offer is voided (pay2seed §8) every lease on it is voided, the hub pushes paid2seed.lease.voided, and a seeder learns of it on its next poll at the latest.

4. Proof of seeding

4.1 Storage challenges

Once per period, a verifier named in the offer sends the seeder:

{
  "openswarm": "0.1",
  "type": "paid2seed.challenge",
  "lease": "sha256:5c02…",
  "period": 7,
  "nonce": "1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f",
  "pieces": [17, 2048, 91, 4400],
  "deadline": "2026-09-07T12:10:00.000Z",
  "createdAt": "2026-09-07T12:00:00.000Z",
  "sigs": [{ "alg": "ed25519", "key": "ed25519:c9f1…", "sig": "…" }]
}

The seeder answers within deadline:

{
  "openswarm": "0.1",
  "type": "paid2seed.proof",
  "challenge": "sha256:9d3a…",
  "answers": ["sha256:…", "sha256:…", "sha256:…", "sha256:…"],
  "createdAt": "2026-09-07T12:00:04.000Z",
  "sigs": [{ "alg": "ed25519", "key": "ed25519:a41e…", "sig": "…" }]
}

Each answer is SHA-256(nonce || piece bytes) for the piece at that index, which a verifier holding a copy can check and which cannot be answered from the piece hash alone. pieces MUST be at least four indices chosen uniformly at random per challenge; deadline MUST be at least ten minutes after createdAt, enough to read four pieces from a spinning disk and not enough to fetch them from the swarm.

4.2 Probes

A verifier connects to the seeder as an ordinary peer, over BEP 3 for a vanilla torrent or the ipfile extension for a private swarm, and requests the challenge's pieces. The seeder's ordinary serving is the proof; the verifier records { "lease", "period", "pieces", "receivedBytes", "peer" } signed by its own key, type still paid2seed.proof. A probe MUST fetch from the seeder's own announced address; a swarm being alive is not evidence that this seeder is keeping it alive.

For a private swarm whose pricing.perGib is non-zero the verifier pays vouchers for the probe like any leecher, and those are the seeder's in the usual way. Hubs SHOULD prefer probes for public swarms and challenges for private swarms they already pin.

4.3 Delivery

A hub pushes challenges to a seeder's registered webhook when it has one, and otherwise leaves them to be polled from GET /leases/<id>/challenges. A seeder without a webhook MUST poll at least every everyHours / 4 hours. deadline is measured from createdAt, not delivery, so a seeder that polls slowly fails challenges; the cadence is the seeder's to meet.

5. Settlement

5.1 Accrual

A lease earns, for each period in which its proof passed:

earnedUsd = priceUsdPerGibMonth * (sizeBytes / 2^30) * (everyHours / 720)

in integer micro-USD, rounding down. A failed or missed period earns nothing and moves the lease to lapsed; two consecutive failures end it and reopen the slot.

5.2 Receipts

{
  "openswarm": "0.1",
  "type": "paid2seed.receipt",
  "lease": "sha256:5c02…",
  "period": 7,
  "proof": "sha256:e77b…",
  "earnedUsd": "0.002604",
  "balanceUsd": "0.018228",
  "createdAt": "2026-09-07T12:00:05.000Z",
  "sigs": [{ "alg": "ed25519", "key": "ed25519:c9f1…", "sig": "…" }]
}

5.3 Fees and payout

The hub's fee is charged to the requester at purchase, hubBps of budgetUsd with hubBps no less than the hub record's minHubBps; it is never deducted from a seeder's earnings. Payout is ippay §5.6, on the same schedule and floor as voucher earnings.

5.4 Payees

A seeder is an ippay payee (ippay §6.1). Registering once covers vouchers, key grants and leases. A seeder key that is not registered cannot take a lease, because there would be nowhere to pay.

6. The seeder's hub

6.1 API, seeder side

Paths relative to the pay2seed.base in the hub record (pay2seed §6.1). The requester side is pay2seed §6.2.

Method and path Auth Purpose
GET /offers?visibility&basis&minPrice&maxSize&feed&status none The market: open offers with free slots.
POST /leases seeder signed Take a slot (§3.2).
GET /leases/<id> none The lease, status, periods proven, earnedUsd.
GET /seeders/<key>/leases?status none A seeder's leases; what a client resumes after a restart.
GET /leases/<id>/challenges?since seeder Pending challenges for a seeder that cannot receive them pushed.
POST /proofs seeder or verifier signed Answer a challenge or report a probe. Returns the receipt.
GET /seeders/<key> none { "standing", "leases", "proven", "failed", "abandoned", "balanceUsd" }.
POST /webhooks signed Register a CloudEvents endpoint for a seeder key.

6.2 The market listing

GET /offers is what a seeder client shows its operator, or chooses from on its own. Each row carries the offer, the attestation's basis, description and visibility, the free slot count, and the projected earnedUsd for a full lease. A client MUST show basis and visibility wherever it shows an offer, so an operator knows whether they are being asked to hold ciphertext for a stranger's backup or to seed an openly licensed dataset in the clear.

6.3 Standing

Seeder standing is proven - 2 * failed - 3 * abandoned over the trailing year, floored at zero. Requester standing is honoured - 3 * voided, the same way. Both are public and are one hub's opinion signed by that hub; a second hub MAY read it and MAY ignore it.

7. Seeder client behaviour

A seeder client (torlink's headless daemon is the reference) does the following, and a conformant one does all of it:

  1. Holds a seeder key, registered as an ippay payee with a payout address, and stores the key as an OpenCreds key item.
  2. Polls GET /offers on the hubs it is configured for, filtered by the operator's policy: which visibility and basis values to accept, the minimum priceUsdPerGibMonth, the maximum sizeBytes, free disk to keep in reserve.
  3. Takes a lease, fetches the swarm (as a paying peer when offer.pass is set), adds the offer's trackers, and seeds with a per-torrent seed time of at least lease.endsAt, so its own reaper never drops a swarm it is being paid to keep.
  4. For a feed offer, follows the feed (ipdb §5.4), fetches each new segment within one period of its appearance in the head, seeds all of them, and re-announces the head as a BEP 44 mutable item at least once per period so the catalogue stays findable when the publisher is offline.
  5. Answers challenges from local pieces within deadline, and serves probes as ordinary peer traffic.
  6. On restart, resumes every lease in GET /seeders/<key>/leases?status=proven,fetching before taking new ones.
  7. Stops seeding and deletes the data when a lease ends or is voided, unless the operator has chosen to keep it; for a private swarm the ciphertext is worthless without a grant, and for a personal one it MUST be deleted.
  8. Shows the operator, for every held lease, the basis, visibility, earnedUsd so far, and the next challenge due.

8. Relationship to ipfile.pin

An ipfile.pin job (c0mpute §3.1) is a pay2seed.offer with visibility: "private", seeders.min = seeders.max = 1, days and proof.everyHours copied, priceUsdPerGibMonth derived from max_price_usd, and c0mpute's storage challenge standing in for §4.1. A c0mpute node that wins such a job holds a lease in this document's sense and MAY report proofs to the hub to build standing there. A hub MAY republish a pay2seed.offer as an ipfile.pin job on the c0mpute auction when no other seeder takes it, which is how the market reaches the paid storage network without the requester doing anything.

9. Events

CloudEvents as core §8, source the hub base:

type Data Subscriber
pay2seed.attestation.registered, .honoured, .voided attestation id requester
pay2seed.offer.listed, .active, .settled, .voided offer id, status requester, market followers
paid2seed.lease.granted, .proven, .lapsed, .ended, .voided lease id, period, earnedUsd seeder, requester
paid2seed.challenge.issued the challenge record seeder
pay2seed.notice.received, .resolved notice id, outcome requester

10. Security notes

  • Sybil seeders. One machine holding several leases on one offer under several keys earns several times for one copy. seeders.max bounds the loss; probes from distinct verifiers to the announced addresses, and a hub refusing leases to keys that share a payout address on one offer, bound it further. Standing makes it expensive to repeat.
  • Answer relaying. A seeder that holds nothing could fetch challenged pieces from the swarm within deadline. Ten minutes is the floor; a hub SHOULD set shorter deadlines for small swarms, and probes measure the seeder's own serving rather than what it can fetch.
  • Payout addresses are the identity that matters. A seeder key is cheap; the address it pays to is where a hub's Sybil checks bite.

11. Implementations

Piece Where Status
Reference hub, seeder side: leases, challenges, probes, receipts, standing profullstack/media-streamer (bittorrented.com) planned
Seeder client: market polling, leases, per-lease seed time, challenges torlink (torlnk serve and watch) planned; per-torrent seed time landed as baairon/torlink#186
Shared seeder client and records @profullstack/pay2seed planned
Seeder and verifier on the storage network profullstack/c0mpute via ipfile.pin mapping in §8

12. Conformance

A hub is conformant on this side when it hands out leases as §3, issues at least one challenge or probe per period and pays only proven periods (§4, §5), and publishes seeder standing (§6.3). It MUST also be conformant to pay2seed.

A seeder client is conformant when it does all of §7.

13. Version history

  • 0.1 (2026-09-06): split out of the first pay2seed draft (2026-09-05) as the server half; leases, challenges and probes, accrual and receipts, the seeder API and client, the ipfile.pin mapping.