Tap a reaction while somebody is talking and it shows on their screen for a moment. Agreement without interrupting, on a voice call as well as video. The signal rides the ratchet like every other call signal, is accepted only for the call in progress and only once it is up, and is recorded nowhere: when the call ends there is no trace of it. An older build ignores an action it does not know, and the emoji key is absent on every other kind, so nothing else on the wire changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Flamenet Messenger
An end-to-end encrypted messenger: the apps, the server they talk to, and a specification complete enough that someone else could build a compatible client from it.
The site is at https://flamenetmessenger.com, which is served by the relay in this family and explains what the apps do, what the server keeps, and where the limits are.
This repository holds all of it, and is the source of truth for every part. A protocol and the thing that speaks it drifting apart in separate repositories is how "compatible" quietly stops being true, so the interop harness here compiles the iOS engine and runs it against the JavaScript one on every change, which only works while they sit in the same checkout.
Sources/ the app (SwiftUI), including its Swift E2E engine
Tests/ unit tests — run on any Mac, no device
UITests/ the driven-simulator suites
Resources/ assets, entitlements, the shared sticker manifest
docs/ design notes, the roadmap, the padding design
The rest of the product lives in its own repositories, each with its own pipeline:
| Repo | What it is |
|---|---|
| flamenet-e2e | The protocol: JS engine, specification, published vectors, the interop driver |
| flamenet-relay | The server |
| flamenet-messenger-android | The Android app |
those three lived here as subtrees, and the public repos were rsync extracts of them, refreshed by hand. The extracts drifted for days at a time. They are standalone now, split out with their history, work on each product happens in its own repository, and a patch to a subtree here has nowhere to go because the subtrees are gone.
What still spans repos, and how it is wired rather than wished for:
flamenet-e2e's interop driver compiles this app'sSources/E2E/*.swift, it finds this repo as a sibling checkout, or viaFNMSG_APP_DIR.- The Android engine fetches the published vectors from
flamenet-e2eat HEAD in CI rather than carrying a copy that would drift. - The relay's conformance job clones
flamenet-e2efor the §8 harness.
The three engines
The protocol is implemented three times rather than an accident. Two implementations that agree may agree because one was written while reading the other; a third, written against the specification and the published vectors, is what finds the places where the spec says less than the first two quietly assumed.
They are checked against each other rather than trusted:
| Engine | Where | State |
|---|---|---|
| JavaScript | flamenet-e2e/src/, 8 files |
Complete, the reference implementation, and the one the published vectors are generated from |
| Swift | Sources/E2E/, 20 files |
Complete, and compiled directly into the interop harness rather than reimplemented for it |
| Kotlin | flamenet-messenger-android/engine/, 18 files |
Complete: X3DH, the ratchet, sealed sender, the key-transparency log, safety numbers |
Running the tests
Each repository tests itself; there is no longer a command that runs all of them, because they are no longer one checkout.
# this repo — the app's unit suite, the merge gate
xcodegen generate && xcodebuild -project FlamenetMessenger.xcodeproj \
-scheme FlamenetMessenger -sdk iphonesimulator \
-destination "platform=iOS Simulator,name=iPhone 17" \
test -only-testing:FlamenetMessengerTests
The cross-repository suites live in flamenet-e2e and need it checked out as a
sibling of this one (or FNMSG_APP_DIR pointing here):
cd ../flamenet-e2e
node test/conformance.mjs # published vectors, both backends
node test/interop.mjs # compiles THIS app's Sources/E2E and runs it against the JS engine — needs a Mac
node test/relay.mjs # drives a compiled relay over real HTTP; needs FN_RELAY_DIR
relay.mjs drives a binary, so swift build in the relay checkout has
to have run first, a stale binary passes tests for code you just changed.
UI tests (UITests/) are not in any gate: they need a local relay on port
18810 and, for the conversation suites, two booted simulators. Without the
relay they skip rather than fail, so read the executed count and not the
tick.
Running the relay
The server is its own repository now:
git clone https://gitlab.com/paulhitt/flamenet-relay.git
cd flamenet-relay
cp .env.example .env # then fill in the secrets it asks for
docker compose up -d
Open http://localhost:8080. The relay serves this project's website itself,
so a working install explains what it is rather than answering with a 404.
For production, see that repository's docs/HOSTING.md.
Building the iOS app
xcodegen generate
open FlamenetMessenger.xcodeproj
Requires Xcode and XcodeGen
(brew install xcodegen).
FlamenetMessenger.xcodeproj/project.pbxproj is generated and gitignored.
Declare sources in project.yml and regenerate; never hand-edit it.
The app is pinned to relay.flamenet.io; the sign-in screen accepts another
address for a relay you run yourself.
The app has not been released. SCOPING-v1.md tracks what stands between it and
a first TestFlight build.
What is published
All four repositories are public under the MIT licence, this one included. That was not always true: the app was private while the engine and server were mirrored out of it by an allowlist, and those mirrors were the only public copies. They are standalone repositories now, so what you are reading is the source rather than an extract of it.
Being public is a constraint on what may be committed: no keystores, no passphrases, no secrets, ever, including in test fixtures and in the history.
Where to read next
- The protocol:
flamenet-e2e/docs/SPEC.md, also served at/spec - What an auditor would need:
flamenet-e2e/docs/AUDIT-SCOPE.md, served at/audit - What the relay stores: served at
/metadata - What is not done: served at
/roadmap - How to contribute: CONTRIBUTING.md · security: SECURITY.md