<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Cradicle Explorer</title>
    <link href="/css/bootstrap/bootstrap.min.css" rel="stylesheet">
    <style>
      .form-control-dark::placeholder {
          color: #aaa;
          opacity: 1;
      }
    </style>
    <link rel="stylesheet" href="/assets/fontawesome/css/all.min.css">
    <link rel="icon" type="image/png" href="/favicon.png">


                <link href="/css/dashboard.css" rel="stylesheet">
                </head>
                <body>
                <header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
                  <a class="navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6" href="/">Cradicle Explorer</a>
                  <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                  </button>
                  <form method="get" action="/cgi-bin/main" style="width:100%;"><input class="form-control form-control-dark w-100 rounded-0 border-0" type="text" name="q" placeholder="Search repos" aria-label="Search"></form>
                  <div class="navbar-nav flex-row">
                    <div class="nav-item text-nowrap">
                      <a class="nav-link px-3 active" href="/cgi-bin/repo?id=z42Cs1HxQRi7ejenC9FtZadBRaMfG">de-mls</a>
                    </div>
                  </div>
                </header>
                <div class="container-fluid">
                  <div class="row">
                    <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-dark sidebar collapse">
                      <div class="position-sticky pt-3 sidebar-sticky">
                        <ul class="nav flex-column">
                          <li class="nav-item">
                            <a class="nav-link active" href="/cgi-bin/repo?id=z42Cs1HxQRi7ejenC9FtZadBRaMfG">
                              <i class="align-text-bottom fa-solid fa-info"></i>
                              Info
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=z42Cs1HxQRi7ejenC9FtZadBRaMfG&issue=list">
                              <i class="align-text-bottom fa-solid fa-layer-group"></i>
                              Issues
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=z42Cs1HxQRi7ejenC9FtZadBRaMfG&patch=list">
                              <i class="align-text-bottom fa-solid fa-vest-patches"></i>
                              Patches
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=z42Cs1HxQRi7ejenC9FtZadBRaMfG&wallet=list">
                              <i class="align-text-bottom fa-solid fa-wallet"></i>
                              Wallets
                            </a>
                          </li>
                          <li class="nav-item">
                            <a class="nav-link" href="/cgi-bin/repo?id=z42Cs1HxQRi7ejenC9FtZadBRaMfG&source=.">
                              <i class="align-text-bottom fa-solid fa-code"></i>
                              Source
                            </a>
                          </li>
                        <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
                          <span></span>
                        </h6>
                        <ul class="nav flex-column mb-2">
                        
                        </ul>
                      </div>
                    </nav>
                <main class="col-md-9 ms-sm-auto col-lg-10">
                  <div class="container px-1 py-3">
        

    <div class="list-group">
    <div class="list-group-item">
    <div style="font-size:1.3rem;">de-mls</div>
    <div class="repo-item"></div>
    <div>rad:z42Cs1HxQRi7ejenC9FtZadBRaMfG</div>
    </div>
    <div class="list-group-item">
    <div>Visibility</div>
    <div class="repo-item">public</div>
    </div>
    <div class="list-group-item">
    <div>Delegates</div><div class="repo-item">did:key:z6MkuRd31dLXvQTavnVGbZL2jfpKY9rJjSktE89LBa8zAmdp</div>
    </div>
    <div class="list-group-item">
    <div>Default branch</div>
    <div><span class="repo-item">main &#8594 4fba916322d46bf008dbbf28fed6186de71c0cb4</span> (Wed Mar 18 10:30:56 2026)</div>
    </div>
    <div class="list-group-item">
    <div>Threshold</div>
    <div class="repo-item">1</div>
    </div>
    </div>
    
        <div class="list-group mt-3">
        <div class="list-group-item">
        <div class="mb-2" style="font-weight:bold;"><i class="fa-solid fa-book"></i> README.md</div>
        <pre style="margin:0; font-size:0.85rem; overflow-x:auto; color:#fafafa;"># De-MLS

