mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-27 01:28:57 +00:00
261 lines
8.7 KiB
Dart
261 lines
8.7 KiB
Dart
import 'package:bitbox/bitbox.dart' as bitbox;
|
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
|
import 'package:cw_core/crypto_currency.dart';
|
|
import 'package:cw_core/encryption_file_utils.dart';
|
|
import 'package:cw_core/transaction_priority.dart';
|
|
import 'package:cw_core/unspent_coins_info.dart';
|
|
import 'package:cw_core/wallet_info.dart';
|
|
import 'package:cw_core/wallet_keys_file.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:hive/hive.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
import 'bitcoin_cash_base.dart';
|
|
|
|
part 'bitcoin_cash_wallet.g.dart';
|
|
|
|
class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
|
|
|
|
abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|
BitcoinCashWalletBase({
|
|
required String mnemonic,
|
|
required String password,
|
|
required WalletInfo walletInfo,
|
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
required Uint8List seedBytes,
|
|
required EncryptionFileUtils encryptionFileUtils,
|
|
String? passphrase,
|
|
BitcoinAddressType? addressPageType,
|
|
List<BitcoinAddressRecord>? initialAddresses,
|
|
ElectrumBalance? initialBalance,
|
|
Map<String, int>? initialRegularAddressIndex,
|
|
Map<String, int>? initialChangeAddressIndex,
|
|
required bool mempoolAPIEnabled,
|
|
}) : super(
|
|
mnemonic: mnemonic,
|
|
password: password,
|
|
walletInfo: walletInfo,
|
|
unspentCoinsInfo: unspentCoinsInfo,
|
|
network: BitcoinCashNetwork.mainnet,
|
|
initialAddresses: initialAddresses,
|
|
initialBalance: initialBalance,
|
|
seedBytes: seedBytes,
|
|
currency: CryptoCurrency.bch,
|
|
encryptionFileUtils: encryptionFileUtils,
|
|
passphrase: passphrase,
|
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
|
hdWallets: {CWBitcoinDerivationType.bip39: bitcoinCashHDWallet(seedBytes)},
|
|
) {
|
|
walletAddresses = BitcoinCashWalletAddresses(
|
|
walletInfo,
|
|
initialAddresses: initialAddresses,
|
|
network: network,
|
|
initialAddressPageType: addressPageType,
|
|
isHardwareWallet: walletInfo.isHardwareWallet,
|
|
hdWallets: hdWallets,
|
|
);
|
|
autorun((_) {
|
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
|
});
|
|
}
|
|
|
|
@override
|
|
BitcoinCashNetwork get network => BitcoinCashNetwork.mainnet;
|
|
|
|
static Future<BitcoinCashWallet> create({
|
|
required String mnemonic,
|
|
required String password,
|
|
required WalletInfo walletInfo,
|
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
required EncryptionFileUtils encryptionFileUtils,
|
|
String? passphrase,
|
|
String? addressPageType,
|
|
List<BitcoinAddressRecord>? initialAddresses,
|
|
ElectrumBalance? initialBalance,
|
|
Map<String, int>? initialRegularAddressIndex,
|
|
Map<String, int>? initialChangeAddressIndex,
|
|
required bool mempoolAPIEnabled,
|
|
}) async {
|
|
return BitcoinCashWallet(
|
|
mnemonic: mnemonic,
|
|
password: password,
|
|
walletInfo: walletInfo,
|
|
unspentCoinsInfo: unspentCoinsInfo,
|
|
initialAddresses: initialAddresses,
|
|
initialBalance: initialBalance,
|
|
seedBytes: MnemonicBip39.toSeed(mnemonic, passphrase: passphrase),
|
|
encryptionFileUtils: encryptionFileUtils,
|
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
|
addressPageType: P2pkhAddressType.p2pkh,
|
|
passphrase: passphrase,
|
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
|
);
|
|
}
|
|
|
|
static Future<BitcoinCashWallet> open({
|
|
required String name,
|
|
required WalletInfo walletInfo,
|
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
required String password,
|
|
required EncryptionFileUtils encryptionFileUtils,
|
|
required bool mempoolAPIEnabled,
|
|
}) async {
|
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
|
|
|
ElectrumWalletSnapshot? snp = null;
|
|
|
|
try {
|
|
snp = await ElectrumWalletSnapshot.load(
|
|
encryptionFileUtils,
|
|
name,
|
|
walletInfo.type,
|
|
password,
|
|
BitcoinCashNetwork.mainnet,
|
|
);
|
|
} catch (e) {
|
|
if (!hasKeysFile) rethrow;
|
|
}
|
|
|
|
final WalletKeysData keysData;
|
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
|
if (!hasKeysFile) {
|
|
keysData =
|
|
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
|
} else {
|
|
keysData = await WalletKeysFile.readKeysFile(
|
|
name,
|
|
walletInfo.type,
|
|
password,
|
|
encryptionFileUtils,
|
|
);
|
|
}
|
|
|
|
return BitcoinCashWallet(
|
|
mnemonic: keysData.mnemonic!,
|
|
password: password,
|
|
walletInfo: walletInfo,
|
|
unspentCoinsInfo: unspentCoinsInfo,
|
|
initialAddresses: snp?.addresses.map((addr) {
|
|
try {
|
|
BitcoinCashAddress(addr.address);
|
|
return BitcoinAddressRecord(
|
|
addr.address,
|
|
index: addr.index,
|
|
isChange: addr.isChange,
|
|
addressType: P2pkhAddressType.p2pkh,
|
|
network: BitcoinCashNetwork.mainnet,
|
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
|
|
derivationType: CWBitcoinDerivationType.bip39,
|
|
);
|
|
} catch (_) {
|
|
return BitcoinAddressRecord(
|
|
AddressUtils.getCashAddrFormat(addr.address),
|
|
index: addr.index,
|
|
isChange: addr.isChange,
|
|
addressType: P2pkhAddressType.p2pkh,
|
|
network: BitcoinCashNetwork.mainnet,
|
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
|
|
derivationType: CWBitcoinDerivationType.bip39,
|
|
);
|
|
}
|
|
}).toList(),
|
|
initialBalance: snp?.balance,
|
|
seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!, passphrase: keysData.passphrase),
|
|
encryptionFileUtils: encryptionFileUtils,
|
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
|
addressPageType: P2pkhAddressType.p2pkh,
|
|
passphrase: keysData.passphrase,
|
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
|
);
|
|
}
|
|
|
|
bitbox.ECPair generateKeyPair({required Bip32Slip10Secp256k1 hd, required int index}) =>
|
|
bitbox.ECPair.fromPrivateKey(
|
|
Uint8List.fromList(hd.childKey(Bip32KeyIndex(index)).privateKey.raw),
|
|
);
|
|
|
|
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
|
|
int inputsCount = 0;
|
|
int totalValue = 0;
|
|
|
|
for (final input in unspentCoins) {
|
|
if (input.isSending) {
|
|
inputsCount++;
|
|
totalValue += input.value;
|
|
}
|
|
if (amount != null && totalValue >= amount) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (amount != null && totalValue < amount) return 0;
|
|
|
|
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
|
|
|
return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
|
|
}
|
|
|
|
@override
|
|
int feeRate(TransactionPriority priority) {
|
|
if (priority is ElectrumTransactionPriority) {
|
|
switch (priority) {
|
|
case ElectrumTransactionPriority.slow:
|
|
return 1;
|
|
case ElectrumTransactionPriority.medium:
|
|
return 5;
|
|
case ElectrumTransactionPriority.fast:
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
@override
|
|
Future<String> signMessage(String message, {String? address = null}) async {
|
|
Bip32Slip10Secp256k1 HD = bip32;
|
|
|
|
final record = walletAddresses.allAddresses.firstWhere((element) => element.address == address);
|
|
|
|
if (record.isChange) {
|
|
HD = HD.childKey(Bip32KeyIndex(1));
|
|
} else {
|
|
HD = HD.childKey(Bip32KeyIndex(0));
|
|
}
|
|
|
|
HD = HD.childKey(Bip32KeyIndex(record.index));
|
|
final priv = ECPrivate.fromWif(
|
|
WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
|
|
netVersion: network.wifNetVer,
|
|
);
|
|
return priv.signMessage(StringUtils.encode(message));
|
|
}
|
|
|
|
@override
|
|
Future<int> calcFee({
|
|
required List<UtxoWithAddress> utxos,
|
|
required List<BitcoinBaseOutput> outputs,
|
|
String? memo,
|
|
required int feeRate,
|
|
}) async =>
|
|
feeRate *
|
|
ForkedTransactionBuilder.estimateTransactionSize(
|
|
utxos: utxos,
|
|
outputs: outputs,
|
|
network: network,
|
|
memo: memo,
|
|
);
|
|
|
|
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(List<int> seedBytes) =>
|
|
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
|
|
}
|