Skip to content

Repository files navigation

Willow

A peer-to-peer Discord replacement built with Rust. No central servers, no accounts, no middlemen. End-to-end encrypted by default.

Features

  • Text chat with channels, threads, reactions, pins, and emoji
  • End-to-end encryption — ChaCha20-Poly1305 with X25519 key exchange
  • Peer-to-peer — iroh networking with QUIC transport and gossip protocol
  • File sharing — content-addressed chunking, transferred peer-to-peer
  • Servers & permissions — roles, fine-grained permissions, invites
  • Event-sourced state — deterministic, mergeable, offline-friendly
  • Runs in the browser — Leptos + WASM web UI

Architecture

 Leptos Web UI (willow-web)
│
Client Library (willow-client)
│
┌────────┴─────────┐
│ │
State Machine Network Layer
(willow-state, pure) (willow-network, iroh)
│ │
┌──────┴──────┐ ┌─────┴─────┐
│ Channels │ │ Relay │
│ Messaging │ │ Workers │
│ Crypto │ │ (replay, │
│ Identity │ │ storage) │
└─────────────┘ └───────────┘
│
┌─────┴─────┐
│ Actor │
│ Framework │
└───────────┘

Crates:

CratePurpose
willow-statePure event-sourced state machine (zero I/O)
willow-clientUI-agnostic client library wrapping state + networking
willow-transportBinary serialization & protocol framing
willow-identityEd25519 identity, message signing, profiles
willow-messagingChat messages, HLC ordering, message store
willow-cryptoE2E encryption (ChaCha20-Poly1305, X25519)
willow-networkiroh-based P2P networking (native + WASM)
willow-actorLightweight actor framework (dual-target native + WASM)
willow-commonShared wire protocol types (WireMessage, worker types)
willow-workerShared worker library (roles, actor runtime, peer lifecycle)
willow-relayRelay server bridging TCP and WebSocket peers
willow-replayBounded-memory state sync worker (in-memory event buffer)
willow-storageArchival disk-backed history worker (SQLite)
willow-agentMCP server exposing ClientHandle to AI agents
willow-webLeptos web UI

State Management

All shared state is event-sourced — derived deterministically from a per-author Merkle-DAG of signed events. There is no mutable database; the event log is the data.

 Author A Author B Author C
│ │ │
[e1]─→[e2]─→[e3] [e1]─→[e2] [e1]
│ │ │
└──────────────────┴──────────────────┘
│
topological sort + replay
│
ServerState
(channels, roles, members,
messages, profiles, keys)

How it works:

  • Events are content-addressed (SHA-256), signed by their author, and linked into a DAG via sequence numbers and dependency pointers.
  • ServerState is the materialized view — rebuilt deterministically by topologically sorting all events and replaying them through materialize(). Peers converge to identical state when they have the same DAG.
  • EventKind variants cover server structure (create/delete/rename channels and roles), chat (messages, edits, deletes, reactions, pins), permissions (grant/revoke), identity (profiles), encryption (key rotation), and governance (proposals and votes).
  • Sync protocol uses compact HeadsSummary (author → seq + hash) so peers can efficiently request only missing events. Full Snapshot bootstraps far-behind peers.

Trust & permissions:

  • Admin status is granted only through governance votes (Propose + Vote), never by direct event — protecting against single-actor escalation.
  • Non-admin permissions (ManageChannels, ManageRoles, SendMessages, CreateInvite, SyncProvider) can be granted directly by admins.
  • All permission checks are enforced deterministically during event replay.

Client layer (willow-client): ClientHandle wraps the pure state machine with actor-based state management, reactive UI views, a mutations API, and persistence — bridging the deterministic core with async networking and pub/sub event distribution.

Getting Started

Prerequisites

  • Rust (stable)
  • just (command runner)
  • trunk (for the web UI)
  • wasm32-unknown-unknown target: rustup target add wasm32-unknown-unknown

Local Development

Start the full stack with a single command:

just dev

This launches all services:

ServiceAddressDescription
Relaylocalhost:3340iroh relay HTTP + bootstrap
Replay nodeconnects via relayIn-memory state sync (max 1000 events/server)
Storage nodeconnects via relayArchival SQLite storage
Web UIhttp://localhost:8080Leptos app via trunk serve

All service logs are color-coded and interleaved in the terminal. Press Ctrl+C to stop everything.

Identity keys and data persist in .dev/ so peer IDs stay stable across restarts. After the first run, use just dev-quick to skip the build step.

just dev-quick # skip build, start immediately
just dev-clean # reset all local dev data

Running Individual Services

just relay # relay server only
just serve-web # web UI only (trunk serve)

Docker

just docker-build # build all images
just docker-up # start full stack
just docker-down # stop full stack
just docker-logs # tail all logs

Testing

Willow has a multi-tier test suite:

just check # fmt + clippy + test + WASM check (run before committing)
just test# all cargo tests
just test-state # pure state machine (instant)
just test-client # client library
just test-relay # relay history sync
just test-browser # in-browser Leptos tests (requires Firefox + geckodriver)

License

See LICENSE for details.

About

p2p rich communication

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages