This commit is contained in:
fosse 2024-02-16 15:37:08 -05:00
parent 3d65ccffa2
commit 263044f39b
5 changed files with 326 additions and 99 deletions

View file

@ -0,0 +1,40 @@
import 'dart:convert';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/balance.dart';
class LightningBalance extends Balance {
const LightningBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
: super(confirmed, unconfirmed);
static LightningBalance? fromJSON(String? jsonSource) {
if (jsonSource == null) {
return null;
}
final decoded = json.decode(jsonSource) as Map;
return LightningBalance(
confirmed: decoded['confirmed'] as int? ?? 0,
unconfirmed: decoded['unconfirmed'] as int? ?? 0,
frozen: decoded['frozen'] as int? ?? 0);
}
final int confirmed;
final int unconfirmed;
final int frozen;
@override
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
@override
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
@override
String get formattedUnAvailableBalance {
final frozenFormatted = bitcoinAmountToString(amount: frozen);
return frozenFormatted == '0.0' ? '' : frozenFormatted;
}
String toJSON() =>
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
}

View file

@ -1,10 +1,24 @@
import 'dart:convert';
import 'dart:io';
import 'package:breez_sdk/breez_sdk.dart';
import 'package:breez_sdk/bridge_generated.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/crypto_currency.dart';
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';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_lightning/lightning_balance.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
@ -13,17 +27,40 @@ import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cw_lightning/.secrets.g.dart' as secrets;
import 'package:cw_core/wallet_base.dart';
part 'lightning_wallet.g.dart';
class LightningWallet = LightningWalletBase with _$LightningWallet;
abstract class LightningWalletBase extends ElectrumWallet with Store {
int satBalance = 0;
// abstract class LightningWalletBase extends ElectrumWallet with Store {
class LightningWalletBase
extends WalletBase<LightningBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
with Store {
final bitcoin.HDWallet hd;
final String mnemonic;
String _password;
late ElectrumClient electrumClient;
@override
@observable
late ObservableMap<CryptoCurrency, LightningBalance> balance;
@override
late ElectrumWalletAddresses walletAddresses;
bitcoin.NetworkType networkType = bitcoin.bitcoin;
@override
BitcoinWalletKeys get keys =>
BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
@override
@observable
SyncStatus syncStatus;
LightningWalletBase(
{required String mnemonic,
@ -31,22 +68,23 @@ abstract class LightningWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
ElectrumClient? electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
LightningBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc) {
: hd = bitcoin.HDWallet.fromSeed(seedBytes, network: bitcoin.bitcoin).derivePath("m/0'/0"),
syncStatus = NotConnectedSyncStatus(),
mnemonic = mnemonic,
_password = password,
balance = ObservableMap<CryptoCurrency, LightningBalance>.of({
CryptoCurrency.btc:
initialBalance ?? const LightningBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
}),
super(walletInfo) {
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
walletAddresses = BitcoinWalletAddresses(walletInfo,
electrumClient: electrumClient,
electrumClient: electrumClient ?? ElectrumClient(),
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
@ -54,6 +92,8 @@ abstract class LightningWalletBase extends ElectrumWallet with Store {
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
networkType: networkType);
this.electrumClient = electrumClient ?? ElectrumClient();
// initialize breeze:
try {
setupBreez(seedBytes);
@ -72,7 +112,6 @@ abstract class LightningWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0}) async {
return LightningWallet(
@ -81,7 +120,6 @@ abstract class LightningWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex);
@ -100,7 +138,6 @@ abstract class LightningWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
@ -109,7 +146,11 @@ abstract class LightningWalletBase extends ElectrumWallet with Store {
Future<void> setupBreez(Uint8List seedBytes) async {
// Initialize SDK logs listener
final sdk = BreezSDK();
sdk.initialize();
try {
sdk.initialize();
} catch (e) {
print("Error initializing Breez: $e");
}
NodeConfig breezNodeConfig = NodeConfig.greenlight(
config: GreenlightNodeConfig(
@ -128,20 +169,162 @@ abstract class LightningWalletBase extends ElectrumWallet with Store {
workingDir = "$workingDir/wallets/lightning/${walletInfo.name}/breez/";
new Directory(workingDir).createSync(recursive: true);
breezConfig = breezConfig.copyWith(workingDir: workingDir);
await sdk.connect(config: breezConfig, seed: seedBytes);
try {
await sdk.disconnect();
await sdk.connect(config: breezConfig, seed: seedBytes);
} catch (e) {
print("Error connecting to Breez: $e");
}
sdk.nodeStateStream.listen((event) {
// print("Node state: $event");
print("Node state: $event");
if (event == null) return;
// int balanceSat = event.maxPayableMsat ~/ 1000;
// print("sats: $balanceSat");
balance[CryptoCurrency.btc] = ElectrumBalance(
confirmed: event.maxPayableMsat,
unconfirmed: event.maxReceivableMsat,
int balanceSat = event.maxPayableMsat ~/ 1000;
print("sats: $balanceSat");
balance[CryptoCurrency.btc] = LightningBalance(
confirmed: event.maxPayableMsat ~/ 1000,
unconfirmed: event.maxReceivableMsat ~/ 1000,
frozen: 0,
);
});
print("initialized breez: ${(await sdk.isInitialized())}");
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
throw UnimplementedError("calculateEstimatedFee");
}
@action
@override
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
// TODO: CW-563 Implement sync
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
rethrow;
}
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@override
void close() {}
@action
@override
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
throw UnimplementedError("createTransaction");
}
@override
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
// String address = _publicAddress!;
// final transactions = await _client.fetchTransactions(address);
// final Map<String, NanoTransactionInfo> result = {};
// for (var transactionModel in transactions) {
// final bool isSend = transactionModel.type == "send";
// result[transactionModel.hash] = NanoTransactionInfo(
// id: transactionModel.hash,
// amountRaw: transactionModel.amount,
// height: transactionModel.height,
// direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
// confirmed: transactionModel.confirmed,
// date: transactionModel.date ?? DateTime.now(),
// confirmations: transactionModel.confirmed ? 1 : 0,
// to: isSend ? transactionModel.account : address,
// from: isSend ? address : transactionModel.account,
// );
// }
// return result;
return {};
}
@override
Future<void> rescan({required int height}) async => throw UnimplementedError();
Future<void> init() async {
await walletAddresses.init();
await transactionHistory.init();
await save();
}
String toJSON() => json.encode({
'mnemonic': mnemonic,
'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance[currency]?.toJSON()
});
@override
Future<void> save() async {
final path = await makePath();
await write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
}
Future<void> updateBalance() async {
// balance[currency] = await _fetchBalances();
await save();
}
@override
String get seed => mnemonic;
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
// String toJSON() => json.encode({
// 'seedKey': _hexSeed,
// 'mnemonic': _mnemonic,
// 'currentBalance': balance[currency]?.currentBalance.toString() ?? "0",
// 'receivableBalance': balance[currency]?.receivableBalance.toString() ?? "0",
// 'derivationType': _derivationType.toString()
// });
@override
Future<void> renameWalletFiles(String newWalletName) async {
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
final currentWalletFile = File(currentWalletPath);
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
// Copies current wallet files into new wallet name's dir and files
if (currentWalletFile.existsSync()) {
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
await currentWalletFile.copy(newWalletPath);
}
if (currentTransactionsFile.existsSync()) {
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
}
// Delete old name's dir and files
await Directory(currentDirPath).delete(recursive: true);
}
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/src/screens/receive/widgets/lightning_input_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
@ -13,11 +14,13 @@ import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart'
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/lightning_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/lightning_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@ -190,8 +193,6 @@ class LightningInvoicePage extends BasePage {
text: S.of(context).create_invoice,
onPressed: () {
FocusScope.of(context).unfocus();
lightningViewModel.createInvoice(
amount: _amountController.text, description: _descriptionController.text);
lightningInvoicePageViewModel.setRequestParams(
inputAmount: _amountController.text,
inputDescription: _descriptionController.text,
@ -233,24 +234,30 @@ class LightningInvoicePage extends BasePage {
});
reaction((_) => lightningInvoicePageViewModel.state, (ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
// Navigator.pushNamed(context, Routes.anonPayReceivePage, arguments: state.payload);
lightningViewModel.createInvoice(
amount: state.payload["amount"] as String,
description: state.payload["description"] as String?,
);
}
// if (state is ExecutedSuccessfullyState) {
// // Navigator.pushNamed(context, Routes.anonPayReceivePage, arguments: state.payload);
// lightningViewModel.createInvoice(
// amount: state.payload["amount"] as String,
// description: state.payload["description"] as String?,
// );
// }
if (state is ExecutedSuccessfullyState) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
// alertTitle: S.of(context).invoice_created,
alertTitle: "Invoice created TODO",
alertContent: state.payload as String,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
return AlertWithTwoActions(
// alertTitle: S.of(context).invoice_created,
alertTitle: "Invoice created TODO: CW-563",
alertContent: state.payload as String,
rightButtonText: S.of(context).ok,
actionRightButton: () => Navigator.of(context).pop(),
actionLeftButton: () async {
Clipboard.setData(ClipboardData(text: state.payload as String));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
leftButtonText: S.of(context).copy,
);
});
}

View file

@ -18,15 +18,15 @@ import 'package:mobx/mobx.dart';
part 'balance_view_model.g.dart';
class BalanceRecord {
const BalanceRecord({
required this.availableBalance,
required this.additionalBalance,
required this.frozenBalance,
required this.fiatAvailableBalance,
required this.fiatAdditionalBalance,
required this.fiatFrozenBalance,
required this.asset,
required this.formattedAssetTitle});
const BalanceRecord(
{required this.availableBalance,
required this.additionalBalance,
required this.frozenBalance,
required this.fiatAvailableBalance,
required this.fiatAdditionalBalance,
required this.fiatFrozenBalance,
required this.asset,
required this.formattedAssetTitle});
final String fiatAdditionalBalance;
final String fiatAvailableBalance;
final String fiatFrozenBalance;
@ -41,12 +41,10 @@ class BalanceViewModel = BalanceViewModelBase with _$BalanceViewModel;
abstract class BalanceViewModelBase with Store {
BalanceViewModelBase(
{required this.appStore,
required this.settingsStore,
required this.fiatConvertationStore})
: isReversing = false,
isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard,
wallet = appStore.wallet! {
{required this.appStore, required this.settingsStore, required this.fiatConvertationStore})
: isReversing = false,
isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard,
wallet = appStore.wallet! {
reaction((_) => appStore.wallet, _onWalletChange);
}
@ -60,8 +58,7 @@ abstract class BalanceViewModelBase with Store {
bool isReversing;
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
wallet;
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
@computed
double get price {
@ -97,7 +94,7 @@ abstract class BalanceViewModelBase with Store {
String get asset {
final typeFormatted = walletTypeToString(appStore.wallet!.type);
switch(wallet.type) {
switch (wallet.type) {
case WalletType.haven:
return '$typeFormatted Assets';
default:
@ -120,7 +117,7 @@ abstract class BalanceViewModelBase with Store {
@computed
String get availableBalanceLabel {
switch(wallet.type) {
switch (wallet.type) {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
@ -135,7 +132,7 @@ abstract class BalanceViewModelBase with Store {
@computed
String get additionalBalanceLabel {
switch(wallet.type) {
switch (wallet.type) {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
@ -144,6 +141,8 @@ abstract class BalanceViewModelBase with Store {
case WalletType.nano:
case WalletType.banano:
return S.current.receivable_balance;
case WalletType.lightning:
return "CW-563 Max Receivable";
default:
return S.current.unconfirmed;
}
@ -228,15 +227,17 @@ abstract class BalanceViewModelBase with Store {
Map<CryptoCurrency, BalanceRecord> get balances {
return wallet.balance.map((key, value) {
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return MapEntry(key, BalanceRecord(
availableBalance: '---',
additionalBalance: '---',
frozenBalance: '---',
fiatAdditionalBalance: isFiatDisabled ? '' : '---',
fiatAvailableBalance: isFiatDisabled ? '' : '---',
fiatFrozenBalance: isFiatDisabled ? '' : '---',
asset: key,
formattedAssetTitle: _formatterAsset(key)));
return MapEntry(
key,
BalanceRecord(
availableBalance: '---',
additionalBalance: '---',
frozenBalance: '---',
fiatAdditionalBalance: isFiatDisabled ? '' : '---',
fiatAvailableBalance: isFiatDisabled ? '' : '---',
fiatFrozenBalance: isFiatDisabled ? '' : '---',
asset: key,
formattedAssetTitle: _formatterAsset(key)));
}
final fiatCurrency = settingsStore.fiatCurrency;
final price = fiatConvertationStore.prices[key] ?? 0;
@ -245,25 +246,23 @@ abstract class BalanceViewModelBase with Store {
// throw Exception('Price is null for: $key');
// }
final additionalFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString()
+ ' '
+ _getFiatBalance(
price: price,
cryptoAmount: value.formattedAdditionalBalance));
final additionalFiatBalance = isFiatDisabled
? ''
: (fiatCurrency.toString() +
' ' +
_getFiatBalance(price: price, cryptoAmount: value.formattedAdditionalBalance));
final availableFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString()
+ ' '
+ _getFiatBalance(
price: price,
cryptoAmount: value.formattedAvailableBalance));
final frozenFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString()
+ ' '
+ _getFiatBalance(
price: price,
cryptoAmount: getFormattedFrozenBalance(value)));
final availableFiatBalance = isFiatDisabled
? ''
: (fiatCurrency.toString() +
' ' +
_getFiatBalance(price: price, cryptoAmount: value.formattedAvailableBalance));
final frozenFiatBalance = isFiatDisabled
? ''
: (fiatCurrency.toString() +
' ' +
_getFiatBalance(price: price, cryptoAmount: getFormattedFrozenBalance(value)));
return MapEntry(
key,
@ -276,12 +275,11 @@ abstract class BalanceViewModelBase with Store {
fiatFrozenBalance: frozenFiatBalance,
asset: key,
formattedAssetTitle: _formatterAsset(key)));
});
});
}
@computed
bool get hasAdditionalBalance => !isEVMCompatibleChain(wallet.type);
@computed
List<BalanceRecord> get formattedBalances {
@ -358,9 +356,7 @@ abstract class BalanceViewModelBase with Store {
@action
void _onWalletChange(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
TransactionInfo>?
wallet) {
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
if (wallet == null) {
return;
}
@ -371,7 +367,7 @@ abstract class BalanceViewModelBase with Store {
}
@action
Future<void> disableIntroCakePayCard () async {
Future<void> disableIntroCakePayCard() async {
const cardDisplayStatus = false;
wallet.walletInfo.showIntroCakePayCard = cardDisplayStatus;
await wallet.walletInfo.save();
@ -401,6 +397,6 @@ abstract class BalanceViewModelBase with Store {
}
}
String getFormattedFrozenBalance(Balance walletBalance) => walletBalance.formattedUnAvailableBalance;
String getFormattedFrozenBalance(Balance walletBalance) =>
walletBalance.formattedUnAvailableBalance;
}

View file

@ -78,12 +78,13 @@ abstract class LightningViewModelBase with Store {
Future<String> createInvoice({required String amount, String? description}) async {
final sdk = await BreezSDK();
print(amount);
print("@@@@@@@@@@@@@@");
final req = ReceivePaymentRequest(
amountMsat: int.parse(amount) * 1000,
amountMsat: (double.parse(amount) * 100000000).round(),
description: description ?? '',
);
final res = await sdk.receivePayment(req: req);
print(res.lnInvoice.bolt11);
return res.lnInvoice.bolt11;
}
@ -96,7 +97,7 @@ abstract class LightningViewModelBase with Store {
// final res = await sdk.receivePayment(req: req);
// print(res.lnInvoice.);
// return res.lnInvoice.bolt11;
// TODO: figure out how to get the limits
return ['1000', '20000'];
// TODO: CW-563 figure out how to get the limits
return ['0', '20000'];
}
}