Unified flutter_rust_bridge bindings for Bitcoin and Liquid wallet operations.
Over the past years, we contributed to the open source community by creating flutter_rust_bridge bindings for several Bitcoin/Liquid libraries:
- ark-wallet-dart — Ark protocol
- bbqr-dart — Animated QR encoding
- boltz-dart — Boltz atomic swaps
- lwk-dart — Liquid Wallet Kit
Each of these packages works standalone and is used by other projects in the community.
However, our wallet app Bull Bitcoin depends on all of them. This creates two problems:
- Build time — Each package compiles its own native library (
.so/.dylib). Building them all individually is slow, especially for Android where each targets 3 architectures. - Binary size — Shared Rust dependencies (bitcoin, secp256k1, tokio, openssl, etc.) are duplicated across each native library.
bull_sdk is a single flutter_rust_bridge package that generates unified Dart bindings by scanning the Rust API of each sub-crate as external dependencies:
# flutter_rust_bridge.yaml
rust_input: crate::api, ark_wallet::ark, bbqr, dart_bbqr::api, boltz::api, lwk::apiThis produces one native library containing all the Rust code, with one FRB dispatcher handling all FFI calls.
- Standalone packages are preserved — Each sub-crate remains a fully functional standalone flutter_rust_bridge package. The community can keep using
boltz-dartorlwk-dartindependently. - Sub-crate
frb_generatedis cfg-gated — When used as a dependency ofbull_sdk, each sub-crate'sfrb_generated.rsis disabled via#[cfg(not(feature = "bull_sdk"))]to avoid duplicate trait implementations.bull_sdkprovides its own unifiedfrb_generated.rs. - Mirror types for data-variant enums — Rust enums with data (like
TxFee,ArkTransaction) become opaque when scanned as external crate types. We use#[frb(mirror)]to generate proper sealed Dart classes. - Post-processing — After FRB codegen,
fix_frb_generated.shpatches the generated Rust code to wrap error types inFrbWrapperand convert mirrored types via.into().
bull-sdk/
├── Cargo.toml # Cargo workspace
├── packages/
│ ├── bull_sdk/ # Unified FRB package (single native library)
│ │ ├── flutter_rust_bridge.yaml
│ │ ├── fix_frb_generated.sh # Post-processing script
│ │ ├── rust/ # Bridge crate
│ │ └── lib/
│ │ ├── bull_sdk.dart # BullSdk.init()
│ │ ├── ark.dart # Ark wallet types
│ │ ├── bbqr.dart # BBQr types
│ │ ├── boltz.dart # Boltz swap types
│ │ └── lwk.dart # Liquid Wallet Kit types
│ ├── ark-wallet/ # git submodule → SatoshiPortal/ark-wallet-dart
│ ├── bbqr/ # git submodule → SatoshiPortal/bbqr-dart
│ ├── boltz/ # git submodule → SatoshiPortal/boltz-dart
│ ├── lwk/ # git submodule → SatoshiPortal/lwk-dart
│ ├── boltz-stream/ # Pure Dart — BoltzWebSocket (depends on bull_sdk)
│ └── satoshifier/ # git submodule → SatoshiPortal/dart-satoshifier
cd packages/bull_sdk
flutter_rust_bridge_codegen generate
bash fix_frb_generated.sh
cargo check -p rust_lib_bull_sdkAlways run fix_frb_generated.sh after codegen — it patches error type wrapping and mirror type conversions that FRB cannot handle automatically for external crate types.
import 'package:bull_sdk/bull_sdk.dart';
import 'package:bull_sdk/boltz.dart' as boltz;
import 'package:bull_sdk/lwk.dart' as lwk;
import 'package:bull_sdk/bbqr.dart' as bbqr;
import 'package:bull_sdk/ark.dart' as ark;
await BullSdk.init();