A Flutter FFI plugin that brings the peat peer-to-peer mesh protocol to
mobile and desktop apps. It wraps the peat Rust stack — peat-schema,
peat-protocol, peat-mesh, and peat-btle — and exposes it to Dart through
peat-ffi (UniFFI), so a Flutter app can join a local mesh, discover peers, and
sync CRDT (Automerge) documents directly between devices.
peat is designed for fully offline / disconnected environments: there is no
central server. Nodes find each other on the local network (mDNS over UDP
multicast) or over the short-range Bluetooth LE transport (peat-btle) and
converge on shared state device-to-device.
This repository is the Flutter/Dart binding layer for peat. Its job is to:
- Provide a Dart facade (
PeatFlutterNode) over the UniFFI-generatedpeat-ffibindings — subscribing to document changes, publishing/reading proto-typed messages, and driving inbound/outbound frame exchange. - Bundle the compiled native
peat-ffilibrary for each target platform.
Android, iOS, macOS, Linux, and Windows (all via FFI).
This plugin was built under a few deliberate constraints worth knowing before you build or contribute:
- Native dependency. The plugin links against
peat-ffi, a Rust library from thepeatworkspace. Building the native library from source requires a checkout of that workspace as a sibling directory (default../peat, override withPEAT_WORKSPACE_DIR). - Generated sources are committed.
lib/src/generated/(FFI bindings) andlib/src/proto/(proto stubs) are committed so that consumers can build without the Rust / UniFFI /protoctoolchain. They only need to be regenerated when the native surface changes. - Bindings are maintained by hand. The
uniffi-bindgen-dartgenerator is not currently publicly available, solib/src/generated/peat_ffi.dartis maintained manually.just check-bindingsverifies it against the compiled library. See thejustfilefor the specific invariants. - Pre-1.0. The API surface is still evolving (current version
0.0.1).
Add the plugin to a Flutter app and drive a node from Dart:
import 'package:peat_flutter/peat_flutter.dart';
final node = await PeatFlutterNode.create(/* ... */);
final sub = node.subscribeChanges((doc) {
// react to synced document state
});See example/lib/main.dart for a complete demo app
(a peer-to-peer "water counter" that sums per-node state across a mesh cell).
cd example
flutter pub get
flutter run # pick a connected device or desktop targetThe example bundles a prebuilt
peat-ffifor the target platform. To build the native library yourself, see Building native code.
Tasks are driven by just (see the
justfile):
just analyze # dart analyze lib/
just gen-proto # regenerate Dart proto stubs from peat-schema
just check-bindings # verify the generated FFI bindings against libpeat_ffi
just regen # gen-proto + check-bindings
just build-host # build peat-ffi for the native host
just build-android # build peat-ffi for all Android ABIs (needs cargo-ndk + NDK)
just build-ios # build PeatFFI.xcframework (macOS host only)
just build-macos # build the universal macOS dylib (macOS host only)dart analyze lib/ # static analysis of the package (the CI gate; == just analyze)
flutter test # unit tests (when present)
dart format . # apply formatting (reported by CI, not yet gated)CI runs these in .github/workflows. The package
analysis (dart analyze lib/) gates merges; the example app's analysis and
formatting are reported informationally while the tree predates a format pass.
The pubspec.yaml marks this as an FFI plugin for each platform, which invokes
the native build and bundles the binaries:
- Android: Gradle invokes the Android NDK;
cargo-ndkcross-compilespeat-ffi. Seeandroid/build.gradle. - iOS / macOS:
ios/build-rust.sh/macos/build-rust.shproduce aPeatFFI.xcframework/ universal dylib that the podspecs wire in. - Linux / Windows:
src/CMakeLists.txtdrivescargoat CMake build time.
FFI bindings in Dart are generated from the native header by
package:ffigen:
dart run ffigen --config ffigen.yamlThen reconcile any manual fixes per the notes in the justfile and
run just check-bindings.
peat's local peer discovery (peat_mesh::discovery::MdnsDiscovery, backed by
iroh's swarm-discovery crate) finds peers via raw UDP multicast
(224.0.0.251 / ff02::fb, port 5353) — it does not use Apple's Bonjour
API. Since iOS 14, an app's own sockets cannot send/receive multicast unless the
app carries the com.apple.developer.networking.multicast entitlement. (The
NSBonjourServices / NSLocalNetworkUsageDescription keys in Info.plist only
cover the Bonjour API, which routes through the system mDNSResponder daemon —
they do not unlock raw multicast.)
Symptom without the entitlement: two iOS devices can each reach an unrestricted node (e.g. a macOS peer) and sync transitively through it, but never discover each other directly. mDNS needs multicast to flow both ways; an un-entitled iOS node can't advertise itself, so two of them never complete the handshake. A macOS node (no such restriction) brokers discovery and relays documents between them.
This repo already wires the entitlement: example/ios/Runner/Runner.entitlements
declares the key and CODE_SIGN_ENTITLEMENTS is set for Debug/Release/Profile.
The multicast entitlement must be granted by Apple for your team + bundle ID before Automatic signing will include it — otherwise device builds fail with "Provisioning profile doesn't include the Multicast Networking capability."
-
Submit the request: https://developer.apple.com/contact/request/networking-multicast (sign in as the team's Account Holder/Admin).
-
Provide the bundle ID (e.g.
com.defenseunicorns.peatFlutterExample) and a justification, for example:peat-water is a peer-to-peer local mesh application. It uses mDNS-based service discovery over raw UDP multicast (via iroh/swarm-discovery) to find peers on the same LAN and sync CRDT (Automerge) documents directly between devices, including in fully offline/disconnected environments with no internet or central server. The multicast entitlement is required so the app can advertise and discover peers device-to-device; without it, two iOS devices cannot find each other on the local network.
-
Approval typically takes 1–3 business days. Once granted, the Multicast Networking capability becomes available for the App ID; clean and rebuild (Xcode may need Settings → Accounts → Download Manual Profiles). No code change is needed — the wiring is already in place.
Interim builds while the request is processing: temporarily clear
CODE_SIGN_ENTITLEMENTS on the Runner target so device builds keep signing
(macOS-bridged sync still works). Restore it after approval.
lib/— the Dart API of the plugin (peat_flutter.dart,src/peat_node.dart) and committed generated sources (src/generated/,src/proto/).src/—CMakeLists.txtdriving the native build for Linux/Windows.- platform folders (
android,ios,macos,linux,windows) — native build/bundling for each target. example/— a runnable demo app.docs/— additional documentation, including ADRs.
Contributions are welcome! Please read CONTRIBUTING.md and our Code of Conduct. To report a security issue, see SECURITY.md.
Licensed under the Apache License, Version 2.0.
Copyright 2026 Defense Unicorns
SPDX-License-Identifier: Apache-2.0