diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart index 2b025965b..b14753ffc 100644 --- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart +++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart @@ -23,9 +23,28 @@ class BitcoinReceivePageOption implements ReceivePageOption { BitcoinReceivePageOption.p2sh, BitcoinReceivePageOption.p2tr, BitcoinReceivePageOption.p2wsh, - BitcoinReceivePageOption.p2pkh + BitcoinReceivePageOption.p2pkh, + BitcoinReceivePageOption.silent_payments, ]; + BitcoinAddressType toType() { + switch (this) { + case BitcoinReceivePageOption.p2tr: + return SegwitAddresType.p2tr; + case BitcoinReceivePageOption.p2wsh: + return SegwitAddresType.p2wsh; + case BitcoinReceivePageOption.p2pkh: + return P2pkhAddressType.p2pkh; + case BitcoinReceivePageOption.p2sh: + return P2shAddressType.p2wpkhInP2sh; + case BitcoinReceivePageOption.silent_payments: + return SilentPaymentsAddresType.p2sp; + case BitcoinReceivePageOption.p2wpkh: + default: + return SegwitAddresType.p2wpkh; + } + } + factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) { switch (type) { case SegwitAddresType.p2tr: diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index f79abb70d..dfd7c8fe5 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -1,3 +1,4 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -37,7 +38,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { password: password, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - networkType: bitcoin.bitcoin, + networkType: networkParam == null + ? bitcoin.bitcoin + : networkParam == BitcoinNetwork.mainnet + ? bitcoin.bitcoin + : bitcoin.testnet, initialAddresses: initialAddresses, initialBalance: initialBalance, seedBytes: seedBytes, @@ -64,6 +69,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, + String? addressPageType, + BasedUtxoNetwork? network, List? initialAddresses, List? initialSilentAddresses, ElectrumBalance? initialBalance, @@ -71,6 +78,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Map? initialChangeAddressIndex, int initialSilentAddressIndex = 0, }) async { + final seedBytes = await mnemonicToSeedBytes(mnemonic); return BitcoinWallet( mnemonic: mnemonic, password: password, @@ -79,10 +87,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialAddresses: initialAddresses, initialSilentAddresses: initialSilentAddresses, initialSilentAddressIndex: initialSilentAddressIndex, - silentAddress: await SilentPaymentOwner.fromMnemonic(mnemonic, + silentAddress: await SilentPaymentOwner.fromPrivateKeys( + scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SCAN_PATH).privKey!), + spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SPEND_PATH).privKey!), hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: initialBalance, - seedBytes: await mnemonicToSeedBytes(mnemonic), + seedBytes: seedBytes, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, addressPageType: addressPageType, @@ -96,7 +112,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required Box unspentCoinsInfo, required String password, }) async { - final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); + final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, + walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null); + + final seedBytes = await mnemonicToSeedBytes(snp.mnemonic); return BitcoinWallet( mnemonic: snp.mnemonic, password: password, @@ -105,10 +124,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialAddresses: snp.addresses, initialSilentAddresses: snp.silentAddresses, initialSilentAddressIndex: snp.silentAddressIndex, - silentAddress: await SilentPaymentOwner.fromMnemonic(snp.mnemonic, + silentAddress: await SilentPaymentOwner.fromPrivateKeys( + scanPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SCAN_PATH).privKey!), + spendPrivkey: ECPrivate.fromHex(bitcoin.HDWallet.fromSeed( + seedBytes, + network: snp.network == BitcoinNetwork.testnet ? bitcoin.testnet : bitcoin.bitcoin, + ).derivePath(SPEND_PATH).privKey!), hrp: snp.network == BitcoinNetwork.testnet ? 'tsp' : 'sp'), initialBalance: snp.balance, - seedBytes: await mnemonicToSeedBytes(snp.mnemonic), + seedBytes: seedBytes, initialRegularAddressIndex: snp.regularAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex, addressPageType: snp.addressPageType, diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index fe7d1e507..27a43ddcb 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -2,12 +2,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart'; +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:collection/collection.dart'; +import 'package:http/http.dart' as http; String jsonrpcparams(List params) { final _params = params?.map((val) => '"${val.toString()}"')?.join(','); @@ -22,10 +22,7 @@ String jsonrpc( '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; class SocketTask { - SocketTask({ - required this.isSubscription, - this.completer, - this.subject}); + SocketTask({required this.isSubscription, this.completer, this.subject}); final Completer? completer; final BehaviorSubject? subject; @@ -51,16 +48,19 @@ class ElectrumClient { Timer? _aliveTimer; String unterminatedString; - Future connectToUri(Uri uri) async => + Uri? uri; + + Future connectToUri(Uri uri) async { + this.uri = uri; await connect(host: uri.host, port: uri.port); + } Future connect({required String host, required int port}) async { try { await socket?.close(); } catch (_) {} - socket = await SecureSocket.connect(host, port, - timeout: connectionTimeout, onBadCertificate: (_) => true); + socket = await Socket.connect(host, port, timeout: connectionTimeout); _setIsConnected(true); socket!.listen((Uint8List event) { @@ -104,21 +104,20 @@ class ElectrumClient { } if (isJSONStringCorrect(unterminatedString)) { - final response = - json.decode(unterminatedString) as Map; + final response = json.decode(unterminatedString) as Map; _handleResponse(response); unterminatedString = ''; } } on TypeError catch (e) { - if (!e.toString().contains('Map') && !e.toString().contains('Map')) { + if (!e.toString().contains('Map') && + !e.toString().contains('Map')) { return; } unterminatedString += message; if (isJSONStringCorrect(unterminatedString)) { - final response = - json.decode(unterminatedString) as Map; + final response = json.decode(unterminatedString) as Map; _handleResponse(response); // unterminatedString = null; unterminatedString = ''; @@ -142,8 +141,7 @@ class ElectrumClient { } } - Future> version() => - call(method: 'server.version').then((dynamic result) { + Future> version() => call(method: 'server.version').then((dynamic result) { if (result is List) { return result.map((dynamic val) => val.toString()).toList(); } @@ -178,11 +176,10 @@ class ElectrumClient { }); Future>> getListUnspentWithAddress( - String address, NetworkType networkType) => + String address, BasedUtxoNetwork network) => call( - method: 'blockchain.scripthash.listunspent', - params: [scriptHash(address, networkType: networkType)]) - .then((dynamic result) { + method: 'blockchain.scripthash.listunspent', + params: [scriptHash(address, network: network)]).then((dynamic result) { if (result is List) { return result.map((dynamic val) { if (val is Map) { @@ -229,8 +226,7 @@ class ElectrumClient { return []; }); - Future> getTransactionRaw( - {required String hash}) async => + Future> getTransactionRaw({required String hash}) async => callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000) .then((dynamic result) { if (result is Map) { @@ -240,8 +236,7 @@ class ElectrumClient { return {}; }); - Future getTransactionHex( - {required String hash}) async => + Future getTransactionHex({required String hash}) async => callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000) .then((dynamic result) { if (result is String) { @@ -252,29 +247,43 @@ class ElectrumClient { }); Future broadcastTransaction( - {required String transactionRaw}) async => - call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) - .then((dynamic result) { - if (result is String) { - return result; + {required String transactionRaw, BasedUtxoNetwork? network}) async { + if (network == BitcoinNetwork.testnet) { + return http + .post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'), + headers: {'Content-Type': 'application/json; charset=utf-8'}, + body: transactionRaw) + .then((http.Response response) { + if (response.statusCode == 200) { + return response.body; } - return ''; + throw Exception('Failed to broadcast transaction: ${response.body}'); }); + } - Future> getMerkle( - {required String hash, required int height}) async => - await call( - method: 'blockchain.transaction.get_merkle', - params: [hash, height]) as Map; + return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) + .then((dynamic result) { + if (result is String) { + return result; + } - Future> getHeader({required int height}) async => - await call(method: 'blockchain.block.get_header', params: [height]) + return ''; + }); + } + + Future> getMerkle({required String hash, required int height}) async => + await call(method: 'blockchain.transaction.get_merkle', params: [hash, height]) as Map; + Future> getHeader({required int height}) async => + await call(method: 'blockchain.block.get_header', params: [height]) as Map; + + Future> getTweaks({required int height}) async => + await call(method: 'blockchain.block.tweaks', params: [height]) as Map; + Future estimatefee({required int p}) => - call(method: 'blockchain.estimatefee', params: [p]) - .then((dynamic result) { + call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { if (result is double) { return result; } @@ -314,20 +323,17 @@ class ElectrumClient { return []; }); - Future> feeRates() async { + Future> feeRates({BasedUtxoNetwork? network}) async { + if (network == BitcoinNetwork.testnet) { + return [1, 1, 1]; + } try { final topDoubleString = await estimatefee(p: 1); final middleDoubleString = await estimatefee(p: 5); final bottomDoubleString = await estimatefee(p: 100); - final top = - (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) - .round(); - final middle = - (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000) - .round(); - final bottom = - (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000) - .round(); + final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); + final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); + final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); return [bottom, middle, top]; } catch (_) { @@ -365,16 +371,14 @@ class ElectrumClient { } BehaviorSubject? subscribe( - {required String id, - required String method, - List params = const []}) { + {required String id, required String method, List params = const []}) { try { final subscription = BehaviorSubject(); _regisrySubscription(id, subscription); socket!.write(jsonrpc(method: method, id: _id, params: params)); return subscription; - } catch(e) { + } catch (e) { print(e.toString()); return null; } @@ -391,9 +395,7 @@ class ElectrumClient { } Future callWithTimeout( - {required String method, - List params = const [], - int timeout = 4000}) async { + {required String method, List params = const [], int timeout = 4000}) async { try { final completer = Completer(); _id += 1; @@ -407,7 +409,7 @@ class ElectrumClient { }); return completer.future; - } catch(e) { + } catch (e) { print(e.toString()); } } @@ -418,8 +420,8 @@ class ElectrumClient { onConnectionStatusChange = null; } - void _registryTask(int id, Completer completer) => _tasks[id.toString()] = - SocketTask(completer: completer, isSubscription: false); + void _registryTask(int id, Completer completer) => + _tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false); void _regisrySubscription(String id, BehaviorSubject subject) => _tasks[id] = SocketTask(subject: subject, isSubscription: true); @@ -440,8 +442,7 @@ class ElectrumClient { } } - void _methodHandler( - {required String method, required Map request}) { + void _methodHandler({required String method, required Map request}) { switch (method) { case 'blockchain.scripthash.subscribe': final params = request['params'] as List; @@ -472,8 +473,8 @@ class ElectrumClient { _methodHandler(method: method, request: response); return; } - - if (id != null){ + + if (id != null) { _finish(id, result); } } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index c99551e78..e756bcf4f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -5,6 +5,7 @@ import 'dart:isolate'; import 'dart:math'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:collection/collection.dart'; import 'package:cw_bitcoin/address_to_output_script.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; @@ -133,7 +134,7 @@ abstract class ElectrumWalletBase Map?> _scripthashesUpdateSubject; BehaviorSubject? _chainTipUpdateSubject; bool _isTransactionUpdating; - // Future? _isolate; + Future? _isolate; void Function(FlutterErrorDetails)? _onError; Timer? _autoSaveTimer; @@ -147,66 +148,66 @@ abstract class ElectrumWalletBase Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); } - // @action - // Future _setListeners(int height, {int? chainTip}) async { - // final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; - // syncStatus = AttemptingSyncStatus(); + @action + Future _setListeners(int height, {int? chainTip}) async { + final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; + syncStatus = AttemptingSyncStatus(); - // if (_isolate != null) { - // final runningIsolate = await _isolate!; - // runningIsolate.kill(priority: Isolate.immediate); - // } + if (_isolate != null) { + final runningIsolate = await _isolate!; + runningIsolate.kill(priority: Isolate.immediate); + } - // final receivePort = ReceivePort(); - // _isolate = Isolate.spawn( - // startRefresh, - // ScanData( - // sendPort: receivePort.sendPort, - // primarySilentAddress: walletAddresses.primarySilentAddress!, - // networkType: networkType, - // height: height, - // chainTip: currentChainTip, - // electrumClient: ElectrumClient(), - // transactionHistoryIds: transactionHistory.transactions.keys.toList(), - // node: electrumClient.uri.toString(), - // labels: walletAddresses.labels, - // )); + final receivePort = ReceivePort(); + _isolate = Isolate.spawn( + startRefresh, + ScanData( + sendPort: receivePort.sendPort, + primarySilentAddress: walletAddresses.primarySilentAddress!, + network: network, + height: height, + chainTip: currentChainTip, + electrumClient: ElectrumClient(), + transactionHistoryIds: transactionHistory.transactions.keys.toList(), + node: electrumClient.uri.toString(), + labels: walletAddresses.labels, + )); - // await for (var message in receivePort) { - // if (message is BitcoinUnspent) { - // if (!unspentCoins.any((utx) => - // utx.hash.contains(message.hash) && - // utx.vout == message.vout && - // utx.address.contains(message.address))) { - // unspentCoins.add(message); + await for (var message in receivePort) { + if (message is BitcoinUnspent) { + if (!unspentCoins.any((utx) => + utx.hash.contains(message.hash) && + utx.vout == message.vout && + utx.address.contains(message.address))) { + unspentCoins.add(message); - // if (unspentCoinsInfo.values.any((element) => - // element.walletId.contains(id) && - // element.hash.contains(message.hash) && - // element.address.contains(message.address))) { - // _addCoinInfo(message); + if (unspentCoinsInfo.values.any((element) => + element.walletId.contains(id) && + element.hash.contains(message.hash) && + element.address.contains(message.address))) { + _addCoinInfo(message); - // await walletInfo.save(); - // await save(); - // } + await walletInfo.save(); + await save(); + } - // balance[currency] = await _fetchBalances(); - // } - // } + balance[currency] = await _fetchBalances(); + } + } - // if (message is Map) { - // transactionHistory.addMany(message); - // await transactionHistory.save(); - // } + if (message is Map) { + transactionHistory.addMany(message); + await transactionHistory.save(); + } - // // check if is a SyncStatus type since "is SyncStatus" doesn't work here - // if (message is SyncResponse) { - // syncStatus = message.syncStatus; - // walletInfo.restoreHeight = message.height; - // await walletInfo.save(); - // } - // } - // } + // check if is a SyncStatus type since "is SyncStatus" doesn't work here + if (message is SyncResponse) { + syncStatus = message.syncStatus; + walletInfo.restoreHeight = message.height; + await walletInfo.save(); + } + } + } @action @override @@ -248,11 +249,11 @@ abstract class ElectrumWalletBase }; syncStatus = ConnectedSyncStatus(); - // final currentChainTip = await electrumClient.getCurrentBlockChainTip(); + final currentChainTip = await electrumClient.getCurrentBlockChainTip(); - // if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { - // _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); - // } + if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + } } catch (e) { print(e.toString()); syncStatus = FailedSyncStatus(); @@ -643,7 +644,7 @@ abstract class ElectrumWalletBase @override Future rescan({required int height, int? chainTip, ScanData? scanData}) async { - // _setListeners(height); + _setListeners(height); } @override @@ -908,7 +909,7 @@ abstract class ElectrumWalletBase try { final currentHeight = await electrumClient.getCurrentBlockChainTip(); if (currentHeight != null) walletInfo.restoreHeight = currentHeight; - // _setListeners(walletInfo.restoreHeight, chainTip: currentHeight); + _setListeners(walletInfo.restoreHeight, chainTip: currentHeight); } catch (e, s) { print(e.toString()); _onError?.call(FlutterErrorDetails( @@ -1011,10 +1012,10 @@ abstract class ElectrumWalletBase class ScanData { final SendPort sendPort; - final SilentPaymentReceiver primarySilentAddress; + final SilentPaymentOwner primarySilentAddress; final int height; final String node; - final bitcoin.NetworkType networkType; + final BasedUtxoNetwork network; final int chainTip; final ElectrumClient electrumClient; final List transactionHistoryIds; @@ -1025,7 +1026,7 @@ class ScanData { required this.primarySilentAddress, required this.height, required this.node, - required this.networkType, + required this.network, required this.chainTip, required this.electrumClient, required this.transactionHistoryIds, @@ -1038,7 +1039,7 @@ class ScanData { primarySilentAddress: scanData.primarySilentAddress, height: newHeight, node: scanData.node, - networkType: scanData.networkType, + network: scanData.network, chainTip: scanData.chainTip, transactionHistoryIds: scanData.transactionHistoryIds, electrumClient: scanData.electrumClient, @@ -1054,333 +1055,220 @@ class SyncResponse { SyncResponse(this.height, this.syncStatus); } -// Future startRefresh(ScanData scanData) async { -// var cachedBlockchainHeight = scanData.chainTip; +Future startRefresh(ScanData scanData) async { + var cachedBlockchainHeight = scanData.chainTip; -// Future getNodeHeightOrUpdate(int baseHeight) async { -// if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { -// final electrumClient = scanData.electrumClient; -// if (!electrumClient.isConnected) { -// final node = scanData.node; -// await electrumClient.connectToUri(Uri.parse(node)); -// } + Future getNodeHeightOrUpdate(int baseHeight) async { + if (cachedBlockchainHeight < baseHeight || cachedBlockchainHeight == 0) { + final electrumClient = scanData.electrumClient; + if (!electrumClient.isConnected) { + final node = scanData.node; + await electrumClient.connectToUri(Uri.parse(node)); + } -// cachedBlockchainHeight = -// await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; -// } + cachedBlockchainHeight = + await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; + } -// return cachedBlockchainHeight; -// } + return cachedBlockchainHeight; + } -// var lastKnownBlockHeight = 0; -// var initialSyncHeight = 0; + var lastKnownBlockHeight = 0; + var initialSyncHeight = 0; -// var syncHeight = scanData.height; -// var currentChainTip = scanData.chainTip; + var syncHeight = scanData.height; + var currentChainTip = scanData.chainTip; -// if (syncHeight <= 0) { -// syncHeight = currentChainTip; -// } + if (syncHeight <= 0) { + syncHeight = currentChainTip; + } -// if (initialSyncHeight <= 0) { -// initialSyncHeight = syncHeight; -// } + if (initialSyncHeight <= 0) { + initialSyncHeight = syncHeight; + } -// if (lastKnownBlockHeight == syncHeight) { -// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); -// return; -// } + if (lastKnownBlockHeight == syncHeight) { + scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); + return; + } -// // Run this until no more blocks left to scan txs. At first this was recursive -// // i.e. re-calling the startRefresh function but this was easier for the above values to retain -// // their initial values -// while (true) { -// lastKnownBlockHeight = syncHeight; + // Run this until no more blocks left to scan txs. At first this was recursive + // i.e. re-calling the startRefresh function but this was easier for the above values to retain + // their initial values + while (true) { + lastKnownBlockHeight = syncHeight; -// final syncingStatus = -// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); -// scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); + final syncingStatus = + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); + scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); -// if (syncingStatus.blocksLeft <= 0) { -// scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); -// return; -// } + if (syncingStatus.blocksLeft <= 0) { + scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); + return; + } -// // print(["Scanning from height:", syncHeight]); + print(["Scanning from height:", syncHeight]); -// try { -// final networkPath = -// scanData.networkType.network == bitcoin.BtcNetwork.mainnet ? "" : "/testnet"; + try { + // Get all the tweaks from the block + final electrumClient = scanData.electrumClient; + if (!electrumClient.isConnected) { + final node = scanData.node; + await electrumClient.connectToUri(Uri.parse(node)); + } + final tweaks = await electrumClient.getTweaks(height: syncHeight); -// // This endpoint gets up to 10 latest blocks from the given height -// final tenNewestBlocks = -// (await http.get(Uri.parse("https://blockstream.info$networkPath/api/blocks/$syncHeight"))) -// .body; -// var decodedBlocks = json.decode(tenNewestBlocks) as List; + for (var i = 0; i < tweaks.length; i++) { + try { + // final txid = tweaks.keys.toList()[i]; + final details = tweaks.values.toList()[i]; + print(["details", details]); + final output_pubkeys = (details["output_pubkeys"] as List); -// decodedBlocks.sort((a, b) => (a["height"] as int).compareTo(b["height"] as int)); -// decodedBlocks = -// decodedBlocks.where((element) => (element["height"] as int) >= syncHeight).toList(); + // print(["Scanning tx:", txid]); -// // for each block, get up to 25 txs -// for (var i = 0; i < decodedBlocks.length; i++) { -// final blockJson = decodedBlocks[i]; -// final blockHash = blockJson["id"]; -// final txCount = blockJson["tx_count"] as int; + // TODO: if tx already scanned & stored skip + // if (scanData.transactionHistoryIds.contains(txid)) { + // // already scanned tx, continue to next tx + // pos++; + // continue; + // } -// // print(["Scanning block index:", i, "with tx count:", txCount]); + final result = SilentPayment.scanTweak( + scanData.primarySilentAddress.b_scan, + scanData.primarySilentAddress.B_spend, + details["tweak"] as String, + output_pubkeys.map((e) => BytesUtils.fromHexString(e)).toList(), + labels: scanData.labels, + ); -// int startIndex = 0; -// // go through each tx in block until no more txs are left -// while (startIndex < txCount) { -// // This endpoint gets up to 25 txs from the given block hash and start index -// final twentyFiveTxs = json.decode((await http.get(Uri.parse( -// "https://blockstream.info$networkPath/api/block/$blockHash/txs/$startIndex"))) -// .body) as List; + if (result.isEmpty) { + // no results tx, continue to next tx + continue; + } -// // print(["Scanning txs index:", startIndex]); + if (result.length > 1) { + print("MULTIPLE UNSPENT COINS FOUND!"); + } else { + print("UNSPENT COIN FOUND!"); + } -// // For each tx, apply silent payment filtering and do shared secret calculation when applied -// for (var i = 0; i < twentyFiveTxs.length; i++) { -// try { -// final tx = twentyFiveTxs[i]; -// final txid = tx["txid"] as String; + // result.forEach((key, value) async { + // final outpoint = output_pubkeys[key]; -// // print(["Scanning tx:", txid]); + // if (outpoint == null) { + // return; + // } -// // TODO: if tx already scanned & stored skip -// // if (scanData.transactionHistoryIds.contains(txid)) { -// // // already scanned tx, continue to next tx -// // pos++; -// // continue; -// // } + // final tweak = value[0]; + // String? label; + // if (value.length > 1) label = value[1]; -// List pubkeys = []; -// List outpoints = []; + // final txInfo = ElectrumTransactionInfo( + // WalletType.bitcoin, + // id: txid, + // height: syncHeight, + // amount: outpoint.value!, + // fee: 0, + // direction: TransactionDirection.incoming, + // isPending: false, + // date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000), + // confirmations: currentChainTip - syncHeight, + // to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress( + // scanData.primarySilentAddress.scanPubkey, + // scanData.primarySilentAddress.spendPubkey, + // label != null ? label.fromHex : "0".fromHex, + // hrp: scanData.primarySilentAddress.hrp, + // version: scanData.primarySilentAddress.version) + // .toString(), + // unspent: null, + // ); -// bool skip = false; + // final status = json.decode((await http + // .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) + // .body) as List; -// for (var i = 0; i < (tx["vin"] as List).length; i++) { -// final input = tx["vin"][i]; -// final prevout = input["prevout"]; -// final scriptPubkeyType = prevout["scriptpubkey_type"]; -// String? pubkey; + // bool spent = false; + // for (final s in status) { + // if ((s["spent"] as bool) == true) { + // spent = true; -// if (scriptPubkeyType == "v0_p2wpkh" || scriptPubkeyType == "v1_p2tr") { -// final witness = input["witness"]; -// if (witness == null) { -// skip = true; -// // print("Skipping, no witness"); -// break; -// } + // scanData.sendPort.send({txid: txInfo}); -// if (witness.length == 2) { -// pubkey = witness[1] as String; -// } else if (witness.length == 1) { -// pubkey = "02" + (prevout["scriptpubkey"] as String).fromHex.sublist(2).hex; -// } -// } + // final sentTxId = s["txid"] as String; + // final sentTx = json.decode( + // (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) + // .body); -// if (scriptPubkeyType == "p2pkh") { -// pubkey = bitcoin.P2pkhAddress( -// scriptSig: bitcoin.Script.fromRaw(hexData: input["scriptsig"] as String)) -// .pubkey; -// } + // int amount = 0; + // for (final out in (sentTx["vout"] as List)) { + // amount += out["value"] as int; + // } -// if (pubkey == null) { -// skip = true; -// // print("Skipping, invalid witness"); -// break; -// } + // final height = s["status"]["block_height"] as int; -// pubkeys.add(pubkey); -// outpoints.add( -// bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int)); -// } + // scanData.sendPort.send({ + // sentTxId: ElectrumTransactionInfo( + // WalletType.bitcoin, + // id: sentTxId, + // height: height, + // amount: amount, + // fee: 0, + // direction: TransactionDirection.outgoing, + // isPending: false, + // date: DateTime.fromMillisecondsSinceEpoch( + // (s["status"]["block_time"] as int) * 1000), + // confirmations: currentChainTip - height, + // ) + // }); + // } + // } -// if (skip) { -// // skipped tx, continue to next tx -// continue; -// } + // if (spent) { + // return; + // } -// Map outpointsByP2TRpubkey = {}; -// for (var i = 0; i < (tx["vout"] as List).length; i++) { -// final output = tx["vout"][i]; -// if (output["scriptpubkey_type"] != "v1_p2tr") { -// // print("Skipping, not a v1_p2tr output"); -// continue; -// } + // final unspent = BitcoinUnspent( + // BitcoinAddressRecord( + // bitcoin.P2trAddress(program: key, networkType: scanData.network).address, + // index: 0, + // isHidden: true, + // isUsed: true, + // silentAddressLabel: null, + // silentPaymentTweak: tweak, + // type: bitcoin.AddressType.p2tr, + // ), + // txid, + // outpoint.value!, + // outpoint.index, + // silentPaymentTweak: tweak, + // type: bitcoin.AddressType.p2tr, + // ); -// final script = (output["scriptpubkey"] as String).fromHex; + // // found utxo for tx, send unspent coin to main isolate + // scanData.sendPort.send(unspent); -// // final alreadySpentOutput = (await electrumClient.getHistory( -// // scriptHashFromScript(script, networkType: scanData.networkType))) -// // .length > -// // 1; + // // also send tx data for tx history + // txInfo.unspent = unspent; + // scanData.sendPort.send({txid: txInfo}); + // }); + } catch (_) {} + } -// // if (alreadySpentOutput) { -// // print("Skipping, invalid witness"); -// // break; -// // } + // Finished scanning block, add 1 to height and continue to next block in loop + syncHeight += 1; + currentChainTip = await getNodeHeightOrUpdate(syncHeight); + scanData.sendPort.send(SyncResponse(syncHeight, + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); + } catch (e, stacktrace) { + print(stacktrace); + print(e.toString()); -// final p2tr = bitcoin.P2trAddress( -// program: script.sublist(2).hex, networkType: scanData.networkType); -// final address = p2tr.address; - -// print(["Verifying taproot address:", address]); - -// outpointsByP2TRpubkey[script.sublist(2).hex] = -// bitcoin.Outpoint(txid: txid, index: i, value: output["value"] as int); -// } - -// if (pubkeys.isEmpty || outpoints.isEmpty || outpointsByP2TRpubkey.isEmpty) { -// // skipped tx, continue to next tx -// continue; -// } - -// final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); - -// final result = bitcoin.scanOutputs( -// scanData.primarySilentAddress.scanPrivkey, -// scanData.primarySilentAddress.spendPubkey, -// bitcoin.getSumInputPubKeys(pubkeys), -// outpointHash, -// outpointsByP2TRpubkey.keys.map((e) => e.fromHex).toList(), -// labels: scanData.labels, -// ); - -// if (result.isEmpty) { -// // no results tx, continue to next tx -// continue; -// } - -// if (result.length > 1) { -// print("MULTIPLE UNSPENT COINS FOUND!"); -// } else { -// print("UNSPENT COIN FOUND!"); -// } - -// result.forEach((key, value) async { -// final outpoint = outpointsByP2TRpubkey[key]; - -// if (outpoint == null) { -// return; -// } - -// final tweak = value[0]; -// String? label; -// if (value.length > 1) label = value[1]; - -// final txInfo = ElectrumTransactionInfo( -// WalletType.bitcoin, -// id: txid, -// height: syncHeight, -// amount: outpoint.value!, -// fee: 0, -// direction: TransactionDirection.incoming, -// isPending: false, -// date: DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000), -// confirmations: currentChainTip - syncHeight, -// to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress( -// scanData.primarySilentAddress.scanPubkey, -// scanData.primarySilentAddress.spendPubkey, -// label != null ? label.fromHex : "0".fromHex, -// hrp: scanData.primarySilentAddress.hrp, -// version: scanData.primarySilentAddress.version) -// .toString(), -// unspent: null, -// ); - -// final status = json.decode((await http -// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$txid/outspends"))) -// .body) as List; - -// bool spent = false; -// for (final s in status) { -// if ((s["spent"] as bool) == true) { -// spent = true; - -// scanData.sendPort.send({txid: txInfo}); - -// final sentTxId = s["txid"] as String; -// final sentTx = json.decode((await http -// .get(Uri.parse("https://blockstream.info/testnet/api/tx/$sentTxId"))) -// .body); - -// int amount = 0; -// for (final out in (sentTx["vout"] as List)) { -// amount += out["value"] as int; -// } - -// final height = s["status"]["block_height"] as int; - -// scanData.sendPort.send({ -// sentTxId: ElectrumTransactionInfo( -// WalletType.bitcoin, -// id: sentTxId, -// height: height, -// amount: amount, -// fee: 0, -// direction: TransactionDirection.outgoing, -// isPending: false, -// date: DateTime.fromMillisecondsSinceEpoch( -// (s["status"]["block_time"] as int) * 1000), -// confirmations: currentChainTip - height, -// ) -// }); -// } -// } - -// if (spent) { -// return; -// } - -// final unspent = BitcoinUnspent( -// BitcoinAddressRecord( -// bitcoin.P2trAddress(program: key, networkType: scanData.networkType).address, -// index: 0, -// isHidden: true, -// isUsed: true, -// silentAddressLabel: null, -// silentPaymentTweak: tweak, -// type: bitcoin.AddressType.p2tr, -// ), -// txid, -// outpoint.value!, -// outpoint.index, -// silentPaymentTweak: tweak, -// type: bitcoin.AddressType.p2tr, -// ); - -// // found utxo for tx, send unspent coin to main isolate -// scanData.sendPort.send(unspent); - -// // also send tx data for tx history -// txInfo.unspent = unspent; -// scanData.sendPort.send({txid: txInfo}); -// }); -// } catch (_) {} -// } - -// // Finished scanning batch of txs in block, add 25 to start index and continue to next block in loop -// startIndex += 25; -// } - -// // Finished scanning block, add 1 to height and continue to next block in loop -// syncHeight += 1; -// currentChainTip = await getNodeHeightOrUpdate(syncHeight); -// scanData.sendPort.send(SyncResponse(syncHeight, -// SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); -// } -// } catch (e, stacktrace) { -// print(stacktrace); -// print(e.toString()); - -// scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); -// break; -// } -// } -// } + scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); + break; + } + } +} class EstimatedTxResult { EstimatedTxResult( diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 934f72b09..f861e64c9 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -216,7 +216,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { for (int i = 0; i < silentAddresses.length; i++) { final silentAddressRecord = silentAddresses[i]; final silentAddress = - SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).spendPubkey.toHex(); + SilentPaymentDestination.fromAddress(silentAddressRecord.address, 0).B_spend.toHex(); if (silentAddressRecord.silentPaymentTweak != null) labels[silentAddress] = silentAddressRecord.silentPaymentTweak!; @@ -232,7 +232,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final address = BitcoinAddressRecord( SilentPaymentAddress.createLabeledSilentPaymentAddress( - primarySilentAddress!.scanPubkey, primarySilentAddress!.spendPubkey, tweak, + primarySilentAddress!.B_scan, primarySilentAddress!.B_spend, tweak, hrp: primarySilentAddress!.hrp, version: primarySilentAddress!.version) .toString(), index: currentSilentAddressIndex, diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 222e95acc..5641909c0 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,3 +1,4 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -20,17 +21,18 @@ part 'litecoin_wallet.g.dart'; class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; abstract class LitecoinWalletBase extends ElectrumWallet with Store { - LitecoinWalletBase( - {required String mnemonic, - required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required Uint8List seedBytes, - List? initialAddresses, - ElectrumBalance? initialBalance, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super( + LitecoinWalletBase({ + required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + String? addressPageType, + List? initialAddresses, + ElectrumBalance? initialBalance, + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex, + }) : super( mnemonic: mnemonic, password: password, walletInfo: walletInfo, @@ -41,41 +43,41 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { seedBytes: seedBytes, currency: CryptoCurrency.ltc) { walletAddresses = LitecoinWalletAddresses( - walletInfo, - electrumClient: electrumClient, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: hd, - sideHd: bitcoin.HDWallet - .fromSeed(seedBytes, network: networkType) - .derivePath("m/0'/1"), - networkType: networkType,); + walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd, + sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"), + network: network, + ); autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); } - static Future create({ - required String mnemonic, - required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - List? initialAddresses, - ElectrumBalance? initialBalance, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0 - }) async { + static Future create( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + String? addressPageType, + List? initialAddresses, + ElectrumBalance? initialBalance, + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex}) async { return LitecoinWallet( - mnemonic: mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: await mnemonicToSeedBytes(mnemonic), - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex); + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await mnemonicToSeedBytes(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + addressPageType: addressPageType, + ); } static Future open({ @@ -84,17 +86,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { required Box unspentCoinsInfo, required String password, }) async { - final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password); + final snp = + await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet); return LitecoinWallet( - mnemonic: snp.mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses, - initialBalance: snp.balance, - seedBytes: await mnemonicToSeedBytes(snp.mnemonic), - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex); + mnemonic: snp.mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: await mnemonicToSeedBytes(snp.mnemonic), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex, + addressPageType: snp.addressPageType, + ); } @override diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index a317fa9f2..99b7445fc 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -1,39 +1,27 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; part 'litecoin_wallet_addresses.g.dart'; -class LitecoinWalletAddresses = LitecoinWalletAddressesBase - with _$LitecoinWalletAddresses; +class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses; -abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses - with Store { +abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store { LitecoinWalletAddressesBase( - WalletInfo walletInfo, - {required bitcoin.HDWallet mainHd, - required bitcoin.HDWallet sideHd, - required bitcoin.NetworkType networkType, - required ElectrumClient electrumClient, - List? initialAddresses, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super( - walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: mainHd, - sideHd: sideHd, - electrumClient: electrumClient, - networkType: networkType); + WalletInfo walletInfo, { + required super.mainHd, + required super.sideHd, + required super.network, + super.initialAddresses, + super.initialRegularAddressIndex, + super.initialChangeAddressIndex, + }) : super(walletInfo); @override - String getAddress({required int index, required bitcoin.HDWallet hd}) => - generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); -} \ No newline at end of file + String getAddress( + {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) => + generateP2WPKHAddress(hd: hd, index: index, network: network); +} diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index b6acab7f4..13a0e2688 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -34,6 +34,10 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_base.git ref: cake-update-v2 + blockchain_utils: + git: + url: https://github.com/rafael-xmr/blockchain_utils + ref: cake-update-v1 dev_dependencies: flutter_test: diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 1b87e2231..2e4788c2d 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -28,17 +28,18 @@ part 'bitcoin_cash_wallet.g.dart'; class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet; abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { - BitcoinCashWalletBase( - {required String mnemonic, - required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required Uint8List seedBytes, - List? initialAddresses, - ElectrumBalance? initialBalance, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super( + BitcoinCashWalletBase({ + required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + String? addressPageType, + List? initialAddresses, + ElectrumBalance? initialBalance, + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex, + }) : super( mnemonic: mnemonic, password: password, walletInfo: walletInfo, @@ -48,40 +49,42 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { initialBalance: initialBalance, seedBytes: seedBytes, currency: CryptoCurrency.bch) { - walletAddresses = BitcoinCashWalletAddresses(walletInfo, - electrumClient: electrumClient, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: hd, - sideHd: bitcoin.HDWallet.fromSeed(seedBytes) - .derivePath("m/44'/145'/0'/1"), - networkType: networkType); + walletAddresses = BitcoinCashWalletAddresses( + walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd, + sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"), + network: network, + ); autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); } - static Future create( {required String mnemonic, required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, + String? addressPageType, List? initialAddresses, ElectrumBalance? initialBalance, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) async { + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex}) async { return BitcoinCashWallet( - mnemonic: mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: await Mnemonic.toSeed(mnemonic), - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex); + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await Mnemonic.toSeed(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + addressPageType: addressPageType, + ); } static Future open({ @@ -90,17 +93,20 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { required Box unspentCoinsInfo, required String password, }) async { - final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); + final snp = await ElectrumWalletSnapshot.load( + name, walletInfo.type, password, BitcoinCashNetwork.mainnet); return BitcoinCashWallet( - mnemonic: snp.mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp.addresses, - initialBalance: snp.balance, - seedBytes: await Mnemonic.toSeed(snp.mnemonic), - initialRegularAddressIndex: snp.regularAddressIndex, - initialChangeAddressIndex: snp.changeAddressIndex); + mnemonic: snp.mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: await Mnemonic.toSeed(snp.mnemonic), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex, + addressPageType: snp.addressPageType, + ); } @override @@ -270,20 +276,18 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { electrumClient: electrumClient, amount: amount, fee: fee); } - bitbox.ECPair generateKeyPair( - {required bitcoin.HDWallet hd, - required int index}) => + bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) => bitbox.ECPair.fromWIF(hd.derive(index).wif!); @override - int feeAmountForPriority( - BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => + int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount, + {int? size}) => feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); - int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) => feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) { int inputsCount = 0; int totalValue = 0; @@ -323,9 +327,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { @override String signMessage(String message, {String? address = null}) { final index = address != null - ? walletAddresses.addresses + ? walletAddresses.allAddresses .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) - .index : null; + .index + : null; final HD = index == null ? hd : hd.derive(index); return base64Encode(HD.signMessage(message)); } diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart index 1709c4d8f..492b2daa8 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -1,6 +1,5 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; @@ -11,24 +10,18 @@ part 'bitcoin_cash_wallet_addresses.g.dart'; class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses; abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store { - BitcoinCashWalletAddressesBase(WalletInfo walletInfo, - {required bitcoin.HDWallet mainHd, - required bitcoin.HDWallet sideHd, - required bitcoin.NetworkType networkType, - required ElectrumClient electrumClient, - List? initialAddresses, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super(walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: mainHd, - sideHd: sideHd, - electrumClient: electrumClient, - networkType: networkType); + BitcoinCashWalletAddressesBase( + WalletInfo walletInfo, { + required super.mainHd, + required super.sideHd, + required super.network, + super.initialAddresses, + super.initialRegularAddressIndex, + super.initialChangeAddressIndex, + }) : super(walletInfo); @override - String getAddress({required int index, required bitcoin.HDWallet hd}) => - generateP2PKHAddress(hd: hd, index: index, networkType: networkType); + String getAddress( + {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) => + generateP2PKHAddress(hd: hd, index: index, network: network); } diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 7130b3c58..fd7ed5a0f 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -33,8 +33,10 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_base.git ref: cake-update-v2 - - + blockchain_utils: + git: + url: https://github.com/rafael-xmr/blockchain_utils + ref: cake-update-v1 dev_dependencies: flutter_test: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 73c6ee9b0..d49fa1cbb 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -338,6 +338,12 @@ class CWBitcoin extends Bitcoin { return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType); } + @override + bool hasSelectedSilentPayments(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp; + } + @override List getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 0fbdcc940..a11adf8c8 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -25,7 +25,7 @@ class AddressValidator extends TextValidator { return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; case CryptoCurrency.btc: - return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; + return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$'; case CryptoCurrency.nano: return '[0-9a-zA-Z_]'; case CryptoCurrency.banano: @@ -268,7 +268,7 @@ class AddressValidator extends TextValidator { '([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)' '([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)' - '|${bitcoin.SilentPaymentAddress.REGEX.pattern}\$'; + '|${SilentPaymentAddress.regex.pattern}\$'; case CryptoCurrency.ltc: return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index fbb86fa9f..226b97f50 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -4,7 +4,7 @@ import 'package:cw_core/sync_status.dart'; String syncStatusTitle(SyncStatus syncStatus) { if (syncStatus is SyncingSyncStatus) { return syncStatus.blocksLeft == 1 - ? S.current.Block_remaining('${syncStatus.blocksLeft}') + ? S.current.block_remaining : S.current.Blocks_remaining('${syncStatus.blocksLeft}'); } diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index b4460edc9..8e48d1e23 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -198,6 +198,11 @@ class AddressPage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { + if (option is BitcoinReceivePageOption) { + addressListViewModel.setAddressType(option.toType()); + return; + } + switch (option) { case ReceivePageOption.anonPayInvoice: Navigator.pushNamed( diff --git a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart index 64125b145..0ae750cf9 100644 --- a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart +++ b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart @@ -45,7 +45,7 @@ class PresentReceiveOptionPicker extends StatelessWidget { fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color), ), Observer( - builder: (_) => Text(describeOption(receiveOptionViewModel.selectedReceiveOption), + builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(), style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color))) ], ), @@ -101,7 +101,7 @@ class PresentReceiveOptionPicker extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(describeOption(option), + Text(option.toString(), textAlign: TextAlign.left, style: textSmall( color: Theme.of(context) diff --git a/lib/src/screens/subaddress/address_edit_or_create_page.dart b/lib/src/screens/subaddress/address_edit_or_create_page.dart index e067c78d0..ed6369a73 100644 --- a/lib/src/screens/subaddress/address_edit_or_create_page.dart +++ b/lib/src/screens/subaddress/address_edit_or_create_page.dart @@ -13,8 +13,7 @@ class AddressEditOrCreatePage extends BasePage { : _formKey = GlobalKey(), _labelController = TextEditingController(), super() { - _labelController.addListener( - () => addressEditOrCreateViewModel.label = _labelController.text); + _labelController.addListener(() => addressEditOrCreateViewModel.label = _labelController.text); _labelController.text = addressEditOrCreateViewModel.label; } @@ -55,10 +54,8 @@ class AddressEditOrCreatePage extends BasePage { : S.of(context).new_subaddress_create, color: Theme.of(context).primaryColor, textColor: Colors.white, - isLoading: - addressEditOrCreateViewModel.state is AddressIsSaving, - isDisabled: - addressEditOrCreateViewModel.label?.isEmpty ?? true, + isLoading: addressEditOrCreateViewModel.state is AddressIsSaving, + isDisabled: addressEditOrCreateViewModel.label?.isEmpty ?? true, ), ) ], @@ -70,14 +67,13 @@ class AddressEditOrCreatePage extends BasePage { if (_isEffectsInstalled) { return; } - reaction((_) => addressEditOrCreateViewModel.state, - (AddressEditOrCreateState state) { - if (state is AddressSavedSuccessfully) { - WidgetsBinding.instance - .addPostFrameCallback((_) => Navigator.of(context).pop()); - } - }); + reaction((_) => addressEditOrCreateViewModel.state, (AddressEditOrCreateState state) { + if (state is AddressSavedSuccessfully) { + WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.of(context).pop()); + } + }); _isEffectsInstalled = true; } -} \ No newline at end of file +} + diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index cd8206b32..2f14cb346 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -42,7 +42,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:flutter/services.dart'; import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/entities/provider_types.dart'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; part 'dashboard_view_model.g.dart'; @@ -273,12 +273,10 @@ abstract class DashboardViewModelBase with Store { @observable WalletBase, TransactionInfo> wallet; - bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; - // bool get hasRescan => - // (wallet.type == WalletType.bitcoin && - // wallet.walletAddresses.addressPageType == bitcoin.AddressType.p2sp) || - // wallet.type == WalletType.monero || - // wallet.type == WalletType.haven; + bool get hasRescan => + (wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet)) || + wallet.type == WalletType.monero || + wallet.type == WalletType.haven; final KeyService keyService; @@ -340,15 +338,13 @@ abstract class DashboardViewModelBase with Store { bool hasExchangeAction; @computed - bool get isEnabledBuyAction => - !settingsStore.disableBuy && availableBuyProviders.isNotEmpty; + bool get isEnabledBuyAction => !settingsStore.disableBuy && availableBuyProviders.isNotEmpty; @observable bool hasBuyAction; @computed - bool get isEnabledSellAction => - !settingsStore.disableSell && availableSellProviders.isNotEmpty; + bool get isEnabledSellAction => !settingsStore.disableSell && availableSellProviders.isNotEmpty; @observable bool hasSellAction; @@ -473,7 +469,8 @@ abstract class DashboardViewModelBase with Store { Future> checkAffectedWallets() async { // await load file - final vulnerableSeedsString = await rootBundle.loadString('assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt'); + final vulnerableSeedsString = await rootBundle + .loadString('assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt'); final vulnerableSeeds = vulnerableSeedsString.split("\n"); final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 330633e81..d7eb6f1a5 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -387,8 +387,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo wallet.type == WalletType.bitcoinCash; @computed - bool get isAutoGenerateSubaddressEnabled => - _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; + bool get isAutoGenerateSubaddressEnabled => wallet.type == WalletType.bitcoin + ? !bitcoin!.hasSelectedSilentPayments(wallet) + : _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; List _baseItems; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 553262f79..81df30eef 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "موضوع البيتكوين الظلام", "bitcoin_light_theme": "موضوع البيتكوين الخفيفة", "bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.", + "block_remaining": "1 كتلة متبقية", "Blocks_remaining": "بلوك متبقي ${status}", "bright_theme": "مشرق", "buy": "اشتري", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 8d89f463b..caca6e7a6 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Тъмна тема за биткойн", "bitcoin_light_theme": "Лека биткойн тема", "bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.", + "block_remaining": "1 блок останал", "Blocks_remaining": "${status} оставащи блока", "bright_theme": "Ярко", "buy": "Купуване", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index e81eab570..71ea7e8cb 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tmavé téma bitcoinů", "bitcoin_light_theme": "Světlé téma bitcoinů", "bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.", + "block_remaining": "1 blok zbývající", "Blocks_remaining": "Zbývá ${status} bloků", "bright_theme": "Jasný", "buy": "Koupit", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 3ca972af4..7a676434a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Dunkles Bitcoin-Thema", "bitcoin_light_theme": "Bitcoin Light-Thema", "bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.", + "block_remaining": "1 Block verbleibend", "Blocks_remaining": "${status} verbleibende Blöcke", "bright_theme": "Strahlend hell", "buy": "Kaufen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 2db7602c1..fc9d5ab55 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Theme", "bitcoin_light_theme": "Bitcoin Light Theme", "bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.", + "block_remaining": "1 Block Remaining", "Blocks_remaining": "${status} Blocks Remaining", "bright_theme": "Bright", "buy": "Buy", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 5ca5ff4d3..61ebddcd8 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema oscuro de Bitcoin", "bitcoin_light_theme": "Tema de la luz de Bitcoin", "bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.", + "block_remaining": "1 bloqueo restante", "Blocks_remaining": "${status} Bloques restantes", "bright_theme": "Brillante", "buy": "Comprar", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index aee967d21..7a3d88cee 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Thème sombre Bitcoin", "bitcoin_light_theme": "Thème léger Bitcoin", "bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.", + "block_remaining": "1 bloc restant", "Blocks_remaining": "Blocs Restants : ${status}", "bright_theme": "Vif", "buy": "Acheter", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index dfffa8a8b..b654feb20 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Jigo", "bitcoin_light_theme": "Jigon Hasken Bitcoin", "bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.", + "block_remaining": "1 toshe ragowar", "Blocks_remaining": "${status} Katanga ya rage", "bright_theme": "Mai haske", "buy": "Sayi", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index e67b00726..47b98d954 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "बिटकॉइन डार्क थीम", "bitcoin_light_theme": "बिटकॉइन लाइट थीम", "bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।", + "block_remaining": "1 ब्लॉक शेष", "Blocks_remaining": "${status} शेष रहते हैं", "bright_theme": "उज्ज्वल", "buy": "खरीदें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index c2fee8420..79f857385 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Tamna tema", "bitcoin_light_theme": "Bitcoin Light Theme", "bitcoin_payments_require_1_confirmation": "Bitcoin plaćanja zahtijevaju 1 potvrdu, što može potrajati 20 minuta ili dulje. Hvala na Vašem strpljenju! Dobit ćete e-poruku kada plaćanje bude potvrđeno.", + "block_remaining": "Preostalo 1 blok", "Blocks_remaining": "${status} preostalih blokova", "bright_theme": "Jarka", "buy": "Kupi", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 28a7df3c9..8f648e6d4 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema Gelap Bitcoin", "bitcoin_light_theme": "Tema Cahaya Bitcoin", "bitcoin_payments_require_1_confirmation": "Pembayaran Bitcoin memerlukan 1 konfirmasi, yang bisa memakan waktu 20 menit atau lebih. Terima kasih atas kesabaran Anda! Anda akan diemail saat pembayaran dikonfirmasi.", + "block_remaining": "1 blok tersisa", "Blocks_remaining": "${status} Blok Tersisa", "bright_theme": "Cerah", "buy": "Beli", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 42f0b8d86..e86e3150c 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema oscuro di Bitcoin", "bitcoin_light_theme": "Tema luce Bitcoin", "bitcoin_payments_require_1_confirmation": "I pagamenti in bitcoin richiedono 1 conferma, che può richiedere 20 minuti o più. Grazie per la vostra pazienza! Riceverai un'e-mail quando il pagamento sarà confermato.", + "block_remaining": "1 blocco rimanente", "Blocks_remaining": "${status} Blocchi Rimanenti", "bright_theme": "Colorato", "buy": "Comprare", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index f4b014909..b00962aa7 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "ビットコインダークテーマ", "bitcoin_light_theme": "ビットコインライトテーマ", "bitcoin_payments_require_1_confirmation": "ビットコインの支払いには 1 回の確認が必要で、これには 20 分以上かかる場合があります。お待ち頂きまして、ありがとうございます!支払いが確認されると、メールが送信されます。", + "block_remaining": "残り1ブロック", "Blocks_remaining": "${status} 残りのブロック", "bright_theme": "明るい", "buy": "購入", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 7af7376ce..df294ac70 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "비트코인 다크 테마", "bitcoin_light_theme": "비트코인 라이트 테마", "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.", + "block_remaining": "남은 블록 1 개", "Blocks_remaining": "${status} 남은 블록", "bright_theme": "선명한", "buy": "구입", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 6cba70ab2..c99068869 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Theme", "bitcoin_light_theme": "Bitcoin Light အပြင်အဆင်", "bitcoin_payments_require_1_confirmation": "Bitcoin ငွေပေးချေမှုများသည် မိနစ် 20 သို့မဟုတ် ထို့ထက်ပိုကြာနိုင်သည် 1 အတည်ပြုချက် လိုအပ်သည်။ မင်းရဲ့စိတ်ရှည်မှုအတွက် ကျေးဇူးတင်ပါတယ်။ ငွေပေးချေမှုကို အတည်ပြုပြီးသောအခါ သင့်ထံ အီးမေးလ်ပို့ပါမည်။", + "block_remaining": "ကျန်ရှိနေသေးသော block", "Blocks_remaining": "${status} ဘလောက်များ ကျန်နေပါသည်။", "bright_theme": "တောက်ပ", "buy": "ဝယ်ပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 19451da27..94005f184 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin donker thema", "bitcoin_light_theme": "Bitcoin Light-thema", "bitcoin_payments_require_1_confirmation": "Bitcoin-betalingen vereisen 1 bevestiging, wat 20 minuten of langer kan duren. Dank voor uw geduld! U ontvangt een e-mail wanneer de betaling is bevestigd.", + "block_remaining": "1 blok resterend", "Blocks_remaining": "${status} Resterende blokken", "bright_theme": "Helder", "buy": "Kopen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 74aafd014..9536b16d5 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Ciemny motyw Bitcoina", "bitcoin_light_theme": "Lekki motyw Bitcoin", "bitcoin_payments_require_1_confirmation": "Płatności Bitcoin wymagają 1 potwierdzenia, co może zająć 20 minut lub dłużej. Dziękuję za cierpliwość! Otrzymasz wiadomość e-mail, gdy płatność zostanie potwierdzona.", + "block_remaining": "1 blok pozostałym", "Blocks_remaining": "Pozostało ${status} bloków", "bright_theme": "Biały", "buy": "Kup", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 53d93fe75..da253fc8e 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Tema escuro Bitcoin", "bitcoin_light_theme": "Tema claro de bitcoin", "bitcoin_payments_require_1_confirmation": "Os pagamentos em Bitcoin exigem 1 confirmação, o que pode levar 20 minutos ou mais. Obrigado pela sua paciência! Você receberá um e-mail quando o pagamento for confirmado.", + "block_remaining": "1 bloco restante", "Blocks_remaining": "${status} blocos restantes", "bright_theme": "Brilhante", "buy": "Comprar", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 89a25db06..270dc4b5c 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Биткойн Темная тема", "bitcoin_light_theme": "Легкая биткойн-тема", "bitcoin_payments_require_1_confirmation": "Биткойн-платежи требуют 1 подтверждения, что может занять 20 минут или дольше. Спасибо тебе за твое терпение! Вы получите электронное письмо, когда платеж будет подтвержден.", + "block_remaining": "1 Блок остался", "Blocks_remaining": "${status} Осталось блоков", "bright_theme": "Яркая", "buy": "Купить", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index ef8992329..da7f24cd7 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "ธีมมืด Bitcoin", "bitcoin_light_theme": "ธีมแสง Bitcoin", "bitcoin_payments_require_1_confirmation": "การชำระเงินด้วย Bitcoin ต้องการการยืนยัน 1 ครั้ง ซึ่งอาจใช้เวลา 20 นาทีหรือนานกว่านั้น ขอบคุณสำหรับความอดทนของคุณ! คุณจะได้รับอีเมลเมื่อการชำระเงินได้รับการยืนยัน", + "block_remaining": "เหลือ 1 บล็อก", "Blocks_remaining": "${status} บล็อกที่เหลืออยู่", "bright_theme": "สดใส", "buy": "ซื้อ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 55b6adb51..93826e698 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Madilim na Tema", "bitcoin_light_theme": "Tema ng ilaw ng bitcoin", "bitcoin_payments_require_1_confirmation": "Ang mga pagbabayad sa Bitcoin ay nangangailangan ng 1 kumpirmasyon, na maaaring tumagal ng 20 minuto o mas mahaba. Salamat sa iyong pasensya! Mag -email ka kapag nakumpirma ang pagbabayad.", + "block_remaining": "1 bloke ang natitira", "Blocks_remaining": "Ang natitirang ${status} ay natitira", "bright_theme": "Maliwanag", "buy": "Bilhin", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index e6cab5027..afcf3c5de 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Karanlık Teması", "bitcoin_light_theme": "Bitcoin Hafif Tema", "bitcoin_payments_require_1_confirmation": "Bitcoin ödemeleri, 20 dakika veya daha uzun sürebilen 1 onay gerektirir. Sabrınız için teşekkürler! Ödeme onaylandığında e-posta ile bilgilendirileceksiniz.", + "block_remaining": "Kalan 1 blok", "Blocks_remaining": "${status} Blok Kaldı", "bright_theme": "Parlak", "buy": "Alış", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index e81d97021..9c8bc2c45 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Темна тема Bitcoin", "bitcoin_light_theme": "Світла тема Bitcoin", "bitcoin_payments_require_1_confirmation": "Платежі Bitcoin потребують 1 підтвердження, яке може зайняти 20 хвилин або більше. Дякую за Ваше терпіння! Ви отримаєте електронний лист, коли платіж буде підтверджено.", + "block_remaining": "1 блок, що залишився", "Blocks_remaining": "${status} Залишилось блоків", "bright_theme": "Яскрава", "buy": "Купити", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 465fac003..f4efa051d 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "بٹ کوائن ڈارک تھیم", "bitcoin_light_theme": "بٹ کوائن لائٹ تھیم", "bitcoin_payments_require_1_confirmation": "بٹ کوائن کی ادائیگی میں 1 تصدیق کی ضرورت ہوتی ہے ، جس میں 20 منٹ یا اس سے زیادہ وقت لگ سکتا ہے۔ آپ کے صبر کا شکریہ! ادائیگی کی تصدیق ہونے پر آپ کو ای میل کیا جائے گا۔", + "block_remaining": "1 بلاک باقی", "Blocks_remaining": "${status} بلاکس باقی ہیں۔", "bright_theme": "روشن", "buy": "خریدنے", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index c88f488cd..cf652637c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "Bitcoin Dark Akori", "bitcoin_light_theme": "Bitcoin Light Akori", "bitcoin_payments_require_1_confirmation": "Àwọn àránṣẹ́ Bitcoin nílò ìjẹ́rìísí kan. Ó lè lo ìṣéjú ogun tàbí ìṣéjú jù. A dúpẹ́ fún sùúrù yín! Ẹ máa gba ímeèlì t'ó bá jẹ́rìísí àránṣẹ́ náà.", + "block_remaining": "1 bulọọki to ku", "Blocks_remaining": "Àkójọpọ̀ ${status} kikù", "bright_theme": "Funfun", "buy": "Rà", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 7e05d4471..4a914b233 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -75,6 +75,7 @@ "bitcoin_dark_theme": "比特币黑暗主题", "bitcoin_light_theme": "比特币浅色主题", "bitcoin_payments_require_1_confirmation": "比特币支付需要 1 次确认,这可能需要 20 分钟或更长时间。谢谢你的耐心!确认付款后,您将收到电子邮件。", + "block_remaining": "剩下1个块", "Blocks_remaining": "${status} 剩余的块", "bright_theme": "明亮", "buy": "购买", diff --git a/tool/configure.dart b/tool/configure.dart index 61d8b1b89..c388a1579 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -67,6 +67,7 @@ import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart';"""; const bitcoinCWHeaders = """ +import 'package:cw_bitcoin/bitcoin_receive_page_option.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; @@ -77,6 +78,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; @@ -135,6 +137,11 @@ abstract class Bitcoin { TransactionPriority getLitecoinTransactionPriorityMedium(); TransactionPriority getBitcoinTransactionPrioritySlow(); TransactionPriority getLitecoinTransactionPrioritySlow(); + + Future setAddressType(Object wallet, dynamic option); + BitcoinReceivePageOption getSelectedAddressType(Object wallet); + bool hasSelectedSilentPayments(Object wallet); + List getBitcoinReceivePageOptions(); } """;