part of 'bitcoin.dart'; class CWBitcoin extends Bitcoin { WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({ required String name, required String mnemonic, required String password, required List? derivations, String? passphrase, }) => BitcoinRestoreWalletFromSeedCredentials( name: name, mnemonic: mnemonic, password: password, passphrase: passphrase, derivations: derivations, ); @override WalletCredentials createBitcoinRestoreWalletFromWIFCredentials( {required String name, required String password, required String wif, WalletInfo? walletInfo}) => BitcoinRestoreWalletFromWIFCredentials( name: name, password: password, wif: wif, walletInfo: walletInfo); @override WalletCredentials createBitcoinNewWalletCredentials({ required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic, String? parentAddress, }) => BitcoinNewWalletCredentials( name: name, walletInfo: walletInfo, password: password, passphrase: passphrase, mnemonic: mnemonic, parentAddress: parentAddress, ); @override WalletCredentials createBitcoinHardwareWalletCredentials( {required String name, required HardwareAccountData accountData, WalletInfo? walletInfo}) => BitcoinRestoreWalletFromHardware( name: name, hwAccountData: accountData, walletInfo: walletInfo); @override TransactionPriority getMediumTransactionPriority() => ElectrumTransactionPriority.medium; @override List getWordList() => wordlist; @override Map getWalletKeys(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; final keys = bitcoinWallet.keys; return { 'wif': keys.wif, 'privateKey': keys.privateKey, 'publicKey': keys.publicKey }; } @override List getTransactionPriorities() => ElectrumTransactionPriority.all; @override List getLitecoinTransactionPriorities() => ElectrumTransactionPriority.all; @override TransactionPriority deserializeBitcoinTransactionPriority(int raw) => ElectrumTransactionPriority.deserialize(raw: raw); @override TransactionPriority deserializeLitecoinTransactionPriority(int raw) => ElectrumTransactionPriority.deserialize(raw: raw); @override int getFeeRate(Object wallet, TransactionPriority priority) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.feeRate(priority); } @override Future generateNewAddress(Object wallet, String label) async { final bitcoinWallet = wallet as ElectrumWallet; await bitcoinWallet.walletAddresses.generateNewAddress(label: label); await wallet.save(); } @override Future updateAddress(Object wallet, String address, String label) async { final bitcoinWallet = wallet as ElectrumWallet; bitcoinWallet.walletAddresses.updateAddress(address, label); await wallet.save(); } @override Object createBitcoinTransactionCredentials( List outputs, { required TransactionPriority priority, int? feeRate, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) { final bitcoinFeeRate = priority == ElectrumTransactionPriority.custom && feeRate != null ? feeRate : null; return BitcoinTransactionCredentials( outputs .map((out) => OutputInfo( fiatAmount: out.fiatAmount, cryptoAmount: out.cryptoAmount, address: out.address, note: out.note, sendAll: out.sendAll, extractedAddress: out.extractedAddress, isParsedAddress: out.isParsedAddress, formattedCryptoAmount: out.formattedCryptoAmount, memo: out.memo)) .toList(), priority: priority as ElectrumTransactionPriority, feeRate: bitcoinFeeRate, coinTypeToSpendFrom: coinTypeToSpendFrom, ); } @override @computed List getSubAddresses(Object wallet) { final electrumWallet = wallet as ElectrumWallet; return electrumWallet.walletAddresses.addressesByReceiveType .map( (addr) => ElectrumSubAddress( id: addr.index, name: addr.name, address: addr.address, derivationPath: (addr as BitcoinAddressRecord) .derivationInfo .derivationPath .addElem( Bip32KeyIndex(addr.isChange ? 1 : 0), ) .addElem(Bip32KeyIndex(addr.index)) .toString(), txCount: addr.txCount, balance: addr.balance, isChange: addr.isChange, ), ) .toList(); } @override Future estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority) async { try { final sk = ECPrivate.random(); final electrumWallet = wallet as ElectrumWallet; if (wallet.type == WalletType.bitcoinCash) { final p2pkhAddr = sk.getPublic().toP2pkhAddress(); final estimatedTx = await electrumWallet.estimateSendAllTx( [BitcoinOutput(address: p2pkhAddr, value: BigInt.zero)], getFeeRate(wallet, priority as BitcoinCashTransactionPriority), ); return estimatedTx.amount; } final p2shAddr = sk.getPublic().toP2pkhInP2sh(); final estimatedTx = await electrumWallet.estimateSendAllTx( [BitcoinOutput(address: p2shAddr, value: BigInt.zero)], getFeeRate(wallet, priority), ); return estimatedTx.amount; } catch (_) { return 0; } } @override String getAddress(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.walletAddresses.address; } @override String formatterBitcoinAmountToString({required int amount}) => BitcoinAmountUtils.bitcoinAmountToString(amount: amount); @override double formatterBitcoinAmountToDouble({required int amount}) => BitcoinAmountUtils.bitcoinAmountToDouble(amount: amount); @override int formatterStringDoubleToBitcoinAmount(String amount) => BitcoinAmountUtils.stringDoubleToBitcoinAmount(amount); @override String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate, {int? customRate}) => (priority as ElectrumTransactionPriority).labelWithRate(rate, customRate); @override List getUnspents(Object wallet, {UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.unspentCoins.where((element) { switch (coinTypeToSpendFrom) { case UnspentCoinType.mweb: return element.bitcoinAddressRecord.addressType == SegwitAddresType.mweb; case UnspentCoinType.nonMweb: return element.bitcoinAddressRecord.addressType != SegwitAddresType.mweb; case UnspentCoinType.any: return true; } }).toList(); } Future updateUnspents(Object wallet) async { final bitcoinWallet = wallet as ElectrumWallet; await bitcoinWallet.updateAllUnspents(); } WalletService createBitcoinWalletService( Box walletInfoSource, Box unspentCoinSource, bool alwaysScan, bool isDirect, bool mempoolAPIEnabled, ) { return BitcoinWalletService( walletInfoSource, unspentCoinSource, alwaysScan, isDirect, mempoolAPIEnabled, ); } WalletService createLitecoinWalletService( Box walletInfoSource, Box unspentCoinSource, bool alwaysScan, bool isDirect, bool mempoolAPIEnabled, ) { return LitecoinWalletService( walletInfoSource, unspentCoinSource, alwaysScan, isDirect, mempoolAPIEnabled, ); } @override TransactionPriority getBitcoinTransactionPriorityMedium() => ElectrumTransactionPriority.fast; @override TransactionPriority getBitcoinTransactionPriorityCustom() => ElectrumTransactionPriority.custom; @override TransactionPriority getLitecoinTransactionPriorityMedium() => ElectrumTransactionPriority.medium; @override TransactionPriority getBitcoinTransactionPrioritySlow() => ElectrumTransactionPriority.medium; @override TransactionPriority getLitecoinTransactionPrioritySlow() => ElectrumTransactionPriority.slow; @override Future setAddressType(Object wallet, dynamic option) async { final bitcoinWallet = wallet as ElectrumWallet; await bitcoinWallet.walletAddresses.setAddressType(option as BitcoinAddressType); } @override ReceivePageOption getSelectedAddressType(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType); } @override bool isReceiveOptionSP(ReceivePageOption option) { return option.value == BitcoinReceivePageOption.silent_payments.value; } @override bool hasSelectedSilentPayments(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp; } @override List getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; @override List getLitecoinReceivePageOptions() { if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { return BitcoinReceivePageOption.allLitecoin .where((element) => element != BitcoinReceivePageOption.mweb) .toList(); } return BitcoinReceivePageOption.allLitecoin; } @override BitcoinAddressType getBitcoinAddressType(ReceivePageOption option) { switch (option) { case BitcoinReceivePageOption.p2pkh: return P2pkhAddressType.p2pkh; case BitcoinReceivePageOption.p2sh: return P2shAddressType.p2wpkhInP2sh; case BitcoinReceivePageOption.p2tr: return SegwitAddresType.p2tr; case BitcoinReceivePageOption.p2wsh: return SegwitAddresType.p2wsh; case BitcoinReceivePageOption.mweb: return SegwitAddresType.mweb; case BitcoinReceivePageOption.p2wpkh: default: return SegwitAddresType.p2wpkh; } } @override Future> compareDerivationMethods( {required String mnemonic, required Node node}) async { if (await checkIfMnemonicIsElectrum2(mnemonic)) { return [DerivationType.electrum]; } return [DerivationType.bip39, DerivationType.electrum]; } int _countCharOccurrences(String str, String charToCount) { int count = 0; for (int i = 0; i < str.length; i++) { if (str[i] == charToCount) { count++; } } return count; } @override List getOldDerivationInfos(List list) { final oldList = []; oldList.addAll(list); for (var derivationInfo in list) { final isElectrum = derivationInfo.derivationType == DerivationType.electrum; oldList.add( DerivationInfo( derivationType: DerivationType.old, derivationPath: isElectrum ? derivationInfo.derivationPath : BitcoinAddressUtils.getDerivationFromType( SegwitAddresType.p2wpkh, ).derivationPath.toString(), scriptType: derivationInfo.scriptType, ), ); } return oldList; } @override Future> getDerivationInfosFromMnemonic({ required String mnemonic, required Node node, String? passphrase, }) async { final list = []; var electrumSeedBytes; try { electrumSeedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase); } catch (e) { print("electrum_v2 seed error: $e"); if (passphrase == null || passphrase.isEmpty) { try { // TODO: language pick electrumSeedBytes = ElectrumV1SeedGenerator(mnemonic).generate(); } catch (e) { print("electrum_v1 seed error: $e"); } } } if (electrumSeedBytes != null) { for (final addressType in BITCOIN_ADDRESS_TYPES) { list.add( DerivationInfo( derivationType: DerivationType.electrum, derivationPath: "m/0'", scriptType: addressType.value, ), ); } } var bip39SeedBytes; try { bip39SeedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase); } catch (e) { print("bip39 seed error: $e"); } if (bip39SeedBytes != null) { for (final addressType in BITCOIN_ADDRESS_TYPES) { list.add( DerivationInfo( derivationType: DerivationType.bip39, derivationPath: BitcoinAddressUtils.getDerivationFromType( addressType, ).derivationPath.toString(), scriptType: addressType.value, ), ); } } return list; } @override Map> getElectrumDerivations() { return electrum_derivations; } @override bool hasTaprootInput(PendingTransaction pendingTransaction) { return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs; } @override Future replaceByFee( Object wallet, String transactionHash, String fee) async { final bitcoinWallet = wallet as ElectrumWallet; return await bitcoinWallet.replaceByFee(transactionHash, int.parse(fee)); } @override Future canReplaceByFee(Object wallet, Object transactionInfo) async { final bitcoinWallet = wallet as ElectrumWallet; final tx = transactionInfo as ElectrumTransactionInfo; return bitcoinWallet.canReplaceByFee(tx); } @override int getTransactionVSize(Object wallet, String transactionHex) { final bitcoinWallet = wallet as ElectrumWallet; return BtcTransaction.fromRaw(transactionHex).getVSize(); } @override Future isChangeSufficientForFee(Object wallet, String txId, String newFee) async { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.isChangeSufficientForFee(txId, int.parse(newFee)); } @override int getFeeAmountForPriority( Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size}) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.feeAmountForPriority( priority as ElectrumTransactionPriority, inputsCount, outputsCount); } @override int getEstimatedFeeWithFeeRate(Object wallet, int feeRate, int? amount, {int? outputsCount, int? size}) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.calculateEstimatedFeeWithFeeRate( feeRate, amount, outputsCount: outputsCount, size: size, ); } @override int feeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size}) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.feeAmountWithFeeRate(feeRate, inputsCount, outputsCount, size: size); } @override int getMaxCustomFeeRate(Object wallet) { final electrumWallet = wallet as ElectrumWallet; final feeRates = electrumWallet.feeRates; final maxFee = electrumWallet.feeRates is ElectrumTransactionPriorities ? ElectrumTransactionPriority.fast : BitcoinTransactionPriority.priority; return (electrumWallet.feeRate(maxFee) * 10).round(); } @override void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { (wallet as ElectrumWallet).setLedgerConnection(connection); } @override Future> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection); try { return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); } catch (err) { print(err); throw err; } } @override Future> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}) async { final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection); try { return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); } catch (err) { print(err); throw err; } } @override List getOldSPDerivationInfos() { return [ DerivationInfo( derivationType: DerivationType.bip39, derivationPath: "m/352'/1'/0'/1'/0", description: "Old SP Scan", ), DerivationInfo( derivationType: DerivationType.bip39, derivationPath: "m/352'/1'/0'/0'/0", description: "Old SP Spend", ), ]; } @override List getSilentPaymentAddresses(Object wallet) { final walletAddresses = (wallet as BitcoinWallet).walletAddresses as BitcoinWalletAddresses; return walletAddresses.silentPaymentAddresses .map((addr) => ElectrumSubAddress( id: addr.index, name: addr.name, address: addr.address, derivationPath: Bip32PathParser.parse(addr.derivationPath) .addElem(Bip32KeyIndex(addr.index)) .toString(), txCount: addr.txCount, balance: addr.balance, isChange: addr.isChange, )) .toList(); } @override List getSilentPaymentReceivedAddresses(Object wallet) { final walletAddresses = (wallet as BitcoinWallet).walletAddresses as BitcoinWalletAddresses; return walletAddresses.receivedSPAddresses .map((addr) => ElectrumSubAddress( id: addr.index, name: addr.name, address: addr.address, derivationPath: "", txCount: addr.txCount, balance: addr.balance, isChange: addr.isChange, )) .toList(); } @override bool isBitcoinReceivePageOption(ReceivePageOption option) { return option is BitcoinReceivePageOption; } @override BitcoinAddressType getOptionToType(ReceivePageOption option) { return (option as BitcoinReceivePageOption).toType(); } @override @computed bool getScanningActive(Object wallet) { final bitcoinWallet = wallet as BitcoinWallet; return bitcoinWallet.silentPaymentsScanningActive; } @override Future setScanningActive(Object wallet, bool active) async { final bitcoinWallet = wallet as BitcoinWallet; bitcoinWallet.setSilentPaymentsScanning(active); } @override Future allowToSwitchNodesForScanning(Object wallet, bool allow) async { final bitcoinWallet = wallet as BitcoinWallet; bitcoinWallet.allowedToSwitchNodesForScanning = allow; } @override bool isTestnet(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; return bitcoinWallet.isTestnet; } @override Future registerSilentPaymentsKey(Object wallet, bool active) async { final bitcoinWallet = wallet as BitcoinWallet; return await bitcoinWallet.registerSilentPaymentsKey(); } @override Future checkIfMempoolAPIIsEnabled(Object wallet) async { final bitcoinWallet = wallet as ElectrumWallet; return await bitcoinWallet.mempoolAPIEnabled; } @override Future getHeightByDate({required DateTime date, bool? bitcoinMempoolAPIEnabled}) async { if (bitcoinMempoolAPIEnabled ?? false) { try { return await getBitcoinHeightByDateAPI(date: date); } catch (_) {} } return await getBitcoinHeightByDate(date: date); } @override int getLitecoinHeightByDate({required DateTime date}) => getLtcHeightByDate(date: date); @override Future rescan(Object wallet, {required int height, bool? doSingleScan}) async { final bitcoinWallet = wallet as BitcoinWallet; bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan); } @override Future getNodeIsElectrsSPEnabled(Object wallet) async { final bitcoinWallet = wallet as BitcoinWallet; return bitcoinWallet.getNodeSupportsSilentPayments(); } @override void deleteSilentPaymentAddress(Object wallet, String address) { final walletAddresses = (wallet as BitcoinWallet).walletAddresses as BitcoinWalletAddresses; walletAddresses.deleteSilentPaymentAddress(address); } @override Future updateFeeRates(Object wallet) async { final bitcoinWallet = wallet as ElectrumWallet; await bitcoinWallet.updateFeeRates(); } @override Future setMwebEnabled(Object wallet, bool enabled) async { final litecoinWallet = wallet as LitecoinWallet; litecoinWallet.setMwebEnabled(enabled); } @override bool getMwebEnabled(Object wallet) { final litecoinWallet = wallet as LitecoinWallet; return litecoinWallet.mwebEnabled; } List updateOutputs(PendingTransaction pendingTransaction, List outputs) { final pendingTx = pendingTransaction as PendingBitcoinTransaction; if (!pendingTx.hasSilentPayment) { return outputs; } final updatedOutputs = outputs.map((output) { try { final pendingOut = pendingTx.outputs[outputs.indexOf(output)]; final updatedOutput = output; updatedOutput.stealthAddress = P2trAddress.fromScriptPubkey(script: pendingOut.scriptPubKey) .toAddress(BitcoinNetwork.mainnet); return updatedOutput; } catch (_) {} return output; }).toList(); return updatedOutputs; } @override bool txIsReceivedSilentPayment(TransactionInfo txInfo) { final tx = txInfo as ElectrumTransactionInfo; return tx.isReceivedSilentPayment; } @override bool txIsMweb(TransactionInfo txInfo) { final tx = txInfo as ElectrumTransactionInfo; List inputAddresses = tx.inputAddresses ?? []; List outputAddresses = tx.outputAddresses ?? []; bool inputAddressesContainMweb = false; bool outputAddressesContainMweb = false; for (var address in inputAddresses) { if (address.toLowerCase().contains('mweb')) { inputAddressesContainMweb = true; break; } } for (var address in outputAddresses) { if (address.toLowerCase().contains('mweb')) { outputAddressesContainMweb = true; break; } } // TODO: this could be improved: return inputAddressesContainMweb || outputAddressesContainMweb; } String? getUnusedMwebAddress(Object wallet) { try { final electrumWallet = wallet as ElectrumWallet; final mwebAddress = electrumWallet.walletAddresses.mwebAddresses.firstWhere((element) => !element.isUsed); return mwebAddress.address; } catch (_) { return null; } } String? getUnusedSegwitAddress(Object wallet) { try { final electrumWallet = wallet as ElectrumWallet; final segwitAddress = electrumWallet.walletAddresses.allAddresses.firstWhere( (element) => !element.isUsed && element.addressType == SegwitAddresType.p2wpkh); return segwitAddress.address; } catch (_) { return null; } } }