Skip to content

MostroP2P/app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

675 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mostro App

Non-custodial, peer-to-peer Bitcoin ↔ fiat exchange on Lightning Network — powered by the Mostro protocol over Nostr.

Flutter Rust License: MIT Platforms


Table of Contents

  1. What is Mostro App?
  2. Progress Overview
  3. What is Mostro?
  4. The Mostro Protocol
  5. Architecture & Fundamentals
  6. Supported Platforms
  7. Getting Started (Users)
  8. Getting Started (Developers)
  9. Project Structure
  10. Contributing
  11. Internationalization
  12. Security
  13. License

What is Mostro App?

Mostro App is the official cross-platform client for the Mostro peer-to-peer exchange network. It lets anyone buy or sell Bitcoin for local fiat currency using the Lightning Network — without accounts, without KYC, and without trusting a third party.

Key features:

  • Non-custodial — Your keys stay on your device. No exchange holds your funds.
  • Privacy-first — End-to-end encrypted over Nostr: NIP-44 direct messages with the Mostro daemon, NIP-59 Gift Wrap for peer-to-peer chat. Optional privacy mode hides reputation data.
  • Lightning-native — All BTC settlements happen via Lightning invoices (BOLT 11). Supports Nostr Wallet Connect (NWC) for automated invoice generation.
  • Censorship-resistant — Built on the Nostr network; no central server or domain to block.
  • Multi-platform — Single codebase targets Android, iOS, Web (PWA), macOS, Windows, and Linux.
  • Open source — MIT licensed. Fully auditable, no proprietary components.

Progress Overview

Status of the client's features across the Rust core and the Flutter UI.

Legend: [x] done · [~] partial · [ ] planned

  • Order flow (create / take / fiat-sent / release)
  • Real-time order book
  • Order book filtering
  • My trades / own orders
  • Lightning invoice handling (add / pay hold invoice)
  • Rate counterpart
  • User ↔ user chat
  • Mnemonic identity & key management (BIP-39 / BIP-32, NIP-06)
  • Secure storage (OS keystore)
  • NWC (Nostr Wallet Connect)
  • Relay management (manual add / toggle)
  • Proof-of-Work (NIP-13)
  • About / Mostro info
  • [~] Mostro node switching — single-node switch works; multi-node management pending
  • [~] Range orders — created / taken, but shown as a single amount in the order book
  • [~] Cooperative cancellation — works; context-aware UX pending
  • [~] Disputes — opening works; inbound list / admin chat not wired
  • [~] In-app notifications — screen works; trade/message events not yet wired to feed it
  • [~] Lightning address — not synced to the Rust store, so invoice auto-fill does not trigger
  • [~] Push notifications — client wired (FCM); push server not yet deployed
  • [~] Multi-language — missing translations / inconsistent switching
  • [~] Log report — UI present but capture not working
  • [~] Reputation / privacy mode — toggle wired, effect not confirmed
  • User ↔ user file attachments
  • User ↔ admin chat
  • User ↔ admin file attachments
  • Configurable session retention
  • Session restore from mnemonic
  • Relay auto-discovery (kind 10002)
  • Anti-abuse bond
  • Deep link mostro: URI
  • Tor / anonymous relays

What is Mostro?

Mostro is a decentralized peer-to-peer Bitcoin exchange system. It acts as a non-custodial escrow daemon that facilitates trades between parties who want to exchange Bitcoin (via Lightning) for fiat currency.

Unlike centralized exchanges (e.g., Binance, Coinbase), Mostro:

  • Does not hold user funds or require registration
  • Does not have access to trade secrets or personal data
  • Cannot censor or block orders
  • Relies on cryptographic proofs, not corporate trust
  • Is run by independent operators — anyone can run a Mostro node

The Mostro daemon (server component) is an open-source project written in Rust: MostroP2P/mostro. This repository is the mobile/desktop/web client that talks to any Mostro daemon.


