mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
* feat: initial hive -> SQLite Includes migration script and initial work done to remove dependency on hive generators. Also contains some refactoring to make sync code async. Features new dev screen that lists all SQLite tables Aspects that need testing: - backups (from this version, and from previous versions as well) - wallet loading / wallet list - almost everything else - renaming - deletion - sorting in wallet list - wallet groups - wallet seed/keys - wallet switching - wallet creation / restore - all methods - wallet address page - hardware wallet connection (and prompts from mall screens) - contacts - advanced settings in wallet creation - derivation paths - some minor wownero fixes - base, hardware wallets * fix: build errors * fix: Bad state: No element in backup restore * address comments from review * fix: HardwareWalletType
826 lines
26 KiB
Dart
826 lines
26 KiB
Dart
part of 'bitcoin.dart';
|
|
|
|
class CWBitcoin extends Bitcoin {
|
|
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({
|
|
required String name,
|
|
required String mnemonic,
|
|
required String password,
|
|
required DerivationType derivationType,
|
|
required String derivationPath,
|
|
String? passphrase,
|
|
}) =>
|
|
BitcoinRestoreWalletFromSeedCredentials(
|
|
name: name,
|
|
mnemonic: mnemonic,
|
|
password: password,
|
|
derivationType: derivationType,
|
|
derivationPath: derivationPath,
|
|
passphrase: passphrase,
|
|
);
|
|
|
|
@override
|
|
WalletCredentials createBitcoinWalletFromKeys({
|
|
required String name,
|
|
required String password,
|
|
required String xpub,
|
|
HardwareWalletType? hardwareWalletType,
|
|
}) =>
|
|
BitcoinWalletFromKeysCredentials(
|
|
name: name, password: password, xpub: xpub, hardwareWalletType: hardwareWalletType);
|
|
|
|
@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,
|
|
}) =>
|
|
BitcoinNewWalletCredentials(
|
|
name: name,
|
|
walletInfo: walletInfo,
|
|
password: password,
|
|
passphrase: passphrase,
|
|
mnemonic: mnemonic,
|
|
);
|
|
|
|
@override
|
|
WalletCredentials createBitcoinHardwareWalletCredentials(
|
|
{required String name,
|
|
required HardwareAccountData accountData,
|
|
WalletInfo? walletInfo}) =>
|
|
BitcoinRestoreWalletFromHardware(
|
|
name: name, hwAccountData: accountData, walletInfo: walletInfo);
|
|
|
|
@override
|
|
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
|
|
|
|
@override
|
|
List<String> getWordList() => wordlist;
|
|
|
|
@override
|
|
Map<String, String> getWalletKeys(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
final keys = bitcoinWallet.keys;
|
|
|
|
return bitcoinWallet.keys.toJson();
|
|
}
|
|
|
|
@override
|
|
Map<String, String> getSilentPaymentKeys(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
final keysOwner = bitcoinWallet.walletAddresses.silentAddress;
|
|
|
|
if (keysOwner == null) return {};
|
|
|
|
return <String, String>{
|
|
'privateSpendKey': keysOwner.b_spend.toHex(),
|
|
'publicSpendKey': keysOwner.B_spend.toHex(),
|
|
'privateViewKey': keysOwner.b_scan.toHex(),
|
|
'publicViewKey': keysOwner.B_scan.toHex(),
|
|
};
|
|
}
|
|
|
|
@override
|
|
List<TransactionPriority> getTransactionPriorities() => BitcoinTransactionPriority.all;
|
|
|
|
@override
|
|
List<TransactionPriority> getLitecoinTransactionPriorities() => LitecoinTransactionPriority.all;
|
|
|
|
@override
|
|
TransactionPriority deserializeBitcoinTransactionPriority(int raw) =>
|
|
BitcoinTransactionPriority.deserialize(raw: raw);
|
|
|
|
@override
|
|
TransactionPriority deserializeLitecoinTransactionPriority(int raw) =>
|
|
LitecoinTransactionPriority.deserialize(raw: raw);
|
|
|
|
@override
|
|
int getFeeRate(Object wallet, TransactionPriority priority) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return bitcoinWallet.feeRate(priority);
|
|
}
|
|
|
|
@override
|
|
Future<void> generateNewAddress(Object wallet, String label) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
|
|
await wallet.save();
|
|
}
|
|
|
|
@override
|
|
Future<void> 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<Output> outputs, {
|
|
required TransactionPriority priority,
|
|
int? feeRate,
|
|
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
|
String? payjoinUri,
|
|
}) {
|
|
final bitcoinFeeRate =
|
|
priority == BitcoinTransactionPriority.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,
|
|
extra: out.extra))
|
|
.toList(),
|
|
priority: priority as BitcoinTransactionPriority,
|
|
feeRate: bitcoinFeeRate,
|
|
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
|
payjoinUri: payjoinUri);
|
|
}
|
|
|
|
@override
|
|
@computed
|
|
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
|
final electrumWallet = wallet as ElectrumWallet;
|
|
return electrumWallet.walletAddresses.addressesByReceiveType
|
|
.map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
|
|
id: addr.index,
|
|
name: addr.name,
|
|
address: addr.address,
|
|
txCount: addr.txCount,
|
|
balance: addr.balance,
|
|
isChange: addr.isHidden))
|
|
.toList();
|
|
}
|
|
|
|
@override
|
|
Future<int> estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority,
|
|
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) 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;
|
|
}
|
|
|
|
|
|
if (wallet.type == WalletType.dogecoin) {
|
|
final dogeAddr =
|
|
sk.getPublic().toP2pkhAddress();
|
|
final estimatedTx = await electrumWallet.estimateSendAllTx(
|
|
[BitcoinOutput(address: dogeAddr, value: BigInt.zero)],
|
|
getFeeRate(wallet, priority as BitcoinTransactionPriority),
|
|
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
|
);
|
|
return estimatedTx.amount;
|
|
}
|
|
|
|
final p2shAddr = sk.getPublic().toP2pkhInP2sh();
|
|
final estimatedTx = await electrumWallet.estimateSendAllTx(
|
|
[BitcoinOutput(address: p2shAddr, value: BigInt.zero)],
|
|
getFeeRate(
|
|
wallet,
|
|
wallet.type == WalletType.litecoin
|
|
? priority as LitecoinTransactionPriority
|
|
: priority as BitcoinTransactionPriority,
|
|
),
|
|
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
|
);
|
|
|
|
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}) =>
|
|
bitcoinAmountToString(amount: amount);
|
|
|
|
@override
|
|
double formatterBitcoinAmountToDouble({required int amount}) =>
|
|
bitcoinAmountToDouble(amount: amount);
|
|
|
|
@override
|
|
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
|
|
|
|
@override
|
|
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate,
|
|
{int? customRate}) =>
|
|
(priority as BitcoinTransactionPriority).labelWithRate(rate, customRate);
|
|
|
|
@override
|
|
List<BitcoinUnspent> 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.type == SegwitAddresType.mweb;
|
|
case UnspentCoinType.nonMweb:
|
|
return element.bitcoinAddressRecord.type != SegwitAddresType.mweb;
|
|
case UnspentCoinType.any:
|
|
return true;
|
|
}
|
|
}).toList();
|
|
}
|
|
|
|
Future<void> updateUnspents(Object wallet) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
await bitcoinWallet.updateAllUnspents();
|
|
}
|
|
|
|
WalletService createBitcoinWalletService(
|
|
Box<UnspentCoinsInfo> unspentCoinSource,
|
|
Box<PayjoinSession> payjoinSessionSource,
|
|
bool isDirect) {
|
|
return BitcoinWalletService(unspentCoinSource, payjoinSessionSource, isDirect);
|
|
}
|
|
|
|
WalletService createLitecoinWalletService(Box<UnspentCoinsInfo> unspentCoinSource, bool isDirect) {
|
|
return LitecoinWalletService(unspentCoinSource, isDirect);
|
|
}
|
|
|
|
@override
|
|
TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium;
|
|
|
|
@override
|
|
TransactionPriority getBitcoinTransactionPriorityCustom() => BitcoinTransactionPriority.custom;
|
|
|
|
@override
|
|
TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium;
|
|
|
|
@override
|
|
TransactionPriority getBitcoinTransactionPrioritySlow() => BitcoinTransactionPriority.slow;
|
|
|
|
@override
|
|
TransactionPriority getLitecoinTransactionPrioritySlow() => LitecoinTransactionPriority.slow;
|
|
|
|
@override
|
|
Future<void> 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 hasSelectedSilentPayments(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
|
|
}
|
|
|
|
@override
|
|
List<ReceivePageOption> getBitcoinReceivePageOptions(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
final keys = bitcoinWallet.keys;
|
|
if (keys.privateKey.isEmpty) {
|
|
return BitcoinReceivePageOption.allViewOnly;
|
|
}
|
|
return BitcoinReceivePageOption.all;
|
|
}
|
|
|
|
@override
|
|
List<ReceivePageOption> getLitecoinReceivePageOptions(Object wallet) {
|
|
final litecoinWallet = wallet as ElectrumWallet;
|
|
if (Platform.isLinux ||
|
|
Platform.isMacOS ||
|
|
Platform.isWindows ||
|
|
litecoinWallet.isHardwareWallet) {
|
|
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<List<DerivationType>> 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
|
|
Future<List<DerivationInfo>> getDerivationsFromMnemonic({
|
|
required String mnemonic,
|
|
required Node node,
|
|
String? passphrase,
|
|
}) async {
|
|
List<DerivationInfo> list = [];
|
|
|
|
List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
|
|
if (types.length == 1 && types.first == DerivationType.electrum) {
|
|
return [getElectrumDerivations()[DerivationType.electrum]!.first];
|
|
}
|
|
|
|
final electrumClient = ElectrumClient();
|
|
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
|
|
|
late BasedUtxoNetwork network;
|
|
switch (node.type) {
|
|
case WalletType.litecoin:
|
|
network = LitecoinNetwork.mainnet;
|
|
break;
|
|
case WalletType.bitcoin:
|
|
default:
|
|
network = BitcoinNetwork.mainnet;
|
|
break;
|
|
}
|
|
|
|
for (DerivationType dType in electrum_derivations.keys) {
|
|
late Uint8List seedBytes;
|
|
if (dType == DerivationType.electrum) {
|
|
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
|
} else if (dType == DerivationType.bip39) {
|
|
seedBytes = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
|
|
}
|
|
|
|
for (DerivationInfo dInfo in electrum_derivations[dType]!) {
|
|
try {
|
|
DerivationInfo dInfoCopy = DerivationInfo(
|
|
derivationType: dInfo.derivationType,
|
|
derivationPath: dInfo.derivationPath,
|
|
description: dInfo.description,
|
|
scriptType: dInfo.scriptType,
|
|
);
|
|
|
|
String balancePath = dInfoCopy.derivationPath!;
|
|
int derivationDepth = _countCharOccurrences(balancePath, '/');
|
|
|
|
// for BIP44
|
|
if (derivationDepth == 3 || derivationDepth == 1) {
|
|
// we add "/0" so that we generate account 0
|
|
balancePath += "/0";
|
|
}
|
|
|
|
final hd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(balancePath)
|
|
as Bip32Slip10Secp256k1;
|
|
|
|
// derive address at index 0:
|
|
String? address;
|
|
switch (dInfoCopy.scriptType) {
|
|
case "p2wpkh":
|
|
address = generateP2WPKHAddress(hd: hd, network: network, index: 0);
|
|
break;
|
|
case "p2pkh":
|
|
address = generateP2PKHAddress(hd: hd, network: network, index: 0);
|
|
break;
|
|
case "p2wpkh-p2sh":
|
|
address = generateP2SHAddress(hd: hd, network: network, index: 0);
|
|
break;
|
|
case "p2tr":
|
|
address = generateP2TRAddress(hd: hd, network: network, index: 0);
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
final sh = BitcoinAddressUtils.scriptHash(address, network: network);
|
|
final history = await electrumClient.getHistory(sh);
|
|
|
|
final balance = await electrumClient.getBalance(sh);
|
|
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
|
|
dInfoCopy.address = address;
|
|
dInfoCopy.transactionsCount = history.length;
|
|
|
|
list.add(dInfoCopy);
|
|
} catch (e, s) {
|
|
printV("derivationInfoError: $e");
|
|
printV("derivationInfoStack: $s");
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort the list such that derivations with the most transactions are first:
|
|
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
|
|
|
|
return list;
|
|
}
|
|
|
|
@override
|
|
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations() {
|
|
return electrum_derivations;
|
|
}
|
|
|
|
@override
|
|
bool hasTaprootInput(PendingTransaction pendingTransaction) {
|
|
return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;
|
|
}
|
|
|
|
@override
|
|
Future<PendingBitcoinTransaction> replaceByFee(
|
|
Object wallet, String transactionHash, String fee) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return await bitcoinWallet.replaceByFee(transactionHash, int.parse(fee));
|
|
}
|
|
|
|
@override
|
|
Future<String?> 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 bitcoinWallet.transactionVSize(transactionHex);
|
|
}
|
|
|
|
@override
|
|
Future<bool> 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 BitcoinTransactionPriority, 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 bitcoinWallet = wallet as ElectrumWallet;
|
|
return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 10).round();
|
|
}
|
|
|
|
@override
|
|
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
|
|
(wallet as ElectrumWallet).hardwareWalletService = service;
|
|
}
|
|
|
|
@override
|
|
HardwareWalletService getLedgerHardwareWalletService(
|
|
ledger.LedgerConnection connection, bool isBitcoin) {
|
|
if (isBitcoin) return BitcoinLedgerService(connection);
|
|
return LitecoinLedgerService(connection);
|
|
}
|
|
|
|
@override
|
|
HardwareWalletService getBitboxHardwareWalletService(
|
|
bitbox.BitboxManager manager, bool isBitcoin) {
|
|
if (isBitcoin) return BitcoinBitboxService(manager);
|
|
return LitecoinBitboxService(manager);
|
|
}
|
|
|
|
@override
|
|
HardwareWalletService getTrezorHardwareWalletService(
|
|
trezor.TrezorConnect connect, bool isBitcoin) {
|
|
if (isBitcoin) return BitcoinTrezorService(connect);
|
|
return LitecoinTrezorService(connect);
|
|
}
|
|
|
|
@override
|
|
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return bitcoinWallet.walletAddresses.silentAddresses
|
|
.where((addr) => addr.type != SegwitAddresType.p2tr)
|
|
.map((addr) => ElectrumSubAddress(
|
|
id: addr.index,
|
|
name: addr.name,
|
|
address: addr.address,
|
|
txCount: addr.txCount,
|
|
balance: addr.balance,
|
|
isChange: addr.isHidden))
|
|
.toList();
|
|
}
|
|
|
|
@override
|
|
List<ElectrumSubAddress> getSilentPaymentReceivedAddresses(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return bitcoinWallet.walletAddresses.silentAddresses
|
|
.where((addr) => addr.type == SegwitAddresType.p2tr)
|
|
.map((addr) => ElectrumSubAddress(
|
|
id: addr.index,
|
|
name: addr.name,
|
|
address: addr.address,
|
|
txCount: addr.txCount,
|
|
balance: addr.balance,
|
|
isChange: addr.isHidden))
|
|
.toList();
|
|
}
|
|
|
|
@override
|
|
bool isBitcoinReceivePageOption(ReceivePageOption option) {
|
|
return option is BitcoinReceivePageOption;
|
|
}
|
|
|
|
@override
|
|
bool isPayjoinAvailable(Object wallet) =>
|
|
(wallet is BitcoinWallet) && (wallet as BitcoinWallet).isPayjoinAvailable;
|
|
|
|
@override
|
|
BitcoinAddressType getOptionToType(ReceivePageOption option) {
|
|
return (option as BitcoinReceivePageOption).toType();
|
|
}
|
|
|
|
@override
|
|
@computed
|
|
bool getScanningActive(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return bitcoinWallet.silentPaymentsScanningActive;
|
|
}
|
|
|
|
@override
|
|
Future<void> setScanningActive(Object wallet, bool active) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
bitcoinWallet.setSilentPaymentsScanning(active);
|
|
}
|
|
|
|
Future<void> setIsAlwaysScanningSP(Object wallet, bool active) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
bitcoinWallet.alwaysScan = active;
|
|
bitcoinWallet.save();
|
|
}
|
|
|
|
@computed
|
|
bool getIsAlwaysScanningSP(Object wallet) => (wallet as ElectrumWallet).alwaysScan ?? false;
|
|
|
|
@override
|
|
bool isTestnet(Object wallet) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return bitcoinWallet.isTestnet;
|
|
}
|
|
|
|
@override
|
|
Future<bool> checkIfMempoolAPIIsEnabled(Object wallet) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return await bitcoinWallet.checkIfMempoolAPIIsEnabled();
|
|
}
|
|
|
|
@override
|
|
Future<int> 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<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
|
|
}
|
|
|
|
@override
|
|
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
return bitcoinWallet.getNodeSupportsSilentPayments();
|
|
}
|
|
|
|
@override
|
|
void deleteSilentPaymentAddress(Object wallet, String address) {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address);
|
|
}
|
|
|
|
@override
|
|
Future<void> updateFeeRates(Object wallet) async {
|
|
final bitcoinWallet = wallet as ElectrumWallet;
|
|
await bitcoinWallet.updateFeeRates();
|
|
}
|
|
|
|
@override
|
|
Future<void> 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<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> 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<String> inputAddresses = tx.inputAddresses ?? [];
|
|
List<String> 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.type == SegwitAddresType.p2wpkh);
|
|
return segwitAddress.address;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> commitPsbtUR(Object wallet, List<String> urCodes) {
|
|
final _wallet = wallet as BitcoinWalletBase;
|
|
return _wallet.commitPsbtUR(urCodes);
|
|
}
|
|
|
|
@override
|
|
String getPayjoinEndpoint(Object wallet) {
|
|
final _wallet = wallet as ElectrumWallet;
|
|
if (!isPayjoinAvailable(wallet)) return '';
|
|
return (_wallet.walletAddresses as BitcoinWalletAddresses).payjoinEndpoint ?? '';
|
|
}
|
|
|
|
@override
|
|
void updatePayjoinState(Object wallet, bool value) {
|
|
final _wallet = wallet as ElectrumWallet;
|
|
if (value) {
|
|
(_wallet.walletAddresses as BitcoinWalletAddresses).initPayjoin();
|
|
} else {
|
|
stopPayjoinSessions(wallet);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void resumePayjoinSessions(Object wallet) {
|
|
final _wallet = wallet as ElectrumWallet;
|
|
(_wallet.walletAddresses as BitcoinWalletAddresses).initPayjoin();
|
|
}
|
|
|
|
@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).payjoinEndpoint = null;
|
|
}
|
|
|
|
@override
|
|
List<String>? getTransactionAddresses(Object wallet, TransactionInfo tx) {
|
|
final bitcoinWallet = wallet as BitcoinWallet;
|
|
final bitcoinTx = tx as ElectrumTransactionInfo;
|
|
|
|
if (bitcoinTx.unspents == null || bitcoinTx.unspents!.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
final addresses = <String>[];
|
|
final labels = <String>[];
|
|
try {
|
|
bitcoinTx.unspents!.forEach((unspent) {
|
|
addresses.add(bitcoinWallet.walletAddresses.silentAddresses
|
|
.firstWhere((address) => address.silentPaymentTweak == unspent.silentPaymentLabel)
|
|
.address);
|
|
});
|
|
} catch (e) {}
|
|
|
|
return addresses;
|
|
}
|
|
}
|