New
Apps 121Makers 121This week 121
Promote your app−30%
0views in
the last 30 days

deCDN

Decentralized CDN where bonded operators serve content and clients pay per megabyte

Built with Claude Code

by decdn · from USUnited States · added Oct 29, 2026
no ratings0 comments
Open app Code

deCDN

A decentralized CDN. Anyone who bonds can serve bytes; clients pay per megabyte.

Bonded operators cache and serve BLAKE3-addressed content over iroh QUIC. Clients pay for every megabyte in USDC and verify every byte against its content hash, so any node can serve any blob without being trusted.

decdn get pulling content from the deCDN testnet

CI Security CodeQL Release crates.io docs.rs Docker MSRV License: MIT OR Apache-2.0 Discord

Website · Docs · Network stats · Discord · X

⚡ Try it in one line

Download Qwen2.5 0.5B Instruct, a 491 MB GGUF that runs in llama.cpp or ollama, from the testnet. It takes about a minute on a 100 Mbps line. On macOS or Linux:

curl -fsSL https://up.decdn.org/decdn.sh | sh -s -- get 'decdn:d0a3eac609db6f49de12213574a302e42afd45f15b2cd89aa46b8a3e91f74d13/?ns=1'

On Windows, in PowerShell:

& ([scriptblock]::Create((irm https://up.decdn.org/decdn.ps1))) get 'decdn:d0a3eac609db6f49de12213574a302e42afd45f15b2cd89aa46b8a3e91f74d13/?ns=1'

The file's SHA-256 is 74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db, the one Hugging Face publishes. For a bigger pull, Mistral 7B Instruct v0.3 is about 29 GB; either line takes its URI in place of the one above:

curl -fsSL https://up.decdn.org/decdn.sh | sh -s -- get 'decdn:9194000d7b356650e6924a7746ec4afb0b705838b65913c0e46cba6b15af69e5/?ns=1'

The line installs decdn and asks you to pass one check in the browser (it opens the link, or prints it). Then it downloads the bundle into its own ./<hash>/ directory and verifies every byte on the way in. It refuses to overwrite a file it did not write itself; add --overwrite after the URI to replace one. Run it again after an interruption and it resumes. A sponsor pays for the transfer, so you need no wallet, keys, or account. Later, decdn self-update moves the installed binary to the latest signed release.

The catalogue on decdn.org lists more seeded content. To pay for your own users' downloads the same way, run sponsord, the sponsorship daemon and onramp that ship with every release.

✨ Features

  • Verified bytes, untrusted nodes. Content is BLAKE3-addressed and the hash is a Merkle root, so any byte range verifies on its own, which makes resume and multi-source fetch safe (ADR 002, ADR 038).
  • Pay per megabyte. A client funds one shared PaymentPool in USDC and pays each node with cumulative off-chain vouchers (ADR 003).
  • Anyone can serve. Operators bond TOKEN against their declared bandwidth, register on-chain, then cache and serve content (ADR 001, ADR 026).
  • Multi-source fetch. A large fetch spreads disjoint byte ranges across several holders and reassigns ranges as sources finish or stall (ADR 039).
  • Private origins. S3, R2, B2, HTTP, and filesystem backends are per-node config. No origin URL is ever exposed (ADR 002).
  • Enforced on-chain. Every quote and stream response carries a signature that serves as on-chain slash evidence, a slashed operator has 30 days to appeal, and governance blacklists content hashes (ADR 014, ADR 028, ADR 011).
  • Governed by service. No TOKEN balance carries a vote. Vote weight is trailing-window served bytes (ADR 036).

🧭 Status

Public testnet on Arbitrum Sepolia; production targets Arbitrum One. The contracts ship at full production shape, with governance-tunable economics from day one (ADR 016). The implementation is Rust. Signed release archives and container images ship with each release, and crates publish to crates.io.

Wire, ABI, config, and storage formats change without compatibility shims until the mainnet deployment. Many ADRs are still marked Draft, so the specification can change too.

📦 Install

cargo install --locked decdn-cli decdn-node
docker pull ghcr.io/decdn/decdn-node     # daemon + CLI; mirrored to docker.io/decdn/decdn-node
docker pull ghcr.io/decdn/decdn          # CLI only; mirrored to docker.io/decdn/decdn
docker pull ghcr.io/decdn/sponsord       # sponsorship daemon, for publishers who pay for their users' downloads
docker pull ghcr.io/decdn/sponsord-onramp # its public onramp; both mirrored to docker.io/decdn/…

Each GitHub release also carries decdn-${VERSION}-${TARGET} and decdn-node-${VERSION}-${TARGET} archives (.tar.gz, or .zip on Windows), and sponsord-… and sponsord-onramp-… archives for Linux. SECURITY.md explains how to verify each channel.

A decdn installed from an archive or by the one-liner updates itself: decdn self-update fetches the latest release, verifies its SHA256SUMS.minisig against a maintainer key the binary embeds, and replaces the binary in place. A binary cargo install placed, or one in a directory you cannot write (the container image, a package), is refused with the command to run instead. Once a day, after a command succeeds with the terminal as its output, decdn prints one line when a newer release exists; [self_update] check = false in node.toml or DECDN_SELF_UPDATE_CHECK=0 turns that off. decdn-node never updates itself.

Running the container images

The decdn-node image runs the daemon and also carries the CLI, so docker exec <container> decdn node status reaches the daemon's local admin port. The decdn image runs the CLI. Mount your key directory and a working directory:

docker run --rm -it -v ~/.decdn:/home/decdn/.decdn -v "$PWD":/work -w /work \
  ghcr.io/decdn/decdn get decdn:<hash> -o blob.bin

Both images run as uid 1000. If your user has another uid, add --user "$(id -u):$(id -g)" -e HOME=/home/decdn. Without a TTY, mount a file that holds the keystore password and point DECDN_KEYSTORE_PASSWORD_FILE at it.

A maintainer signs the SHA256SUMS manifest that covers the release archives, and each container image's digest file and SBOM. crates.io artifacts carry no maintainer signature.

Build from source

You need the Rust toolchain that rust-toolchain.toml pins (rustup installs it automatically), a C compiler, and make.

git clone --recurse-submodules https://github.com/decdn/decdn.git
cd decdn
cargo build --release -p decdn-cli -p decdn-node
# Binaries: target/release/decdn and target/release/decdn-node

CONTRIBUTING.md lists the full development prerequisites, including Foundry for the contracts.

🚀 Quick start

Fetch with your own wallet

A client uses its own keystore under ~/.decdn/client, separate from any node identity on the same host. Fund the address that key-gen prints with Arbitrum Sepolia ETH and USDC.

decdn config init --client --output ~/.decdn/client.toml
decdn key-gen --output-dir ~/.decdn/client
decdn --config ~/.decdn/client.toml quote decdn:<blake3-hash>
decdn --config ~/.decdn/client.toml get decdn:<blake3-hash> -o ./blob.bin --receipt ./blob.receipt.json
decdn receipt verify ./blob.receipt.json

quote prices the pull from the rates the nodes sign in their probe answers, and pays nothing for a blob; a bundle with no cached manifest costs one small paid pull for it, from your open pool. With no open pool, quote stops before it opens one: add --open-pool to let it, which escrows the working deposit (10 USDC by default) and pays gas, or fund a pool first with decdn pool open. With or without the flag, an open pool that runs low gets a top-up, which also escrows USDC and pays gas. get prints the same estimate on one line before it starts. It ends with a receipt: the bytes it verified, the nodes that served them, and the USDC it paid. --receipt writes the nodes' signed answers and your vouchers to a file that decdn receipt verify checks offline and decdn receipt settle updates once the nodes redeem (appendix).

get pays each node it uses from a pool you own. It reuses your existing pool or opens one with a 10 USDC working deposit, and it tops the pool up on-chain without prompting when the balance runs low. Fund the wallet with only what you are willing to spend.

decdn: URIs, node selection, and overwrite rules

A decdn: URI names a blob, a whole bundle, or one file of a bundle (appendix):

URI get pulls -o is Without -o
decdn:<hash> the blob a file, or - for stdout ./<hash>
decdn:<hash>/ every file of the bundle whose manifest has this hash a directory ./<hash>/
decdn:<hash>/a/b.txt that one file of the bundle a directory, or - for stdout ./b.txt
any of these with ?ns=<id> the same, routed to a namespace's origins the same the same

Without -o, a bundle or entry pull refuses to replace a file unless an earlier pull wrote it there and it has not changed since. That earlier pull must be of the same entry, or have written the same bytes. It also refuses a hidden path — one with a .-prefixed component — outright; name the directory with -o <dir> to pull such paths. A new version of a bundle has a new hash, so it lands in its own directory; to update the old directory in place, pass -o <the old bundle's directory>: the pull replaces the files that changed and skips the rest. --overwrite fetches every file again.

get probes bonded nodes and picks one that holds the blob. When none does, it tries bonded non-holders nearest first. Each fills the miss from its own origin, or from a peer if it has pull-through on. A node with neither answers not-found, and get moves to the next. One pool pays every provider, so there is no per-provider setup. Use decdn pool {…} to inspect, close, or reclaim a pool.

Run a node

You need Arbitrum Sepolia ETH for gas, TOKEN for the bond, and a keystore password source for the headless daemon. The operator guide covers testnet funds, the bond (ADR 026 § Capacity-bond curve), and the full quickstart.

decdn config init            # writes ~/.decdn/node.toml with the testnet RPC and contract addresses
decdn setup --mbps 100 --region US --multiaddr /ip4/203.0.113.10/udp/4433/quic-v1
decdn-node run
decdn node health            # then: node status, node lanes, node earnings, node top

setup generates the node key and keystore when neither exists, shows the operator terms, bonds, registers the node, and prints a go/no-go summary (ADR 019).

Bond size, pull-through, IPv6, and serving your own content
  • Bond. The bond is max(minBond, bondRequired(mbps)), where bondRequired is a curve over declared bandwidth, super-linear at the default exponent (ADR 026 § Capacity-bond curve, § Minimum bond). The testnet deployed with minBond at 50,000 TOKEN (manifest), which at the default curve (k = 12.6, α = 1.2) covers declarations up to about 997 Mbps; 1,000 Mbps needs about 50,162 TOKEN. decdn node bond --mbps <n> --dry-run prints the live target (it unlocks the keystore first). The maintainers send testnet TOKEN: send your EVM operator address (decdn whoami prints it) through any channel on the contact page.
  • USDC. The node's own buyer pool pays for cache-miss pulls from peers (10 USDC working deposit by default). The node spends it only with cache.node_to_node_pull_through_enabled on, which is the default for a node with no origin backend and off for an origin node. decdn node status shows the setting and the buyer wallet's balance. With it off, or with no USDC to open the pool, a cache miss the node cannot serve from its own backend returns not-found (runbook). Circle's testnet faucet dispenses it.
  • Keystore password. Set DECDN_KEYSTORE_PASSWORD or pass --keystore-password-file (runbook).
  • Network. The node listens on UDP 4433 over IPv4 and IPv6, so a dual-stack host also passes --multiaddr /ip6/<addr>/udp/4433/quic-v1. --region is an ISO 3166-1 alpha-2 country code (ADR 030).
  • Your own content. Import it into a local origin store with decdn origin import --input <path> --to <dir>, then write the node config with decdn config init --origin file://<absolute-dir> (--force replaces an existing one). For an S3 origin, import to a local directory and aws s3 sync it to the bucket: the two layouts are identical.

Publish content

A publisher creates a namespace and seats the bonded operators that serve as its origins. publish moves no bytes. A client passes the namespace with get decdn:<hash>?ns=<id>, and a node that misses the blob pulls it from one of the seated operators' origin backends (ADR 005 § Namespace routing).

decdn publish namespace create                     # prints the namespace id
decdn publish assign <namespace-id> <0xOPERATOR>...

The installed vetting policy decides who may seat origins (ADR 011 § Vetting policies). The testnet runs the manual policy, so a VETTER_ROLE holder must vet the publisher's address before assign succeeds. That step happens out-of-band, not through the CLI.

Local devnet

To run without testnet funds, contracts/dev-deploy.sh starts Anvil and deploys the full protocol, a mock USDC, and a TOKEN faucet. See CONTRIBUTING.md § Local deployment.

The decdn-e2e tests are runnable end-to-end journeys: onboarding, paid fetch, and publishing. They need cargo-nextest and Foundry, and they drive the debug binaries, so build those first:

cargo build -p decdn-cli -p decdn-node
cargo nextest run -p decdn-e2e --features anvil-e2e

🏗️ How it works

┌─────────────────── on-chain (Arbitrum) ────────────────────┐
│  CapacityBond   bonded TOKEN · registry of active nodes    │
│  PaymentPool    USDC deposits · voucher redemption         │
└─────▲──────────────────▲──────────────────────────▲────────┘
      │ deposit ·        │ bond · deposit · redeem  │ bond · redeem
      │ close · reclaim  │                          │
┌─────┴──────┐    ┌──────┴───────┐    ┌─────────────┴─┐    ┌──────────────────┐
│   Client   │◄──►│  Cache node  │◄──►│  Origin node  │◄───┤ S3/R2/B2/HTTP/fs │
└────────────┘    └──────────────┘    └───────────────┘    └──────────────────┘
    ◄─► bytes one way, USDC vouchers the other (cdn/client/v1)
  • Nodes bond TOKEN against their declared bandwidth, register on-chain, then cache and serve content. The node set is the CapacityBond active set, read directly from the contract (ADR 001).
  • Clients pick nodes f

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#cdn · #p2p · #crypto