The Mostro Protocol

The Mostro protocol is a message-passing specification built on top of Nostr. All communication between clients and the Mostro daemon travels as encrypted Nostr events.

Core Concepts

Concept Description
Order Book Mostro daemons publish pending orders as Nostr events of Kind 38383 — parameterized replaceable events.
Trade Messages All trade actions (take order, add invoice, confirm fiat sent, release funds) are sent as NIP-44-encrypted direct messages — signed Kind 14 events authored by the per-trade key and directed to the Mostro node pubkey (Mostro protocol v2).
P2P Chat Direct messages between buyer and seller (and dispute–admin chat) use NIP-59 Gift Wrap (Kind 1059) directed to the peer's pubkey.
Hold Invoices The seller pays a Lightning hold invoice when taking a buy order. Funds are locked until the buyer confirms fiat receipt, then the daemon releases the HTLC.
Reputation Each trade can result in a mutual star rating, published as a Nostr event by the daemon.
Disputes Either party can open a dispute, escalating to a human Mostro operator for resolution.

Trade Flow

Maker creates order  →  Mostro daemon publishes Kind 38383 to relays
Taker sees order     →  sends "take-order" to daemon (NIP-44 direct, Kind 14)
Daemon responds      →  notifies both parties via NIP-44 direct DM (Kind 14)

Seller pays Lightning hold invoice (funds locked in HTLC)
Buyer submits Lightning invoice to receive sats

Fiat transfer happens off-chain (bank transfer, cash, etc.)

Buyer confirms "fiat-sent" (NIP-44 direct, Kind 14)
Seller confirms receipt  →  daemon settles hold invoice
Sats released to buyer   →  trade complete
Both parties rate each other (optional)

Event Kinds Used

Kind Description
38383 Public order book (published by Mostro daemon)
14 NIP-44 direct DM — encrypted trade messages to/from the daemon, signed by the trade key (Mostro protocol v2)
1059 NIP-59 Gift Wrap — P2P chat between peers and dispute–admin chat

Protocol Reference


Architecture & Fundamentals

Mostro App uses a split-architecture model: all cryptography, protocol logic, and network I/O live in a Rust core; the UI shell is written in Flutter/Dart.

┌─────────────────────────────────────────────┐
│               Flutter / Dart UI             │
│  Riverpod state · GoRouter · Material 3     │
│  Sembast (UI-layer persistence, all plats.) │
└──────────────────┬──────────────────────────┘
                   │  flutter_rust_bridge 2.x (FFI / WASM)
┌──────────────────▼──────────────────────────┐
│                  Rust Core                  │
│                                             │
│  nostr-sdk 0.44   →  relay pool, NIP-44/59  │
│  mostro-core 0.13.1 →  protocol FSM, types,   │
│                      transport              │
│  bip32 / bip39    →  HD key derivation      │
│  k256             →  secp256k1 ECDH         │
│  chacha20poly1305 →  file encryption        │
│                                             │
│  SQLite (native)  ·  IndexedDB (WASM/web)   │
└─────────────────────────────────────────────┘

Design Principles

  • Offline-first — Orders and trade state persist locally; outbound messages are queued and retried when relay connectivity is restored.
  • Identity via BIP-32/39 — The master identity key is derived from a BIP-39 mnemonic. Per-trade ephemeral keys are derived via BIP-32 path m/44'/1237'/38383'/0/N to prevent cross-trade correlation.
  • Zero server dependency — The app can function with any compliant Mostro node. The default node pubkey is configurable in settings.
  • Type-safe bridge — The Rust API surface is defined through flutter_rust_bridge annotations; all Dart–Rust interactions are generated, never hand-written.
  • Platform parity — Web builds use wasm-pack to compile the Rust core to WASM; storage and async runtime are feature-gated per target.

Tech Stack

