extend electrum

This commit is contained in:
Matthew Fosse 2024-03-04 09:47:39 -08:00
parent 274540664a
commit 5ad0aa42fb
12 changed files with 133 additions and 128 deletions

View file

@ -23,6 +23,7 @@ import 'package:cw_bitcoin/litecoin_network.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/script_hash.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
@ -45,9 +46,8 @@ part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
with Store {
abstract class ElectrumWalletBase<T extends ElectrumBalance>
extends WalletBase<T, ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
ElectrumWalletBase(
{required String password,
required WalletInfo walletInfo,
@ -55,9 +55,14 @@ abstract class ElectrumWalletBase
required this.networkType,
required this.mnemonic,
required Uint8List seedBytes,
required T Function({
required int confirmed,
required int unconfirmed,
required int frozen,
}) this.balanceFactory,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumClient? electrumClient,
ElectrumBalance? initialBalance,
T? initialBalance,
CryptoCurrency? currency})
: hd = currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes)
@ -69,12 +74,8 @@ abstract class ElectrumWalletBase
isEnabledAutoGenerateSubaddress = true,
unspentCoins = [],
_scripthashesUpdateSubject = {},
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
? {
currency:
initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
}
: {}),
balance = ObservableMap<CryptoCurrency, T>.of(
currency != null ? {currency: initialBalance ?? balanceFactory(confirmed: 0, unconfirmed: 0, frozen: 0)} : {}),
this.unspentCoinsInfo = unspentCoinsInfo,
this.network = networkType == bitcoin.bitcoin
? BitcoinNetwork.mainnet
@ -96,6 +97,11 @@ abstract class ElectrumWalletBase
final bitcoin.HDWallet hd;
final String mnemonic;
final T Function({
required int confirmed,
required int unconfirmed,
required int frozen,
}) balanceFactory;
@override
@observable
@ -109,7 +115,7 @@ abstract class ElectrumWalletBase
@override
@observable
late ObservableMap<CryptoCurrency, ElectrumBalance> balance;
late ObservableMap<CryptoCurrency, T> balance;
@override
@observable
@ -285,7 +291,7 @@ abstract class ElectrumWalletBase
final totalAmount = amount + fee;
if (totalAmount > balance[currency]!.confirmed) {
if (totalAmount > (balance[currency]!.confirmed as int)) {
throw BitcoinTransactionWrongBalanceException(currency);
}
@ -788,7 +794,7 @@ abstract class ElectrumWalletBase
});
}
Future<ElectrumBalance> _fetchBalances() async {
Future<T> _fetchBalances() async {
final addresses = walletAddresses.allAddresses.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[];
for (var i = 0; i < addresses.length; i++) {
@ -828,8 +834,15 @@ abstract class ElectrumWalletBase
}
}
return ElectrumBalance(
confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
// return balanceFactory()
// ..confirmed = totalConfirmed
// ..unconfirmed = totalUnconfirmed
// ..frozen = totalFrozen;
return balanceFactory(
confirmed: totalConfirmed,
unconfirmed: totalUnconfirmed,
frozen: totalFrozen,
);
}
Future<void> updateBalance() async {

View file

@ -1,10 +1,15 @@
import 'dart:convert';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_core/balance.dart';
class LightningBalance extends Balance {
class LightningBalance extends ElectrumBalance {
const LightningBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
: super(confirmed, unconfirmed);
: super(
confirmed: confirmed,
unconfirmed: unconfirmed,
frozen: frozen,
);
static LightningBalance? fromJSON(String? jsonSource) {
if (jsonSource == null) {

View file

@ -1,12 +1,15 @@
import 'dart:convert';
import 'dart:io';
import 'package:bitbox/bitbox.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
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_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/litecoin_network.dart';
@ -33,33 +36,27 @@ 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';
import 'package:cw_bitcoin/electrum_wallet.dart';
part 'lightning_wallet.g.dart';
class LightningWallet = LightningWalletBase with _$LightningWallet;
abstract class LightningWalletBase
extends WalletBase<LightningBalance, LightningTransactionHistory, LightningTransactionInfo>
with Store {
final bitcoin.HDWallet hd;
final String mnemonic;
final String _password;
ElectrumBalance myBalanceFactory(
{required int confirmed, required int unconfirmed, required int frozen}) {
return ElectrumBalance(
confirmed: confirmed,
unconfirmed: unconfirmed,
frozen: frozen,
);
}
abstract class LightningWalletBase extends ElectrumWalletBase<LightningBalance> with Store {
bool _isTransactionUpdating;
late ElectrumClient electrumClient;
@override
@observable
late ObservableMap<CryptoCurrency, LightningBalance> balance;
@override
late ElectrumWalletAddresses walletAddresses;
bitcoin.NetworkType networkType = bitcoin.bitcoin;
late BasedUtxoNetwork network;
@override
BitcoinWalletKeys get keys =>
BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
// @override
// @observable
// ObservableMap<CryptoCurrency, LightningBalance> lnbalance;
@override
@observable
@ -72,45 +69,43 @@ abstract class LightningWalletBase
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
String? addressPageType,
ElectrumClient? electrumClient,
BasedUtxoNetwork? networkParam,
List<BitcoinAddressRecord>? initialAddresses,
LightningBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) : hd = bitcoin.HDWallet.fromSeed(seedBytes, network: bitcoin.bitcoin).derivePath("m/0'/0"),
}) : _isTransactionUpdating = false,
syncStatus = NotConnectedSyncStatus(),
mnemonic = mnemonic,
_password = password,
_isTransactionUpdating = false,
balance = ObservableMap<CryptoCurrency, LightningBalance>.of({
CryptoCurrency.btcln:
initialBalance ?? const LightningBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
}),
super(walletInfo) {
transactionHistory = LightningTransactionHistory(walletInfo: walletInfo, password: password);
this.network = networkType == bitcoin.bitcoin
? BitcoinNetwork.mainnet
: networkType == litecoinNetwork
? LitecoinNetwork.mainnet
: BitcoinNetwork.testnet;
this.isTestnet = networkType == bitcoin.testnet;
super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btcln,
// balanceFactory: myBalanceFactory,
balanceFactory: ({required int confirmed, required int unconfirmed, required int frozen}) {
return LightningBalance(
confirmed: 0,
unconfirmed: 0,
frozen: 0,
);
},
) {
walletAddresses = BitcoinWalletAddresses(
walletInfo,
electrumClient: electrumClient ?? ElectrumClient(),
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
network: networkParam ?? network,
network: network,
);
this.electrumClient = electrumClient ?? ElectrumClient();
// initialize breeze:
// initialize breez:
try {
setupBreez(seedBytes);
} catch (e) {
@ -122,25 +117,27 @@ abstract class LightningWalletBase
});
}
static Future<LightningWallet> create({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) async {
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 {
return LightningWallet(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
seedBytes: await mnemonicToSeedBytes(mnemonic),
initialBalance: initialBalance,
seedBytes: await Mnemonic.toSeed(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
);
}
@ -150,20 +147,19 @@ abstract class LightningWalletBase
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password,
walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null);
final snp = await ElectrumWalletSnapshot.load(
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
return LightningWallet(
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance as LightningBalance?,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
networkParam: snp.network,
);
}
@ -224,11 +220,6 @@ abstract class LightningWalletBase
print("initialized breez: ${(await sdk.isInitialized())}");
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
throw UnimplementedError("calculateEstimatedFee");
}
@action
@override
Future<void> startSync() async {
@ -248,9 +239,6 @@ abstract class LightningWalletBase
throw UnimplementedError("changePassword");
}
@override
void close() {}
@action
@override
Future<void> connectToNode({required Node node}) async {
@ -287,15 +275,15 @@ abstract class LightningWalletBase
}
}
Map<String, LightningTransactionInfo> convertToTxInfo(List<Payment> payments) {
Map<String, LightningTransactionInfo> transactions = {};
Map<String, ElectrumTransactionInfo> convertToTxInfo(List<Payment> payments) {
Map<String, ElectrumTransactionInfo> transactions = {};
for (Payment tx in payments) {
if (tx.paymentType == PaymentType.ClosedChannel) {
continue;
}
bool isSend = tx.paymentType == PaymentType.Sent;
transactions[tx.id] = LightningTransactionInfo(
transactions[tx.id] = ElectrumTransactionInfo(
WalletType.lightning,
isPending: false,
id: tx.id,
@ -303,13 +291,16 @@ abstract class LightningWalletBase
fee: tx.feeMsat ~/ 1000,
date: DateTime.fromMillisecondsSinceEpoch(tx.paymentTime * 1000),
direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
// N/A for lightning:
height: 0,
confirmations: 0,
);
}
return transactions;
}
@override
Future<Map<String, LightningTransactionInfo>> fetchTransactions() async {
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
final sdk = await BreezSDK();
final payments = await sdk.listPayments(req: ListPaymentsRequest());
@ -341,13 +332,6 @@ abstract class LightningWalletBase
'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet',
});
@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 is updated automatically
}

View file

@ -26,10 +26,11 @@ class LightningWalletService extends WalletService<BitcoinNewWalletCredentials,
@override
Future<LightningWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final wallet = await LightningWalletBase.create(
mnemonic: await generateMnemonic(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
mnemonic: await generateMnemonic(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
);
await wallet.save();
await wallet.init();
return wallet;
@ -45,20 +46,22 @@ class LightningWalletService extends WalletService<BitcoinNewWalletCredentials,
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
try {
final wallet = await LightningWalletBase.open(
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
);
await wallet.init();
saveBackup(name);
return wallet;
} catch (_) {
await restoreWalletFilesFromBackup(name);
final wallet = await LightningWalletBase.open(
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
);
await wallet.init();
return wallet;
}
@ -93,7 +96,8 @@ class LightningWalletService extends WalletService<BitcoinNewWalletCredentials,
}
@override
Future<LightningWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
Future<LightningWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async =>
throw UnimplementedError();
@override
@ -111,7 +115,6 @@ class LightningWalletService extends WalletService<BitcoinNewWalletCredentials,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
network: network,
);
await wallet.save();
await wallet.init();

View file

@ -1,5 +1,3 @@
import 'package:breez_sdk/breez_sdk.dart';
import 'package:breez_sdk/bridge_generated.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/di.dart';
@ -161,9 +159,7 @@ class AddressPage extends BasePage {
amountController: _amountController,
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
ThemeType.light))),
SizedBox(
height: 40,
),
SizedBox(height: 16),
Observer(builder: (_) {
if (addressListViewModel.hasAddressList) {
return SelectButton(

View file

@ -27,7 +27,7 @@ class MenuWidgetState extends State<MenuWidget> {
this.fromBottomEdge = 25,
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.lightningIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.lightningIcon = Image.asset('assets/images/lightning_logo.png'),
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
@ -100,7 +100,7 @@ class MenuWidgetState extends State<MenuWidget> {
color: Theme.of(context).extension<CakeMenuTheme>()!.iconColor);
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
color: Theme.of(context).extension<CakeMenuTheme>()!.iconColor);
lightningIcon = Image.asset('assets/images/bitcoin_menu.png',
lightningIcon = Image.asset('assets/images/lightning_logo.png',
color: Theme.of(context).extension<CakeMenuTheme>()!.iconColor);
return Row(

View file

@ -208,9 +208,9 @@ class LightningReceiveOnchainPage extends BasePage {
return Expanded(
child: Container(child: Center(child: CircularProgressIndicator())));
}
int min = (snapshot.data as List<int>)[1];
int max = (snapshot.data as List<int>)[2];
int fee = (snapshot.data as List<int>)[3];
int min = int.parse((snapshot.data as List<String>)[1]);
int max = int.parse((snapshot.data as List<String>)[2]);
int fee = int.parse((snapshot.data as List<String>)[3]);
return Expanded(
child: Text(
S.of(context).lightning_receive_limits(

View file

@ -23,7 +23,7 @@ class AnonpayCurrencyInputField extends StatelessWidget {
final String maxAmount;
@override
Widget build(BuildContext context) {
bool hasDecimals = selectedCurrency.name != "sats";
bool hasDecimals = selectedCurrency.name.toLowerCase() != "sats";
final arrowBottomPurple = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Colors.white,

View file

@ -95,7 +95,7 @@ abstract class LightningInvoicePageViewModelBase with Store {
try {
String bolt11 =
await lightningViewModel.createInvoice(amount: amount, description: description);
await lightningViewModel.createInvoice(amountSats: amount, description: description);
state = ExecutedSuccessfullyState(payload: bolt11);
} catch (e) {
state = FailureState(e.toString());

View file

@ -27,10 +27,10 @@ abstract class LightningViewModelBase with Store {
];
}
Future<String> createInvoice({required String amount, String? description}) async {
Future<String> createInvoice({required String amountSats, String? description}) async {
final sdk = await BreezSDK();
final req = ReceivePaymentRequest(
amountMsat: (double.parse(amount) * 1000).round(),
amountMsat: (double.parse(amountSats) * 1000).round(),
description: description ?? '',
);
final res = await sdk.receivePayment(req: req);

View file

@ -42,6 +42,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
}
}
// base:
SecretKey.base.forEach((sec) {
if (secrets[sec.name] != null) {
return;
@ -49,12 +50,11 @@ Future<void> generateSecretsConfig(List<String> args) async {
secrets[sec.name] = sec.generate();
});
var secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await configFile.writeAsString(secretsJson);
secrets.clear();
// evm:
SecretKey.evmChainsSecrets.forEach((sec) {
if (secrets[sec.name] != null) {
return;
@ -64,8 +64,9 @@ Future<void> generateSecretsConfig(List<String> args) async {
});
secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await evmChainsConfigFile.writeAsString(secretsJson);
secrets.clear();
// btc / lightning:
SecretKey.bitcoinSecrets.forEach((sec) {
if (secrets[sec.name] != null) {
return;
@ -74,7 +75,9 @@ Future<void> generateSecretsConfig(List<String> args) async {
});
secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await bitcoinConfigFile.writeAsString(secretsJson);
secrets.clear();
// solana:
SecretKey.solanaSecrets.forEach((sec) {
if (secrets[sec.name] != null) {
return;
@ -84,4 +87,5 @@ Future<void> generateSecretsConfig(List<String> args) async {
});
secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await solanaConfigFile.writeAsString(secretsJson);
secrets.clear();
}