Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: docs

on:
pull_request:
paths:
- "docs/**"
- ".github/workflows/docs.yml"
- ".markdownlint-cli2.jsonc"
- ".prettierrc.json"
- ".prettierignore"
- "package.json"
- "pnpm-lock.yaml"
push:
branches: [main]
paths:
- "docs/**"
- ".github/workflows/docs.yml"
- ".markdownlint-cli2.jsonc"
- ".prettierrc.json"
- ".prettierignore"
- "package.json"
- "pnpm-lock.yaml"
Comment thread
thiras marked this conversation as resolved.

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Prettier check
run: pnpm exec prettier --check "docs/**/*.{md,mdx,json}"
- name: markdownlint
run: pnpm exec markdownlint-cli2
- name: Mintlify broken-link check
working-directory: docs
run: npx -y mintlify@4.2.555 broken-links
30 changes: 30 additions & 0 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: website

on:
pull_request:
paths-ignore:
- "docs/**"
push:
branches: [main]
paths-ignore:
- "docs/**"

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm format:check
- run: pnpm build
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# Claude Code local-only state
.claude/settings.local.json
15 changes: 15 additions & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"config": {
"default": true,
// Line length — Mintlify pages mix prose with long inline links.
"MD013": false,
// Inline HTML — MDX components are inline JSX.
"MD033": false,
// Bare URLs — allowed in prose; Mintlify auto-renders them as links.
"MD034": false,
// First-line h1 — Mintlify uses `title:` frontmatter, not an h1.
"MD041": false,
},
"globs": ["docs/**/*.{md,mdx}"],
"ignores": ["**/node_modules/**", "docs/.mintlify/**"],
}
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ node_modules/
pnpm-lock.yaml
next-env.d.ts
.husky/_/
.claude/settings.local.json
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pnpm format # prettier --write .
- `components/site/` — page sections composed by `app/page.tsx` (Hero, Problem, …).
- `components/ui/` — low-level primitives (Button, Section, Stat, Mono).
- `lib/` — shared helpers (currently just `links.ts`).
- `docs/` — Mintlify source for `docs.decdn.org` (separate build pipeline, not part of the static export).
- Path alias `@/*` → project root (e.g. `@/lib/links`, not `@/src/...`).

## Gotchas
Expand All @@ -34,3 +35,4 @@ pnpm format # prettier --write .
- **Tailwind v4.** `globals.css` uses `@import "tailwindcss"` and `@theme inline { … }`. There is no `tailwind.config.*` — theme tokens live in CSS. Don't reach for v3 directives.
- **Conventional commits required.** `commitlint` runs in the `commit-msg` husky hook; non-conforming messages are rejected.
- **`metadataBase` is live.** `lib/links.ts` `site` is the real origin and `INDEXABLE` is `true`. Anything anchored on this origin — OG and canonical (via `metadataBase`); JSON-LD, `app/sitemap.ts`, `app/robots.ts` (via `SITE_URL`) — ships to production. Adding a new page = append an entry to `app/sitemap.ts`; don't add config elsewhere. Flip `INDEXABLE` to take the site out of search; only the meta tag flips (see `lib/links.ts` for why `robots.txt` stays unconditional).
- **`docs/` is a different product.** Mintlify Cloud builds it from `docs/docs.json` and serves it at `docs.decdn.org` — independent of `pnpm build`. The website code must not import from `docs/`; ESLint and the website CI workflow ignore it. Edits to MDX go through the `docs` workflow (Prettier + `markdownlint-cli2` + `mintlify broken-links`).
10 changes: 5 additions & 5 deletions docs/overview/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ graph TD

## Participant roles

| Role | Operated by | Participates in CDN? |
| --- | --- | --- |
| **Node (pure cache)** | Independent node operator | Yes — stakes, gossips, probes, delivers |
| **Node (origin-backed)** | Content provider *or* node operator with storage | Yes — same protocol, plus a private origin backend |
| **Client** | End-user or application | Pays for bytes; subscribes to gossip but does not publish |
| Role | Operated by | Participates in CDN? |
| ------------------------ | ------------------------------------------------ | --------------------------------------------------------- |
| **Node (pure cache)** | Independent node operator | Yes — stakes, gossips, probes, delivers |
| **Node (origin-backed)** | Content provider _or_ node operator with storage | Yes — same protocol, plus a private origin backend |
| **Client** | End-user or application | Pays for bytes; subscribes to gossip but does not publish |

See [Participants](/overview/participants) for a deeper breakdown per role.

Expand Down
32 changes: 16 additions & 16 deletions docs/overview/glossary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ title: Glossary
description: Core terms used across the deCDN protocol and this documentation.
---