Layer Technology Version
UI Framework Flutter 3.x
UI Language Dart 3.x
Core Language Rust 1.94+ stable
Rust–Dart Bridge flutter_rust_bridge 2.11.1
State Management Riverpod 2.6.1
Routing GoRouter 14.8.1
Nostr Protocol nostr-sdk 0.44
Mostro Types / FSM / Transport mostro-core 0.13.1
UI-layer Persistence Sembast 3.8.2
Protocol Persistence (native) SQLite via sqlx 0.8
Protocol Persistence (web) IndexedDB 0.4
HD Key Derivation bip32 / bip39 0.5 / 2
ECDH (P2P chat keys) k256 0.13
File Encryption chacha20poly1305 0.10
Async Runtime (native) tokio 1
Async Runtime (web) wasm-bindgen-futures 0.4
Wallet Integration Nostr Wallet Connect NWC spec

Supported Platforms

Platform Status
Android 5.0+ Supported
iOS 13+ Supported
Web (PWA) Supported (WASM)
macOS 10.15+ Supported
Windows 10+ Supported
Linux (GTK) Supported

Getting Started (Users)

Install the App

Android

Download the latest APK from the Releases page and install it, or build from source (see the Developer section below).

iOS

Available via TestFlight (link in Releases) or build from source with Xcode.

Web

Open https://mostro.network/app/ in a modern browser — it always serves the latest build of main. No native app is installed and nothing is uploaded: the identity is generated in your browser and stays there, and the app talks to Nostr relays directly. (https://mostrop2p.github.io/app/ redirects there.)

The very first visit reloads itself once while it installs the service worker that supplies the cross-origin isolation headers the Rust core needs; that is expected. Two requirements follow from it: the page must be loaded over HTTPS (a service worker needs a secure context), and a browser with service workers disabled cannot run the web client.

Desktop (macOS / Windows / Linux)

Download the binary for your platform from the Releases page.

First Run

  1. On first launch, the app generates a BIP-39 mnemonic seed phrase.
  2. Back up your seed phrase immediately — it is the only way to recover your identity and trade history.
  3. Configure your preferred Mostro node pubkey and Nostr relays in Settings → Relays.
  4. (Optional) Connect a Lightning wallet via Settings → Connect Wallet (Nostr Wallet Connect) for automated invoice handling.

Getting Started (Developers)

Prerequisites

Make sure the following tools are installed on your system:

Tool Version Install
Flutter SDK 3.x stable flutter.dev/docs/get-started
Rust toolchain 1.94+ stable curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
WASM target rustup target add wasm32-unknown-unknown
wasm-pack latest cargo install wasm-pack
Rust nightly + rust-src (web only) rustup toolchain install nightly && rustup component add rust-src --toolchain nightly
flutter_rust_bridge CLI 2.11.1 cargo install flutter_rust_bridge_codegen --version 2.11.1 --locked
Xcode (macOS / iOS only) 15+ Mac App Store
Android Studio / NDK (Android only) latest developer.android.com

Clone & Setup

git clone https://github.com/MostroP2P/app.git
cd app

# Install Dart/Flutter dependencies
flutter pub get

# Install the flutter_rust_bridge codegen CLI at the pinned version.
# It MUST match `flutter_rust_bridge` in pubspec.yaml and rust/Cargo.toml (2.11.1):
# a mismatched CLI generates bindings that fail to compile, with an error that
# never mentions versions. Re-running this is cheap if it is already installed.
cargo install flutter_rust_bridge_codegen --version 2.11.1 --locked

# Verify the Rust core compiles
cd rust && cargo build && cd ..

# Regenerate flutter_rust_bridge bindings (needed after any Rust API change).
# The wrapper checks your CLI against the pin before generating.
./scripts/frb-generate.sh

Run

# Auto-detect connected device
flutter run

# Target a specific platform
flutter run -d android
flutter run -d ios
flutter run -d chrome        # Web — needs extra steps, see "Running on Web" below
flutter run -d macos
flutter run -d windows
flutter run -d linux

