lepic-flutter
lepic-flutter is the Dart package lepic: the Flutter binding of LEPIC. It is
generated by flutter_rust_bridge 2.13.0 from LEPIC's crates/lepic-ffi, and its build hook
bundles the prebuilt native library into the app, so the app needs no Rust toolchain of its own
at build time.
Repository: github.com/BTLabs-EPIC/lepic-flutter (available soon). Licence: Apache-2.0. Version 0.1.0, versioned with SemVer on its own.
What is in the repository
| Path | Holds |
|---|---|
lepic.rev | The LEPIC commit this package is generated from and builds |
lib/ | The Dart API; lib/src/rust/ is the generated Dart half of the bridge |
hook/build.dart | The build hook that bundles the native library into an app |
tool/lepic.sh | sync, build, test, emulator-test, generate and check against the pinned LEPIC |
tool/generate.dart | The codegen configuration and the patch applied after it |
test/, emulator_test/ | Host tests, and an Android device test app with no UI |
The Rust half of the bridge, frb_generated.rs, stays in LEPIC's crates/lepic-ffi. Both halves
come from one codegen run, and three checks keep them together:
- In LEPIC,
cargo xtask repo-lintcompares the API files andfrb_generated.rswith a recorded hash (bridge.sha256), so the API cannot change there without a new codegen run. - Here,
tool/lepic.sh checkruns the codegen again at the pin and fails unless the result equals the committed Dart and the pinned LEPIC's Rust half. - At run time,
RustLib.initrefuses a library generated from another API.
How an app uses it
import 'package:lepic/lepic.dart';
await RustLib.init();
final runtime = LepicRuntime(logLevel: LogLevel.info);
runtime.logs().listen((entry) => debugPrint('${entry.level} ${entry.message}'));
final store = lepicSecretStore(myFlutterSecureStorageAdapter); // implements SecretStore
final wallet = await EpicWallet.create(
runtime: runtime,
config: WalletConfig(
dataDir: dir.path,
chain: Chain.mainnet,
nodeUrl: 'https://node.example:3413',
epicboxDomain: 'epicbox.epiccash.com',
epicboxPort: 443,
),
store: store,
password: password,
);
- The app creates one
LepicRuntimeand gives each wallet its ownSecretStoreover the platform's secure storage (one store per wallet; the keys LEPIC writes have fixed names). EpicWalletmethods return Futures and Streams.events()streams balance, transaction, sync and transport changes;logs()streams redacted log records.- Every failure is a thrown
WalletErrorwith a stable numericcode. Codes never change meaning;messageis English for logs and holds no secret. - A panic inside LEPIC arrives as a
WalletErrorof kindpanic; the runtime stays usable. - PIN unlock uses
EpicWallet.createWithPinandEpicWallet.openWithPin. Failed password and PIN attempts are counted in the store and delayed after the third failure. - Only a short, fixed list of secrets crosses between Dart and Rust. See Security model.
The package README documents every call: direct transactions over HTTP and Tor, invoices, contact pairing, slates as QR codes, and the mapping from the app's earlier binding.
Native libraries
hook/build.dart adds the prebuilt liblepic_ffi for the target being built as a Flutter native
asset. The app needs no Gradle, Xcode or CMake changes.
| Target | Library | Built on |
|---|---|---|
| Android arm64-v8a, armeabi-v7a, x86_64 | liblepic_ffi.so | Linux or macOS, with the Android NDK and cargo-ndk |
| iOS device and simulator, arm64 | liblepic_ffi.dylib | macOS with Xcode |
| Linux x86_64 | liblepic_ffi.so | Linux x86_64 |
| Windows x86_64 | lepic_ffi.dll | Windows with Visual Studio Build Tools (C++) |
Build them from the pinned LEPIC, from the package root, before building the app. Each runs
LEPIC's cargo xtask mobile --package --out native for its targets:
tool/lepic.sh build all # every target this host can build
tool/lepic.sh build linux android # or windows, or Rust target triples
tool/lepic.sh build aarch64-linux-android
tool/lepic.sh sync clones LEPIC at the commit in lepic.rev into third_party/lepic. Any
command can use an existing clean checkout at the pin instead, named by LEPIC_DIR.
Next to each library the build writes three files, and the hook checks them before bundling:
| File | Holds | The hook refuses |
|---|---|---|
SHA256SUMS | The library's SHA-256 | A library not listed with that hash |
SOURCE | The LEPIC commit, whether the tree was clean, the rustc version | A library from another commit than lepic.rev, or from a modified tree |
FEATURES | The lepic-ffi features it was built with | The wrong build, when the tor user define is set |
SHA256SUMS catches truncated, stale or mixed-up files. It is not a signature and does not stand
in for trusting where a library directory came from.
Other points:
- The Android libraries link against API 24; the hook fails for an app with a lower
minSdk. They need no C++ runtime. - The Linux library links against the glibc of the machine that built it, so it is built on the oldest distribution the app supports.
- A release app passes the bundled file to
RustLib.init(externalLibrary: ...)by its place in the bundle. - Libraries kept elsewhere (for example CI artifacts) are used by setting
native_dirin the app'spubspec.yaml:
hooks:
user_defines:
lepic:
native_dir: ../lepic-native # holds <rust triple>/liblepic_ffi.*
Without Tor
WFE_TOR=0, the app's switch for its build without Tor, builds the libraries without LEPIC's
tor feature (see LEPIC):
WFE_TOR=0 tool/lepic.sh build linux android
The Dart API is the same for both builds. Without Tor, startTor, refreshTor, onionAddress,
sendTor and resendTor fail with unsupported, and the relay and node are always reached
directly. To make the app build fail when the wrong library is in place, set the tor user
define; without it the hook bundles either:
hooks:
user_defines:
lepic:
tor: false # only a library built without Tor; true: only one with Tor
Regenerating the bindings
After a change to LEPIC's crates/lepic-ffi/src/api, regenerate both halves with the codegen at
the version LEPIC's Cargo.lock resolves and pubspec.yaml pins (the command checks all three):
LEPIC_DIR=../lepic-worktrees/my-change tool/lepic.sh generate
It writes the Dart half to lib/src/rust/, and frb_generated.rs and bridge.sha256 into the
LEPIC checkout. Those two files are committed in LEPIC, lepic.rev moves to that commit, the Dart
half is committed here, and tool/lepic.sh check must pass.
Tests
| Command | What it runs |
|---|---|
tool/lepic.sh test | Builds lepic-ffi with the scripted node (mock feature) for the host and runs flutter test on x86_64 Linux |
tool/lepic.sh emulator-test | Builds the mock library for the attached Android device and runs create, restore, file send, receive and finalize on it |
LEPIC_INTEROP=1 tool/lepic.sh test test/interop_test.dart | End to end against LEPIC's local interop chain: funded over Epicbox, sends back by file |
The host tests read LEPIC's golden vectors from the same checkout. MockChain and MockRelay
work only in a mock build, which no app ships. More on running tests is on the
Testing page.