diff --git a/.gitignore b/.gitignore index 9482d8f94b..adea2ee8f8 100644 --- a/.gitignore +++ b/.gitignore @@ -185,6 +185,8 @@ lib/core/secure_storage.dart lib/core/secure_storage.dart +lib/dnssec_proof/dnssec_proof.dart + macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/cw_bitcoin/.gitignore b/cw_bitcoin/.gitignore index 1985397a2c..c5bd2ffa68 100644 --- a/cw_bitcoin/.gitignore +++ b/cw_bitcoin/.gitignore @@ -72,3 +72,9 @@ build/ !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 + +/lib/payjoin/payjoin.dart +/lib/silent_payments/sp.dart + +/pubspec.yaml +/pubspec.lock diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index a5c209e918..fe131b15bd 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -12,8 +12,7 @@ import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/payjoin/manager.dart'; -import 'package:cw_bitcoin/payjoin/storage.dart'; +import 'package:cw_bitcoin/payjoin/payjoin.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/psbt/signer.dart'; import 'package:cw_bitcoin/psbt/transaction_builder.dart'; @@ -25,7 +24,6 @@ import 'package:cw_core/output_info.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; @@ -78,9 +76,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialBalance: initialBalance, seedBytes: seedBytes, encryptionFileUtils: encryptionFileUtils, - currency: networkParam == BitcoinNetwork.testnet - ? CryptoCurrency.tbtc - : CryptoCurrency.btc, + currency: + networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, alwaysScan: alwaysScan, ) { // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) @@ -89,7 +86,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { // String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1"; // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType); - payjoinManager = PayjoinManager(PayjoinStorage(payjoinBox), this); + payjoinManager = cwPayjoinManager; + + if (payjoinManager != null) { + payjoinManager!.init(payjoinStorage: payjoinBox, wallet: this); + } + walletAddresses = BitcoinWalletAddresses(walletInfo, initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex, @@ -99,14 +101,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { mainHd: hd, sideHd: accountHD.childKey(Bip32KeyIndex(1)), network: networkParam ?? network, - masterHd: - seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, + masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, isHardwareWallet: walletInfo.isHardwareWallet, payjoinManager: payjoinManager); autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = - this.isEnabledAutoGenerateSubaddress; + this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); } @@ -141,8 +141,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { break; case DerivationType.electrum: default: - seedBytes = - await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); break; } @@ -214,10 +213,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { walletInfo.derivationInfo ??= DerivationInfo(); // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= - snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= - snp?.derivationType ?? DerivationType.electrum; + walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; + walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; final mnemonic = keysData.mnemonic; @@ -226,8 +223,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { if (mnemonic != null) { switch (walletInfo.derivationInfo!.derivationType) { case DerivationType.electrum: - seedBytes = - await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); break; case DerivationType.bip39: default: @@ -272,15 +268,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { @override Future close({bool shouldCleanup = false}) async { - payjoinManager.cleanupSessions(); + payjoinManager?.cleanupSessions(); super.close(shouldCleanup: shouldCleanup); } - late final PayjoinManager payjoinManager; + late final PayjoinManager? payjoinManager; bool get isPayjoinAvailable => unspentCoinsInfo.values - .where((element) => - element.walletId == id && element.isSending && !element.isFrozen) + .where((element) => element.walletId == id && element.isSending && !element.isFrozen) .isNotEmpty; Future buildPsbt({ @@ -298,10 +293,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { }) async { final psbtReadyInputs = []; for (final utxo in utxos) { - final rawTx = - await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); - final publicKeyAndDerivationPath = - publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; + final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; psbtReadyInputs.add(PSBTReadyUtxoWithAddress( utxo: utxo.utxo, @@ -355,8 +348,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Future createTransaction(Object credentials) async { credentials = credentials as BitcoinTransactionCredentials; - final tx = (await super.createTransaction(credentials)) - as PendingBitcoinTransaction; + final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction; final payjoinUri = credentials.payjoinUri; if (payjoinUri == null && !tx.shouldCommitUR()) return tx; @@ -381,8 +373,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { masterFingerprint: Uint8List.fromList([0, 0, 0, 0])); if (tx.shouldCommitUR()) { - tx.unsignedPsbt = transaction.asPsbtV0(); - return tx; + tx.unsignedPsbt = transaction.asPsbtV0(); + return tx; } final originalPsbt = @@ -390,9 +382,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { tx.commitOverride = () async { final sender = - await payjoinManager.initSender(payjoinUri!, originalPsbt, int.parse(tx.feeRate)); - payjoinManager.spawnNewSender( - sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); + await payjoinManager!.initSender(payjoinUri!, originalPsbt, int.parse(tx.feeRate)); + payjoinManager! + .spawnNewSender(sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); }; return tx; @@ -406,8 +398,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Future commitPsbt(String finalizedPsbt) { final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt)); - final btcTx = - BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract())); + final btcTx = BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract())); return PendingBitcoinTransaction( btcTx, @@ -422,8 +413,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { ).commit(); } - Future signPsbt( - String preProcessedPsbt, List utxos) async { + Future signPsbt(String preProcessedPsbt, List utxos) async { final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt)); await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) { @@ -486,17 +476,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Future signMessage(String message, {String? address = null}) async { if (walletInfo.isHardwareWallet) { final addressEntry = address != null - ? walletAddresses.allAddresses - .firstWhere((element) => element.address == address) + ? walletAddresses.allAddresses.firstWhere((element) => element.address == address) : null; final index = addressEntry?.index ?? 0; final isChange = addressEntry?.isHidden == true ? 1 : 0; final accountPath = walletInfo.derivationInfo?.derivationPath; - final derivationPath = - accountPath != null ? "$accountPath/$isChange/$index" : null; + final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; - final signature = await _bitcoinLedgerApp!.signMessage( - message: ascii.encode(message), signDerivationPath: derivationPath); + final signature = await _bitcoinLedgerApp! + .signMessage(message: ascii.encode(message), signDerivationPath: derivationPath); return base64Encode(signature); } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index fcd0b7d8cc..814e828960 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,13 +1,12 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/bip/bip/bip32/bip32.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; -import 'package:cw_bitcoin/payjoin/manager.dart'; +import 'package:cw_bitcoin/payjoin/payjoin.dart'; import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; -import 'package:payjoin_flutter/receive.dart' as payjoin; part 'bitcoin_wallet_addresses.g.dart'; @@ -29,9 +28,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S super.masterHd, }) : super(walletInfo); - final PayjoinManager payjoinManager; - - payjoin.Receiver? currentPayjoinReceiver; + final PayjoinManager? payjoinManager; @observable String? payjoinEndpoint = null; @@ -63,11 +60,13 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S @action Future initPayjoin() async { try { - await payjoinManager.initPayjoin(); - currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress); - payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint(); + await payjoinManager!.initPayjoin(); + payjoinManager!.currentPayjoinReceiver = + await payjoinManager!.getUnusedReceiver(primaryAddress); + payjoinEndpoint = + (await payjoinManager!.currentPayjoinReceiver?.pjUri())?.pjEndpoint() as String; - payjoinManager.resumeSessions(); + payjoinManager!.resumeSessions(); } catch (e) { printV(e); // Ignore Connectivity errors @@ -78,10 +77,12 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S @action Future newPayjoinReceiver() async { try { - currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress); - payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint(); + payjoinManager!.currentPayjoinReceiver = + await payjoinManager!.getUnusedReceiver(primaryAddress); + payjoinEndpoint = + (await payjoinManager!.currentPayjoinReceiver?.pjUri())?.pjEndpoint() as String; - payjoinManager.spawnReceiver(receiver: currentPayjoinReceiver!); + payjoinManager!.spawnReceiver(receiver: payjoinManager!.currentPayjoinReceiver!); } catch (e) { printV(e); // Ignore Connectivity errors diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 7eb1c7b347..21627a9a80 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -49,7 +49,7 @@ import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; -import 'package:sp_scanner/sp_scanner.dart'; +import 'package:cw_bitcoin/silent_payments/sp.dart'; import 'package:hex/hex.dart'; import 'package:cw_core/utils/socket_health_logger.dart'; @@ -364,7 +364,7 @@ abstract class ElectrumWalletBase final receivePort = ReceivePort(); _isolate = Isolate.spawn( - _handleScanSilentPayments, + silentPayments!.handleScanSilentPayments, ScanData( sendPort: receivePort.sendPort, silentAddress: walletAddresses.silentAddress!, @@ -2821,325 +2821,6 @@ class SyncResponse { SyncResponse(this.height, this.syncStatus); } -Future _handleScanSilentPayments(ScanData scanData) async { - var node = Uri.parse("tcp://electrs.cakewallet.com:50001"); - - void log(String message, LogLevel level) { - printV("[Scanning] $message", file: scanData.debugLogPath, level: level); - } - - try { - // if (scanData.shouldSwitchNodes) { - var scanningClient = await ElectrumProvider.connect( - ElectrumTCPService.connect(node), - ); - // } - - log("connected to ${node.toString()}", LogLevel.info); - - int syncHeight = scanData.height; - int initialSyncHeight = syncHeight; - - final receiver = Receiver( - scanData.silentAddress.b_scan.toHex(), - scanData.silentAddress.B_spend.toHex(), - scanData.network == BitcoinNetwork.testnet, - scanData.labelIndexes, - scanData.labelIndexes.length, - ); - - log( - "using receiver: b_scan: ${scanData.silentAddress.b_scan.toHex()}, B_scan: ${scanData.silentAddress.B_spend.toHex()}, b_spend: ${scanData.silentAddress.B_spend.toHex()}, B_spend: ${scanData.silentAddress.B_spend.toHex()}, network: ${scanData.network.value}, labelIndexes: ${scanData.labelIndexes}", - LogLevel.info, - ); - - int getCountToScanPerRequest(int syncHeight) { - if (scanData.isSingleScan) { - return 1; - } - - final amountLeft = scanData.chainTip - syncHeight + 1; - return amountLeft; - } - - // Initial status UI update, send how many blocks in total to scan - scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight))); - - final req = ElectrumTweaksSubscribe( - height: syncHeight, - count: getCountToScanPerRequest(syncHeight), - historicalMode: false, - ); - - var _scanningStream = await scanningClient.subscribe(req); - - log( - "initial request: height: $syncHeight, count: ${getCountToScanPerRequest(syncHeight)}", - LogLevel.info, - ); - - void endScanningSuccesfully() { - if (scanData.isSingleScan) - scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); - else - scanData.sendPort.send( - SyncResponse(syncHeight, SyncedTipSyncStatus(scanData.chainTip)), - ); - - _scanningStream?.close(); - _scanningStream = null; - - log( - "ended: syncHeight: $syncHeight, chainTip: ${scanData.chainTip}, isSingleScan: ${scanData.isSingleScan}", - LogLevel.info, - ); - } - - void listenFn(Map event, ElectrumTweaksSubscribe req) async { - final response = req.onResponse(event); - - if (response == null || _scanningStream == null) { - log( - "ending: response = $response, stream = $_scanningStream", - LogLevel.error, - ); - return; - } - - // is success or error msg - final noData = response.message != null; - - if (noData) { - if (scanData.isSingleScan) { - log("ending: noData and isSingleScan", LogLevel.info); - - endScanningSuccesfully(); - return; - } - - // re-subscribe to continue receiving messages, starting from the next unscanned height - final nextHeight = syncHeight + 1; - - if (nextHeight <= scanData.chainTip) { - log( - "resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}", - LogLevel.info, - ); - - final nextStream = scanningClient.subscribe( - ElectrumTweaksSubscribe( - height: nextHeight, - count: getCountToScanPerRequest(nextHeight), - historicalMode: false, - ), - ); - - if (nextStream != null) { - nextStream.listen((event) => listenFn(event, req)); - } else { - scanData.sendPort.send( - SyncResponse(scanData.height, LostConnectionSyncStatus()), - ); - } - } - - log( - "ending: resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}", - LogLevel.info, - ); - return; - } - - final tweakHeight = response.block; - - if (initialSyncHeight < tweakHeight) initialSyncHeight = tweakHeight; - - // Continuous status UI update, send how many blocks left to scan - final syncingStatus = scanData.isSingleScan - ? SyncingSyncStatus(1, 0) - : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, tweakHeight); - - scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); - - try { - final blockTweaks = response.blockTweaks; - - var blockDate = DateTime.now(); - bool isDateNow = true; - - for (final txid in blockTweaks.keys) { - final tweakData = blockTweaks[txid]; - final outputPubkeys = tweakData!.outputPubkeys; - final tweak = tweakData.tweak; - - try { - final addToWallet = {}; - - // receivers.forEach((receiver) { - // NOTE: scanOutputs, from sp_scanner package, called from rust here - final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver); - - if (scanResult.isEmpty) { - continue; - } - - if (addToWallet[receiver.BSpend] == null) { - addToWallet[receiver.BSpend] = scanResult; - } else { - addToWallet[receiver.BSpend].addAll(scanResult); - } - // }); - - if (addToWallet.isEmpty) { - // no results tx, continue to next tx - continue; - } - - log( - "FOUND: addToWallet: ${addToWallet.length}, txid: $txid, tweak: $tweak, height: $tweakHeight", - LogLevel.info, - ); - - // Every tx in the block has the same date (the block date) - // So, if blockDate exists, reuse - if (isDateNow) { - try { - final tweakBlockHash = await ProxyWrapper() - .get( - clearnetUri: Uri.parse( - "https://mempool.cakewallet.com/api/v1/block-height/$tweakHeight", - ), - ) - .timeout(Duration(seconds: 15)); - final blockResponse = await ProxyWrapper() - .get( - clearnetUri: Uri.parse( - "https://mempool.cakewallet.com/api/v1/block/${tweakBlockHash.body}", - ), - ) - .timeout(Duration(seconds: 15)); - - if (blockResponse.statusCode == 200 && - blockResponse.body.isNotEmpty && - jsonDecode(blockResponse.body)['timestamp'] != null) { - blockDate = DateTime.fromMillisecondsSinceEpoch( - int.parse(jsonDecode(blockResponse.body)['timestamp'].toString()) * 1000, - ); - isDateNow = false; - } - } catch (e, stacktrace) { - printV(stacktrace); - printV(e.toString()); - } - } - - // initial placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) on the following loop - final txInfo = ElectrumTransactionInfo( - WalletType.bitcoin, - id: txid, - height: tweakHeight, - amount: 0, - fee: 0, - direction: TransactionDirection.incoming, - isReplaced: false, - // TODO: fetch block data and get the date from it - date: scanData.network == BitcoinNetwork.mainnet - ? (isDateNow ? getDateByBitcoinHeight(tweakHeight) : blockDate) - : DateTime.now(), - confirmations: scanData.chainTip - tweakHeight + 1, - isReceivedSilentPayment: true, - isPending: false, - unspents: [], - ); - - List unspents = []; - - addToWallet.forEach((BSpend, scanResultPerLabel) { - scanResultPerLabel.forEach((label, scanOutput) { - final labelValue = label == "None" ? null : label.toString(); - - (scanOutput as Map).forEach((outputPubkey, tweak) { - final t_k = tweak as String; - - final receivingOutputAddress = ECPublic.fromHex(outputPubkey) - .toTaprootAddress(tweak: false) - .toAddress(scanData.network); - - final matchingOutput = outputPubkeys[outputPubkey]!; - final amount = matchingOutput.amount; - final pos = matchingOutput.vout; - - // final matchingSPWallet = scanData.silentPaymentsWallets.firstWhere( - // (receiver) => receiver.B_spend.toHex() == BSpend.toString(), - // ); - - // final labelIndex = labelValue != null ? scanData.labels[label] : 0; - // final balance = ElectrumBalance(); - // balance.confirmed = amount; - - final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( - receivingOutputAddress, - index: 0, - isHidden: false, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k, - type: SegwitAddresType.p2tr, - txCount: 1, - balance: amount, - ); - - final unspent = BitcoinSilentPaymentsUnspent( - receivedAddressRecord, - txid, - amount, - pos, - silentPaymentTweak: t_k, - silentPaymentLabel: labelValue, - ); - - unspents.add(unspent); - txInfo.unspents!.add(unspent); - txInfo.amount += unspent.value; - }); - }); - }); - - scanData.sendPort.send({txInfo.id: txInfo}); - } catch (e, stacktrace) { - scanData.sendPort.send( - SyncResponse(syncHeight, LostConnectionSyncStatus()), - ); - - log(stacktrace.toString(), LogLevel.error); - log(e.toString(), LogLevel.error); - return; - } - } - } catch (e, stacktrace) { - scanData.sendPort.send( - SyncResponse(syncHeight, LostConnectionSyncStatus()), - ); - - log(stacktrace.toString(), LogLevel.error); - log(e.toString(), LogLevel.error); - return; - } - - syncHeight = tweakHeight; - - if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) { - endScanningSuccesfully(); - } - } - - _scanningStream?.listen((event) => listenFn(event, req)); - } catch (e) { - log("Error in _handleScanSilentPayments: $e", LogLevel.error); - scanData.sendPort.send(SyncResponse(scanData.height, LostConnectionSyncStatus())); - } -} - class EstimatedTxResult { EstimatedTxResult({ required this.utxos, diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart index b1886cb4b0..265ffd6e20 100644 --- a/cw_bitcoin/lib/payjoin/manager.dart +++ b/cw_bitcoin/lib/payjoin/manager.dart @@ -1,31 +1,10 @@ -import 'dart:async'; -import 'dart:isolate'; -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; -import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_persister.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_receive_worker.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_send_worker.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; -import 'package:cw_bitcoin/payjoin/storage.dart'; -import 'package:cw_bitcoin/psbt/signer.dart'; -import 'package:cw_bitcoin/psbt/utils.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:payjoin_flutter/common.dart'; -import 'package:payjoin_flutter/receive.dart'; -import 'package:payjoin_flutter/send.dart'; -import 'package:payjoin_flutter/src/config.dart' as pj_config; -import 'package:payjoin_flutter/src/generated/api.dart' as pj_api; -import 'package:payjoin_flutter/uri.dart' as PayjoinUri; - -class PayjoinManager { - PayjoinManager(this._payjoinStorage, this._wallet); - - final PayjoinStorage _payjoinStorage; - final BitcoinWalletBase _wallet; +part of "payjoin.dart"; + +class CWPayjoinManager extends PayjoinManager { + CWPayjoinManager(); + + PayjoinStorage? _payjoinStorage; + BitcoinWalletBase? _wallet; final Map _activePollers = {}; static const List ohttpRelayUrls = [ @@ -39,10 +18,21 @@ class PayjoinManager { static const payjoinDirectoryUrl = 'https://payjo.in'; + void init({required dynamic payjoinStorage, required BitcoinWalletBase wallet}) { + if (payjoinStorage is! Box) { + throw ArgumentError('payjoinStorage must be an instance of PayjoinStorage'); + } + + _payjoinStorage = PayjoinStorage(payjoinStorage); + _wallet = wallet; + + printV("Initializing Payjoin Manager for wallet ${_wallet!.id}"); + } + Future initPayjoin() => pj_config.PConfig.initializeApp(); Future resumeSessions() async { - final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id); + final allSessions = _payjoinStorage!.readAllOpenSessions(_wallet!.id); final spawnedSessions = allSessions.map((session) { try { @@ -57,7 +47,7 @@ class PayjoinManager { printV("Resuming Payjoin Receiver Session ${receiver.id()}"); return spawnReceiver(receiver: receiver); } on pj_api.FfiSerdeJsonError catch (_) { - _payjoinStorage.markSenderSessionUnrecoverable(session.pjUri!, "Outdated Session"); + _payjoinStorage!.markSenderSessionUnrecoverable(session.pjUri!, "Outdated Session"); } }).nonNulls; @@ -68,16 +58,14 @@ class PayjoinManager { Future initSender( String pjUriString, String originalPsbt, int networkFeesSatPerVb) async { try { - final pjUri = - (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); + final pjUri = (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250); final senderBuilder = await SenderBuilder.fromPsbtAndUri( psbtBase64: originalPsbt, pjUri: pjUri, ); final persister = PayjoinSenderPersister.impl(); - final newSender = - await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); + final newSender = await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); final senderToken = await newSender.persist(persister: persister); return Sender.load(token: senderToken, persister: persister); @@ -87,14 +75,17 @@ class PayjoinManager { } Future spawnNewSender({ - required Sender sender, + required dynamic sender, required String pjUrl, required BigInt amount, bool isTestnet = false, }) async { + if (sender is! Sender) { + throw ArgumentError('sender must be an instance of Sender'); + } + final pjUri = Uri.parse(pjUrl).queryParameters['pj']!; - await _payjoinStorage.insertSenderSession( - sender, pjUri, _wallet.id, amount); + await _payjoinStorage!.insertSenderSession(sender, pjUri, _wallet!.id, amount); return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri); } @@ -115,24 +106,24 @@ class PayjoinManager { return; case PayjoinSenderRequestTypes.psbtToSign: final proposalPsbt = message['psbt'] as String; - final utxos = _wallet.getUtxoWithPrivateKeys(); - final finalizedPsbt = await _wallet.signPsbt(proposalPsbt, utxos); + final utxos = _wallet!.getUtxoWithPrivateKeys(); + final finalizedPsbt = await _wallet!.signPsbt(proposalPsbt, utxos); final txId = getTxIdFromPsbtV0(finalizedPsbt); - _wallet.commitPsbt(finalizedPsbt); + _wallet!.commitPsbt(finalizedPsbt); _cleanupSession(pjUri); - await _payjoinStorage.markSenderSessionComplete(pjUri, txId); + await _payjoinStorage!.markSenderSessionComplete(pjUri, txId); completer.complete(); } } catch (e) { _cleanupSession(pjUri); - await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, e.toString()); + await _payjoinStorage!.markSenderSessionUnrecoverable(pjUri, e.toString()); completer.complete(); } } else if (message is PayjoinSessionError) { _cleanupSession(pjUri); if (message is UnrecoverableError) { - await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, message.message); + await _payjoinStorage!.markSenderSessionUnrecoverable(pjUri, message.message); completer.complete(); } else if (message is RecoverableError) { completer.complete(); @@ -152,9 +143,8 @@ class PayjoinManager { return completer.future; } - Future getUnusedReceiver(String address, - [bool isTestnet = false]) async { - final session = _payjoinStorage.getUnusedActiveReceiverSession(_wallet.id); + Future getUnusedReceiver(String address, [bool isTestnet = false]) async { + final session = _payjoinStorage!.getUnusedActiveReceiverSession(_wallet!.id); if (session != null) { await PayjoinUri.Url.fromStr(payjoinDirectoryUrl); @@ -181,15 +171,19 @@ class PayjoinManager { final receiverToken = await newReceiver.persist(persister: persister); final receiver = await Receiver.load(persister: persister, token: receiverToken); - await _payjoinStorage.insertReceiverSession(receiver, _wallet.id); + await _payjoinStorage!.insertReceiverSession(receiver, _wallet!.id); return receiver; } Future spawnReceiver({ - required Receiver receiver, + required dynamic receiver, bool isTestnet = false, }) async { + if (receiver is! Receiver) { + throw ArgumentError('receiver must be an instance of Receiver'); + } + final completer = Completer(); final receivePort = ReceivePort(); @@ -203,16 +197,14 @@ class PayjoinManager { switch (message['type'] as PayjoinReceiverRequestTypes) { case PayjoinReceiverRequestTypes.processOriginalTx: final tx = message['tx'] as String; - rawAmount = getOutputAmountFromTx(tx, _wallet); + rawAmount = getOutputAmountFromTx(tx, _wallet!); break; case PayjoinReceiverRequestTypes.checkIsOwned: - (_wallet.walletAddresses as BitcoinWalletAddresses) - .newPayjoinReceiver(); - _payjoinStorage.markReceiverSessionInProgress(receiver.id()); + (_wallet!.walletAddresses as BitcoinWalletAddresses).newPayjoinReceiver(); + _payjoinStorage!.markReceiverSessionInProgress(receiver.id()); final inputScript = message['input_script'] as Uint8List; - final isOwned = - _wallet.isMine(Script.fromRaw(byteData: inputScript)); + final isOwned = _wallet!.isMine(Script.fromRaw(byteData: inputScript)); mainToIsolateSendPort?.send({ 'requestId': message['requestId'], 'result': isOwned, @@ -221,8 +213,7 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.checkIsReceiverOutput: final outputScript = message['output_script'] as Uint8List; - final isReceiverOutput = - _wallet.isMine(Script.fromRaw(byteData: outputScript)); + final isReceiverOutput = _wallet!.isMine(Script.fromRaw(byteData: outputScript)); mainToIsolateSendPort?.send({ 'requestId': message['requestId'], 'result': isReceiverOutput, @@ -230,10 +221,10 @@ class PayjoinManager { break; case PayjoinReceiverRequestTypes.getCandidateInputs: - utxos = _wallet.getUtxoWithPrivateKeys(); + utxos = _wallet!.getUtxoWithPrivateKeys(); if (utxos.isEmpty) { - await _wallet.updateAllUnspents(); - utxos = _wallet.getUtxoWithPrivateKeys(); + await _wallet!.updateAllUnspents(); + utxos = _wallet!.getUtxoWithPrivateKeys(); } mainToIsolateSendPort?.send({ 'requestId': message['requestId'], @@ -243,7 +234,7 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.processPsbt: final psbt = message['psbt'] as String; - final signedPsbt = await _wallet.signPsbt(psbt, utxos); + final signedPsbt = await _wallet!.signPsbt(psbt, utxos); mainToIsolateSendPort?.send({ 'requestId': message['requestId'], 'result': signedPsbt, @@ -253,21 +244,19 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.proposalSent: _cleanupSession(receiver.id()); final psbt = message['psbt'] as String; - await _payjoinStorage.markReceiverSessionComplete( - receiver.id(), getTxIdFromPsbtV0(psbt), rawAmount); + await _payjoinStorage! + .markReceiverSessionComplete(receiver.id(), getTxIdFromPsbtV0(psbt), rawAmount); completer.complete(); } } catch (e) { _cleanupSession(receiver.id()); - await _payjoinStorage.markReceiverSessionUnrecoverable( - receiver.id(), e.toString()); + await _payjoinStorage!.markReceiverSessionUnrecoverable(receiver.id(), e.toString()); completer.completeError(e); } } else if (message is PayjoinSessionError) { _cleanupSession(receiver.id()); if (message is UnrecoverableError) { - await _payjoinStorage.markReceiverSessionUnrecoverable( - receiver.id(), message.message); + await _payjoinStorage!.markReceiverSessionUnrecoverable(receiver.id(), message.message); completer.complete(); } else if (message is RecoverableError) { completer.complete(); @@ -301,15 +290,3 @@ class PayjoinManager { _activePollers.remove(sessionId); } } - -class PayjoinPollerSession { - final Isolate isolate; - final ReceivePort port; - - PayjoinPollerSession(this.isolate, this.port); - - void close() { - isolate.kill(); - port.close(); - } -} diff --git a/cw_bitcoin/lib/payjoin/payjoin_persister.dart b/cw_bitcoin/lib/payjoin/payjoin_persister.dart index 4e395e36ad..348752ec25 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_persister.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_persister.dart @@ -1,5 +1,4 @@ -import 'package:payjoin_flutter/src/generated/api/receive.dart'; -import 'package:payjoin_flutter/src/generated/api/send.dart'; +part of 'payjoin.dart'; class PayjoinSenderPersister implements DartSenderPersister { static DartSenderPersister impl() { diff --git a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart index c56148de21..ed56285c63 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart @@ -1,19 +1,4 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; -import 'dart:typed_data'; - -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/payjoin/manager.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; -import 'package:cw_bitcoin/psbt/signer.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:payjoin_flutter/bitcoin_ffi.dart'; -import 'package:payjoin_flutter/common.dart'; -import 'package:payjoin_flutter/receive.dart'; -import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; -import 'package:http/http.dart' as very_insecure_http_do_not_use; // for errors +part of 'payjoin.dart'; enum PayjoinReceiverRequestTypes { processOriginalTx, @@ -45,8 +30,7 @@ class PayjoinReceiverWorker { try { final receiver = Receiver.fromJson(json: receiverJson); - final uncheckedProposal = - await worker.receiveUncheckedProposal(receiver); + final uncheckedProposal = await worker.receiveUncheckedProposal(receiver); final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast(); sendPort.send({ @@ -110,8 +94,7 @@ class PayjoinReceiverWorker { final httpRequest = await client.post(url, headers: {'Content-Type': request.contentType}, body: request.body); - final proposal = await session.processRes( - body: httpRequest.bodyBytes, ctx: extractReq.$2); + final proposal = await session.processRes(body: httpRequest.bodyBytes, ctx: extractReq.$2); if (proposal != null) return proposal; } } @@ -137,8 +120,7 @@ class PayjoinReceiverWorker { return await finalProposal.psbt(); } - Future processPayjoinProposal( - UncheckedProposal proposal) async { + Future processPayjoinProposal(UncheckedProposal proposal) async { await proposal.extractTxToScheduleBroadcast(); // TODO Handle this. send to the main port on a timer? @@ -171,8 +153,7 @@ class PayjoinReceiverWorker { ); final pj5 = await pj4.commitOutputs(); - final listUnspent = - await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); + final listUnspent = await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); final unspent = listUnspent as List; if (unspent.isEmpty) throw RecoverableError('No unspent outputs available'); @@ -183,8 +164,8 @@ class PayjoinReceiverWorker { // Finalize proposal final payjoinProposal = await pj7.finalizeProposal( processPsbt: (String psbt) async { - final result = await _sendRequest( - PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); + final result = + await _sendRequest(PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); return result as String; }, // TODO set maxFeeRateSatPerVb @@ -200,19 +181,16 @@ class PayjoinReceiverWorker { Future _inputPairFromUtxo(UtxoWithPrivateKey utxo) async { final txout = TxOut( value: utxo.utxo.value, - scriptPubkey: Uint8List.fromList( - utxo.ownerDetails.address.toScriptPubKey().toBytes()), + scriptPubkey: Uint8List.fromList(utxo.ownerDetails.address.toScriptPubKey().toBytes()), ); - final psbtin = - PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); + final psbtin = PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); - final previousOutput = - OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); + final previousOutput = OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); final txin = TxIn( previousOutput: previousOutput, - scriptSig: await Script.newInstance(rawOutputScript: []), + scriptSig: await bitcoin_ffi.Script.newInstance(rawOutputScript: []), witness: [], sequence: 0, ); diff --git a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart index 7e85cc7738..27775be11e 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart @@ -1,21 +1,4 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; - -import 'package:cw_bitcoin/payjoin/manager.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:payjoin_flutter/common.dart'; -import 'package:payjoin_flutter/send.dart'; -import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; -import 'package:payjoin_flutter/src/generated/api/send/error.dart' as pj_error; -import 'package:payjoin_flutter/uri.dart' as pj_uri; - -enum PayjoinSenderRequestTypes { - requestPosted, - psbtToSign; -} +part of 'payjoin.dart'; class PayjoinSenderWorker { final SendPort sendPort; @@ -44,11 +27,11 @@ class PayjoinSenderWorker { sendPort.send(e); } } + final client = ProxyWrapper().getHttpIOClient(); /// Run a payjoin sender (V2 protocol first, fallback to V1). Future runSender(Sender sender) async { - try { return await _runSenderV2(sender); } catch (e) { @@ -68,13 +51,11 @@ class PayjoinSenderWorker { Future _runSenderV2(Sender sender) async { try { final postRequest = await sender.extractV2( - ohttpProxyUrl: - await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()), + ohttpProxyUrl: await PayjoinUri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()), ); final postResult = await _postRequest(postRequest.$1); - final getContext = - await postRequest.$2.processResponse(response: postResult); + final getContext = await postRequest.$2.processResponse(response: postResult); sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted, "pj": pjUrl}); diff --git a/cw_bitcoin/lib/payjoin/storage.dart b/cw_bitcoin/lib/payjoin/storage.dart index 5fb9d57161..98650d7518 100644 --- a/cw_bitcoin/lib/payjoin/storage.dart +++ b/cw_bitcoin/lib/payjoin/storage.dart @@ -1,7 +1,4 @@ -import 'package:cw_core/payjoin_session.dart'; -import 'package:hive/hive.dart'; -import 'package:payjoin_flutter/receive.dart'; -import 'package:payjoin_flutter/send.dart'; +part of 'payjoin.dart'; class PayjoinStorage { PayjoinStorage(this._payjoinSessionSources); @@ -23,16 +20,14 @@ class PayjoinStorage { ), ); - PayjoinSession? getUnusedActiveReceiverSession(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - session.status == PayjoinSessionStatus.created.name && - !session.isSenderSession) - .firstOrNull; + PayjoinSession? getUnusedActiveReceiverSession(String walletId) => _payjoinSessionSources.values + .where((session) => + session.walletId == walletId && + session.status == PayjoinSessionStatus.created.name && + !session.isSenderSession) + .firstOrNull; - Future markReceiverSessionComplete( - String sessionId, String txId, String amount) async { + Future markReceiverSessionComplete(String sessionId, String txId, String amount) async { final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; session.status = PayjoinSessionStatus.success.name; @@ -41,8 +36,7 @@ class PayjoinStorage { await session.save(); } - Future markReceiverSessionUnrecoverable( - String sessionId, String reason) async { + Future markReceiverSessionUnrecoverable(String sessionId, String reason) async { final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; session.status = PayjoinSessionStatus.unrecoverable.name; @@ -92,13 +86,10 @@ class PayjoinStorage { await session.save(); } - List readAllOpenSessions(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - ![ - PayjoinSessionStatus.success.name, - PayjoinSessionStatus.unrecoverable.name - ].contains(session.status)) - .toList(); + List readAllOpenSessions(String walletId) => _payjoinSessionSources.values + .where((session) => + session.walletId == walletId && + ![PayjoinSessionStatus.success.name, PayjoinSessionStatus.unrecoverable.name] + .contains(session.status)) + .toList(); } diff --git a/cw_bitcoin/lib/silent_payments/cw_sp.dart b/cw_bitcoin/lib/silent_payments/cw_sp.dart new file mode 100644 index 0000000000..556168565a --- /dev/null +++ b/cw_bitcoin/lib/silent_payments/cw_sp.dart @@ -0,0 +1,323 @@ +part of 'sp.dart'; + +class CWSilentPayments extends SilentPayments { + @override + Future handleScanSilentPayments(ScanData scanData) async { + var node = Uri.parse("tcp://electrs.cakewallet.com:50001"); + + void log(String message, LogLevel level) { + printV("[Scanning] $message", file: scanData.debugLogPath, level: level); + } + + try { + // if (scanData.shouldSwitchNodes) { + var scanningClient = await ElectrumProvider.connect( + ElectrumTCPService.connect(node), + ); + // } + + log("connected to ${node.toString()}", LogLevel.info); + + int syncHeight = scanData.height; + int initialSyncHeight = syncHeight; + + final receiver = Receiver( + scanData.silentAddress.b_scan.toHex(), + scanData.silentAddress.B_spend.toHex(), + scanData.network == BitcoinNetwork.testnet, + scanData.labelIndexes, + scanData.labelIndexes.length, + ); + + log( + "using receiver: b_scan: ${scanData.silentAddress.b_scan.toHex()}, B_scan: ${scanData.silentAddress.B_spend.toHex()}, b_spend: ${scanData.silentAddress.B_spend.toHex()}, B_spend: ${scanData.silentAddress.B_spend.toHex()}, network: ${scanData.network.value}, labelIndexes: ${scanData.labelIndexes}", + LogLevel.info, + ); + + int getCountToScanPerRequest(int syncHeight) { + if (scanData.isSingleScan) { + return 1; + } + + final amountLeft = scanData.chainTip - syncHeight + 1; + return amountLeft; + } + + // Initial status UI update, send how many blocks in total to scan + scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight))); + + final req = ElectrumTweaksSubscribe( + height: syncHeight, + count: getCountToScanPerRequest(syncHeight), + historicalMode: false, + ); + + var _scanningStream = await scanningClient.subscribe(req); + + log( + "initial request: height: $syncHeight, count: ${getCountToScanPerRequest(syncHeight)}", + LogLevel.info, + ); + + void endScanningSuccesfully() { + if (scanData.isSingleScan) + scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); + else + scanData.sendPort.send( + SyncResponse(syncHeight, SyncedTipSyncStatus(scanData.chainTip)), + ); + + _scanningStream?.close(); + _scanningStream = null; + + log( + "ended: syncHeight: $syncHeight, chainTip: ${scanData.chainTip}, isSingleScan: ${scanData.isSingleScan}", + LogLevel.info, + ); + } + + void listenFn(Map event, ElectrumTweaksSubscribe req) async { + final response = req.onResponse(event); + + if (response == null || _scanningStream == null) { + log( + "ending: response = $response, stream = $_scanningStream", + LogLevel.error, + ); + return; + } + + // is success or error msg + final noData = response.message != null; + + if (noData) { + if (scanData.isSingleScan) { + log("ending: noData and isSingleScan", LogLevel.info); + + endScanningSuccesfully(); + return; + } + + // re-subscribe to continue receiving messages, starting from the next unscanned height + final nextHeight = syncHeight + 1; + + if (nextHeight <= scanData.chainTip) { + log( + "resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}", + LogLevel.info, + ); + + final nextStream = scanningClient.subscribe( + ElectrumTweaksSubscribe( + height: nextHeight, + count: getCountToScanPerRequest(nextHeight), + historicalMode: false, + ), + ); + + if (nextStream != null) { + nextStream.listen((event) => listenFn(event, req)); + } else { + scanData.sendPort.send( + SyncResponse(scanData.height, LostConnectionSyncStatus()), + ); + } + } + + log( + "ending: resubscribing: nextHeight: $nextHeight, count: ${getCountToScanPerRequest(nextHeight)}", + LogLevel.info, + ); + return; + } + + final tweakHeight = response.block; + + if (initialSyncHeight < tweakHeight) initialSyncHeight = tweakHeight; + + // Continuous status UI update, send how many blocks left to scan + final syncingStatus = scanData.isSingleScan + ? SyncingSyncStatus(1, 0) + : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, tweakHeight); + + scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); + + try { + final blockTweaks = response.blockTweaks; + + var blockDate = DateTime.now(); + bool isDateNow = true; + + for (final txid in blockTweaks.keys) { + final tweakData = blockTweaks[txid]; + final outputPubkeys = tweakData!.outputPubkeys; + final tweak = tweakData.tweak; + + try { + final addToWallet = {}; + + // receivers.forEach((receiver) { + // NOTE: scanOutputs, from sp_scanner package, called from rust here + final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver); + + if (scanResult.isEmpty) { + continue; + } + + if (addToWallet[receiver.BSpend] == null) { + addToWallet[receiver.BSpend] = scanResult; + } else { + addToWallet[receiver.BSpend].addAll(scanResult); + } + // }); + + if (addToWallet.isEmpty) { + // no results tx, continue to next tx + continue; + } + + log( + "FOUND: addToWallet: ${addToWallet.length}, txid: $txid, tweak: $tweak, height: $tweakHeight", + LogLevel.info, + ); + + // Every tx in the block has the same date (the block date) + // So, if blockDate exists, reuse + if (isDateNow) { + try { + final tweakBlockHash = await ProxyWrapper() + .get( + clearnetUri: Uri.parse( + "https://mempool.cakewallet.com/api/v1/block-height/$tweakHeight", + ), + ) + .timeout(Duration(seconds: 15)); + final blockResponse = await ProxyWrapper() + .get( + clearnetUri: Uri.parse( + "https://mempool.cakewallet.com/api/v1/block/${tweakBlockHash.body}", + ), + ) + .timeout(Duration(seconds: 15)); + + if (blockResponse.statusCode == 200 && + blockResponse.body.isNotEmpty && + jsonDecode(blockResponse.body)['timestamp'] != null) { + blockDate = DateTime.fromMillisecondsSinceEpoch( + int.parse(jsonDecode(blockResponse.body)['timestamp'].toString()) * 1000, + ); + isDateNow = false; + } + } catch (e, stacktrace) { + printV(stacktrace); + printV(e.toString()); + } + } + + // initial placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) on the following loop + final txInfo = ElectrumTransactionInfo( + WalletType.bitcoin, + id: txid, + height: tweakHeight, + amount: 0, + fee: 0, + direction: TransactionDirection.incoming, + isReplaced: false, + // TODO: fetch block data and get the date from it + date: scanData.network == BitcoinNetwork.mainnet + ? (isDateNow ? getDateByBitcoinHeight(tweakHeight) : blockDate) + : DateTime.now(), + confirmations: scanData.chainTip - tweakHeight + 1, + isReceivedSilentPayment: true, + isPending: false, + unspents: [], + ); + + List unspents = []; + + addToWallet.forEach((BSpend, scanResultPerLabel) { + scanResultPerLabel.forEach((label, scanOutput) { + final labelValue = label == "None" ? null : label.toString(); + + (scanOutput as Map).forEach((outputPubkey, tweak) { + final t_k = tweak as String; + + final receivingOutputAddress = ECPublic.fromHex(outputPubkey) + .toTaprootAddress(tweak: false) + .toAddress(scanData.network); + + final matchingOutput = outputPubkeys[outputPubkey]!; + final amount = matchingOutput.amount; + final pos = matchingOutput.vout; + + // final matchingSPWallet = scanData.silentPaymentsWallets.firstWhere( + // (receiver) => receiver.B_spend.toHex() == BSpend.toString(), + // ); + + // final labelIndex = labelValue != null ? scanData.labels[label] : 0; + // final balance = ElectrumBalance(); + // balance.confirmed = amount; + + final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( + receivingOutputAddress, + index: 0, + isHidden: false, + isUsed: true, + network: scanData.network, + silentPaymentTweak: t_k, + type: SegwitAddresType.p2tr, + txCount: 1, + balance: amount, + ); + + final unspent = BitcoinSilentPaymentsUnspent( + receivedAddressRecord, + txid, + amount, + pos, + silentPaymentTweak: t_k, + silentPaymentLabel: labelValue, + ); + + unspents.add(unspent); + txInfo.unspents!.add(unspent); + txInfo.amount += unspent.value; + }); + }); + }); + + scanData.sendPort.send({txInfo.id: txInfo}); + } catch (e, stacktrace) { + scanData.sendPort.send( + SyncResponse(syncHeight, LostConnectionSyncStatus()), + ); + + log(stacktrace.toString(), LogLevel.error); + log(e.toString(), LogLevel.error); + return; + } + } + } catch (e, stacktrace) { + scanData.sendPort.send( + SyncResponse(syncHeight, LostConnectionSyncStatus()), + ); + + log(stacktrace.toString(), LogLevel.error); + log(e.toString(), LogLevel.error); + return; + } + + syncHeight = tweakHeight; + + if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) { + endScanningSuccesfully(); + } + } + + _scanningStream?.listen((event) => listenFn(event, req)); + } catch (e) { + log("Error in _handleScanSilentPayments: $e", LogLevel.error); + scanData.sendPort.send(SyncResponse(scanData.height, LostConnectionSyncStatus())); + } + } +} diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock deleted file mode 100644 index 16186c22a9..0000000000 --- a/cw_bitcoin/pubspec.lock +++ /dev/null @@ -1,1204 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" - url: "https://pub.dev" - source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" - url: "https://pub.dev" - source: hosted - version: "6.11.0" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - asn1lib: - dependency: transitive - description: - name: asn1lib - sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" - url: "https://pub.dev" - source: hosted - version: "1.6.5" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - bbqrdart: - dependency: "direct main" - description: - path: "." - ref: e867e3d0156d0b29858100f30adc2625b9dae586 - resolved-ref: e867e3d0156d0b29858100f30adc2625b9dae586 - url: "https://github.com/mrcyjanek/bbqrdart" - source: git - version: "1.0.0" - bech32: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192" - url: "https://github.com/cake-tech/bech32.git" - source: git - version: "0.2.2" - bip32: - dependency: transitive - description: - name: bip32 - sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - bip39: - dependency: transitive - description: - name: bip39 - sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc - url: "https://pub.dev" - source: hosted - version: "1.0.6" - bitbox: - dependency: "direct main" - description: - path: "." - ref: Add-Support-For-OP-Return-data - resolved-ref: "57b78afb85bd2c30d3cdb9f7884f3878a62be442" - url: "https://github.com/cake-tech/bitbox-flutter.git" - source: git - version: "1.0.1" - bitcoin_base: - dependency: "direct overridden" - description: - path: "." - ref: cake-update-v11 - resolved-ref: "46e5a2d8a5438cb523a6104b70f5e5125a5663b3" - url: "https://github.com/cake-tech/bitcoin_base" - source: git - version: "4.7.0" - blockchain_utils: - dependency: "direct main" - description: - path: "." - ref: cake-update-v2 - resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57" - url: "https://github.com/cake-tech/blockchain_utils" - source: git - version: "3.3.0" - bluez: - dependency: transitive - description: - name: bluez - sha256: "61a7204381925896a374301498f2f5399e59827c6498ae1e924aaa598751b545" - url: "https://pub.dev" - source: hosted - version: "0.8.3" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - bs58check: - dependency: transitive - description: - name: bs58check - sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - build: - dependency: transitive - description: - name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - build_cli_annotations: - dependency: transitive - description: - name: build_cli_annotations - sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172 - url: "https://pub.dev" - source: hosted - version: "2.1.0" - build_config: - dependency: transitive - description: - name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - build_daemon: - dependency: transitive - description: - name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" - url: "https://pub.dev" - source: hosted - version: "4.0.4" - build_resolvers: - dependency: "direct dev" - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" - build_runner: - dependency: "direct dev" - description: - name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" - url: "https://pub.dev" - source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" - built_collection: - dependency: transitive - description: - name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb - url: "https://pub.dev" - source: hosted - version: "8.11.1" - cake_backup: - dependency: transitive - description: - path: "." - ref: main - resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38" - url: "https://github.com/cake-tech/cake_backup.git" - source: git - version: "1.0.0+1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" - source: hosted - version: "2.0.3" - cli_util: - dependency: transitive - description: - name: cli_util - sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.dev" - source: hosted - version: "0.4.2" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - url: "https://pub.dev" - source: hosted - version: "4.10.1" - collection: - dependency: transitive - description: - name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf - url: "https://pub.dev" - source: hosted - version: "1.19.0" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - cryptography: - dependency: "direct main" - description: - name: cryptography - sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - cupertino_icons: - dependency: transitive - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - cw_core: - dependency: "direct main" - description: - path: "../cw_core" - relative: true - source: path - version: "0.0.1" - cw_mweb: - dependency: "direct main" - description: - path: "../cw_mweb" - relative: true - source: path - version: "0.0.1" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" - url: "https://pub.dev" - source: hosted - version: "2.3.8" - dart_varuint_bitcoin: - dependency: transitive - description: - name: dart_varuint_bitcoin - sha256: "4f0ccc9733fb54148b9d3688eea822b7aaabf5cc00025998f8c09a1d45b31b4b" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" - decimal: - dependency: transitive - description: - name: decimal - sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" - url: "https://pub.dev" - source: hosted - version: "2.3.3" - encrypt: - dependency: transitive - description: - name: encrypt - sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" - url: "https://pub.dev" - source: hosted - version: "5.0.3" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - ffi: - dependency: "direct overridden" - description: - name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - ffigen: - dependency: transitive - description: - name: ffigen - sha256: "3e12e80ccb6539bb3917217bb6f32709220efb737de0d0fa8736da0b7cb507da" - url: "https://pub.dev" - source: hosted - version: "12.0.0" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_mobx: - dependency: "direct main" - description: - name: flutter_mobx - sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe - url: "https://pub.dev" - source: hosted - version: "2.3.0" - flutter_rust_bridge: - dependency: transitive - description: - name: flutter_rust_bridge - sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611" - url: "https://pub.dev" - source: hosted - version: "2.9.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_bluetooth: - dependency: transitive - description: - name: flutter_web_bluetooth - sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8 - url: "https://pub.dev" - source: hosted - version: "1.1.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - google_identity_services_web: - dependency: transitive - description: - name: google_identity_services_web - sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" - url: "https://pub.dev" - source: hosted - version: "0.3.3+1" - googleapis_auth: - dependency: transitive - description: - name: googleapis_auth - sha256: b81fe352cc4a330b3710d2b7ad258d9bcef6f909bb759b306bf42973a7d046db - url: "https://pub.dev" - source: hosted - version: "2.0.0" - graphs: - dependency: transitive - description: - name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - grpc: - dependency: "direct main" - description: - name: grpc - sha256: "2dde469ddd8bbd7a33a0765da417abe1ad2142813efce3a86c512041294e2b26" - url: "https://pub.dev" - source: hosted - version: "4.1.0" - hex: - dependency: transitive - description: - name: hex - sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - hive: - dependency: transitive - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_generator: - dependency: "direct dev" - description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - http: - dependency: "direct main" - description: - name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 - url: "https://pub.dev" - source: hosted - version: "1.5.0" - http2: - dependency: transitive - description: - name: http2 - sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - intl: - dependency: "direct main" - description: - name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.dev" - source: hosted - version: "0.19.0" - io: - dependency: transitive - description: - name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" - source: hosted - version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" - url: "https://pub.dev" - source: hosted - version: "10.0.7" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" - url: "https://pub.dev" - source: hosted - version: "3.0.8" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - ledger_bitcoin: - dependency: "direct main" - description: - path: "packages/ledger-bitcoin" - ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 - resolved-ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 - url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" - source: git - version: "0.0.3" - ledger_flutter_plus: - dependency: "direct main" - description: - name: ledger_flutter_plus - sha256: "531da5daba5731d9eca2732881ef2f039b97bf8aa3564e7098dfa99a9b07a8e6" - url: "https://pub.dev" - source: hosted - version: "1.5.3" - ledger_litecoin: - dependency: "direct main" - description: - path: "packages/ledger-litecoin" - ref: HEAD - resolved-ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 - url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" - source: git - version: "0.0.2" - ledger_usb_plus: - dependency: transitive - description: - name: ledger_usb_plus - sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.dev" - source: hosted - version: "1.15.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - mobx: - dependency: "direct main" - description: - name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 - url: "https://pub.dev" - source: hosted - version: "2.5.0" - mobx_codegen: - dependency: "direct dev" - description: - name: mobx_codegen - sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8 - url: "https://pub.dev" - source: hosted - version: "2.7.1" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - on_chain: - dependency: transitive - description: - path: "." - ref: cake-update-v2 - resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273" - url: "https://github.com/cake-tech/on_chain.git" - source: git - version: "3.7.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 - url: "https://pub.dev" - source: hosted - version: "2.2.17" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - payjoin_flutter: - dependency: "direct main" - description: - path: "." - ref: da83a23f3a011cb49eb3b6513cd485b3fb8867ff - resolved-ref: da83a23f3a011cb49eb3b6513cd485b3fb8867ff - url: "https://github.com/OmarHatem28/payjoin-flutter" - source: git - version: "0.23.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 - url: "https://pub.dev" - source: hosted - version: "6.0.2" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pointycastle: - dependency: "direct overridden" - description: - name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" - url: "https://pub.dev" - source: hosted - version: "3.7.4" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - protobuf: - dependency: "direct overridden" - description: - name: protobuf - sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - provider: - dependency: transitive - description: - name: provider - sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" - url: "https://pub.dev" - source: hosted - version: "6.1.5" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - quiver: - dependency: transitive - description: - name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - rational: - dependency: transitive - description: - name: rational - sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 - url: "https://pub.dev" - source: hosted - version: "2.2.3" - rxdart: - dependency: "direct main" - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.dev" - source: hosted - version: "2.5.3" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" - url: "https://pub.dev" - source: hosted - version: "2.4.11" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - socks5_proxy: - dependency: transitive - description: - path: "." - ref: "27ad7c2efae8d7460325c74b90f660085cbd0685" - resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685" - url: "https://github.com/LacticWhale/socks_dart" - source: git - version: "2.1.0" - socks_socket: - dependency: "direct main" - description: - path: "." - ref: e6232c53c1595469931ababa878759a067c02e94 - resolved-ref: e6232c53c1595469931ababa878759a067c02e94 - url: "https://github.com/sneurlax/socks_socket" - source: git - version: "1.1.1" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - sp_scanner: - dependency: "direct main" - description: - path: "." - ref: "sp_v4.0.0" - resolved-ref: "2554cb8bd3ee1d026bc63e76a30d1226960c7cb4" - url: "https://github.com/cake-tech/sp_scanner" - source: git - version: "0.0.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.dev" - source: hosted - version: "0.7.3" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - tor_binary: - dependency: transitive - description: - path: "." - ref: cb811c610871a9517d47134b87c2f590c15c96c5 - resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5 - url: "https://github.com/MrCyjaneK/flutter-tor_binary" - source: git - version: "4.7.14" - tuple: - dependency: transitive - description: - name: tuple - sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 - url: "https://pub.dev" - source: hosted - version: "2.0.2" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - universal_ble: - dependency: transitive - description: - name: universal_ble - sha256: "35d210e93a5938c6a6d1fd3c710cf4ac90b1bdd1b11c8eb2beeb32600672e6e6" - url: "https://pub.dev" - source: hosted - version: "0.17.0" - universal_platform: - dependency: transitive - description: - name: universal_platform - sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - unorm_dart: - dependency: transitive - description: - name: unorm_dart - sha256: "8e3870a1caa60bde8352f9597dd3535d8068613269444f8e35ea8925ec84c1f5" - url: "https://pub.dev" - source: hosted - version: "0.3.1+1" - ur: - dependency: "direct main" - description: - path: "." - ref: "5738f70d0ec3d50977ac3dd01fed62939600238b" - resolved-ref: "5738f70d0ec3d50977ac3dd01fed62939600238b" - url: "https://github.com/bukata-sa/bc-ur-dart" - source: git - version: "0.1.0" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b - url: "https://pub.dev" - source: hosted - version: "14.3.0" - watcher: - dependency: "direct overridden" - description: - name: watcher - sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.dev" - source: hosted - version: "3.0.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" - yaml_edit: - dependency: transitive - description: - name: yaml_edit - sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 - url: "https://pub.dev" - source: hosted - version: "2.2.2" -sdks: - dart: ">=3.6.2 <4.0.0" - flutter: ">=3.27.0" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec_base.yaml similarity index 88% rename from cw_bitcoin/pubspec.yaml rename to cw_bitcoin/pubspec_base.yaml index 8bad17c441..53e22b8481 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec_base.yaml @@ -1,10 +1,3 @@ -name: cw_bitcoin -description: A new Flutter package project. -version: 0.0.1 -publish_to: none -author: Cake Wallet -homepage: https://cakewallet.com - environment: sdk: ">=2.17.5 <3.0.0" flutter: ">=1.20.0" @@ -33,17 +26,9 @@ dependencies: cw_mweb: path: ../cw_mweb grpc: ^4.0.1 - sp_scanner: - git: - url: https://github.com/cake-tech/sp_scanner - ref: sp_v4.0.0 bech32: git: url: https://github.com/cake-tech/bech32.git - payjoin_flutter: - git: - url: https://github.com/OmarHatem28/payjoin-flutter - ref: da83a23f3a011cb49eb3b6513cd485b3fb8867ff #cake-v2 ledger_flutter_plus: ^1.4.1 ledger_bitcoin: git: diff --git a/cw_bitcoin/pubspec_description.yaml b/cw_bitcoin/pubspec_description.yaml new file mode 100644 index 0000000000..94e154b550 --- /dev/null +++ b/cw_bitcoin/pubspec_description.yaml @@ -0,0 +1,6 @@ +name: cw_bitcoin +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com diff --git a/cw_bitcoin/tool/configure.dart b/cw_bitcoin/tool/configure.dart new file mode 100644 index 0000000000..4890d4248f --- /dev/null +++ b/cw_bitcoin/tool/configure.dart @@ -0,0 +1,312 @@ +import 'dart:io'; + +const payjoinOutputPath = 'lib/payjoin/payjoin.dart'; +const spScannerOutputPath = 'lib/silent_payments/sp.dart'; +const pubspecOutputPath = 'pubspec.yaml'; + +Future main(List args) async { + const prefix = '--'; + final hasPayjoin = args.contains('${prefix}payjoin'); + final hasSpScanner = args.contains('${prefix}sp-scanner'); + + await generatePayjoin(hasPayjoin); + await generateSpScanner(hasSpScanner); + + await generatePubspec(hasPayjoin: hasPayjoin, hasSpScanner: hasSpScanner); +} + +Future generatePayjoin(bool hasImplementation) async { + final outputFile = File(payjoinOutputPath); + + var output = ''; + + if (!hasImplementation) { + final payjoinCommonHeaders = """ +import 'dart:async'; +import 'dart:isolate'; +import 'dart:math'; + +import 'package:cw_bitcoin/bitcoin_wallet.dart'; +"""; + + final payjoinEmptyDefinition = 'PayjoinManager? payjoinManager;\n'; + + final payjoinCommonContent = """ +enum PayjoinSenderRequestTypes { + requestPosted, + psbtToSign; +} + +PayjoinManager? cwPayjoinManager; + +abstract class PayjoinManager { + dynamic currentPayjoinReceiver; + + static const List ohttpRelayUrls = [ + 'https://pj.bobspacebkk.com', + 'https://ohttp.achow101.com', + 'https://ohttp.cakewallet.com', + ]; + + static String randomOhttpRelayUrl() => + ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)]; + + static const payjoinDirectoryUrl = 'https://payjo.in'; + + void init({required dynamic payjoinStorage, required BitcoinWalletBase wallet}); + + Future initPayjoin(); + + Future resumeSessions(); + + Future initSender(String pjUriString, String originalPsbt, int networkFeesSatPerVb); + + Future spawnNewSender({ + required dynamic sender, + required String pjUrl, + required BigInt amount, + bool isTestnet = false, + }); + + Future getUnusedReceiver(String address, [bool isTestnet = false]); + + Future initReceiver(String address, [bool isTestnet = false]); + + Future spawnReceiver({ + required dynamic receiver, + bool isTestnet = false, + }); + + void cleanupSessions(); +} + +class PayjoinPollerSession { + final Isolate isolate; + final ReceivePort port; + + PayjoinPollerSession(this.isolate, this.port); + + void close() { + isolate.kill(); + port.close(); + } +}"""; + + output = '$payjoinCommonHeaders\n' + payjoinEmptyDefinition + '\n' + payjoinCommonContent; + } else { + final payjoinCWHeaders = """ +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_bitcoin/bitcoin_wallet.dart'; +import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; +import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; +import 'package:cw_bitcoin/psbt/signer.dart'; +import 'package:cw_bitcoin/psbt/utils.dart'; +import 'package:cw_core/utils/print_verbose.dart'; + +import 'package:payjoin_flutter/common.dart'; +import 'package:payjoin_flutter/receive.dart'; +import 'package:payjoin_flutter/send.dart'; +import 'package:payjoin_flutter/src/config.dart' as pj_config; +import 'package:payjoin_flutter/src/generated/api.dart' as pj_api; +import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; +import 'package:payjoin_flutter/src/generated/api/send/error.dart' as pj_error; +import 'package:payjoin_flutter/uri.dart' as PayjoinUri; +import 'package:payjoin_flutter/src/generated/api/receive.dart'; +import 'package:payjoin_flutter/src/generated/api/send.dart'; +import 'package:payjoin_flutter/bitcoin_ffi.dart' as bitcoin_ffi; + +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:http/http.dart' as very_insecure_http_do_not_use; // for errors + +import 'package:cw_core/payjoin_session.dart'; +import 'package:hive/hive.dart'; +"""; + + final payjoinCWPart = """ +part 'manager.dart'; +part 'payjoin_send_worker.dart'; +part 'payjoin_receive_worker.dart'; +part 'payjoin_persister.dart'; +part 'storage.dart'; +"""; + + final payjoinCWDefinition = 'PayjoinManager? cwPayjoinManager = CWPayjoinManager();\n'; + + final payjoinContent = """ +enum PayjoinSenderRequestTypes { + requestPosted, + psbtToSign; +} + +abstract class PayjoinManager { + dynamic currentPayjoinReceiver; + + static const List ohttpRelayUrls = [ + 'https://pj.bobspacebkk.com', + 'https://ohttp.achow101.com', + 'https://ohttp.cakewallet.com', + ]; + + static String randomOhttpRelayUrl() => + ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)]; + + static const payjoinDirectoryUrl = 'https://payjo.in'; + + void init({required dynamic payjoinStorage, required BitcoinWalletBase wallet}); + + Future initPayjoin(); + + Future resumeSessions(); + + Future initSender(String pjUriString, String originalPsbt, int networkFeesSatPerVb); + + Future spawnNewSender({ + required dynamic sender, + required String pjUrl, + required BigInt amount, + bool isTestnet = false, + }); + + Future getUnusedReceiver(String address, [bool isTestnet = false]); + + Future initReceiver(String address, [bool isTestnet = false]); + + Future spawnReceiver({ + required dynamic receiver, + bool isTestnet = false, + }); + + void cleanupSessions(); +} + +class PayjoinPollerSession { + final Isolate isolate; + final ReceivePort port; + + PayjoinPollerSession(this.isolate, this.port); + + void close() { + isolate.kill(); + port.close(); + } +}"""; + + output = + '$payjoinCWHeaders\n' + payjoinCWPart + '\n' + payjoinCWDefinition + '\n' + payjoinContent; + } + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + +Future generateSpScanner(bool hasImplementation) async { + final outputFile = File(spScannerOutputPath); + var output = ""; + + if (!hasImplementation) { + final spCommonHeaders = """ +import 'dart:async'; + +import 'package:cw_bitcoin/electrum_wallet.dart'; +"""; + + const spEmptyDefinition = 'SilentPayments? silentPayments;\n'; + + final spCommonContent = """ +abstract class SilentPayments { + Future handleScanSilentPayments(ScanData scanData); +}"""; + + output = '$spCommonHeaders\n' + '$spEmptyDefinition\n' + '$spCommonContent'; + } else { + final spCwHeaders = """ +import 'dart:async'; +import 'dart:convert'; + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/electrum_transaction_info.dart'; +import 'package:cw_core/get_height_by_date.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:sp_scanner/sp_scanner.dart'; +"""; + + final spCwPart = """ +part 'cw_sp.dart'; +"""; + + final spCwDefinition = 'SilentPayments? silentPayments = CWSilentPayments();\n'; + + final spCwContent = """ +abstract class SilentPayments { + Future handleScanSilentPayments(ScanData scanData); +}"""; + + output = '$spCwHeaders\n' + '$spCwPart\n' + '$spCwDefinition\n' + '$spCwContent'; + } + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + +Future generatePubspec({required bool hasPayjoin, required bool hasSpScanner}) async { + final inputFile = File(pubspecOutputPath); + final inputText = await inputFile.readAsString(); + final inputLines = inputText.split('\n'); + final dependenciesIndex = inputLines.indexWhere((line) => Platform.isWindows + // On Windows it could contains `\r` (Carriage Return). It could be fixed in newer dart versions. + ? line.toLowerCase() == 'dependencies:\r' || line.toLowerCase() == 'dependencies:' + : line.toLowerCase() == 'dependencies:'); + + var output = ''; + + if (hasPayjoin) { + final cwPayjoin = """ + payjoin_flutter: + git: + url: https://github.com/OmarHatem28/payjoin-flutter + ref: da83a23f3a011cb49eb3b6513cd485b3fb8867ff #cake-v2 + """; + output += "\n$cwPayjoin"; + } + + if (hasSpScanner) { + final cwSpScanner = """ + sp_scanner: + git: + url: https://github.com/cake-tech/sp_scanner + ref: sp_v4.0.0 + """; + output += "\n$cwSpScanner"; + } + + final outputLines = output.split('\n'); + inputLines.insertAll(dependenciesIndex + 1, outputLines); + final outputContent = inputLines.join('\n'); + final outputFile = File(pubspecOutputPath); + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(outputContent); +} diff --git a/cw_bitcoin/tool/generate_pubspec.dart b/cw_bitcoin/tool/generate_pubspec.dart new file mode 100644 index 0000000000..20846d37cf --- /dev/null +++ b/cw_bitcoin/tool/generate_pubspec.dart @@ -0,0 +1,25 @@ +import 'dart:io'; + +const pubspecBasePath = 'pubspec_base.yaml'; +const pubspecDescriptionPath = 'pubspec_description.yaml'; +const outputPubspecPath = 'pubspec.yaml'; + +Future main(List args) async { + final pubspecBase = File(pubspecBasePath); + final pubspecDescription = File(pubspecDescriptionPath); + + if (!pubspecBase.existsSync() || !pubspecDescription.existsSync()) { + throw ("$pubspecBasePath or $pubspecDescriptionPath doesn't exists"); + } + + final pubspecBaseContent = await pubspecBase.readAsString(); + final pubspecDescriptionContent = await pubspecDescription.readAsString(); + final pubSpecContent = pubspecDescriptionContent + '\n' + pubspecBaseContent; + final outputPubspec = File(outputPubspecPath); + + if (outputPubspec.existsSync()) { + await outputPubspec.delete(); + } + + await outputPubspec.writeAsString(pubSpecContent); +} diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 6fc827068f..98817403af 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -45,10 +45,7 @@ class MoneroNewWalletCredentials extends WalletCredentials { class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials { MoneroRestoreWalletFromHardwareCredentials( - {required String name, - required this.ledgerConnection, - int height = 0, - String? password}) + {required String name, required this.ledgerConnection, int height = 0, String? password}) : super(name: name, password: password, height: height); LedgerConnection ledgerConnection; } @@ -111,8 +108,7 @@ class MoneroWalletService extends WalletService< WalletType getType() => WalletType.monero; @override - Future create(MoneroNewWalletCredentials credentials, - {bool? isTestnet}) async { + Future create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); @@ -130,14 +126,13 @@ class MoneroWalletService extends WalletService< final polyseed = Polyseed.create(); final lang = PolyseedLang.getByEnglishName(credentials.language); - if (credentials.passphrase != null) - polyseed.crypt(credentials.passphrase!); + if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!); - final heightOverride = getMoneroHeigthByDate( - date: DateTime.now().subtract(Duration(days: 2))); + final heightOverride = + getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2))); - return _restoreFromPolyseed(path, credentials.password!, polyseed, - credentials.walletInfo!, lang, + return _restoreFromPolyseed( + path, credentials.password!, polyseed, credentials.walletInfo!, lang, overrideHeight: heightOverride, passphrase: credentials.passphrase); } @@ -180,14 +175,11 @@ class MoneroWalletService extends WalletService< if (walletFilesExist(path)) await repairOldAndroidWallet(name); - await monero_wallet_manager - .openWallet(path: path, password: password); + await monero_wallet_manager.openWallet(path: path, password: password); final walletInfo = walletInfoSource.values .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet( - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - password: password); + walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); if (wallet.isHardwareWallet) { wallet.setLedgerConnection(gLedger!); @@ -241,8 +233,8 @@ class MoneroWalletService extends WalletService< @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values.firstWhere( - (info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWalletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); final currentWallet = MoneroWallet( walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource, @@ -296,10 +288,7 @@ class MoneroWalletService extends WalletService< enableLedgerExchange(credentials.ledgerConnection); await monero_wallet_manager.restoreWalletFromHardwareWallet( - path: path, - password: password!, - restoreHeight: height!, - deviceName: 'Ledger'); + path: path, password: password!, restoreHeight: height!, deviceName: 'Ledger'); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, @@ -316,8 +305,7 @@ class MoneroWalletService extends WalletService< } @override - Future restoreFromSeed( - MoneroRestoreWalletFromSeedCredentials credentials, + Future restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { // Restore from Polyseed try { @@ -331,8 +319,7 @@ class MoneroWalletService extends WalletService< try { if (isBip39Seed(credentials.mnemonic)) { - final path = - await pathForWallet(name: credentials.name, type: getType()); + final path = await pathForWallet(name: credentials.name, type: getType()); return _restoreFromBip39( path: path, @@ -380,14 +367,12 @@ class MoneroWalletService extends WalletService< int? overrideHeight, }) async { walletInfo.derivationInfo = DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/44'/128'/0'/0/0", + derivationType: DerivationType.bip39, + derivationPath: "m/44'/128'/0'/0/0", ); - final legacyMnemonic = - getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? ""); - final height = - overrideHeight ?? getMoneroHeigthByDate(date: DateTime.now()); + final legacyMnemonic = getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? ""); + final height = overrideHeight ?? getMoneroHeigthByDate(date: DateTime.now()); walletInfo.isRecovery = true; walletInfo.restoreHeight = height; @@ -400,10 +385,8 @@ class MoneroWalletService extends WalletService< restoreHeight: height, ); - currentWallet!.setCacheAttribute( - key: "cakewallet.seed.bip39", value: mnemonic); - currentWallet!.setCacheAttribute( - key: "cakewallet.passphrase", value: passphrase ?? ''); + currentWallet!.setCacheAttribute(key: "cakewallet.seed.bip39", value: mnemonic); + currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase ?? ''); currentWallet!.store(); @@ -423,8 +406,7 @@ class MoneroWalletService extends WalletService< final path = await pathForWallet(name: credentials.name, type: getType()); final polyseedCoin = PolyseedCoin.POLYSEED_MONERO; final lang = PolyseedLang.getByPhrase(credentials.mnemonic); - final polyseed = - Polyseed.decode(credentials.mnemonic, lang, polyseedCoin); + final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin); return _restoreFromPolyseed( path, credentials.password!, polyseed, credentials.walletInfo!, lang, @@ -436,8 +418,8 @@ class MoneroWalletService extends WalletService< } } - Future _restoreFromPolyseed(String path, String password, - Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, + Future _restoreFromPolyseed( + String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight, String? passphrase}) async { @@ -479,9 +461,8 @@ class MoneroWalletService extends WalletService< restoreHeight: height, spendKey: spendKey); - currentWallet!.setCacheAttribute(key: "cakewallet.seed", value: seed); - currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase??''); + currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase ?? ''); final wallet = MoneroWallet( walletInfo: walletInfo, @@ -527,8 +508,7 @@ class MoneroWalletService extends WalletService< if (walletFilesExist(path)) await repairOldAndroidWallet(name); - await monero_wallet_manager - .openWallet(path: path, password: password); + await monero_wallet_manager.openWallet(path: path, password: password); final walletInfo = walletInfoSource.values .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet( @@ -546,8 +526,7 @@ class MoneroWalletService extends WalletService< @override bool requireHardwareWalletConnection(String name) { return walletInfoSource.values - .firstWhereOrNull( - (info) => info.id == WalletBase.idFor(name, getType())) + .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType())) ?.isHardwareWallet ?? false; } @@ -567,4 +546,4 @@ Future closeWalletAwaitIfShould(int wmaddr, int waddr) async { monero.WalletManager_errorString(Pointer.fromAddress(wmaddr)); })); } -} \ No newline at end of file +} diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index e8c2559b5f..ba6c61c8ec 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -184,10 +184,8 @@ class CWBitcoin extends Bitcoin { return estimatedTx.amount; } - if (wallet.type == WalletType.dogecoin) { - final dogeAddr = - sk.getPublic().toP2pkhAddress(); + final dogeAddr = sk.getPublic().toP2pkhAddress(); final estimatedTx = await electrumWallet.estimateSendAllTx( [BitcoinOutput(address: dogeAddr, value: BigInt.zero)], getFeeRate(wallet, priority as BitcoinTransactionPriority), @@ -796,8 +794,9 @@ class CWBitcoin extends Bitcoin { @override void stopPayjoinSessions(Object wallet) { final _wallet = wallet as ElectrumWallet; - (_wallet.walletAddresses as BitcoinWalletAddresses).payjoinManager.cleanupSessions(); - (_wallet.walletAddresses as BitcoinWalletAddresses).currentPayjoinReceiver = null; + (_wallet.walletAddresses as BitcoinWalletAddresses).payjoinManager!.cleanupSessions(); + (_wallet.walletAddresses as BitcoinWalletAddresses).payjoinManager!.currentPayjoinReceiver = + null; (_wallet.walletAddresses as BitcoinWalletAddresses).payjoinEndpoint = null; } diff --git a/lib/dnssec_proof/cw_dnssec_proof.dart b/lib/dnssec_proof/cw_dnssec_proof.dart new file mode 100644 index 0000000000..4b791232ce --- /dev/null +++ b/lib/dnssec_proof/cw_dnssec_proof.dart @@ -0,0 +1,18 @@ +part of "dnssec_proof.dart"; + +class CWDnssecProof extends DnssecProof { + CWDnssecProof(); + + Future fetchDnsProof(String bip353Name) async { + if (bip353Name.startsWith('₿')) { + bip353Name = bip353Name.substring(1); + } + final parts = bip353Name.split('@'); + if (parts.length != 2) return null; + final userPart = parts[0]; + final domainPart = parts[1]; + final bip353Domain = '$userPart.user._bitcoin-payment.$domainPart.'; + final proof = await Isolate.run(() => DnsProver.getTxtProof(bip353Domain)); + return base64.encode(proof); + } +} diff --git a/lib/dogecoin/dogecoin.dart b/lib/dogecoin/dogecoin.dart deleted file mode 100644 index 2523336281..0000000000 --- a/lib/dogecoin/dogecoin.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_service.dart'; -import 'package:hive/hive.dart'; - -import 'package:cw_dogecoin/cw_dogecoin.dart'; - -part 'cw_dogecoin.dart'; - -DogeCoin? dogecoin = CWDogeCoin(); - -abstract class DogeCoin { - - WalletService createDogeCoinWalletService( - Box walletInfoSource, Box unspentCoinSource, bool isDirect); - - WalletCredentials createDogeCoinNewWalletCredentials( - {required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); - - WalletCredentials createDogeCoinRestoreWalletFromSeedCredentials( - {required String name, required String mnemonic, required String password, String? passphrase}); - - TransactionPriority deserializeDogeCoinTransactionPriority(int raw); - - TransactionPriority getDefaultTransactionPriority(); - - List getTransactionPriorities(); - - TransactionPriority getDogeCoinTransactionPrioritySlow(); -} diff --git a/lib/entities/bip_353_record.dart b/lib/entities/bip_353_record.dart index 9a2a50e0a5..7427639bd2 100644 --- a/lib/entities/bip_353_record.dart +++ b/lib/entities/bip_353_record.dart @@ -1,13 +1,10 @@ -import 'dart:convert'; -import 'dart:isolate'; - import 'package:basic_utils/basic_utils.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/alert_with_picker_option.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:dnssec_proof/dnssec_proof.dart'; +import 'package:cake_wallet/dnssec_proof/dnssec_proof.dart'; import 'package:flutter/material.dart'; class Bip353Record { @@ -26,16 +23,7 @@ class Bip353Record { }; static Future fetchDnsProof(String bip353Name) async { - if (bip353Name.startsWith('₿')) { - bip353Name = bip353Name.substring(1); - } - final parts = bip353Name.split('@'); - if (parts.length != 2) return null; - final userPart = parts[0]; - final domainPart = parts[1]; - final bip353Domain = '$userPart.user._bitcoin-payment.$domainPart.'; - final proof = await Isolate.run(() => DnsProver.getTxtProof(bip353Domain)); - return base64.encode(proof); + return dnssecProof!.fetchDnsProof(bip353Name); } static Future?> fetchUriByCryptoCurrency( @@ -146,7 +134,7 @@ class Bip353Record { context: context, builder: (dialogContext) { return AlertWithPickerOption( - alertTitle: S.of(context).multiple_addresses_detected + '\n$bip353Name', + alertTitle: S.of(context).multiple_addresses_detected + '\n$bip353Name', alertTitleTextSize: 14, alertSubtitle: S.of(context).please_choose_one + ':', options: displayItems, @@ -168,5 +156,4 @@ class Bip353Record { final end = value.substring(value.length - back); return '$start...$end'; } - } diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 9c51749e8f..1f22b40f5d 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -62,12 +62,12 @@ class CWMoneroSubaddressList extends MoneroSubaddressList { final moneroWallet = _wallet as MoneroWallet; final subAddresses = moneroWallet.walletAddresses.subaddressList.subaddresses .map((sub) => Subaddress( - id: sub.id, - address: sub.address, - label: sub.label, - received: sub.balance??"unknown", - txCount: sub.txCount??0, - )) + id: sub.id, + address: sub.address, + label: sub.label, + received: sub.balance ?? "unknown", + txCount: sub.txCount ?? 0, + )) .toList(); return ObservableList.of(subAddresses); } @@ -90,11 +90,11 @@ class CWMoneroSubaddressList extends MoneroSubaddressList { return moneroWallet.walletAddresses.subaddressList .getAll() .map((sub) => Subaddress( - id: sub.id, - label: sub.label, - address: sub.address, - txCount: sub.txCount??0, - received: sub.balance??'unknown')) + id: sub.id, + label: sub.label, + address: sub.address, + txCount: sub.txCount ?? 0, + received: sub.balance ?? 'unknown')) .toList(); } @@ -233,10 +233,7 @@ class CWMonero extends Monero { required ledger.LedgerConnection ledgerConnection, }) => MoneroRestoreWalletFromHardwareCredentials( - name: name, - password: password, - height: height, - ledgerConnection: ledgerConnection); + name: name, password: password, height: height, ledgerConnection: ledgerConnection); @override WalletCredentials createMoneroRestoreWalletFromSeedCredentials( @@ -246,7 +243,11 @@ class CWMonero extends Monero { required int height, required String mnemonic}) => MoneroRestoreWalletFromSeedCredentials( - name: name, password: password, passphrase: passphrase, height: height, mnemonic: mnemonic); + name: name, + password: password, + passphrase: passphrase, + height: height, + mnemonic: mnemonic); @override WalletCredentials createMoneroNewWalletCredentials({ @@ -268,6 +269,54 @@ class CWMonero extends Monero { mnemonic: mnemonic, ); + @override + Future getNodeHeight(Object wallet) async { + final moneroWallet = wallet as MoneroWallet; + return await moneroWallet.getNodeHeight(); + } + + @override + String seed(Object wallet) { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.seed; + } + + @override + String seedLegacy(Object wallet, String? language) { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.seedLegacy(language); + } + + @override + MoneroWalletKeys keys(Object wallet) { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.keys; + } + + @override + bool isBackgroundSyncRunning(Object wallet) { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.isBackgroundSyncRunning; + } + + @override + Future rescan(Object wallet, {required int height}) async { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.rescan(height: height); + } + + @override + Future startBackgroundSync(Object wallet) async { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.startBackgroundSync(); + } + + @override + Future stopBackgroundSync(Object wallet, String password) async { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.stopBackgroundSync(password); + } + @override Map getKeys(Object wallet) { final moneroWallet = wallet as MoneroWallet; @@ -384,14 +433,13 @@ class CWMonero extends Monero { Future getCurrentHeight() async { return monero_wallet_api.getCurrentHeight(); } - + @override bool importKeyImagesUR(Object wallet, String ur) { final moneroWallet = wallet as MoneroWallet; return moneroWallet.importKeyImagesUR(ur); } - @override Future commitTransactionUR(Object wallet, String ur) { final moneroWallet = wallet as MoneroWallet; diff --git a/lib/view_model/dev/monero_background_sync.dart b/lib/view_model/dev/monero_background_sync.dart index 3eb4292b15..745b8cccd9 100644 --- a/lib/view_model/dev/monero_background_sync.dart +++ b/lib/view_model/dev/monero_background_sync.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/monero/monero.dart'; -import 'package:cw_monero/monero_wallet.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; @@ -56,26 +55,24 @@ abstract class DevMoneroBackgroundSyncBase with Store { bool isBackgroundSyncing = false; Future _setValues() async { - final w = (wallet as MoneroWallet); localBlockHeight = (await monero!.getCurrentHeight()).toString(); - nodeBlockHeight = (await w.getNodeHeight()).toString(); - final keys = w.keys; + nodeBlockHeight = (await monero!.getNodeHeight(wallet)).toString(); + final keys = monero!.keys(wallet); primaryAddress = keys.primaryAddress; publicViewKey = keys.publicViewKey; privateViewKey = keys.privateViewKey; publicSpendKey = keys.publicSpendKey; privateSpendKey = keys.privateSpendKey; passphrase = keys.passphrase; - seed = w.seed; - seedLegacy = w.seedLegacy("English"); + seed = monero!.seed(wallet); + seedLegacy = monero!.seedLegacy(wallet, "English"); tick = refreshTimer?.tick ?? -1; - isBackgroundSyncing = w.isBackgroundSyncRunning; + isBackgroundSyncing = monero!.isBackgroundSyncRunning(wallet); } @action Future manualRescan() async { - final w = (wallet as MoneroWallet); - await wallet.rescan(height: await w.getNodeHeight() - 10000); + await monero!.rescan(wallet, height: await monero!.getNodeHeight(wallet) - 10000); } @action @@ -93,14 +90,13 @@ abstract class DevMoneroBackgroundSyncBase with Store { @action void startBackgroundSync() { - final w = (wallet as MoneroWallet); - w.startBackgroundSync(); + monero!.startBackgroundSync(wallet); } @action Future stopBackgroundSync() async { - final w = (wallet as MoneroWallet); final keyService = getIt.get(); - await w.stopBackgroundSync(await keyService.getWalletPassword(walletName: wallet.name)); + await monero! + .stopBackgroundSync(wallet, await keyService.getWalletPassword(walletName: wallet.name)); } } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 1f7620452b..973f13fdfd 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -9,7 +9,6 @@ import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_monero/monero_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/decred/decred.dart'; @@ -77,7 +76,7 @@ abstract class WalletKeysViewModelBase with Store { final langName = PolyseedLang.getByPhrase(_wallet.seed!).nameEnglish; if (_wallet.type == WalletType.monero) { - return (_wallet as MoneroWalletBase).seedLegacy(langName); + return monero!.seedLegacy(_wallet, langName); } else if (_wallet.type == WalletType.wownero) { return wownero!.getLegacySeed(_wallet, langName); } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index e774557650..bdc9e906ec 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -117,10 +117,6 @@ dependencies: url: https://github.com/vespr-wallet/ledger-flutter-plus ref: 60817d4b20144f9da9029f5034790272795b9d38 hashlib: 1.19.2 - on_chain: - git: - url: https://github.com/cake-tech/on_chain.git - ref: cake-update-v2 reown_walletkit: ^1.1.5+1 blockchain_utils: git: @@ -139,10 +135,6 @@ dependencies: git: url: https://github.com/mrcyjanek/bbqrdart ref: e867e3d0156d0b29858100f30adc2625b9dae586 - dnssec_proof: - git: - url: https://github.com/MrCyjaneK/dnssec_proof.git - ref: a2b0bdac343afc14fc38dfb81f28147eab50be05 dev_dependencies: flutter_test: diff --git a/scripts/android/app_config.sh b/scripts/android/app_config.sh index 5fe5e503dc..c6cfb362c1 100755 --- a/scripts/android/app_config.sh +++ b/scripts/android/app_config.sh @@ -7,6 +7,6 @@ fi ./app_properties.sh ./app_icon.sh -./pubspec_gen.sh +./pubspec_gen.sh $@ ./manifest.sh true #force overwrite manifest ./inject_app_details.sh diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index 8170b7ff40..35cf888b86 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -3,21 +3,124 @@ MONERO_COM=monero.com CAKEWALLET=cakewallet HAVEN=haven -CONFIG_ARGS="" -case $APP_ANDROID_TYPE in - $MONERO_COM) - CONFIG_ARGS="--monero" - ;; - $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin" - ;; -esac +VALID_FLAGS=( + --monero + --bitcoin + --litecoin + --ethereum + --polygon + --nano + --bitcoinCash + --solana + --tron + --wownero + --zano + --decred + --dogecoin +) + +SELECTED_FLAGS=() +NON_COIN_FLAGS=() + +is_valid_flag() { + local flag="$1" + for valid_flag in "${VALID_FLAGS[@]}"; do + if [[ "$flag" == "$valid_flag" ]]; then + return 0 + fi + done + return 1 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --*=*) + echo "Please pass flags without = (e.g. '--bitcoin'), not '$1'" + exit 1 + ;; + + --*) + if is_valid_flag "$1"; then + SELECTED_FLAGS+=("$1") + shift + else + NON_COIN_FLAGS+=("$1") + shift + fi + ;; + + *) + echo "Unknown argument: $1" + exit 1 + ;; + esac +done + +if [[ ${#SELECTED_FLAGS[@]} -eq 0 ]]; then + if [[ -z "$APP_ANDROID_TYPE" ]]; then + echo "Please set APP_ANDROID_TYPE environment variable" + exit 1 + fi + + case $APP_ANDROID_TYPE in + $MONERO_COM) + SELECTED_FLAGS=("--monero") + ;; + $CAKEWALLET) + SELECTED_FLAGS=( + "--monero" + "--bitcoin" + "--litecoin" + "--bitcoinCash" + "--ethereum" + "--polygon" + "--nano" + "--solana" + "--tron" + "--wownero" + "--zano" + "--decred" + "--dogecoin" + ) + ;; + esac + + echo "Using default for $APP_ANDROID_TYPE: ${SELECTED_FLAGS[*]}" +else + echo "Using individual flags: ${SELECTED_FLAGS[*]}" +fi cd ../.. cp -rf pubspec_description.yaml pubspec.yaml flutter pub get dart run tool/generate_pubspec.dart flutter pub get -dart run tool/configure.dart $CONFIG_ARGS + +if [[ ${#NON_COIN_FLAGS[@]} -gt 0 ]]; then + echo "Configuring CW with additional flags: ${NON_COIN_FLAGS[*]}" + dart run tool/configure.dart "${SELECTED_FLAGS[@]}" "${NON_COIN_FLAGS[@]}" +else + echo "Configuring CW without additional flags" + dart run tool/configure.dart "${SELECTED_FLAGS[@]}" +fi + +for flag in "${SELECTED_FLAGS[@]}"; do + if [[ "$flag" == "--bitcoin" ]]; then + cd cw_bitcoin + dart run tool/generate_pubspec.dart + + if [[ ${#NON_COIN_FLAGS[@]} -gt 0 ]]; then + echo "Configuring Bitcoin with additional flags: ${NON_COIN_FLAGS[*]}" + dart run tool/configure.dart "${NON_COIN_FLAGS[@]}" + else + echo "Configuring Bitcoin without additional flags" + dart run tool/configure.dart + fi + + cd .. + break + fi +done + cd scripts/android diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index 78a8f7d829..ee448613a0 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -31,7 +31,7 @@ case $APP_IOS_TYPE in ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin" + CONFIG_ARGS="--monero --bitcoin --litecoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin" ;; esac diff --git a/scripts/linux/app_config.sh b/scripts/linux/app_config.sh index 77bb6c272d..bdec7629cd 100755 --- a/scripts/linux/app_config.sh +++ b/scripts/linux/app_config.sh @@ -13,7 +13,7 @@ CONFIG_ARGS="" case $APP_LINUX_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --excludeFlutterSecureStorage";; + CONFIG_ARGS="--monero --bitcoin --litecoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --excludeFlutterSecureStorage";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index dfea3dee26..e4cd64641d 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in $MONERO_COM) CONFIG_ARGS="--monero";; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin";; + CONFIG_ARGS="--monero --bitcoin --litecoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/configure.dart b/tool/configure.dart index 540768cf3a..deecb661e5 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -14,12 +14,13 @@ const decredOutputPath = 'lib/decred/decred.dart'; const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const secureStoragePath = 'lib/core/secure_storage.dart'; +const dnssecProofPath = 'lib/dnssec_proof/dnssec_proof.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; const pubspecOutputPath = 'pubspec.yaml'; Future main(List args) async { const prefix = '--'; - final hasBitcoin = args.contains('${prefix}bitcoin'); + final hasLitecoin = args.contains('${prefix}litecoin'); final hasMonero = args.contains('${prefix}monero'); final hasEthereum = args.contains('${prefix}ethereum'); final hasBitcoinCash = args.contains('${prefix}bitcoinCash'); @@ -33,6 +34,9 @@ Future main(List args) async { final hasDecred = args.contains('${prefix}decred'); final hasDogecoin = args.contains('${prefix}dogecoin'); final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); + final hasDnssecProof = args.contains('${prefix}dnssecProof'); + + final hasBitcoin = args.contains('${prefix}bitcoin') || hasLitecoin; await generateBitcoin(hasBitcoin); await generateMonero(hasMonero); @@ -47,6 +51,7 @@ Future main(List args) async { // await generateBanano(hasEthereum); await generateDecred(hasDecred); await generateDogecoin(hasDogecoin); + await generateDnssecProof(hasDnssecProof); await generatePubspec( hasMonero: hasMonero, @@ -63,10 +68,12 @@ Future main(List args) async { hasZano: hasZano, hasDecred: hasDecred, hasDogecoin: hasDogecoin, + hasDnssecProof: hasDnssecProof, ); await generateWalletTypes( hasMonero: hasMonero, hasBitcoin: hasBitcoin, + hasLitecoin: hasLitecoin, hasEthereum: hasEthereum, hasNano: hasNano, hasBanano: hasBanano, @@ -297,7 +304,8 @@ import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; -import 'package:polyseed/polyseed.dart';"""; +import 'package:cw_core/monero_wallet_keys.dart'; +"""; const moneroCWHeaders = """ import 'package:cw_core/account.dart' as monero_account; import 'package:cw_core/get_height_by_date.dart'; @@ -323,6 +331,7 @@ import 'package:cw_monero/mnemonics/portuguese.dart'; import 'package:cw_monero/mnemonics/french.dart'; import 'package:cw_monero/mnemonics/italian.dart'; import 'package:cw_monero/pending_monero_transaction.dart'; +import 'package:polyseed/polyseed.dart'; """; const moneroCwPart = "part 'cw_monero.dart';"; const moneroContent = """ @@ -426,7 +435,15 @@ abstract class Monero { required int height}); WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection}); -WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic}); + WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic}); + Future getNodeHeight(Object wallet); + String seed(Object wallet); + String seedLegacy(Object wallet, String? language); + MoneroWalletKeys keys(Object wallet); + bool isBackgroundSyncRunning(Object wallet); + Future rescan(Object wallet, {required int height}); + Future startBackgroundSync(Object wallet); + Future stopBackgroundSync(Object wallet, String password); Map getKeys(Object wallet); int? getRestoreHeight(Object wallet); Object createMoneroTransactionCreationCredentials({required List outputs, required TransactionPriority priority}); @@ -1461,9 +1478,7 @@ abstract class DogeCoin { final output = '$dogecoinCommonHeaders\n' + (hasImplementation ? '$dogecoinCWHeaders\n' : '\n') + (hasImplementation ? '$dogecoinCwPart\n\n' : '\n') + - (hasImplementation - ? dogecoinCWDefinition - : dogecoinEmptyDefinition) + + (hasImplementation ? dogecoinCWDefinition : dogecoinEmptyDefinition) + '\n' + dogecoinContent; @@ -1474,6 +1489,58 @@ abstract class DogeCoin { await outputFile.writeAsString(output); } +Future generateDnssecProof(bool hasImplementation) async { + final outputFile = File(dnssecProofPath); + var output = ""; + + if (!hasImplementation) { + const dnssecProofDefinition = 'DnssecProof? dnssecProof;\n'; + + final dnssecProofCommonContent = """ +abstract class DnssecProof { + Future fetchDnsProof(String bip353Name); +}"""; + + output = '$dnssecProofDefinition\n' + '$dnssecProofCommonContent'; + } else { + final dnssecProofCwHeaders = """ +import 'dart:convert'; +import 'dart:isolate'; + +import 'package:basic_utils/basic_utils.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/alert_with_picker_option.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:dnssec_proof/dnssec_proof.dart'; +import 'package:flutter/material.dart'; +"""; + + final dnssecProofCwPart = """ +part "cw_dnssec_proof.dart"; +"""; + + final dnssecProofCwDefinition = 'DnssecProof? dnssecProof = CWDnssecProof();\n'; + + final dnssecProofCwContent = """ +abstract class DnssecProof { + Future fetchDnsProof(String bip353Name); +}"""; + + output = '$dnssecProofCwHeaders\n' + + '$dnssecProofCwPart\n' + + '$dnssecProofCwDefinition\n' + + '$dnssecProofCwContent'; + } + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generatePubspec({ required bool hasMonero, required bool hasBitcoin, @@ -1489,6 +1556,7 @@ Future generatePubspec({ required bool hasZano, required bool hasDecred, required bool hasDogecoin, + required bool hasDnssecProof, }) async { const cwCore = """ cw_core: @@ -1626,6 +1694,28 @@ Future generatePubspec({ output += '\n$cwDogecoin'; } + if (hasEthereum || hasPolygon || hasSolana || hasTron) { + final cwOnChain = """ + on_chain: + git: + url: https://github.com/cake-tech/on_chain.git + ref: cake-update-v2 + """; + + output += '\n$cwOnChain'; + } + + if (hasDnssecProof) { + final dnssecProof = """ + dnssec_proof: + git: + url: https://github.com/MrCyjaneK/dnssec_proof.git + ref: a2b0bdac343afc14fc38dfb81f28147eab50be05 + """; + + output += '\n$dnssecProof'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); @@ -1641,6 +1731,7 @@ Future generatePubspec({ Future generateWalletTypes({ required bool hasMonero, required bool hasBitcoin, + required bool hasLitecoin, required bool hasEthereum, required bool hasNano, required bool hasBanano, @@ -1675,7 +1766,7 @@ Future generateWalletTypes({ outputContent += '\tWalletType.ethereum,\n'; } - if (hasBitcoin) { + if (hasLitecoin) { outputContent += '\tWalletType.litecoin,\n'; }