Running on Web

Web needs two steps that the other platforms don't. On native, the Flutter build compiles the Rust core for you; on web it does not — you compile the Rust core to WebAssembly yourself, and the page must be served with cross-origin isolation headers. Skip either step and the app builds but shows a blank page.

One-time prerequisites — in addition to the table above. The wasm build compiles the Rust core with -Z build-std, which requires a nightly toolchain with rust-src (and the wasm target on that same nightly toolchain):

rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
cargo install wasm-pack

1. Compile the Rust core to WASM. This produces web/pkg/ (rust.js + rust_bg.wasm). Re-run it after any change under rust/src/:

./scripts/build-web.sh              # add --release for an optimized build

Do not run flutter_rust_bridge_codegen build-web directly: on current nightly it silently produces WASM without shared memory, which crashes at runtime with DataCloneError: ... #<Memory> could not be cloned when FRB spawns its worker pool. The script adds the required linker flags and verifies the output (see issue #212).

If you skip this step entirely, the console shows Refused to execute script from '.../pkg/rust.js' because its MIME type ('text/html') is not executable — the WASM was never built.

2. Run in Chrome with cross-origin isolation headers. The Rust core uses SharedArrayBuffer, which browsers enable only under COOP/COEP:

flutter run -d chrome \
  --web-header "Cross-Origin-Opener-Policy=same-origin" \
  --web-header "Cross-Origin-Embedder-Policy=require-corp"

Without the headers the console shows Buffers cannot be shared due to missing cross-origin headers and the page stays blank.

On first launch the app generates an identity and connects to relays; you should see the order book populate from the daemon's Kind 38383 events without running anything locally.

Deploying the web client

main is published automatically to https://mostro.network/app/ by .github/workflows/deploy-pages.yml on every merge. (The organization serves its Pages sites from that domain, so this project page lives under mostro.network/app/ rather than mostrop2p.github.io/app/, which 301s to it. The sub-path is the same either way.) The build itself lives in the reusable .github/workflows/web-build.yml, which the deploy calls and CI also runs on every pull request — so a change that breaks the web target fails before it lands, and what CI validates cannot drift from what ships. It runs the same steps as above (scripts/frb-generate.sh, then scripts/build-web.sh --release) and finishes with:

flutter build web --release --base-href "/app/" --pwa-strategy=none

It then verifies the bundle and smoke-tests it in headless Chrome (test/web/smoke/smoke.mjs): the release bundle is served cross-origin isolated under /app/, and the test asserts the page is isolated, the Flutter view mounted, a Rust bridge call returned, and nothing errored. Static checks alone cannot catch that — every blank-page cause below greps perfectly clean.

Three things make it work on a static host that cannot set HTTP headers:

  • web/coi-serviceworker.min.js (vendored, MIT — see web/coi-serviceworker.LICENSE) is loaded as the first script in web/index.html. It installs a service worker that re-serves every request with the COOP/COEP headers injected, which is what makes SharedArrayBuffer — and therefore the Rust core's wasm threads — available. The cost is one automatic reload on a visitor's first load. It stays out of the way during local development, where the headers already come from --web-header.
  • --base-href "/app/" — project pages are served from a sub-path; without it every asset 404s and the page is blank.
  • --pwa-strategy=none — Flutter's own (deprecated) service worker would register over the same scope and evict the isolation shim, silently un-isolating the page. Offline caching is the trade-off.

The shim registers a service worker, which browsers only allow in a secure context — the site has to be reachable over HTTPS (Pages: Settings → Pages → Enforce HTTPS), or the page loads un-isolated and stays blank.

Deploying elsewhere works too, but the bundle is not portable across paths: --base-href is baked in at build time, so rebuild with the path the site is actually served from (/ at a domain root, /whatever/ under a sub-path). What carries over unchanged is the isolation choice — either set Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp at the server/CDN and drop the shim, or keep the shim and let it supply them.