| Term | Definition |
| --- | --- |
| **Blob** | A content-addressed byte sequence. Every blob is identified by its hash; clients verify received bytes against the known hash. |
| **Channel (payment)** | On-chain escrow between client and node that backs off-chain vouchers. Opens with a deposit; settles with the latest voucher. |
| **Client** | A lightweight QUIC endpoint that streams content and pays per MB. No stake, no gossip publish. |
| **DHT** | Kademlia-subset content discovery. Nodes self-publish records when caching a blob. |
| **Gossip** | Epidemic broadcast over topic channels for node metadata and rate updates. |
| **Hash sequence** | An ordered collection of blob hashes (a directory or manifest equivalent). |
| **Node** | A staked QUIC endpoint that caches and delivers blobs. Some are origin-backed; others are pure caches. |
| Term | Definition |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Blob** | A content-addressed byte sequence. Every blob is identified by its hash; clients verify received bytes against the known hash. |
| **Channel (payment)** | On-chain escrow between client and node that backs off-chain vouchers. Opens with a deposit; settles with the latest voucher. |
| **Client** | A lightweight QUIC endpoint that streams content and pays per MB. No stake, no gossip publish. |
| **DHT** | Kademlia-subset content discovery. Nodes self-publish records when caching a blob. |
| **Gossip** | Epidemic broadcast over topic channels for node metadata and rate updates. |
| **Hash sequence** | An ordered collection of blob hashes (a directory or manifest equivalent). |
| **Node** | A staked QUIC endpoint that caches and delivers blobs. Some are origin-backed; others are pure caches. |
| **Origin-backed node** | A node configured with an S3-compatible store, NFS mount, or local disk — the canonical source for specific blobs. Never experiences a true cache miss. |
| **Origin backend** | The opaque storage behind an origin-backed node (S3, R2, B2, MinIO, NFS, local disk). Never exposed to the network. |
| **Peer mesh** | The flat set of all staked nodes. Discovery via gossip; content location via DHT. |
| **Probe** | Parallel latency + availability check that runs before any delivery commitment. |
| **Selection score** | A combined ranking over advertised price, observed latency, and reputation. Lower is better. |
| **Stake** | TOKEN locked on-chain by a node to join the mesh. Returned on deregistration after an unbonding period. |
| **TOKEN** | The deCDN governance and staking token. Not used for payments. |
| **Voucher** | A signed off-chain payment message. The primary payment instrument between client and node. |
| **Origin backend** | The opaque storage behind an origin-backed node (S3, R2, B2, MinIO, NFS, local disk). Never exposed to the network. |
| **Peer mesh** | The flat set of all staked nodes. Discovery via gossip; content location via DHT. |
| **Probe** | Parallel latency + availability check that runs before any delivery commitment. |
| **Selection score** | A combined ranking over advertised price, observed latency, and reputation. Lower is better. |
| **Stake** | TOKEN locked on-chain by a node to join the mesh. Returned on deregistration after an unbonding period. |
| **TOKEN** | The deCDN governance and staking token. Not used for payments. |
| **Voucher** | A signed off-chain payment message. The primary payment instrument between client and node. |
14 changes: 7 additions & 7 deletions docs/overview/how-it-works.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ If pull-through is disabled in the node's config, the node returns a redirect po

## Where the boundary sits

| Layer | Trust |
| --- | --- |
| Bytes delivered | **Verified** against known hash |
| Node availability | Trusted — slashed if phantom-announced |
| Node rate honesty | Trusted — slashed if advertised rate contradicts charged rate |
| Origin backend correctness | **Assumed** — outside protocol scope (operator concern) |
| L2 RPC provider honesty | **Assumed** — mitigated via multi-source bootstrap |
| Layer | Trust |
| -------------------------- | ------------------------------------------------------------- |
| Bytes delivered | **Verified** against known hash |
| Node availability | Trusted — slashed if phantom-announced |
| Node rate honesty | Trusted — slashed if advertised rate contradicts charged rate |
| Origin backend correctness | **Assumed** — outside protocol scope (operator concern) |
| L2 RPC provider honesty | **Assumed** — mitigated via multi-source bootstrap |

See [privacy](/protocol/privacy) for the adversary model.
16 changes: 8 additions & 8 deletions docs/overview/participants.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ A **node** is a staked QUIC endpoint that caches and serves content-addressed bl

Two deployment shapes:

| Shape | What's different | When to run |
| --- | --- | --- |
| **Pure cache** | No origin backend. Pulls missed blobs from other nodes. | You want to earn on popular content; you don't control any origin. |
| Shape | What's different | When to run |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Pure cache** | No origin backend. Pulls missed blobs from other nodes. | You want to earn on popular content; you don't control any origin. |
| **Origin-backed** | Configured with an S3-compatible backend, NFS mount, or local disk. The hash→object-key mapping is a local content catalog. | You are the content provider, or you are running a "last-mile" seed node for a provider. |

"Origin-backed" is a deployment choice, not a protocol distinction — the network cannot tell them apart at runtime.
Expand All @@ -45,8 +45,8 @@ An S3 bucket, R2 bucket, MinIO instance, NFS mount, or local disk. Holds the can

## Who stakes, who pays

