Skip to main content

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​

PathHolds
lepic.revThe 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.dartThe build hook that bundles the native library into an app
tool/lepic.shsync, build, test, emulator-test, generate and check against the pinned LEPIC
tool/generate.dartThe 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-lint compares the API files and frb_generated.rs with a recorded hash (bridge.sha256), so the API cannot change there without a new codegen run.
  • Here, tool/lepic.sh check runs 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.init refuses 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 LepicRuntime and gives each wallet its own SecretStore over the platform's secure storage (one store per wallet; the keys LEPIC writes have fixed names).
  • EpicWallet methods return Futures and Streams. events() streams balance, transaction, sync and transport changes; logs() streams redacted log records.
  • Every failure is a thrown WalletError with a stable numeric code. Codes never change meaning; message is English for logs and holds no secret.
  • A panic inside LEPIC arrives as a WalletError of kind panic; the runtime stays usable.
  • PIN unlock uses EpicWallet.createWithPin and EpicWallet.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.

TargetLibraryBuilt on
Android arm64-v8a, armeabi-v7a, x86_64liblepic_ffi.soLinux or macOS, with the Android NDK and cargo-ndk
iOS device and simulator, arm64liblepic_ffi.dylibmacOS with Xcode
Linux x86_64liblepic_ffi.soLinux x86_64
Windows x86_64lepic_ffi.dllWindows 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:

FileHoldsThe hook refuses
SHA256SUMSThe library's SHA-256A library not listed with that hash
SOURCEThe LEPIC commit, whether the tree was clean, the rustc versionA library from another commit than lepic.rev, or from a modified tree
FEATURESThe lepic-ffi features it was built withThe wrong build, when the tor user define is set
note

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_dir in the app's pubspec.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​

CommandWhat it runs
tool/lepic.sh testBuilds lepic-ffi with the scripted node (mock feature) for the host and runs flutter test on x86_64 Linux
tool/lepic.sh emulator-testBuilds 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.dartEnd 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.