Build for Production

# Android APK
flutter build apk --release

# Android App Bundle (Play Store)
flutter build appbundle --release

# iOS
flutter build ios --release

# Web — compile the Rust core first (see "Running on Web"), then:
./scripts/build-web.sh --release
flutter build web --release --base-href "/app/" --pwa-strategy=none

# macOS
flutter build macos --release

# Windows
flutter build windows --release

# Linux
flutter build linux --release

Testing

# Rust unit tests
cargo test

# Rust linter (must pass with zero warnings)
cargo clippy -- -D warnings

# Flutter static analysis
flutter analyze

# Flutter tests
flutter test

All CI checks must pass before merging. Run both suites locally before opening a PR.

Regenerating the Bridge

If you modify any #[frb]-annotated Rust function in rust/src/api/:

./scripts/frb-generate.sh

This wraps flutter_rust_bridge_codegen generate and refuses to run when your local codegen CLI does not match the version pinned in pubspec.yaml — generating with a mismatched CLI produces bindings that fail to compile. Pass --check to verify without generating.

Do not hand-edit files under lib/src/rust/ — they are auto-generated and will be overwritten on the next codegen run. They are gitignored and regenerated on the fly, both locally and in CI, so there is nothing to commit.


Project Structure

app/
├── lib/                        # Flutter/Dart UI shell
│   ├── core/                   #   App root, routing (GoRouter), theme, design tokens
│   ├── features/               #   Feature modules (one per user flow)
│   │   ├── walkthrough/        #     Onboarding flow
│   │   ├── home/               #     Order book + filters
│   │   ├── order/              #     Create / take order, invoices
│   │   ├── trades/             #     Active trades, trade detail
│   │   ├── chat/               #     P2P encrypted chat
│   │   ├── disputes/           #     Dispute management
│   │   ├── rate/               #     Post-trade rating
│   │   ├── notifications/      #     Notification center
│   │   ├── drawer/             #     Navigation drawer / side menu
│   │   ├── account/            #     Identity, key backup
│   │   ├── about/              #     Mostro node info
│   │   └── settings/           #     Relays, wallet, preferences
│   ├── shared/                 #   Cross-feature providers, widgets, utils
│   ├── l10n/                   #   Localization strings (EN, ES, IT, FR, DE)
│   └── src/rust/               #   Auto-generated Rust bridge (DO NOT EDIT)
│
├── rust/                       # Rust core
│   └── src/
│       ├── api/                #   Public bridge API surface (9 modules)
│       ├── crypto/             #   BIP-32/39 derivation, ECDH, file encryption
│       ├── db/                 #   SQLite (native) / IndexedDB (WASM)
│       ├── mostro/             #   Protocol FSM & state machine
│       ├── nostr/              #   Relay pool, gift wrap, event parsers
│       ├── nwc/                #   Nostr Wallet Connect client
│       └── queue/              #   Offline outbound message queue
│
├── rust_builder/               # iOS/macOS Cargokit build integration
├── test/                       # Flutter widget tests
├── specs/                      # Design specs, planning docs, API contracts
├── assets/                     # Walkthrough images, fiat currency data
├── pubspec.yaml                # Dart/Flutter manifest
└── rust/Cargo.toml             # Rust manifest

Contributing

Contributions are welcome. Please read this section before opening an issue or pull request.

Reporting Bugs

  1. Search existing issues first to avoid duplicates.
  2. Open a new issue using the Bug Report template.
  3. Include: platform, Flutter/Rust versions, reproduction steps, and relevant logs (flutter run --verbose or adb logcat).

Requesting Features

  1. Open a Feature Request issue.
  2. Describe the use case — what problem does it solve and who benefits?
  3. Features that align with the Mostro protocol roadmap are prioritized.