| Participant | Stakes? | Pays? | Earns? |
| --- | --- | --- | --- |
| Pure cache node | ✅ TOKEN | ✅ Pays upstream on cache miss | ✅ Per-MB voucher revenue |
| Origin-backed node | ✅ TOKEN | ❌ | ✅ Per-MB voucher revenue |
| Client | ❌ | ✅ Per-MB voucher | ❌ |
| Participant | Stakes? | Pays? | Earns? |
| ------------------ | -------- | ------------------------------ | ------------------------- |
| Pure cache node | ✅ TOKEN | ✅ Pays upstream on cache miss | ✅ Per-MB voucher revenue |
| Origin-backed node | ✅ TOKEN | ❌ | ✅ Per-MB voucher revenue |
| Client | ❌ | ✅ Per-MB voucher | ❌ |
12 changes: 6 additions & 6 deletions docs/protocol/content-addressing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The on-chain corruption evidence path uses the same chunking ([slashing](/protoc

Origin backends speak their own namespace: S3 objects are addressed by key, NFS by path, local disk by filename. Blobs are addressed by BLAKE3 hash. The mapping lives in a **content catalog** — a small database (PostgreSQL or SQLite) maintained by the operator:

```
```text
catalog: hash → { s3_bucket, s3_key, size_bytes, content_type }
```

Expand All @@ -36,8 +36,8 @@ This also protects origin operators from direct egress cost attacks: an adversar

## What clients verify vs. trust

| Property | Verified | Trusted |
| --- | --- | --- |
| Byte correctness | BLAKE3 hash tree | — |
| Blob availability | — | Node's signed availability claim (slashable via phantom-announcement path) |
| Origin durability | — | Origin operator's S3/NFS configuration (off-protocol) |
| Property | Verified | Trusted |
| ----------------- | ---------------- | -------------------------------------------------------------------------- |
| Byte correctness | BLAKE3 hash tree | — |
| Blob availability | — | Node's signed availability claim (slashable via phantom-announcement path) |
| Origin durability | — | Origin operator's S3/NFS configuration (off-protocol) |
14 changes: 7 additions & 7 deletions docs/protocol/governance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ OpenZeppelin Governor with TOKEN voting, **4% quorum**, and a **2-day timelock**

Even governance cannot set parameters outside these bounds:

| Parameter | Bounds |
| --- | --- |
| Slash percentage | 5% – 50% |
| Dispute window | 12 h – 72 h |
| Min stake | > 0 |
| Protocol fee | < 100% |
| Channel close delay | > 0 |
| Parameter | Bounds |
| ------------------- | ----------- |
| Slash percentage | 5% – 50% |
| Dispute window | 12 h – 72 h |
| Min stake | > 0 |
| Protocol fee | < 100% |
| Channel close delay | > 0 |

Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to set stake to zero simply reverts.

Expand Down
12 changes: 6 additions & 6 deletions docs/protocol/privacy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ Privacy is treated as an **accountability-first** design: probes are public, on-

## Adversary model (four tiers)

| Tier | Example adversary | What they can observe |
| --- | --- | --- |
| **Passive observer** | On-path network middlebox | QUIC metadata (IPs, SNI unless ECH is deployed), timing, volume |
| **Active participant** | Malicious node or client | Probes, gossip activity, payment channel open/close |
| **Infrastructure operator** | L2 RPC provider, relay host | On-chain events at read time; relay endpoints see encrypted QUIC flows |
| **Compromised endpoint** | Malicious origin or content-provider infrastructure | Plaintext bytes, client subscription identity |
| Tier | Example adversary | What they can observe |
| --------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- |
| **Passive observer** | On-path network middlebox | QUIC metadata (IPs, SNI unless ECH is deployed), timing, volume |
| **Active participant** | Malicious node or client | Probes, gossip activity, payment channel open/close |
| **Infrastructure operator** | L2 RPC provider, relay host | On-chain events at read time; relay endpoints see encrypted QUIC flows |
| **Compromised endpoint** | Malicious origin or content-provider infrastructure | Plaintext bytes, client subscription identity |

## Privacy surfaces

Expand Down
12 changes: 6 additions & 6 deletions docs/protocol/reputation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ A report's weight is a function of the reporter's track record and counterparty

Interaction outcomes generate reports. Examples:

| Event | Local score change | Gossip report? |
| --- | --- | --- |
| Clean delivery, valid voucher accepted | Small positive | Optional |
| Hash mismatch mid-stream | Large negative | Yes |
| Probe claimed availability but stream failed | Negative | Yes (candidate for phantom-announcement slashing) |
| Charged rate ≠ advertised rate | Negative | Yes (candidate for rate-manipulation slashing) |
| Event | Local score change | Gossip report? |
| -------------------------------------------- | ------------------ | ------------------------------------------------- |
| Clean delivery, valid voucher accepted | Small positive | Optional |
| Hash mismatch mid-stream | Large negative | Yes |
| Probe claimed availability but stream failed | Negative | Yes (candidate for phantom-announcement slashing) |
| Charged rate ≠ advertised rate | Negative | Yes (candidate for rate-manipulation slashing) |

## Why not purely on-chain?

Expand Down
Loading
Loading