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.
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
PaymentPoolin 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)), wherebondRequiredis a curve over declared bandwidth, super-linear at the default exponent (ADR 026 § Capacity-bond curve, § Minimum bond). The testnet deployed withminBondat 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-runprints the live target (it unlocks the keystore first). The maintainers send testnet TOKEN: send your EVM operator address (decdn whoamiprints 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_enabledon, which is the default for a node with no origin backend and off for an origin node.decdn node statusshows 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_PASSWORDor 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.--regionis 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 withdecdn config init --origin file://<absolute-dir>(--forcereplaces an existing one). For an S3 origin, import to a local directory andaws s3 syncit 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
CapacityBondactive set, read directly from the contract (ADR 001). - Clients pick nodes f