Submitting a Pull Request

  1. Fork the repository and create a branch from main:

    git checkout -b feat/your-feature-name
  2. Follow the code style:

    • Dart: standard Flutter conventions, enforced by analysis_options.yaml
    • Rust: cargo fmt + cargo clippy -- -D warnings (zero warnings required)
    • Commits: use Conventional Commitsfeat:, fix:, refactor:, docs:, test:
  3. Write tests for any new Rust API functions. Flutter widget tests are encouraged for new screens.

  4. Keep PRs focused — one concern per PR. Large refactors should be discussed in an issue first.

  5. Ensure all checks pass locally before pushing:

    cargo test && cargo clippy -- -D warnings
    flutter analyze && flutter test
  6. Open the PR against main, fill in the PR template, and link the related issue.

  7. Maintainers will review within a few days. Address feedback with new commits — do not force-push a branch under review.

Branch Policy

Branch pattern Purpose
main Protected — requires passing CI and one approving review
feat/<name> New features
fix/<name> Bug fixes
docs/<name> Documentation only
refactor/<name> Code restructuring without behavior change

Development Notes

  • Bridge changes: Any modification to rust/src/api/ requires re-running ./scripts/frb-generate.sh. The generated files are gitignored — there is nothing to commit.
  • Serde conventions: mostro-core uses #[serde(rename_all = "kebab-case")] — all protocol status strings on the wire are kebab-case (e.g., "waiting-buyer-invoice", "fiat-sent", "in-progress").
  • pub vs pub(crate): Only types that must be exposed to the Dart bridge should be pub. Internal helpers and types wrapping nostr-sdk structs should be pub(crate) to prevent broken FRB stub generation.
  • Key derivation: Per-trade keys follow BIP-32 path m/44'/1237'/38383'/0/N. Never reuse the master identity key for trade-level messages.
  • Offline-first: New trade actions must go through the outbound message queue (rust/src/queue/) so they are retried on reconnect. Do not call the relay client directly from action handlers.

Code of Conduct

This project follows the Contributor Covenant Code of Conduct. Be respectful, constructive, and inclusive. Violations can be reported to the maintainers via the contact listed in the GitHub organization profile.


Internationalization

The app is localized in:

Language Code File
English en lib/l10n/app_en.arb
Spanish es lib/l10n/app_es.arb
Italian it lib/l10n/app_it.arb
French fr lib/l10n/app_fr.arb
German de lib/l10n/app_de.arb

To add a new language:

  1. Copy lib/l10n/app_en.arb to lib/l10n/app_<code>.arb
  2. Translate all string values (keep the "@@locale" key correct)
  3. Run flutter gen-l10n to regenerate the Dart localizations — it auto-detects the new .arb file (no l10n.yaml change needed)
  4. Open a PR — translation contributions are always welcome

Security

Responsible Disclosure

If you discover a security vulnerability, please do not open a public issue. Instead:

  • Open a GitHub Security Advisory (preferred), or
  • Email the maintainers directly via the contact in the GitHub organization profile.

We aim to acknowledge reports within 72 hours and provide a fix within 30 days for critical issues.

Security Model

  • Keys never leave the device — the BIP-39 seed is stored in platform secure storage (flutter_secure_storage, backed by Android Keystore / iOS Keychain / Linux SecretService).
  • End-to-end encrypted trade messages — all communication between the app and the Mostro daemon uses NIP-44 encrypted direct messages (secp256k1 ECDH + ChaCha20 + HMAC-SHA256). Peer-to-peer and dispute chat use NIP-59 Gift Wrap.
  • Per-trade ephemeral keys — a new BIP-32 child key is derived for each trade, preventing cross-trade correlation even if a single trade key is compromised.
  • The Mostro daemon never sees plaintext — all messages are encrypted to the daemon's public key; only the holder of the corresponding private key can decrypt them.
  • No telemetry — the app does not collect analytics, crash reports, or any usage data.

License

MIT License

See LICENSE for the full text.


Mostro App is an independent open-source project. It is not affiliated with any centralized exchange or financial institution.

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages