lightening_wallet 0.4.0
lightening_wallet: ^0.4.0 copied to clipboard
High-performance Lightning wallet for Flutter using Rust FFI with LDK-node. Supports Bitcoin on-chain and Lightning Network payments.
Flutter Lightning Wallet (Rust FFI) #
A high-performance Lightning wallet library for Flutter, built with Rust and using dart:ffi for native bindings. This library provides Bitcoin and Lightning Network functionality through a unified Dart API.
Features #
- High Performance: All wallet logic runs in native Rust code
- Bitcoin Wallet: BIP39/BIP32 HD wallet with native address derivation
- Lightning Network: LDK-node integration for Lightning payments
- UTXO Management: Native UTXO tracking with caching
- Transaction Building: Coin selection and transaction signing
- Payment History: SQLite-based payment database
- Event Streaming: Real-time wallet events
- Cross-platform: Supports Android and iOS
Architecture #
lightening_wallet/
├── rust/ # Rust core library
│ ├── src/
│ │ ├── lib.rs # C FFI bindings
│ │ ├── coordinator.rs # Main orchestrator
│ │ ├── wallet/ # Bitcoin wallet logic
│ │ ├── ldk/ # LDK-node integration
│ │ ├── storage/ # SQLite & caching
│ │ └── events.rs # Event system
│ └── Cargo.toml
├── lib/ # Dart API
│ ├── lightening_wallet.dart # Main export
│ └── src/
│ ├── bindings.dart # FFI bindings
│ ├── lightning_wallet.dart # High-level API
│ └── models.dart # Data models
├── android/ # Android plugin config
├── ios/ # iOS plugin config & headers
└── scripts/ # Build scripts
Installation #
Prerequisites #
- Flutter 3.10+
- Rust 1.70+ with
cargo - Android NDK (if building for Android)
- Xcode (if building for iOS)
cargo-ndkfor Android builds:cargo install cargo-ndk
Add to your project #
Add to your pubspec.yaml:
dependencies:
lightening_wallet:
path: /path/to/lightening_wallet
Or if published to pub.dev:
dependencies:
lightening_wallet: ^0.1.42
Build the native library #
Before using the plugin, you need to build the Rust library for your target platforms.
Android
# Using the build script (recommended)
./scripts/build-android.sh
# Or manually with cargo-ndk
cd rust
cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -t x86 \
-o ../android/src/main/jniLibs build --release
Requirements:
- Android NDK installed (set
ANDROID_NDK_HOMEenvironment variable) cargo-ndkinstalled:cargo install cargo-ndk- Rust Android targets:
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
iOS
# Using the build script (recommended)
./scripts/build-ios.sh
This will:
- Build for iOS device (aarch64-apple-ios)
- Build for iOS simulators (x86_64-apple-ios, aarch64-apple-ios-sim)
- Create a universal XCFramework
Requirements:
- Xcode with command line tools
- Rust iOS targets:
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
Usage #
Initialize the library #
import 'package:lightening_wallet/lightening_wallet.dart';
void main() {
// Initialize the native library (required before any other calls)
LightningWallet.initialize();
runApp(MyApp());
}
Generate a new wallet #
// Generate a new BIP39 mnemonic
final result = LightningWallet.generateMnemonic();
if (result.success) {
final mnemonic = result.data!;
print('Mnemonic: $mnemonic');
}
Initialize wallet #
final initResult = LightningWallet.initializeWallet(
userId: 'user123',
mnemonic: mnemonic,
network: BitcoinNetwork.testnet,
dbPath: '/path/to/wallet/data',
);
if (initResult.success) {
print('Wallet initialized!');
} else {
print('Error: ${initResult.error}');
}
Get balance #
final balanceResult = LightningWallet.getBalance('user123');
if (balanceResult.success) {
final balance = balanceResult.data!;
print('On-chain confirmed: ${balance.onchainConfirmed} sats');
print('Lightning balance: ${balance.lightningBalance} sats');
print('Total: ${balance.total} sats');
}
Sync wallet #
final syncResult = LightningWallet.sync('user123');
if (syncResult.success) {
print('Wallet synced!');
}
Get receiving address #
final addressResult = LightningWallet.getReceivingAddress('user123');
if (addressResult.success) {
print('Receive to: ${addressResult.data}');
}
List payment history #
final paymentsResult = LightningWallet.listPayments(
'user123',
limit: 10,
offset: 0,
);
if (paymentsResult.success) {
for (final payment in paymentsResult.data!) {
print('${payment.paymentType}: ${payment.amountSats} sats');
}
}
Poll for events #
final eventsResult = LightningWallet.getEvents('user123');
if (eventsResult.success) {
for (final event in eventsResult.data!) {
switch (event.eventType) {
case 'BalanceUpdated':
print('Balance updated!');
break;
case 'PaymentReceived':
print('Payment received!');
break;
case 'SyncCompleted':
print('Sync completed!');
break;
}
}
}
Disconnect wallet #
LightningWallet.disconnect('user123');
API Reference #
Dart API (LightningWallet) #
Wallet Initialization & Management
static void initialize()
Initialize the native library. Must be called before any other methods.
static WalletResponse<String> generateMnemonic()
Generate a new 24-word BIP39 mnemonic phrase using cryptographically secure randomness.
Returns: A space-separated string of 24 BIP39 words.
static WalletResponse<void> initializeWallet({required String userId, required String mnemonic, required BitcoinNetwork network, required String dbPath})
Initialize a wallet instance with mnemonic and network configuration.
Parameters:
userId- Unique identifier for this wallet instance (supports multiple concurrent wallets)mnemonic- 24-word BIP39 mnemonic phrasenetwork- Target network (BitcoinNetwork.bitcoinfor mainnet,BitcoinNetwork.testnetfor testnet)dbPath- Path to store wallet data (SQLite database, LDK state)
static WalletResponse<WalletBalance> getBalance(String userId)
Get the current wallet balance including on-chain and Lightning funds.
Returns: WalletBalance with onchainConfirmed, onchainUnconfirmed, lightningBalance, and total (all in satoshis).
static WalletResponse<void> sync(String userId)
Sync the wallet with the blockchain. Updates on-chain balance, processes Lightning events, and refreshes channel states.
static WalletResponse<String> getReceivingAddress(String userId)
Generate a fresh on-chain receiving address (BIP84 native SegWit).
Returns: A bech32 Bitcoin address.
static WalletResponse<List<Payment>> listPayments(String userId, {int? limit, int? offset})
List payment history with optional pagination. Returns both Lightning and on-chain transactions.
Parameters:
limit- Maximum number of payments to return (optional)offset- Number of payments to skip for pagination (optional)
static WalletResponse<List<WalletEvent>> getEvents(String userId)
Drain pending wallet events from the event queue. Events are removed after being returned.
Returns: List of WalletEvent objects representing balance updates, payment notifications, sync status, etc.
static WalletResponse<void> disconnect(String userId)
Disconnect and cleanup the wallet. Stops the Lightning node and releases resources.
Peer Management
static WalletResponse<void> connectPeer(String userId, {required String nodeId, required String address, required int port})
Connect to a Lightning Network peer.
Parameters:
nodeId- 33-byte hex-encoded public key of the peeraddress- IP address or hostname of the peerport- TCP port (typically 9735)
static WalletResponse<void> disconnectPeer(String userId, {required String nodeId})
Disconnect from a Lightning Network peer.
static WalletResponse<List<PeerInfo>> listPeers(String userId)
List all connected peers.
Returns: List of PeerInfo with nodeId, address, port, and isConnected status.
Channel Management
static WalletResponse<String> openChannel(String userId, {required String counterpartyNodeId, required int channelValueSats, int pushMsat = 0, String? peerAddress, int? peerPort})
Open a new Lightning channel with a peer.
Parameters:
counterpartyNodeId- 33-byte hex-encoded public key of the channel partnerchannelValueSats- Total channel capacity in satoshispushMsat- Amount (in millisatoshis) to push to the counterparty on open (default: 0)peerAddress- Optional peer address if not already connectedpeerPort- Optional peer port if not already connected
Returns: Hex-encoded channel ID.
static WalletResponse<void> closeChannel(String userId, {required String channelId, bool force = false})
Close a Lightning channel.
Parameters:
channelId- Hex-encoded channel ID to closeforce- If true, force close the channel unilaterally (use only if peer is unresponsive)
static WalletResponse<List<ChannelInfo>> listChannels(String userId)
List all Lightning channels.
Returns: List of ChannelInfo with detailed channel state including:
channelId- Unique channel identifiercounterpartyNodeId- Peer's public keychannelValueSats- Total channel capacitybalanceSats- Local balanceoutboundCapacitySats- Available outbound liquidityinboundCapacitySats- Available inbound liquidityisUsable- Whether channel can route paymentsisPublic- Whether channel is announced to networkisReady- Whether channel is fully confirmedisClosing- Whether channel is being closedconfirmationsRequired- Block confirmations needed (if pending)
Invoice Management
static WalletResponse<InvoiceInfo> createInvoice(String userId, {int? amountSats, String? description, int expirySecs = 3600})
Create a BOLT11 Lightning invoice.
Parameters:
amountSats- Invoice amount in satoshis (optional for "any amount" invoices)description- Human-readable description (optional)expirySecs- Invoice expiry time in seconds (default: 3600 = 1 hour)
Returns: InvoiceInfo with:
bolt11- BOLT11-encoded invoice stringpaymentHash- Unique payment identifieramountSats- Invoice amount (if specified)description- Invoice descriptioncreatedAt- Unix timestamp of creationexpiresAt- Unix timestamp of expiry
Payment Operations
static WalletResponse<PaymentInfo> payInvoice(String userId, {required String bolt11, int? amountSats})
Pay a BOLT11 Lightning invoice.
Parameters:
bolt11- BOLT11-encoded invoice stringamountSats- Amount to pay (required for "any amount" invoices, optional otherwise)
Returns: PaymentInfo with payment status and details.
static WalletResponse<PaymentInfo> sendKeysend(String userId, {required String destinationPubkey, required int amountSats, Map<int, List<int>>? customRecords})
Send a spontaneous keysend payment (no invoice required).
Parameters:
destinationPubkey- 33-byte hex-encoded destination public keyamountSats- Amount to send in satoshiscustomRecords- Optional TLV custom records (for Podcasting 2.0, etc.)
Returns: PaymentInfo with payment status.
Podcasting 2.0 Support: Use custom TLV records for value-for-value streaming payments:
// Standard TLV types for Podcasting 2.0
const podcastTlv = 7629169; // Podcast name
const episodeTlv = 7629171; // Episode GUID
const actionTlv = 7629173; // Action (stream/boost)
const timestampTlv = 7629175; // Timestamp in episode
const appNameTlv = 7629177; // App name
static WalletResponse<String> sendOnchain(String userId, {required String address, required int amountSats})
Send an on-chain Bitcoin transaction.
Parameters:
address- Destination Bitcoin addressamountSats- Amount to send in satoshis
Returns: Transaction ID (txid) as hex string.
static WalletResponse<String> getNodeId(String userId)
Get this wallet's Lightning node public key.
Returns: 33-byte hex-encoded public key.
Types #
WalletResponse #
Generic response wrapper for all API calls.
class WalletResponse<T> {
final bool success; // Whether the operation succeeded
final T? data; // Result data (if success)
final String? error; // Error message (if failed)
}
WalletBalance #
Comprehensive balance information across on-chain and Lightning.
class WalletBalance {
final int onchainConfirmed; // Confirmed on-chain balance (sats)
final int onchainUnconfirmed; // Unconfirmed on-chain balance (sats)
final int lightningBalance; // Total Lightning channel balance (sats)
final int total; // Sum of all balances (sats)
}
Payment #
Payment record for both Lightning and on-chain transactions.
class Payment {
final int? id; // Database ID
final String paymentHash; // Unique payment identifier
final String paymentType; // 'sent', 'received', 'onchain_sent', 'onchain_received'
final int amountSats; // Payment amount in satoshis
final int? feeSats; // Fee paid (if applicable)
final String status; // 'pending', 'completed', 'failed'
final int timestamp; // Unix timestamp
final String? description; // Payment description/memo
final String? destination; // Destination node ID or address
final String? txid; // On-chain transaction ID
final String? preimage; // Lightning payment preimage
final String? bolt11; // Original BOLT11 invoice
}
ChannelInfo #
Detailed Lightning channel state information.
class ChannelInfo {
final String channelId; // Unique channel identifier
final String counterpartyNodeId; // Peer's public key
final int channelValueSats; // Total channel capacity
final int balanceSats; // Local balance
final int outboundCapacitySats; // Available for sending
final int inboundCapacitySats; // Available for receiving
final bool isUsable; // Can route payments
final bool isPublic; // Announced to network
final bool isReady; // Fully confirmed
final bool isClosing; // Being closed
final int? confirmationsRequired; // Blocks until ready
}
PeerInfo #
Connected Lightning peer information.
class PeerInfo {
final String nodeId; // Peer's public key
final String? address; // IP address or hostname
final int? port; // TCP port
final bool isConnected; // Connection status
}
InvoiceInfo #
BOLT11 invoice details.
class InvoiceInfo {
final String bolt11; // BOLT11-encoded invoice
final String paymentHash; // Unique payment identifier
final int? amountSats; // Invoice amount (null for "any amount")
final String? description; // Invoice description
final int createdAt; // Unix timestamp of creation
final int expiresAt; // Unix timestamp of expiry
}
PaymentInfo #
Payment result information.
class PaymentInfo {
final String paymentHash; // Unique payment identifier
final String paymentType; // 'sent', 'received', 'onchain_sent', 'onchain_received'
final int amountSats; // Payment amount
final int? feeSats; // Fee paid
final String status; // 'pending', 'completed', 'failed'
final int timestamp; // Unix timestamp
final String? description; // Payment description
final String? destination; // Destination node/address
final String? preimage; // Payment preimage (proof of payment)
final String? bolt11; // Original invoice
}
WalletEvent #
Real-time wallet event types.
// Event types returned by getEvents()
enum WalletEventType {
balanceUpdated, // Balance changed
paymentReceived, // Incoming payment completed
paymentSent, // Outgoing payment completed
paymentFailed, // Payment failed
channelOpened, // New channel opened
channelClosed, // Channel closed
syncStarted, // Blockchain sync started
syncCompleted, // Blockchain sync completed
syncFailed, // Blockchain sync failed
transactionConfirmed, // On-chain tx confirmed
error, // General error
}
BitcoinNetwork #
Supported Bitcoin networks.
enum BitcoinNetwork {
bitcoin, // Mainnet (real funds)
testnet, // Testnet (test funds)
}
Rust FFI Reference #
The native Rust library exposes C-compatible FFI functions. All functions return JSON strings with the format:
{"success": true, "data": {...}}
// or
{"success": false, "error": "error message"}
Core Functions #
| C Function | Parameters | Description |
|---|---|---|
wallet_initialize |
user_id, mnemonic, network, db_path |
Initialize wallet instance |
wallet_generate_mnemonic |
None | Generate 24-word BIP39 mnemonic |
wallet_get_balance |
user_id |
Get wallet balance |
wallet_sync |
user_id |
Sync with blockchain |
wallet_get_receiving_address |
user_id |
Get new receiving address |
wallet_list_payments |
user_id, limit, offset |
List payment history |
wallet_get_events |
user_id |
Drain pending events |
wallet_disconnect |
user_id |
Disconnect and cleanup |
wallet_free_string |
ptr |
Free allocated string memory |
Peer Functions #
| C Function | Parameters | Description |
|---|---|---|
wallet_connect_peer |
user_id, node_id, address, port |
Connect to peer |
wallet_disconnect_peer |
user_id, node_id |
Disconnect from peer |
wallet_list_peers |
user_id |
List connected peers |
Channel Functions #
| C Function | Parameters | Description |
|---|---|---|
wallet_open_channel |
user_id, counterparty_node_id, channel_value_sats, push_msat, peer_address, peer_port |
Open channel |
wallet_close_channel |
user_id, channel_id, force |
Close channel |
wallet_list_channels |
user_id |
List all channels |
Invoice Functions #
| C Function | Parameters | Description |
|---|---|---|
wallet_create_invoice |
user_id, amount_sats, description, expiry_secs |
Create BOLT11 invoice |
Payment Functions #
| C Function | Parameters | Description |
|---|---|---|
wallet_pay_invoice |
user_id, bolt11, amount_sats |
Pay BOLT11 invoice |
wallet_send_keysend |
user_id, destination_pubkey, amount_sats, custom_records_json |
Send keysend payment |
wallet_send_onchain |
user_id, address, amount_sats |
Send on-chain transaction |
wallet_get_node_id |
user_id |
Get node public key |
Android JNI Functions #
For Android, the library also exports JNI-compatible functions:
| JNI Function | Equivalent C Function |
|---|---|
Java_..._nativeInitialize |
wallet_initialize |
Java_..._nativeGenerateMnemonic |
wallet_generate_mnemonic |
Java_..._nativeGetBalance |
wallet_get_balance |
Java_..._nativeSyncWallet |
wallet_sync |
Java_..._nativeGetReceivingAddress |
wallet_get_receiving_address |
Java_..._nativeListPayments |
wallet_list_payments |
Java_..._nativeGetEvents |
wallet_get_events |
Java_..._nativeDisconnect |
wallet_disconnect |
Rust Module Reference #
WalletCoordinator (coordinator.rs) #
Main orchestrator that manages wallet components.
impl WalletCoordinator {
pub fn new(db_path: &str) -> Result<Self, CoordinatorError>;
pub fn initialize(&self, mnemonic: &str, network: BitcoinNetwork) -> Result<(), CoordinatorError>;
pub fn sync(&self) -> Result<(), CoordinatorError>;
pub fn get_lightning_node(&self) -> Arc<LightningNode>;
pub fn get_database(&self) -> Arc<Database>;
pub fn get_event_emitter(&self) -> Arc<EventEmitter>;
pub fn disconnect(&self) -> Result<(), CoordinatorError>;
}
KeyManager (wallet/keys.rs) #
BIP39/BIP32 HD wallet key management.
impl KeyManager {
pub fn from_mnemonic(mnemonic: &str, network: Network) -> Result<Self, KeyError>;
pub fn generate_mnemonic() -> String;
pub fn get_lightning_seed(&self) -> Result<[u8; 32], KeyError>; // Derives at m/535'/0'
}
LightningNode (ldk/node.rs) #
LDK-node wrapper for Lightning Network operations.
impl LightningNode {
// Lifecycle
pub fn new() -> Self;
pub fn initialize(&self, entropy: [u8; 32], network: Network, storage_path: &str, esplora_server: Option<&str>) -> Result<(), LightningError>;
pub fn start(&self) -> Result<(), LightningError>;
pub fn stop(&self) -> Result<(), LightningError>;
pub fn sync_wallets(&self) -> Result<(), LightningError>;
// Peer Management
pub fn connect_peer(&self, params: ConnectPeerParams) -> Result<(), LightningError>;
pub fn disconnect_peer(&self, node_id: &str) -> Result<(), LightningError>;
pub fn list_peers(&self) -> Result<Vec<PeerInfo>, LightningError>;
pub fn get_node_id(&self) -> Result<String, LightningError>;
// Channel Management
pub fn open_channel(&self, params: OpenChannelParams) -> Result<String, LightningError>;
pub fn close_channel(&self, params: CloseChannelParams) -> Result<(), LightningError>;
pub fn list_channels(&self) -> Result<Vec<ChannelInfo>, LightningError>;
// Invoices
pub fn create_invoice(&self, params: CreateInvoiceParams) -> Result<InvoiceInfo, LightningError>;
// Payments
pub fn pay_invoice(&self, params: PayInvoiceParams) -> Result<PaymentInfo, LightningError>;
pub fn send_keysend(&self, params: KeysendParams) -> Result<PaymentInfo, LightningError>;
pub fn send_onchain(&self, params: SendOnChainParams) -> Result<String, LightningError>;
pub fn list_payments(&self) -> Result<Vec<PaymentInfo>, LightningError>;
// Balance
pub fn get_onchain_address(&self) -> Result<String, LightningError>;
pub fn get_spendable_onchain_balance(&self) -> Result<u64, LightningError>;
pub fn get_total_onchain_balance(&self) -> Result<u64, LightningError>;
}
Database (storage/db.rs) #
SQLite storage for payment history.
impl Database {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, DatabaseError>;
pub fn list_payments(&self, limit: Option<usize>, offset: Option<usize>) -> Result<Vec<Payment>, DatabaseError>;
}
EventEmitter (events.rs) #
Bounded event queue for real-time notifications.
impl EventEmitter {
pub fn new(capacity: usize) -> Self;
pub fn emit(&self, event: WalletEvent);
pub fn drain_events(&self) -> Vec<WalletEvent>;
}
Podcasting20Builder (ldk/podcasting.rs) #
Fluent builder for Podcasting 2.0 value-for-value TLV records.
impl Podcasting20Builder {
pub fn new() -> Self;
pub fn podcast(self, name: &str) -> Self; // TLV 7629169
pub fn episode(self, guid: &str) -> Self; // TLV 7629171
pub fn action(self, action: &str) -> Self; // TLV 7629173
pub fn timestamp(self, timestamp: u64) -> Self; // TLV 7629175
pub fn app_name(self, name: &str) -> Self; // TLV 7629177
pub fn custom(self, key: u64, value: &str) -> Self;
pub fn custom_bytes(self, key: u64, value: Vec<u8>) -> Self;
pub fn build(self) -> HashMap<u64, Vec<u8>>;
}
// Usage example:
let custom_records = Podcasting20Builder::new()
.podcast("My Podcast")
.episode("episode-guid-123")
.action("stream")
.app_name("MyApp")
.build();
Error Types #
CoordinatorError #
pub enum CoordinatorError {
AlreadyInitialized, // Wallet already initialized
KeyError(String), // Key derivation/management error
StorageError(String), // Database/storage error
LightningError(String), // Lightning node error
}
KeyError #
pub enum KeyError {
InvalidMnemonic(String), // Invalid BIP39 mnemonic
Derivation(String), // BIP32 derivation failed
InvalidNetwork(String), // Network mismatch
}
LightningError #
pub enum LightningError {
NotInitialized, // Node not initialized
AlreadyInitialized, // Node already initialized
LdkError(String), // LDK-node error
NetworkError(String), // Network communication error
PeerError(String), // Peer connection error
ChannelError(String), // Channel operation error
PaymentError(String), // Payment failed
InvoiceError(String), // Invoice creation/parsing error
StorageError(String), // Storage error
}
DatabaseError #
pub enum DatabaseError {
Connection(String), // Database connection failed
Query(String), // Query execution error
NotFound(String), // Record not found
}
Development #
Run Rust tests #
cd rust
cargo test
Build Rust library (debug) #
cd rust
cargo build
Format Rust code #
cd rust
cargo fmt
Lint Rust code #
cd rust
cargo clippy
Regenerate FFI bindings #
If you modify the C header file, regenerate bindings:
dart run ffigen
Troubleshooting #
Android: Library not found #
Make sure you've built the native library and the .so files are in android/src/main/jniLibs/:
android/src/main/jniLibs/
├── arm64-v8a/liblightening_wallet.so
├── armeabi-v7a/liblightening_wallet.so
├── x86_64/liblightening_wallet.so
└── x86/liblightening_wallet.so
iOS: Framework not found #
Ensure the XCFramework was built correctly:
./scripts/build-ios.sh
Check that ios/LighteningWallet.xcframework exists.
Platform not supported #
The library only supports Android and iOS. Desktop platforms are not currently supported.
License #
MIT
Credits #
This library integrates and builds upon: