Example integration and demo for the
de-mls library: a Dioxus desktop chat client
that drives de-mls's Conversation handle over a Waku relay, with
consensus-based membership. This repo is the runnable proof of concept — the
protocol library itself lives in the de-mls repo.
| Crate / Path | Description |
|---|---|
| crates/de_mls_ds | Delivery service — the DeliveryService trait and a Waku relay implementation |
| crates/de_mls_gateway | Integrator layer — per-conversation registry, identity, key-package minting, the OpenMLS provider |
| crates/de_mls_ui_protocol | Shared UI ↔ gateway message types (AppCmd, AppEvent, MemberInfo) + hex display |
| crates/ui_bridge | Bootstrap glue that hosts the async command loop for the desktop client |
| apps/de_mls_desktop_ui | Dioxus desktop UI — login, chat, stewardship, and voting flows |
This repo carries no protocol code: the
de-mls library is consumed as a git
dependency on its main branch (see the workspace Cargo.toml), so the PoC
always exercises the latest library API. Run cargo update -p de-mls to pull
the newest upstream commit.
- Rust (stable toolchain)
- Nim (only if rebuilding libwaku, see below) —
brew install nim
The Waku transport links against a local
logos-messaging-nim
build in libs/. A prebuilt macOS arm64 libwaku.dylib is checked in, so
on Apple Silicon the project builds and runs out of the box — no make
needed.
Rebuild it (or build libwaku.so on Linux) with:
make # clones, builds, and copies libwaku into ./libs/make builds libwaku with --undef:metrics by default to avoid libwaku
metrics thread-label errors in embedded-node usage. Override if needed:
make LIBWAKU_NIM_PARAMS=""The library's install-name is @rpath/libwaku.dylib (make normalizes and
re-signs it after every rebuild), and each binary-producing crate carries a
build.rs that embeds the workspace libs/ rpath — so test and app binaries
load the library from any checkout location.
| Feature | Crate | Description |
|---|---|---|
waku |
de-mls-ds |
Enables the Waku relay transport (WakuDeliveryService) and links to libwaku |
The waku feature lives on de-mls-ds; the gateway and desktop crates enable it
automatically. Without it, de-mls-ds still provides the transport-agnostic
DeliveryService trait and packet types — libwaku is needed only for the
concrete WakuDeliveryService.
The delivery service (DS) is the transport layer between the conversation and the network. It is synchronous, so it can be used from any Rust context.
crates/de_mls_ds/src/
├── lib.rs Public API re-exports
├── transport.rs DeliveryService trait, OutboundPacket, InboundPacket
├── error.rs DeliveryServiceError
├── topic_filter.rs TopicFilter — HashSet-based allowlist for inbound routing
└── waku/ Waku relay implementation (libwaku FFI)
├── mod.rs WakuDeliveryService, WakuConfig, content-topic helpers
├── sys.rs Raw C FFI bindings (trampoline pattern)
└── wrapper.rs Safe WakuNodeCtx wrapper (Drop calls waku_stop)
| Type | Feature | Description |
|---|---|---|
DeliveryService |
— | Trait — publish() and subscribe(), both synchronous |
OutboundPacket |
— | Payload + conversation id + subtopic + app id (self-message filter) |
InboundPacket |
— | Payload + conversation id + subtopic + app id + timestamp |
TopicFilter |
— | Allowlist used by the gateway to filter inbound packets by conversation |
WakuDeliveryService |
waku |
Concrete impl — runs an embedded Waku node on a background std::thread |
WakuConfig |
waku |
Node port, discv5 settings |
WakuStartResult |
waku |
Returned by start() — contains the service + optional local ENR |
use de_mls_ds::{WakuDeliveryService, WakuConfig, DeliveryService, OutboundPacket};
let result = WakuDeliveryService::start(WakuConfig {
node_port: 60000,
discv5: true,
discv5_udp_port: 61000,
..Default::default()
})?;
let mut ds = result.service;
// Open a pull-style inbound channel (multiple receivers allowed).
let rx = ds.inbound_receiver();
std::thread::spawn(move || {
while let Ok(pkt) = rx.recv() {
println!("{} bytes from {}", pkt.payload.len(), pkt.conversation_id);
}
});
// Publish a message.
ds.publish(OutboundPacket::new(
b"hello".to_vec(), "app_msg", "my-conversation", b"instance-id",
))?;
// Shut down explicitly, or just drop all clones.
ds.shutdown();WakuDeliveryService::start() spawns a "waku-node" thread that owns the
libwaku context. Outbound messages are queued via std::sync::mpsc; inbound
events are fanned out to all subscribers. The background thread is wrapped in
catch_unwind so a panic in the FFI layer won't crash the process.
Messages are routed by Waku content topics with the format:
/{conversation_name}/{version}/{subtopic}/protoTwo subtopics are used: app_msg (application messages) and welcome
(MLS Welcome messages for conversation joins). The pubsub topic is
fixed to /waku/2/rs/15/1 (cluster 15, shard 1).
Each User generates a random UUID (app_id) stored in the Waku message
meta field. On receive, the application drops packets whose app_id
matches the local user's. Waku relay (gossipsub) does not filter
self-messages natively.
| Variable | Required | Description |
|---|---|---|
NODE_PORT |
Yes | TCP port for the embedded Waku node |
DISCV5_BOOTSTRAP_ENRS |
No | Comma-separated ENR strings for discv5 bootstrap |
discv5 peer discovery is always enabled. The discv5 UDP port is derived
automatically as NODE_PORT + 1000. Use a unique NODE_PORT per local
client so the embedded Waku nodes do not collide.
Nodes on the same clusterId/shard discover each other automatically via discv5.
No external relay required — just run multiple local nodes.
Node 1 (bootstrap node):
NODE_PORT=60001 cargo run -p de-mls-desktop-uiCopy the Local ENR: enr:-QE... line from the logs.
Nodes 2–4 (bootstrap off node 1):
NODE_PORT=60002 DISCV5_BOOTSTRAP_ENRS="enr:-QE..." cargo run -p de-mls-desktop-ui
NODE_PORT=60003 DISCV5_BOOTSTRAP_ENRS="enr:-QE..." cargo run -p de-mls-desktop-ui
NODE_PORT=60004 DISCV5_BOOTSTRAP_ENRS="enr:-QE..." cargo run -p de-mls-desktop-uiAll nodes discover each other via the DHT and form a gossipsub relay mesh automatically.
-
Login screen – paste an Ethereum-compatible secp256k1 private key (hex, with or without
0x) and clickEnter.
On success the app derives your wallet address, stores it in session state, and navigates to the home layout. -
Header bar – shows the derived address and allows runtime log-level changes (
error→trace).
Log files rotate daily underapps/de_mls_desktop_ui/logs/. -
Groups panel – lists every MLS group returned by the gateway.
UseCreateorJointo open a modal, enter the group name, and the UI automatically refreshes the list and opens the group. -
Chat panel – displays live conversation messages for the active group.
Compose text messages at the bottom; the UI also offers:Leave groupto request a self-ban (the backend fills in your address)Request banto request ban for another user Member lists are fetched automatically when a group is opened so you can pick existing members from the ban modal.
-
Consensus panel – keeps stewards and members aligned:
- Shows whether you are a steward for the active group
- Lists pending steward requests collected during the current epoch
- Surfaces the proposal currently open for voting with
YES/NObuttons - Stores the latest proposal decisions with timestamps for quick auditing
de-mls is transport- and identity-agnostic: it owns the protocol and exposes a
per-conversation de_mls::Conversation handle. The integration that wraps it
lives in crates/de_mls_gateway — it keeps a per-conversation registry,
builds each Conversation from a wallet-derived credential, owns the OpenMLS
provider and key-package minting, and pumps the delivery service. The default
backends (consensus over hashgraph-like-consensus, in-memory peer scoring)
come from de_mls::defaults; the steward list and its service are owned by
the library itself.
For the library API itself, see the de-mls repository.
cargo test -p de-mls-gateway– gateway integration tests (in-memory transport)cargo build -p de-mls-gateway– build the gateway with the Waku transport (uses the checked-inlibs/libwaku.dylibon macOS arm64; elsewhere runmakefirst)cargo update -p de-mls– pull the latest de-mlsmaincommitcargo fmt --all --check/cargo clippy --all-targets -- -D warnings– CI enforces bothRUST_BACKTRACE=full– helpful when debugging state-machine transitions during development
Logs for the desktop UI live in apps/de_mls_desktop_ui/logs/; core logs are emitted to stdout as well.
Issues and pull requests are welcome. Please include reproduction steps, relevant logs, and test coverage where possible.