diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 72c928b67..6ce7ef9c7 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:typed_data'; class BitcoinAddressRecord { BitcoinAddressRecord(this.address, @@ -25,7 +24,7 @@ class BitcoinAddressRecord { final String address; final bool isHidden; final String? silentAddressLabel; - final Uint8List? silentPaymentTweak; + final String? silentPaymentTweak; final int index; bool get isUsed => _isUsed; diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index fd5ed325d..e5be417ec 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -1,18 +1,20 @@ -import 'dart:typed_data'; - import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/unspent_transaction_output.dart'; class BitcoinUnspent extends Unspent { BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout, - {this.silentPaymentTweak}) + {this.silentPaymentTweak, bool? isTaproot}) : bitcoinAddressRecord = addressRecord, + isTaproot = isTaproot ?? false, super(addressRecord.address, hash, value, vout, null); factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map json) => BitcoinUnspent( - address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int); + address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int, + silentPaymentTweak: json['silent_payment_tweak'] as String?, + isTaproot: json['is_taproot'] as bool?); final BitcoinAddressRecord bitcoinAddressRecord; - Uint8List? silentPaymentTweak; + String? silentPaymentTweak; + bool isTaproot = false; } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 051a215ab..27877c315 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:isolate'; import 'dart:math'; +import 'package:http/http.dart' as http; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:hive/hive.dart'; @@ -25,26 +26,18 @@ import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; -import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:collection/collection.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/sync_status.dart'; -import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:flutter/foundation.dart'; import 'package:hex/hex.dart'; -import 'package:hive/hive.dart'; -import 'package:mobx/mobx.dart'; -import 'package:rxdart/subjects.dart'; part 'electrum_wallet.g.dart'; @@ -141,15 +134,66 @@ abstract class ElectrumWalletBase Map?> _scripthashesUpdateSubject; BehaviorSubject? _chainTipUpdateSubject; bool _isTransactionUpdating; - int _initialSyncHeight = 0; Future? _isolate; void Function(FlutterErrorDetails)? _onError; + Timer? _autoSaveTimer; + static const int _autoSaveInterval = 30; Future init() async { await walletAddresses.init(); await transactionHistory.init(); - await save(); + + _autoSaveTimer = + Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); + } + + void _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); + } + + final receivePort = ReceivePort(); + _isolate = Isolate.spawn( + startRefresh, + ScanData( + sendPort: receivePort.sendPort, + silentAddress: walletAddresses.silentAddress!.toString(), + scanPrivkeyCompressed: + walletAddresses.silentAddress!.scanPrivkey.toCompressedHex().fromHex, + spendPubkeyCompressed: + walletAddresses.silentAddress!.spendPubkey.toCompressedHex().fromHex, + networkType: networkType, + height: height, + chainTip: currentChainTip, + electrumClient: ElectrumClient(), + transactionHistoryIds: transactionHistory.transactions.keys.toList(), + node: electrumClient.uri.toString(), + )); + + await for (var message in receivePort) { + if (message is BitcoinUnspent) { + unspentCoins.add(message); + walletAddresses.addresses.add(message.bitcoinAddressRecord); + await save(); + + await updateUnspent(); + await updateBalance(); + await updateTransactions(); + _subscribeForUpdates(); + } + + // 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 @@ -190,6 +234,12 @@ abstract class ElectrumWalletBase } }; syncStatus = ConnectedSyncStatus(); + + final currentChainTip = await electrumClient.getCurrentBlockChainTip(); + + if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { + _setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); + } } catch (e) { print(e.toString()); syncStatus = FailedSyncStatus(); @@ -299,14 +349,13 @@ abstract class ElectrumWalletBase outpoints.add(bitcoin.Outpoint(txid: utx.hash, index: utx.vout)); - print(utx.bitcoinAddressRecord.silentPaymentTweak); if (utx.bitcoinAddressRecord.silentPaymentTweak != null) { // https://github.com/bitcoin/bips/blob/c55f80c53c98642357712c1839cfdc0551d531c4/bip-0352.mediawiki#user-content-Spending final d = bitcoin.PrivateKey.fromHex(bitcoin.getSecp256k1(), walletAddresses.silentAddress!.spendPrivkey.toCompressedHex()) - .tweakAdd(utx.bitcoinAddressRecord.silentPaymentTweak!.bigint)!; + .tweakAdd(utx.bitcoinAddressRecord.silentPaymentTweak!.fromHex.bigint)!; - inputPrivKeys.add(bitcoin.PrivateKeyInfo(d, true)); + inputPrivKeys.add(bitcoin.PrivateKeyInfo(d, utx.isTaproot)); final point = bitcoin.ECPublic.fromHex(d.publicKey.toHex()).toTapPoint(); final p2tr = bitcoin.P2trAddress(program: point); @@ -330,7 +379,7 @@ abstract class ElectrumWalletBase network: networkType) .privateKey! .hex), - false)); + utx.isTaproot)); bitcoin.ECPair keyPair = generateKeyPair( hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, @@ -372,7 +421,6 @@ abstract class ElectrumWalletBase final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; if (outputAddress.startsWith('tsp1')) { - print([outputAddress, outputAmount!]); silentPaymentDestinations .add(bitcoin.SilentPaymentDestination.fromAddress(outputAddress, outputAmount!)); } else { @@ -382,9 +430,8 @@ abstract class ElectrumWalletBase if (silentPaymentDestinations.isNotEmpty) { final outpointsHash = bitcoin.SilentPayment.hashOutpoints(outpoints); - final aSum = bitcoin.SilentPayment.getSumInputPrivKeys(inputPrivKeys); final generatedOutputs = bitcoin.SilentPayment.generateMultipleRecipientPubkeys( - aSum, outpointsHash, silentPaymentDestinations); + inputPrivKeys, outpointsHash, silentPaymentDestinations); generatedOutputs.forEach((recipientSilentAddress, generatedOutput) { generatedOutput.forEach((output) { @@ -544,60 +591,12 @@ abstract class ElectrumWalletBase @action @override Future rescan({required int height, int? chainTip, ScanData? scanData}) async { - syncStatus = AttemptingSyncStatus(); - - walletInfo.restoreHeight = height; - await walletInfo.save(); - - final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; - - if (_isolate != null) { - final runningIsolate = await _isolate!; - runningIsolate.kill(priority: Isolate.immediate); + if (height >= walletInfo.restoreHeight) { + syncStatus = SyncedSyncStatus(); + return; } - // if (currentChainTip <= height) { - // syncStatus = SyncedSyncStatus(); - // return; - // } - - final receivePort = ReceivePort(); - _isolate = Isolate.spawn( - startRefresh, - ScanData( - sendPort: receivePort.sendPort, - silentAddress: walletAddresses.silentAddress!.toString(), - scanPrivkeyCompressed: - walletAddresses.silentAddress!.scanPrivkey.toCompressedHex().fromHex, - spendPubkeyCompressed: - walletAddresses.silentAddress!.spendPubkey.toCompressedHex().fromHex, - networkType: networkType, - height: walletInfo.restoreHeight, - chainTip: currentChainTip, - initialSyncHeight: _initialSyncHeight, - electrumClient: ElectrumClient(), - transactionHistoryIds: transactionHistory.transactions.keys.toList(), - node: electrumClient.uri.toString())); - - await for (var message in receivePort) { - if (message is BitcoinUnspent) { - unspentCoins.add(message); - walletAddresses.addresses.add(message.bitcoinAddressRecord); - await save(); - - await updateUnspent(); - await updateBalance(); - await updateTransactions(); - _subscribeForUpdates(); - } - - // 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(); - } - } + _setListeners(height); } @override @@ -605,6 +604,7 @@ abstract class ElectrumWalletBase try { await electrumClient.close(); } catch (_) {} + _autoSaveTimer?.cancel(); } Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); @@ -780,6 +780,20 @@ abstract class ElectrumWalletBase } }); }); + await _chainTipUpdateSubject?.close(); + _chainTipUpdateSubject = electrumClient.chainTipUpdate(); + _chainTipUpdateSubject!.listen((event) async { + try { + rescan(height: walletInfo.restoreHeight); + } catch (e, s) { + print(e.toString()); + _onError?.call(FlutterErrorDetails( + exception: e, + stack: s, + library: this.runtimeType.toString(), + )); + } + }); } Future _fetchBalances() async { @@ -864,8 +878,6 @@ abstract class ElectrumWalletBase final currentHeight = await electrumClient.getCurrentBlockChainTip(); if (currentHeight != null) walletInfo.restoreHeight = currentHeight; } - - _initialSyncHeight = walletInfo.restoreHeight; } } @@ -929,40 +941,35 @@ class ScanData { final String node; final bitcoin.NetworkType networkType; final int chainTip; - final int initialSyncHeight; final ElectrumClient electrumClient; final List transactionHistoryIds; - final int? checkpointTxPos; + ScanData({ + required this.sendPort, + required this.scanPrivkeyCompressed, + required this.spendPubkeyCompressed, + required this.silentAddress, + required this.height, + required this.node, + required this.networkType, + required this.chainTip, + required this.electrumClient, + required this.transactionHistoryIds, + }); - ScanData( - {required this.sendPort, - required this.scanPrivkeyCompressed, - required this.spendPubkeyCompressed, - required this.silentAddress, - required this.height, - required this.node, - required this.networkType, - required this.chainTip, - required this.initialSyncHeight, - required this.electrumClient, - required this.transactionHistoryIds, - this.checkpointTxPos}); - - factory ScanData.withCheckpoint(ScanData scanData, int newHeight, int? checkpointTx) { + factory ScanData.fromHeight(ScanData scanData, int newHeight) { return ScanData( - sendPort: scanData.sendPort, - scanPrivkeyCompressed: scanData.scanPrivkeyCompressed, - spendPubkeyCompressed: scanData.spendPubkeyCompressed, - silentAddress: scanData.silentAddress, - height: newHeight, - node: scanData.node, - networkType: scanData.networkType, - chainTip: scanData.chainTip, - initialSyncHeight: scanData.initialSyncHeight, - electrumClient: scanData.electrumClient, - transactionHistoryIds: scanData.transactionHistoryIds, - checkpointTxPos: checkpointTx); + sendPort: scanData.sendPort, + scanPrivkeyCompressed: scanData.scanPrivkeyCompressed, + spendPubkeyCompressed: scanData.spendPubkeyCompressed, + silentAddress: scanData.silentAddress, + height: newHeight, + node: scanData.node, + networkType: scanData.networkType, + chainTip: scanData.chainTip, + transactionHistoryIds: scanData.transactionHistoryIds, + electrumClient: scanData.electrumClient, + ); } } @@ -974,181 +981,235 @@ class SyncResponse { } Future startRefresh(ScanData scanData) async { - final currentChainTip = scanData.chainTip; - // if (scanData.height >= currentChainTip) { - // scanData.sendPort.send(SyncResponse(scanData.height, SyncedSyncStatus())); - // return; - // } + var cachedBlockchainHeight = scanData.chainTip; - var checkpointTxPos = scanData.checkpointTxPos; - final height = scanData.height; + 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)); + } - print(["HEIGHT:", height]); - - try { - final track = currentChainTip - height; - final diff = (currentChainTip - scanData.initialSyncHeight) - track; - final ptc = diff <= 0 ? 0.0 : diff / track; - scanData.sendPort.send(SyncResponse(height, SyncingSyncStatus(track, ptc))); - - final electrumClient = scanData.electrumClient; - if (!electrumClient.isConnected) { - final node = scanData.node; - await electrumClient.connectToUri(Uri.parse(node)); + cachedBlockchainHeight = + await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight; } - // tx pos always begin from 1 --> we know pos 0 is coinbase tx - int pos = checkpointTxPos ?? 1; - while (true) { - try { - final txid = await electrumClient.getTxidFromPos(height: height, pos: pos); - print(["scanning tx:", txid]); + return cachedBlockchainHeight; + } - // TODO: if already tx already scanned & stored skip - // if (scanData.transactionHistoryIds.contains(txid)) { - // // already scanned tx, continue to next tx - // checkpointTxPos = pos; - // pos++; - // continue; - // } + var lastKnownBlockHeight = 0; + var initialSyncHeight = 0; - List pubkeys = []; - List outpoints = []; + var syncHeight = scanData.height; + var currentChainTip = scanData.chainTip; - try { - final txBundle = await getTransactionExpanded( - hash: txid, - height: height, - electrumClient: electrumClient, - networkType: scanData.networkType); + if (syncHeight <= 0) { + syncHeight = currentChainTip; + } - bool skip = false; - txBundle.originalTransaction.ins.forEach((input) { - if (input.witness == null) { - skip = true; - return; - } + if (initialSyncHeight <= 0) { + initialSyncHeight = syncHeight; + } - if (input.witness!.length != 2) { - skip = true; - return; - } + if (lastKnownBlockHeight == syncHeight) { + scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); + return; + } - final pubkey = input.witness![1].hex; - pubkeys.add(pubkey); - outpoints.add(bitcoin.Outpoint( - txid: HEX.encode(input.hash!.reversed.toList()), index: input.index!)); - }); + // 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; - if (skip) { - // skipped tx, save checkpoint in case of issues and continue to next tx - checkpointTxPos = pos; - pos++; - continue; + final syncingStatus = + SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight); + scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); + + if (syncingStatus.blocksLeft <= 0) { + scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); + return; + } + + print(["Scanning from height:", syncHeight]); + + try { + final networkPath = + scanData.networkType.network == bitcoin.BtcNetwork.mainnet ? "" : "/testnet"; + + // 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; + + decodedBlocks.sort((a, b) => (a["height"] as int).compareTo(b["height"] as int)); + decodedBlocks = + decodedBlocks.where((element) => (element["height"] as int) >= syncHeight).toList(); + + // 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; + + // print(["Scanning block index:", i, "with tx count:", txCount]); + + 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; + + // print(["Scanning txs index:", startIndex]); + + // 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; + + // print(["Scanning tx:", txid]); + + // TODO: if tx already scanned & stored skip + // if (scanData.transactionHistoryIds.contains(txid)) { + // // already scanned tx, continue to next tx + // pos++; + // continue; + // } + + List pubkeys = []; + List outpoints = []; + + bool skip = false; + + for (var i = 0; i < (tx["vin"] as List).length; i++) { + final input = tx["vin"][i]; + if (input["witness"] == null) { + skip = true; + // print("Skipping, no witness"); + break; + } + + if (input["witness"].length != 2) { + skip = true; + // print("Skipping, invalid witness"); + break; + } + + final pubkey = input["witness"][1] as String; + pubkeys.add(pubkey); + outpoints.add(bitcoin.Outpoint( + txid: HEX.encode((input["txid"] as String).fromHex.reversed.toList()), + index: input["vout"] as int)); + } + + if (skip) { + // skipped tx, continue to next tx + continue; + } + + 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"); + break; + } + + final script = (output["scriptpubkey"] as String).fromHex; + + // final alreadySpentOutput = (await electrumClient.getHistory( + // scriptHashFromScript(script, networkType: scanData.networkType))) + // .length > + // 1; + + // if (alreadySpentOutput) { + // print("Skipping, invalid witness"); + // break; + // } + + final p2tr = bitcoin.P2trAddress(program: script.sublist(2).hex); + final address = p2tr.toAddress(scanData.networkType); + + // print(["Verifying taproot address:", address]); + + outpointsByP2TRpubkey[p2tr.toScriptPubKey().toBytes()] = + 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( + bitcoin.PrivateKey.fromHex( + bitcoin.getSecp256k1(), scanData.scanPrivkeyCompressed.hex), + bitcoin.PublicKey.fromHex( + bitcoin.getSecp256k1(), scanData.spendPubkeyCompressed.hex), + bitcoin.getSumInputPubKeys(pubkeys), + outpointHash, + outpointsByP2TRpubkey.keys.toList()); + + 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!"); + } + print(result); + + result.forEach((key, value) { + final outpoint = outpointsByP2TRpubkey[key.fromHex]; + + if (outpoint == null) { + return; + } + + // found utxo for tx + scanData.sendPort.send(BitcoinUnspent( + BitcoinAddressRecord( + key, + index: 0, + isHidden: false, + isUsed: true, + silentAddressLabel: null, + silentPaymentTweak: value, + ), + outpoint.txid, + outpoint.value!, + outpoint.index, + silentPaymentTweak: value, + )); + }); + } catch (_) {} } - Map outpointsByP2TRpubkey = {}; - int i = 0; - txBundle.originalTransaction.outs.forEach((output) { - if (bitcoin.classifyOutput(output.script!) != "taproot") { - return; - } - - final address = bitcoin.P2trAddress(program: output.script!.sublist(2).hex).toAddress( - scanData.networkType.bech32 == bitcoin.testnet.bech32 - ? bitcoin.NetworkInfo.TESTNET - : bitcoin.NetworkInfo.BITCOIN); - - print(["verifying taproot address:", address]); - - outpointsByP2TRpubkey[address] = - bitcoin.Outpoint(txid: txid, index: i, value: output.value!); - - i++; - }); - - if (pubkeys.isEmpty || outpoints.isEmpty || outpointsByP2TRpubkey.isEmpty) { - // skipped tx, save checkpoint in case of issues and continue to next tx - checkpointTxPos = pos; - pos++; - continue; - } - - Uint8List sumOfInputPublicKeys = - bitcoin.getSumInputPubKeys(pubkeys).toCompressedHex().fromHex; - final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); - - final result = bitcoin.scanOutputs( - scanData.scanPrivkeyCompressed, - scanData.spendPubkeyCompressed, - sumOfInputPublicKeys, - outpointHash, - outpointsByP2TRpubkey.keys.toList()); - - if (result.isEmpty) { - // no results tx, save checkpoint in case of issues and continue to next tx - checkpointTxPos = pos; - pos++; - continue; - } - - if (result.length > 1) { - print("MULTIPLE UNSPENT COINS FOUND!"); - } else { - print("UNSPENT COIN FOUND!"); - } - print(result); - - result.forEach((key, value) { - final outpoint = outpointsByP2TRpubkey[key]; - - if (outpoint == null) { - return; - } - - // found utxo for tx - scanData.sendPort.send(BitcoinUnspent( - BitcoinAddressRecord( - key, - index: 0, - isHidden: false, - isUsed: true, - silentAddressLabel: null, - silentPaymentTweak: value, - ), - outpoint.txid, - outpoint.value!, - outpoint.index, - silentPaymentTweak: value, - )); - }); - } catch (e, stacktrace) { - print(stacktrace); - print(e.toString()); + // Finished scanning batch of txs in block, add 25 to start index and continue to next block in loop + startIndex += 25; } - pos++; - } catch (e, stacktrace) { - print(stacktrace); - print(e.toString()); - - // last position, no more txs for the given block height - 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()); + + break; } - - final newHeight = height + 1; - if (newHeight < currentChainTip) { - // recursive, scan next block again until at the current tip - return startRefresh(ScanData.withCheckpoint(scanData, newHeight, 1)); - } - - // otherwise, finished scanning - scanData.sendPort.send(SyncResponse(currentChainTip, SyncedSyncStatus())); - } catch (e, stacktrace) { - print(stacktrace); - print(e.toString()); - - startRefresh(ScanData.withCheckpoint(scanData, height, checkpointTxPos)); } } diff --git a/cw_bitcoin/lib/litecoin_network.dart b/cw_bitcoin/lib/litecoin_network.dart index d7ad2f837..adaf5f7be 100644 --- a/cw_bitcoin/lib/litecoin_network.dart +++ b/cw_bitcoin/lib/litecoin_network.dart @@ -1,9 +1,15 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart'; final litecoinNetwork = NetworkType( - messagePrefix: '\x19Litecoin Signed Message:\n', - bech32: 'ltc', - bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), - pubKeyHash: 0x30, - scriptHash: 0x32, - wif: 0xb0); + messagePrefix: '\x19Litecoin Signed Message:\n', + bech32: 'ltc', + bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0, + p2pkhPrefix: bitcoin.p2pkhPrefix, + network: bitcoin.network, + p2shPrefix: bitcoin.p2shPrefix, + extendPublic: bitcoin.extendPublic, + extendPrivate: bitcoin.extendPrivate, +); diff --git a/cw_bitcoin/lib/script_hash.dart b/cw_bitcoin/lib/script_hash.dart index 76a1bfcf0..5fde8025b 100644 --- a/cw_bitcoin/lib/script_hash.dart +++ b/cw_bitcoin/lib/script_hash.dart @@ -1,9 +1,25 @@ +import 'dart:typed_data'; + import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:crypto/crypto.dart'; String scriptHash(String address, {required bitcoin.NetworkType networkType}) { - final outputScript = - bitcoin.Address.addressToOutputScript(address, networkType); + final outputScript = bitcoin.Address.addressToOutputScript(address, networkType); + final parts = sha256.convert(outputScript).toString().split(''); + var res = ''; + + for (var i = parts.length - 1; i >= 0; i--) { + final char = parts[i]; + i--; + final nextChar = parts[i]; + res += nextChar; + res += char; + } + + return res; +} + +String scriptHashFromScript(Uint8List outputScript, {required bitcoin.NetworkType networkType}) { final parts = sha256.convert(outputScript).toString().split(''); var res = ''; diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 616ae272e..4c32b8a9a 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -88,7 +88,7 @@ packages: description: path: "." ref: silent-payments - resolved-ref: c96f9d5344e7c6a6164d0c184d77cbd6d8e21e65 + resolved-ref: "18dc839f06b98a9319334e3fac316e8be9ca52dc" url: "https://github.com/cake-tech/bitcoin_flutter" source: git version: "2.0.2" @@ -168,10 +168,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2" url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.8.0" characters: dependency: transitive description: @@ -200,10 +200,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.8.0" collection: dependency: transitive description: @@ -256,7 +256,7 @@ packages: description: path: "." ref: silent-payments - resolved-ref: ad7b1cccb54b5feba8ead4a0f67cb98a5a01a88b + resolved-ref: "09343cf1bc03e74715413972dbec62a2505f5011" url: "https://github.com/cake-tech/dart-elliptic" source: git version: "0.3.10" diff --git a/cw_core/lib/sync_status.dart b/cw_core/lib/sync_status.dart index 8b4c3210c..afddc7c7a 100644 --- a/cw_core/lib/sync_status.dart +++ b/cw_core/lib/sync_status.dart @@ -14,6 +14,16 @@ class SyncingSyncStatus extends SyncStatus { @override String toString() => '$blocksLeft'; + + factory SyncingSyncStatus.fromHeightValues(int chainTip, int initialSyncHeight, int syncHeight) { + final track = chainTip - initialSyncHeight; + final diff = track - (chainTip - syncHeight); + final ptc = diff <= 0 ? 0.0 : diff / track; + final left = chainTip - syncHeight; + + // sum 1 because if at the chain tip, will say "0 blocks left" + return SyncingSyncStatus(left + 1, ptc); + } } class SyncedSyncStatus extends SyncStatus { diff --git a/lib/entities/background_tasks.dart b/lib/entities/background_tasks.dart index 9681c45de..fffde3629 100644 --- a/lib/entities/background_tasks.dart +++ b/lib/entities/background_tasks.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +// import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -8,9 +8,9 @@ import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; +// import 'package:cw_bitcoin/bitcoin_address_record.dart'; +// import 'package:cw_bitcoin/bitcoin_unspent.dart'; +// import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; @@ -21,7 +21,7 @@ import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/di.dart'; const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task"; -const bitcoinSilentPaymentsSyncTaskKey = "com.fotolockr.cakewallet.bitcoin_sync_task"; +// const bitcoinSilentPaymentsSyncTaskKey = "com.fotolockr.cakewallet.bitcoin_sync_task"; @pragma('vm:entry-point') void callbackDispatcher() { @@ -85,129 +85,129 @@ void callbackDispatcher() { } break; - case bitcoinSilentPaymentsSyncTaskKey: + // case bitcoinSilentPaymentsSyncTaskKey: - /// The work manager runs on a separate isolate from the main flutter isolate. - /// thus we initialize app configs first; hive, getIt, etc... - await initializeAppConfigs(); + // /// The work manager runs on a separate isolate from the main flutter isolate. + // /// thus we initialize app configs first; hive, getIt, etc... + // await initializeAppConfigs(); - final walletLoadingService = getIt.get(); + // final walletLoadingService = getIt.get(); - final name = getIt.get().getString(PreferencesKey.currentWalletName); - final wallet = - await walletLoadingService.load(WalletType.bitcoin, name!) as BitcoinWallet; + // final name = getIt.get().getString(PreferencesKey.currentWalletName); + // final wallet = + // await walletLoadingService.load(WalletType.bitcoin, name!) as BitcoinWallet; - String? checkpointTx = inputData!["checkpoint_tx"] as String?; + // String? checkpointTx = inputData!["checkpoint_tx"] as String?; - try { - final height = wallet.walletInfo.restoreHeight; + // try { + // final height = wallet.walletInfo.restoreHeight; - print(["HEIGHT:", height]); + // print(["HEIGHT:", height]); - List txids = []; - int pos = 0; - while (pos < 150) { - var txid = await wallet.electrumClient.getTxidFromPos(height: height, pos: pos); - print(["TXID", txid]); - txids.add(txid); - pos++; - } + // List txids = []; + // int pos = 0; + // while (pos < 150) { + // var txid = await wallet.electrumClient.getTxidFromPos(height: height, pos: pos); + // print(["TXID", txid]); + // txids.add(txid); + // pos++; + // } - if (checkpointTx != null && checkpointTx.isNotEmpty) { - txids = txids.sublist(txids.indexOf(checkpointTx) + 1); - } + // if (checkpointTx != null && checkpointTx.isNotEmpty) { + // txids = txids.sublist(txids.indexOf(checkpointTx) + 1); + // } - List unspentCoins = []; + // List unspentCoins = []; - for (var txid in txids) { - checkpointTx = txid.toString(); + // for (var txid in txids) { + // checkpointTx = txid.toString(); - List pubkeys = []; - List outpoints = []; - Map outpointsByP2TRpubkey = {}; + // List pubkeys = []; + // List outpoints = []; + // Map outpointsByP2TRpubkey = {}; - // final txInfo = - // await wallet.fetchTransactionInfo(hash: txid.toString(), height: height); + // // final txInfo = + // // await wallet.fetchTransactionInfo(hash: txid.toString(), height: height); - // print(txInfo); + // // print(txInfo); - bool skip = false; - // obj["vin"].forEach((input) { - // if (input["witness"] == null) { - // skip = true; - // return; - // } + // bool skip = false; + // // obj["vin"].forEach((input) { + // // if (input["witness"] == null) { + // // skip = true; + // // return; + // // } - // final witness = input["witness"] as List; - // if (witness.length != 2) { - // skip = true; - // return; - // } + // // final witness = input["witness"] as List; + // // if (witness.length != 2) { + // // skip = true; + // // return; + // // } - // final pubkey = witness[1] as String; - // pubkeys.add(pubkey); - // outpoints.add(bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int)); - // }); + // // final pubkey = witness[1] as String; + // // pubkeys.add(pubkey); + // // outpoints.add(bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int)); + // // }); - // if (skip) continue; + // // if (skip) continue; - // int i = 0; - // obj['vout'].forEach((out) { - // if (out["scriptpubkey_type"] != "v1_p2tr") { - // return; - // } + // // int i = 0; + // // obj['vout'].forEach((out) { + // // if (out["scriptpubkey_type"] != "v1_p2tr") { + // // return; + // // } - // outpointsByP2TRpubkey[out['scriptpubkey_address'] as String] = - // bitcoin.Outpoint(txid: txid.toString(), index: i, value: out["value"] as int); + // // outpointsByP2TRpubkey[out['scriptpubkey_address'] as String] = + // // bitcoin.Outpoint(txid: txid.toString(), index: i, value: out["value"] as int); - // i++; - // }); + // // i++; + // // }); - if (pubkeys.isEmpty && outpoints.isEmpty && outpointsByP2TRpubkey.isEmpty) { - continue; - } + // if (pubkeys.isEmpty && outpoints.isEmpty && outpointsByP2TRpubkey.isEmpty) { + // continue; + // } - Uint8List sumOfInputPublicKeys = - bitcoin.getSumInputPubKeys(pubkeys).toCompressedHex().fromHex; - final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); + // Uint8List sumOfInputPublicKeys = + // bitcoin.getSumInputPubKeys(pubkeys).toCompressedHex().fromHex; + // final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); - final result = bitcoin.scanOutputs( - wallet.walletAddresses.silentAddress!.scanPrivkey.toCompressedHex().fromHex, - wallet.walletAddresses.silentAddress!.spendPubkey.toCompressedHex().fromHex, - sumOfInputPublicKeys, - outpointHash, - outpointsByP2TRpubkey.keys.toList()); + // final result = bitcoin.scanOutputs( + // wallet.walletAddresses.silentAddress!.scanPrivkey.toCompressedHex().fromHex, + // wallet.walletAddresses.silentAddress!.spendPubkey.toCompressedHex().fromHex, + // sumOfInputPublicKeys, + // outpointHash, + // outpointsByP2TRpubkey.keys.toList()); - if (result.isEmpty) { - continue; - } + // if (result.isEmpty) { + // continue; + // } - result.forEach((key, value) { - final outpoint = outpointsByP2TRpubkey[key]; + // result.forEach((key, value) { + // final outpoint = outpointsByP2TRpubkey[key]; - if (outpoint == null) { - return; - } + // if (outpoint == null) { + // return; + // } - unspentCoins.add(BitcoinUnspent( - BitcoinAddressRecord(key, index: 0), - outpoint.txid, - outpoint.value!, - outpoint.index, - silentPaymentTweak: value, - )); - }); - } + // unspentCoins.add(BitcoinUnspent( + // BitcoinAddressRecord(key, index: 0), + // outpoint.txid, + // outpoint.value!, + // outpoint.index, + // silentPaymentTweak: value, + // )); + // }); + // } - break; - } catch (e, stacktrace) { - print(stacktrace); - print(e.toString()); - // timeout, wait 30sec - // return Future.delayed(const Duration(seconds: 30), - // () => startRefresh({"checkpoint_tx": checkpointTx ?? ""})); - } - break; + // break; + // } catch (e, stacktrace) { + // print(stacktrace); + // print(e.toString()); + // // timeout, wait 30sec + // // return Future.delayed(const Duration(seconds: 30), + // // () => startRefresh({"checkpoint_tx": checkpointTx ?? ""})); + // } + // break; } return Future.value(true); @@ -276,59 +276,59 @@ class BackgroundTasks { ); } - bool hasBitcoin = getIt - .get() - .wallets - .any((element) => element.type == WalletType.bitcoin); + // bool hasBitcoin = getIt + // .get() + // .wallets + // .any((element) => element.type == WalletType.bitcoin); - /// if its not android nor ios, or the user has no monero wallets; exit - if (hasBitcoin) { - final settingsStore = getIt.get(); + // /// if its not android nor ios, or the user has no monero wallets; exit + // if (hasBitcoin) { + // final settingsStore = getIt.get(); - final SyncMode syncMode = settingsStore.currentSyncMode; - final bool syncAll = settingsStore.currentSyncAll; + // final SyncMode syncMode = settingsStore.currentSyncMode; + // final bool syncAll = settingsStore.currentSyncAll; - if (syncMode.type == SyncType.disabled) { - cancelSyncTask(); - return; - } + // if (syncMode.type == SyncType.disabled) { + // cancelSyncTask(); + // return; + // } - await Workmanager().initialize( - callbackDispatcher, - isInDebugMode: kDebugMode, - ); + // await Workmanager().initialize( + // callbackDispatcher, + // isInDebugMode: kDebugMode, + // ); - final inputData = {"sync_all": syncAll}; - final constraints = Constraints( - networkType: - syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected, - requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive, - requiresCharging: syncMode.type == SyncType.unobtrusive, - requiresDeviceIdle: syncMode.type == SyncType.unobtrusive, - ); + // final inputData = {"sync_all": syncAll}; + // final constraints = Constraints( + // networkType: + // syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected, + // requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive, + // requiresCharging: syncMode.type == SyncType.unobtrusive, + // requiresDeviceIdle: syncMode.type == SyncType.unobtrusive, + // ); - if (Platform.isIOS) { - await Workmanager().registerOneOffTask( - bitcoinSilentPaymentsSyncTaskKey, - bitcoinSilentPaymentsSyncTaskKey, - initialDelay: syncMode.frequency, - existingWorkPolicy: ExistingWorkPolicy.replace, - inputData: inputData, - constraints: constraints, - ); - return; - } + // if (Platform.isIOS) { + // await Workmanager().registerOneOffTask( + // bitcoinSilentPaymentsSyncTaskKey, + // bitcoinSilentPaymentsSyncTaskKey, + // initialDelay: syncMode.frequency, + // existingWorkPolicy: ExistingWorkPolicy.replace, + // inputData: inputData, + // constraints: constraints, + // ); + // return; + // } - await Workmanager().registerPeriodicTask( - bitcoinSilentPaymentsSyncTaskKey, - bitcoinSilentPaymentsSyncTaskKey, - initialDelay: syncMode.frequency, - frequency: syncMode.frequency, - existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep, - inputData: inputData, - constraints: constraints, - ); - } + // await Workmanager().registerPeriodicTask( + // bitcoinSilentPaymentsSyncTaskKey, + // bitcoinSilentPaymentsSyncTaskKey, + // initialDelay: syncMode.frequency, + // frequency: syncMode.frequency, + // existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep, + // inputData: inputData, + // constraints: constraints, + // ); + // } } catch (error, stackTrace) { print(error); print(stackTrace); @@ -344,128 +344,3 @@ class BackgroundTasks { } } } - -Future> startRefresh(Map data) async { - // final rootIsolateToken = data["rootIsolateToken"] as RootIsolateToken; - - // BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); - - /// The work manager runs on a separate isolate from the main flutter isolate. - /// thus we initialize app configs first; hive, getIt, etc... - await initializeAppConfigs(); - - final walletLoadingService = getIt.get(); - - final name = getIt.get().getString(PreferencesKey.currentWalletName); - final wallet = await walletLoadingService.load(WalletType.bitcoin, name!) as BitcoinWallet; - - String? checkpointTx = data["checkpoint_tx"] as String?; - - try { - final height = wallet.walletInfo.restoreHeight; - - print(["HEIGHT:", height]); - - List txids = []; - int pos = 0; - while (pos < 150) { - var txid = await wallet.electrumClient.getTxidFromPos(height: height, pos: pos); - print(["TXID", txid]); - txids.add(txid); - pos++; - } - - if (checkpointTx != null && checkpointTx.isNotEmpty) { - txids = txids.sublist(txids.indexOf(checkpointTx) + 1); - } - - List unspentCoins = []; - - for (var txid in txids) { - checkpointTx = txid.toString(); - - List pubkeys = []; - List outpoints = []; - Map outpointsByP2TRpubkey = {}; - - // final txInfo = await wallet.fetchTransactionInfo(hash: txid.toString(), height: height); - - // print(txInfo); - - bool skip = false; - // obj["vin"].forEach((input) { - // if (input["witness"] == null) { - // skip = true; - // return; - // } - - // final witness = input["witness"] as List; - // if (witness.length != 2) { - // skip = true; - // return; - // } - - // final pubkey = witness[1] as String; - // pubkeys.add(pubkey); - // outpoints.add(bitcoin.Outpoint(txid: input["txid"] as String, index: input["vout"] as int)); - // }); - - // if (skip) continue; - - // int i = 0; - // obj['vout'].forEach((out) { - // if (out["scriptpubkey_type"] != "v1_p2tr") { - // return; - // } - - // outpointsByP2TRpubkey[out['scriptpubkey_address'] as String] = - // bitcoin.Outpoint(txid: txid.toString(), index: i, value: out["value"] as int); - - // i++; - // }); - - if (pubkeys.isEmpty && outpoints.isEmpty && outpointsByP2TRpubkey.isEmpty) { - continue; - } - - Uint8List sumOfInputPublicKeys = - bitcoin.getSumInputPubKeys(pubkeys).toCompressedHex().fromHex; - final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints); - - final result = bitcoin.scanOutputs( - wallet.walletAddresses.silentAddress!.scanPrivkey.toCompressedHex().fromHex, - wallet.walletAddresses.silentAddress!.spendPubkey.toCompressedHex().fromHex, - sumOfInputPublicKeys, - outpointHash, - outpointsByP2TRpubkey.keys.toList()); - - if (result.isEmpty) { - continue; - } - - result.forEach((key, value) { - final outpoint = outpointsByP2TRpubkey[key]; - - if (outpoint == null) { - return; - } - - unspentCoins.add(BitcoinUnspent( - BitcoinAddressRecord(key, index: 0), - outpoint.txid, - outpoint.value!, - outpoint.index, - silentPaymentTweak: value, - )); - }); - } - - return unspentCoins; - } catch (e, stacktrace) { - print(stacktrace); - print(e.toString()); - // timeout, wait 30sec - return Future.delayed( - const Duration(seconds: 30), () => startRefresh({"checkpoint_tx": checkpointTx ?? ""})); - } -} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 54b70c724..411aa201c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,7 +8,6 @@ #include #include -#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -18,9 +17,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) devicelocale_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DevicelocalePlugin"); devicelocale_plugin_register_with_registrar(devicelocale_registrar); - g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); - flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index c7dfb4d7a..1a3958652 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,7 +5,6 @@ list(APPEND FLUTTER_PLUGIN_LIST cw_monero devicelocale - flutter_secure_storage_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 28670772b..b166295a5 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,6 @@ import Foundation import cw_monero import device_info_plus import devicelocale -import flutter_secure_storage_macos import in_app_review import package_info_plus import path_provider_foundation @@ -21,7 +20,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))