[![Crates.io](https://img.shields.io/crates/v/de-mls.svg)](https://crates.io/crates/de-mls)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

Decentralized MLS proof-of-concept that coordinates secure group membership through
off-chain consensus and a Waku relay.  
This repository now ships a native desktop client built with Dioxus that drives the MLS core directly.

## What&#x27;s Included

| Crate / Path               | Description                                                                     |
| -------------------------- | ------------------------------------------------------------------------------- |
| **de-mls** (`src/`)        | Core library — MLS groups, consensus, and the delivery service layer            |
| **crates/de_mls_gateway**  | Bridges UI commands (`AppCmd`) to the core runtime and streams `AppEvent`s back |
| **crates/ui_bridge**       | Bootstrap glue that hosts the async command loop for desktop clients            |
| **apps/de_mls_desktop_ui** | Dioxus desktop UI with login, chat, stewardship, and voting flows               |
| **tests/**                 | Integration tests for the MLS state machine and consensus paths                 |

## Prerequisites

- **Rust** (stable toolchain)
- **Nim** (for building libwaku) — `brew install nim`

## Building libwaku

The project uses a local `libwaku.dylib` built from [logos-messaging-nim](https://github.com/logos-messaging/logos-messaging-nim):

```bash
make          # clones, builds, and copies libwaku.dylib into ./libs/
```

`make` builds `libwaku` with `--undef:metrics` by default to avoid libwaku
metrics thread-label errors in embedded-node usage. Override if needed:

```bash
make LIBWAKU_NIM_PARAMS=&quot;&quot;
```

`build.rs` links against `libs/libwaku.dylib` (or `libwaku.so` on Linux) and embeds an rpath automatically.

## Feature Flags

| Feature | Default | Description                                                                     |
| ------- | ------- | ------------------------------------------------------------------------------- |
| `waku`  | off     | Enables the Waku relay transport (`WakuDeliveryService`) and links to `libwaku` |

The core library (`de_mls`) compiles and tests **without** `libwaku` present.
The `waku` feature is required only when you need the concrete `WakuDeliveryService`
implementation — the gateway and desktop crates enable it automatically.

```toml
# Use the transport-agnostic types only (no libwaku needed):
de_mls = { path = &quot;...&quot; }

# Enable the Waku transport (requires libwaku):
de_mls = { path = &quot;...&quot;, features = [&quot;waku&quot;] }
```

## Delivery Service (`src/ds/`)

The delivery service (DS) is the transport layer that sits between the MLS core
and the network. It is fully **synchronous** — no tokio dependency — so it can
be used from any Rust context.

### Module layout

``` text
src/ds/
├── mod.rs              Public API re-exports
├── transport.rs        DeliveryService trait, OutboundPacket, InboundPacket
├── error.rs            DeliveryServiceError
├── topic_filter.rs     TopicFilter — async 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)
```

### Key types

| Type                  | Feature | Description                                                              |
| --------------------- | ------- | ------------------------------------------------------------------------ |
| `DeliveryService`     | —       | Trait — `send()` and `subscribe()`, both synchronous                     |
| `OutboundPacket`      | —       | Payload + group id + subtopic + app id (self-message filter)             |
| `InboundPacket`       | —       | Payload + group id + subtopic + app id + timestamp                       |
| `TopicFilter`         | —       | Async allowlist used by the gateway to filter inbound packets by group   |
| `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        |

### Basic usage

```rust
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 ds = result.service;

// Subscribe (can be called multiple times for fan-out).
let rx = ds.subscribe();
std::thread::spawn(move || {
    while let Ok(pkt) = rx.recv() {
        println!(&quot;{} bytes from group {}&quot;, pkt.payload.len(), pkt.group_id);
    }
});

// Send a message.
ds.send(OutboundPacket::new(
    b&quot;hello&quot;.to_vec(), &quot;app_msg&quot;, &quot;my-group&quot;, b&quot;instance-id&quot;,
))?;

// Shut down explicitly, or just drop all clones.
ds.shutdown();
```

### Threading model

`WakuDeliveryService::start()` spawns a `&quot;waku-node&quot;` 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&#x27;t crash the process.

In async code (e.g. the gateway), wrap `ds.send()` in
`tokio::task::spawn_blocking` to avoid blocking the tokio runtime.

### Content topics

Messages are routed by Waku content topics with the format:

``` bash
/{group_name}/{version}/{subtopic}/proto
```

Two subtopics are used: `app_msg` (application messages) and `welcome`
(MLS Welcome messages for group joins). The pubsub topic is fixed to
`/waku/2/rs/15/1` (cluster 15, shard 1).

### Self-message filtering

Each group entry in the app layer generates a random UUID (`app_id`) stored in
the Waku message `meta` field. On receive, the application compares
`packet.app_id` against the local entry&#x27;s id and drops self-originated messages.
Waku relay (gossipsub) does not filter self-messages natively.

## Quick Start

### Environment Variables

| 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 (`NODE_PORT + 1000`).
Use a unique `NODE_PORT` per local client so the embedded Waku nodes do not collide.

### Running Multiple Nodes For Example

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):

```bash
NODE_PORT=60001 DISCV5=true DISCV5_UDP_PORT=9001 cargo run -p de-mls-desktop-ui
```

Copy the `Local ENR: enr:-QE...` line from the logs.

**Nodes 2–4** (bootstrap off node 1):

```bash
NODE_PORT=60002 DISCV5=true DISCV5_UDP_PORT=9002 DISCV5_BOOTSTRAP_ENRS=&quot;enr:-QE...&quot; cargo run -p de-mls-desktop-ui
NODE_PORT=60003 DISCV5=true DISCV5_UDP_PORT=9003 DISCV5_BOOTSTRAP_ENRS=&quot;enr:-QE...&quot; cargo run -p de-mls-desktop-ui
NODE_PORT=60004 DISCV5=true DISCV5_UDP_PORT=9004 DISCV5_BOOTSTRAP_ENRS=&quot;enr:-QE...&quot; cargo run -p de-mls-desktop-ui
```

All nodes discover each other via the DHT and form a gossipsub relay mesh automatically.

## Using the Desktop UI

- **Login screen** – paste an Ethereum-compatible secp256k1 private key (hex, with or without `0x`)
  and click `Enter`.  
  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 under `apps/de_mls_desktop_ui/logs/`.

- **Groups panel** – lists every MLS group returned by the gateway.  
  Use `Create` or `Join` to 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 group` to request a self-ban (the backend fills in your address)
  - `Request ban` to 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`/`NO` buttons
  - Stores the latest proposal decisions with timestamps for quick auditing

## Development Tips

- `cargo test -p de_mls` – runs core tests (no libwaku required)
- `cargo test -p de_mls --features waku` – includes Waku transport tests (needs libwaku in `libs/`)
- `cargo fmt --all --check` / `cargo clippy` – keep formatting and linting consistent with the codebase
- `RUST_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.

## Contributing

Issues and pull requests are welcome. Please include reproduction steps, relevant logs,
and test coverage where possible.
</pre>
        </div>
        </div>

</div>
</main>
</div>
</div>


</body>
</html>

