cake_wallet/cw_lightning/lib/lightning_wallet.dart

421 lines
13 KiB
Dart
Raw Normal View History

2024-05-14 23:03:45 +00:00
import 'dart:async';
2024-02-16 20:37:08 +00:00
import 'dart:convert';
2024-02-08 19:45:21 +00:00
import 'dart:io';
2024-03-04 17:47:39 +00:00
import 'package:bitbox/bitbox.dart';
2024-02-23 17:29:11 +00:00
import 'package:bitcoin_base/bitcoin_base.dart';
2024-02-08 19:45:21 +00:00
import 'package:breez_sdk/breez_sdk.dart';
import 'package:breez_sdk/bridge_generated.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
2024-02-23 17:29:11 +00:00
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
2024-02-08 19:45:21 +00:00
import 'package:cw_core/crypto_currency.dart';
2024-02-16 20:37:08 +00:00
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
2024-02-22 03:03:19 +00:00
import 'package:cw_core/transaction_direction.dart';
2024-02-08 19:45:21 +00:00
import 'package:cw_core/unspent_coins_info.dart';
2024-02-16 20:37:08 +00:00
import 'package:cw_lightning/lightning_balance.dart';
2024-02-22 03:03:19 +00:00
import 'package:cw_lightning/lightning_transaction_info.dart';
2024-02-08 19:45:21 +00:00
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_core/wallet_info.dart';
2024-02-08 19:59:06 +00:00
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
2024-02-08 19:45:21 +00:00
import 'package:cw_lightning/.secrets.g.dart' as secrets;
2024-03-04 17:47:39 +00:00
import 'package:cw_bitcoin/electrum_wallet.dart';
2024-06-26 19:34:23 +00:00
import 'package:bip39/bip39.dart' as bip39;
2024-02-08 19:45:21 +00:00
2024-02-08 19:59:06 +00:00
part 'lightning_wallet.g.dart';
2024-02-08 19:45:21 +00:00
class LightningWallet = LightningWalletBase with _$LightningWallet;
2024-03-05 17:21:08 +00:00
abstract class LightningWalletBase extends ElectrumWallet with Store {
2024-03-04 17:47:39 +00:00
bool _isTransactionUpdating;
2024-02-16 20:37:08 +00:00
@override
@observable
SyncStatus syncStatus;
2024-02-15 22:27:05 +00:00
2024-02-23 17:29:11 +00:00
LightningWalletBase({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
LightningBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
2024-03-04 17:47:39 +00:00
}) : _isTransactionUpdating = false,
2024-02-16 20:37:08 +00:00
syncStatus = NotConnectedSyncStatus(),
2024-03-05 17:21:08 +00:00
_balance = ObservableMap<CryptoCurrency, LightningBalance>(),
2024-05-08 18:51:25 +00:00
mnemonic = mnemonic,
2024-03-04 17:47:39 +00:00
super(
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btcln,
) {
2024-03-05 17:21:08 +00:00
_balance[CryptoCurrency.btcln] =
initialBalance ?? LightningBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
2024-05-08 18:51:25 +00:00
String derivationPath = walletInfo.derivationInfo!.derivationPath!;
2024-05-03 18:37:23 +00:00
String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
2024-02-23 17:29:11 +00:00
walletAddresses = BitcoinWalletAddresses(
walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
2024-05-03 18:37:23 +00:00
mainHd: hd.derivePath(derivationPath),
sideHd: hd.derivePath(sideDerivationPath),
2024-03-04 17:47:39 +00:00
network: network,
2024-02-23 17:29:11 +00:00
);
2024-02-08 19:45:21 +00:00
2024-03-04 17:47:39 +00:00
// initialize breez:
2024-02-13 18:13:17 +00:00
try {
setupBreez(seedBytes);
} catch (e) {
print("Error initializing Breez: $e");
}
2024-02-08 19:45:21 +00:00
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
}
2024-03-05 17:21:08 +00:00
late final ObservableMap<CryptoCurrency, LightningBalance> _balance;
2024-05-14 23:03:45 +00:00
StreamSubscription<List<Payment>>? _paymentsSub;
StreamSubscription<NodeState?>? _nodeStateSub;
2024-06-27 19:29:00 +00:00
StreamSubscription<LogEntry>? _logStream;
2024-03-05 17:21:08 +00:00
@override
@computed
ObservableMap<CryptoCurrency, LightningBalance> get balance => _balance;
2024-03-04 17:47:39 +00:00
static Future<LightningWallet> create(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
LightningBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex}) async {
2024-06-26 19:34:23 +00:00
late final Uint8List seedBytes;
// electrum:
if (validateMnemonic(mnemonic)) {
seedBytes = await mnemonicToSeedBytes(mnemonic);
2024-06-26 19:34:23 +00:00
// bip39:
} else if (bip39.validateMnemonic(mnemonic)) {
seedBytes = await bip39.mnemonicToSeed(mnemonic);
2024-06-26 19:34:23 +00:00
} else {
throw Exception("Invalid mnemonic!");
}
2024-02-08 19:59:06 +00:00
return LightningWallet(
2024-02-23 17:29:11 +00:00
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
2024-03-04 17:47:39 +00:00
initialBalance: initialBalance,
2024-06-26 19:34:23 +00:00
seedBytes: seedBytes,
2024-02-23 17:29:11 +00:00
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
2024-03-04 17:47:39 +00:00
addressPageType: addressPageType,
2024-02-23 17:29:11 +00:00
);
2024-02-08 19:45:21 +00:00
}
2024-02-08 19:59:06 +00:00
static Future<LightningWallet> open({
2024-02-08 19:45:21 +00:00
required String name,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
2024-04-17 16:20:04 +00:00
final snp =
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, BitcoinNetwork.mainnet);
print("OPENING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
2024-02-08 19:59:06 +00:00
return LightningWallet(
2024-05-08 18:51:25 +00:00
mnemonic: snp.mnemonic!,
2024-02-23 17:29:11 +00:00
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
2024-03-04 18:34:46 +00:00
initialBalance: LightningBalance(
confirmed: snp.balance.confirmed,
unconfirmed: snp.balance.unconfirmed,
frozen: snp.balance.frozen,
),
2024-05-31 16:29:37 +00:00
seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
2024-02-23 17:29:11 +00:00
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
);
2024-02-08 19:45:21 +00:00
}
2024-05-30 18:02:57 +00:00
Future<void> _handleNodeState(NodeState? nodeState) async {
if (nodeState == null) return;
_balance[CryptoCurrency.btcln] = LightningBalance(
confirmed: nodeState.maxPayableMsat ~/ 1000,
unconfirmed: nodeState.maxReceivableMsat ~/ 1000,
frozen: 0,
);
}
Future<void> _handlePayments(List<Payment> payments) async {
_isTransactionUpdating = true;
final txs = convertToTxInfo(payments);
transactionHistory.addMany(txs);
_isTransactionUpdating = false;
}
2024-05-30 19:38:03 +00:00
@override
Future<void> renameWalletFiles(String newWalletName) async {
await stopBreez(true);
await super.renameWalletFiles(newWalletName);
2024-05-31 16:29:37 +00:00
await setupBreez(await Mnemonic.toSeed(mnemonic));
2024-05-30 19:38:03 +00:00
}
2024-06-27 19:29:00 +00:00
void _logSdkEntries(LogEntry entry) {
switch (entry.level) {
case "ERROR":
case "WARN":
case "INFO":
// case "DEBUG":
// case "TRACE":
2024-06-27 19:29:00 +00:00
print("BREEZ:${entry.level}: ${entry.line}");
break;
}
}
2024-02-13 18:13:17 +00:00
Future<void> setupBreez(Uint8List seedBytes) async {
2024-03-04 18:34:46 +00:00
final sdk = await BreezSDK();
2024-06-27 19:29:00 +00:00
_logStream?.cancel();
_logStream = sdk.logStream.listen(_logSdkEntries);
2024-02-16 20:37:08 +00:00
try {
2024-03-04 18:34:46 +00:00
if (!(await sdk.isInitialized())) {
sdk.initialize();
}
2024-02-16 20:37:08 +00:00
} catch (e) {
print("Error initializing Breez: $e");
2024-05-20 16:12:18 +00:00
return;
2024-02-16 20:37:08 +00:00
}
2024-02-08 19:45:21 +00:00
2024-04-17 16:20:04 +00:00
GreenlightCredentials greenlightCredentials = GreenlightCredentials(
2024-06-25 17:51:13 +00:00
developerKey: base64.decode(secrets.greenlightKey),
developerCert: base64.decode(secrets.greenlightCert),
2024-04-17 16:20:04 +00:00
);
2024-02-13 18:13:17 +00:00
NodeConfig breezNodeConfig = NodeConfig.greenlight(
config: GreenlightNodeConfig(
2024-04-17 16:20:04 +00:00
partnerCredentials: greenlightCredentials,
inviteCode: null,
2024-02-13 18:13:17 +00:00
),
);
Config breezConfig = await sdk.defaultConfig(
envType: EnvironmentType.Production,
apiKey: secrets.breezApiKey,
nodeConfig: breezNodeConfig,
);
2024-02-08 19:45:21 +00:00
2024-05-30 18:02:57 +00:00
String workingDir = await pathForWalletDir(name: walletInfo.name, type: type);
2024-05-31 16:29:37 +00:00
workingDir = "$workingDir/breez/";
2024-05-30 18:02:57 +00:00
2024-02-13 18:13:17 +00:00
new Directory(workingDir).createSync(recursive: true);
breezConfig = breezConfig.copyWith(workingDir: workingDir);
2024-05-02 21:09:28 +00:00
// disconnect if already connected
2024-02-16 20:37:08 +00:00
try {
2024-05-17 19:47:11 +00:00
if (await sdk.isInitialized()) {
await sdk.disconnect();
}
2024-05-14 23:03:45 +00:00
} catch (e, s) {
print("ERROR disconnecting from Breez: $e\n$s");
}
try {
await sdk.connect(
req: ConnectRequest(
config: breezConfig,
seed: seedBytes,
),
);
2024-05-20 16:12:18 +00:00
} catch (e, s) {
print("Error connecting to Breez: $e\n$s");
2024-02-16 20:37:08 +00:00
}
2024-02-13 18:13:17 +00:00
2024-05-30 20:49:32 +00:00
await _nodeStateSub?.cancel();
2024-05-14 23:03:45 +00:00
_nodeStateSub = sdk.nodeStateStream.listen((event) {
2024-05-30 18:02:57 +00:00
_handleNodeState(event);
2024-02-15 22:27:05 +00:00
});
2024-05-30 18:02:57 +00:00
await _handleNodeState(await sdk.nodeInfo());
2024-02-15 22:27:05 +00:00
2024-05-30 20:49:32 +00:00
await _paymentsSub?.cancel();
2024-05-30 18:02:57 +00:00
_paymentsSub = sdk.paymentsStream.listen((List<Payment> payments) {
_handlePayments(payments);
2024-02-22 17:32:06 +00:00
});
2024-05-30 18:02:57 +00:00
await _handlePayments(await sdk.listPayments(req: ListPaymentsRequest()));
2024-02-22 03:03:19 +00:00
2024-02-15 22:27:05 +00:00
print("initialized breez: ${(await sdk.isInitialized())}");
2024-02-08 19:45:21 +00:00
}
2024-02-16 20:37:08 +00:00
2024-05-23 19:20:22 +00:00
Future<void> stopBreez(bool disconnect) async {
if (disconnect) {
final sdk = await BreezSDK();
if (await sdk.isInitialized()) {
await sdk.disconnect();
}
2024-05-17 19:47:11 +00:00
}
2024-05-14 23:03:45 +00:00
await _nodeStateSub?.cancel();
await _paymentsSub?.cancel();
}
2024-02-16 20:37:08 +00:00
@action
@override
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
2024-02-22 03:03:19 +00:00
await updateTransactions();
2024-02-16 20:37:08 +00:00
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
rethrow;
}
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@action
@override
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
2024-02-22 03:03:19 +00:00
await updateTransactions();
2024-02-16 20:37:08 +00:00
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
throw UnimplementedError("createTransaction");
}
2024-02-22 03:03:19 +00:00
Future<bool> updateTransactions() async {
try {
if (_isTransactionUpdating) {
return false;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
return true;
} catch (_) {
_isTransactionUpdating = false;
return false;
}
}
2024-03-04 18:34:46 +00:00
Map<String, LightningTransactionInfo> convertToTxInfo(List<Payment> payments) {
Map<String, LightningTransactionInfo> transactions = {};
2024-02-22 03:03:19 +00:00
2024-02-22 17:47:29 +00:00
for (Payment tx in payments) {
if (tx.paymentType == PaymentType.ClosedChannel) {
continue;
2024-02-22 03:03:19 +00:00
}
2024-02-22 17:47:29 +00:00
bool isSend = tx.paymentType == PaymentType.Sent;
2024-03-04 18:34:46 +00:00
transactions[tx.id] = LightningTransactionInfo(
2024-02-22 03:03:19 +00:00
isPending: false,
id: tx.id,
amount: tx.amountMsat ~/ 1000,
2024-03-01 17:55:44 +00:00
fee: tx.feeMsat ~/ 1000,
2024-02-22 03:03:19 +00:00
date: DateTime.fromMillisecondsSinceEpoch(tx.paymentTime * 1000),
direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
);
}
2024-02-22 17:32:06 +00:00
return transactions;
}
@override
2024-03-04 18:34:46 +00:00
Future<Map<String, LightningTransactionInfo>> fetchTransactions() async {
2024-02-22 17:32:06 +00:00
final sdk = await BreezSDK();
final payments = await sdk.listPayments(req: ListPaymentsRequest());
final transactions = convertToTxInfo(payments);
2024-02-22 03:03:19 +00:00
2024-02-22 17:32:06 +00:00
return transactions;
2024-02-16 20:37:08 +00:00
}
@override
2024-05-29 23:40:57 +00:00
Future<void> rescan({
required int height,
int? chainTip,
ScanData? scanData,
bool? doSingleScan,
2024-06-13 18:19:38 +00:00
bool? usingElectrs,
2024-05-29 23:40:57 +00:00
}) async {
2024-02-22 03:03:19 +00:00
updateTransactions();
}
2024-02-16 20:37:08 +00:00
Future<void> init() async {
await walletAddresses.init();
await transactionHistory.init();
await save();
}
String toJSON() => json.encode({
'mnemonic': mnemonic,
2024-02-23 17:29:11 +00:00
'account_index': walletAddresses.currentReceiveAddressIndexByType,
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
'address_page_type': walletInfo.addressPageType == null
? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(),
'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet',
2024-02-16 20:37:08 +00:00
});
2024-02-27 19:26:15 +00:00
Future<void> updateBalance() async {
// balance is updated automatically
}
2024-05-08 18:51:25 +00:00
String mnemonic;
2024-02-16 20:37:08 +00:00
@override
String get seed => mnemonic;
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
2024-05-14 23:03:45 +00:00
@override
2024-05-23 19:20:22 +00:00
Future<void> close({bool? switchingToSameWalletType}) async {
2024-05-14 23:03:45 +00:00
try {
await electrumClient.close();
} catch (_) {}
try {
2024-05-23 19:20:22 +00:00
bool shouldDisconnect = switchingToSameWalletType == null || !switchingToSameWalletType;
await stopBreez(shouldDisconnect);
2024-05-14 23:03:45 +00:00
} catch (e, s) {
print("Error stopping breez: $e\n$s");
}
}
2024-02-08 19:45:21 +00:00
}