Envoy is a companion app for the Passport hardware wallet. To learn more visit foundation.xyz/envoy
Envoy is a Bitcoin wallet Flutter application with Rust backend libraries for cryptographic operations. The codebase uses Flutter Rust Bridge (FRB) to connect Dart and Rust code.
envoy-dev/
├── lib/ # Main Flutter app code
├── test/ # Dart tests
├── packages/ # Internal packages (mostly Rust FFI)
│ ├── backup/ # Backup encryption (FRB)
│ ├── foundation_api/ # Core crypto APIs (FRB)
│ ├── http_tor/ # HTTP over Tor (FRB)
│ ├── kebab_mcp/ # MCP server for hardware test rig
│ ├── ngwallet/ # Next-gen wallet functions (FRB)
│ ├── shards/ # Shamir secret sharing (FRB)
│ └── ur/ # Uniform Resources encoding (FRB)
├── rust/ # Workspace for all Rust crates
└── scripts/ # Build and utility scripts
just fmtFormats both Rust (cargo fmt) and Dart code. The Dart formatter excludes generated files, cargokit, and FRB output.
just copyDownloads translations from Localazy and generates Dart localization files. Run this after adding new user-facing strings.
just generateRuns build_runner for Freezed classes, JSON serialization, and other code generation.
./scripts/generate_frb.shRun this when you modify Rust API code in any FRB package. This script:
- Regenerates Dart bindings from Rust code
- Builds the Rust libraries
- Runs build_runner if needed
- Formats generated files
FRB packages: backup, foundation_api, http_tor, ngwallet, shards, ur
just lintRuns formatting, REUSE license check, Flutter analyze, and Cargo clippy.
flutter test # Run all Dart tests
flutter test test/ur_test.dart # Run specific test
cargo test # Run Rust testsNote: Some tests require Rust libraries to be built first. Run cargo build before running tests that use FRB packages.
Each FRB package follows this structure:
packages/{name}/
├── lib/
│ ├── {name}.dart # Public Dart API
│ └── src/rust/ # Generated FRB bindings (DO NOT EDIT)
│ ├── api/ # Generated Dart wrappers
│ ├── frb_generated.dart
│ ├── frb_generated.io.dart
│ └── frb_generated.web.dart
├── rust/
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs
│ ├── api/ # Rust API code (edit this)
│ │ └── {module}.rs
│ └── frb_generated.rs # Generated (DO NOT EDIT)
├── rust_builder/ # Cargokit build system
├── flutter_rust_bridge.yaml # FRB configuration
└── pubspec.yaml
- Edit Rust files in
packages/{name}/rust/src/api/ - Run
./scripts/generate_frb.shto regenerate bindings - Update the Dart wrapper in
packages/{name}/lib/{name}.dartif the API changed
Common FRB annotations for Rust functions:
#[flutter_rust_bridge::frb(sync)]- Synchronous function (blocks Dart)#[flutter_rust_bridge::frb(init)]- Initialization function- Return
Result<T, String>for functions that can fail
The formatter (just fmt) excludes:
**/cargokit/**- Vendored build tools**/src/rust/**- FRB-generated Dart code**/*.freezed.dart,**/*.g.dart- Generated files**/frb_generated*- FRB output
See analysis_options.yaml for full list. Key exclusions:
- All FRB packages under
packages/ - Generated files (
*.freezed.dart,*.g.dart,frb_generated*) - Cargokit directories
All source files must have SPDX license headers. Use REUSE tool:
reuse lint # Check compliance
reuse addheader --license GPL-3.0-or-later --copyright "Foundation Devices Inc." <file>The CI (ci.yml) runs:
- License header check (
reuse lint) - Dart formatting check
- Flutter analyze
- Rust formatting check
- Rust build (
cargo build) - Dart tests (
flutter test) - Clippy lints
- Rust tests (
cargo test)
Build the Rust libraries first:
cargo build # For debug
cargo build --release # For releaseMake sure the package's Rust crate is listed in the root Cargo.toml workspace members.
Generated files are excluded from analysis. If you see errors in src/rust/ or *.freezed.dart, they're likely stale. Regenerate with ./scripts/generate_frb.sh or just generate.
You are reviewing a PR in Envoy, Foundation Devices' Bitcoin wallet mobile app (Flutter/Dart UI, Rust core via FFI, iOS + Android, companion to the Passport hardware wallet).
Other Foundation Devices repos Envoy depends on or talks to. Consult them when a diff touches the integration surface; assume the contract on the other side is fixed unless the PR description says otherwise.
foundation-api— Rust monorepo for the device-to-device API built on Blockchain Commons' GSTP. Defines Quantum Link (QL) messages, the Beefcake Transfer Protocol (BTP) for MTU-sized chunking, and BLE/SE abstractions. Envoy wraps it as thefoundation_apiFRB package and uses it to talk to Passport over BLE.ngwallet— Foundation's next-gen Bitcoin wallet core, built on a Foundation-forked BDK. Owns the wallet logic: account/key derivation, PSBT construction and signing, fee handling, RBF, UTXO selection,sign_message. Envoy wraps it as thengwalletFRB package; every transaction Envoy builds or signs flows through it.envoy-server— Private Rust + Axum backend Envoy calls into. Two responsibilities: (1) serve Passport firmware release metadata (verified via GitHub webhook HMAC), and (2) act as the encrypted backup gateway against S3 or local storage.backup-server— Private Rust + Axum service for encrypted backup storage using post-quantum signatures (libcrux-ml-dsa/ ML-DSA). Sits alongside or augmentsenvoy-server's backup path.
First, check whether you have reviewed this PR before — look for earlier reviews or review comments you authored on it.
- If this is your first review: review the entire diff and raise every issue you find. Be thorough; this is the moment to surface everything about the existing code, because later reviews will not revisit it.
- If you have reviewed this PR before: comment only on what changed in the commits pushed since your last review. Do not raise new issues about code that was already present at your previous review, even if you only noticed it now. Before reviewing the new changes, revisit each finding you raised earlier on this PR and check whether the new commits address it: if one is now fixed, reply on its thread citing the commit that fixed it (for example,
Resolved by <sha>.) and resolve that thread; leave findings that still stand open.
Give every finding a priority — the reviewer triages from it, and any finding promoted to a Linear ticket inherits it:
- Urgent — must fix before merge: a correctness, security, or data-loss bug.
- High — should fix before merge: likely to bite, but not catastrophic.
- Medium — worth fixing; can be deferred to a follow-up ticket.
- Low — minor; nice-to-have.
Lead every inline comment with the priority in brackets, then a prefix that signals the action expected:
- (no prefix) — change this, or justify why not.
Optional:— an improvement; can be dismissed without justification.Note:— FYI only, no action required.
For example: [Urgent] <problem>. <fix>. or [Low] Optional: <suggestion>. or [Medium] Note: <observation>.
Resolve only your own threads, and only when the code genuinely addresses them — never resolve a comment authored by a human.
Urgent:
- Anything that could leak, log, or weaken handling of seeds, xprivs, descriptors, PSBTs, BIP39 words, or session secrets. Includes logging, crash reports, analytics, screenshots, clipboard, deep links, and accidental serialisation.
- Incorrect Bitcoin maths: fee calculation, sat/byte vs sat/vB, dust thresholds, change derivation, address-type assumptions, signature/PSBT round-trips.
- FFI boundary bugs across the Dart ↔ Rust seam: lifetime/ownership, nullability, blocking the UI isolate, missing
dispose, leaks of pointers orBox-ed values, panics not converted to errors. - Concurrency bugs around BLE (connect/disconnect/reconnect, GATT writes, queue ordering) and around wallet sync.
- Android permissions regressions, especially BLE on Android 11+ (
BLUETOOTH_CONNECT/SCAN, location prompt skips, foreground service requirements) and iOS Bluetooth state restoration / background modes.
High:
- Missing or incorrect error handling on network, BLE, or FFI calls that could land the user in a stuck UI state.
- State management mistakes (Riverpod/Provider): rebuild loops, stale references after hot reload, accidental
watchinbuildthat triggers infinite rebuilds. - i18n strings hard-coded in English in user-facing widgets that should go through the localisation pipeline (
just copy). - Obvious perf cliffs on lower-end Android (heavy work on the UI isolate, large
setStateblasts, missingconst).
Medium:
- Latent bugs that only trigger under uncommon conditions, or error paths that leave the user without a way to recover.
- Missing test coverage on a non-critical path the PR changes.
- New TODOs or technical debt added without a tracking ticket.
Low:
- Typos in user-facing strings, dartdoc/rustdoc, or code comments.
- Formatting / style —
dart formatand the linter cover it. - Renames or comment rewording.
- Speculative refactors ("you could extract this...") unless the code as written is wrong.
- Things the PR author explicitly called out in the description.
Skip preamble. Skip "great work!". Skip emoji.
Post each finding as its own inline comment, anchored to the exact line it concerns — one finding per comment, never batched into a single review. Use the [Priority] Prefix: ... format above: state the problem, then the fix, in one short paragraph.
Post exactly one top-level summary comment, and keep it to a single short paragraph: the overall verdict, optionally with a count of findings by priority. Do not restate the individual findings there — they live in the inline comments. If you keep a working checklist while reviewing, edit it out when you finish: the final summary comment must be just that one paragraph, not the checklist.
If you find nothing to flag, post the summary comment anyway with a short verdict (for example, "Reviewed the diff — no issues found.") rather than only a reaction or emoji.