dev
dart 233 lines 7.88 KB
Raw
1 import 'package:bitbox/bitbox.dart' as bitbox;
2 import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:blockchain_utils/blockchain_utils.dart';
4 import 'package:cw_bitcoin/bitcoin_address_record.dart';
5 import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
6 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
7 import 'package:cw_bitcoin/electrum_balance.dart';
8 import 'package:cw_bitcoin/electrum_wallet.dart';
9 import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
10 import 'package:cw_core/crypto_currency.dart';
11 import 'package:cw_core/encryption_file_utils.dart';
12 import 'package:cw_core/transaction_priority.dart';
13 import 'package:cw_core/unspent_coins_info.dart';
14 import 'package:cw_core/wallet_info.dart';
15 import 'package:cw_core/wallet_keys_file.dart';
16 import 'package:flutter/foundation.dart';
17 import 'package:hive/hive.dart';
18 import 'package:mobx/mobx.dart';
19
20 import 'bitcoin_cash_base.dart';
21
22 part 'bitcoin_cash_wallet.g.dart';
23
24 class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
25
26 abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
27 BitcoinCashWalletBase({
28 required String mnemonic,
29 required String password,
30 required WalletInfo walletInfo,
31 required DerivationInfo derivationInfo,
32 required Box<UnspentCoinsInfo> unspentCoinsInfo,
33 required Uint8List seedBytes,
34 required EncryptionFileUtils encryptionFileUtils,
35 String? passphrase,
36 BitcoinAddressType? addressPageType,
37 List<BitcoinAddressRecord>? initialAddresses,
38 ElectrumBalance? initialBalance,
39 Map<String, int>? initialRegularAddressIndex,
40 Map<String, int>? initialChangeAddressIndex,
41 }) : super(
42 mnemonic: mnemonic,
43 password: password,
44 walletInfo: walletInfo,
45 derivationInfo: derivationInfo,
46 unspentCoinsInfo: unspentCoinsInfo,
47 network: BitcoinCashNetwork.mainnet,
48 initialAddresses: initialAddresses,
49 initialBalance: initialBalance,
50 seedBytes: seedBytes,
51 currency: CryptoCurrency.bch,
52 encryptionFileUtils: encryptionFileUtils,
53 passphrase: passphrase) {
54 walletAddresses = BitcoinCashWalletAddresses(
55 walletInfo,
56 initialAddresses: initialAddresses,
57 initialRegularAddressIndex: initialRegularAddressIndex,
58 initialChangeAddressIndex: initialChangeAddressIndex,
59 mainHdByType: mainHdByType,
60 sideHdByType: sideHdByType,
61 legacyMainHd: mainHd,
62 legacySideHd: sideHd,
63 network: network,
64 initialAddressPageType: addressPageType,
65 isHardwareWallet: walletInfo.isHardwareWallet,
66 );
67 autorun((_) {
68 this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
69 });
70 }
71
72 static Future<BitcoinCashWallet> create(
73 {required String mnemonic,
74 required String password,
75 required WalletInfo walletInfo,
76 required Box<UnspentCoinsInfo> unspentCoinsInfo,
77 required EncryptionFileUtils encryptionFileUtils,
78 String? passphrase,
79 String? addressPageType,
80 List<BitcoinAddressRecord>? initialAddresses,
81 ElectrumBalance? initialBalance,
82 Map<String, int>? initialRegularAddressIndex,
83 Map<String, int>? initialChangeAddressIndex}) async {
84 return BitcoinCashWallet(
85 mnemonic: mnemonic,
86 password: password,
87 walletInfo: walletInfo,
88 derivationInfo: await walletInfo.getDerivationInfo(),
89 unspentCoinsInfo: unspentCoinsInfo,
90 initialAddresses: initialAddresses,
91 initialBalance: initialBalance,
92 seedBytes: MnemonicBip39.toSeed(mnemonic, passphrase: passphrase),
93 encryptionFileUtils: encryptionFileUtils,
94 initialRegularAddressIndex: initialRegularAddressIndex,
95 initialChangeAddressIndex: initialChangeAddressIndex,
96 addressPageType: P2pkhAddressType.p2pkh,
97 passphrase: passphrase,
98 );
99 }
100
101 static Future<BitcoinCashWallet> open({
102 required String name,
103 required WalletInfo walletInfo,
104 required Box<UnspentCoinsInfo> unspentCoinsInfo,
105 required String password,
106 required EncryptionFileUtils encryptionFileUtils,
107 }) async {
108 final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
109
110 ElectrumWalletSnapshot? snp = null;
111
112 try {
113 snp = await ElectrumWalletSnapshot.load(
114 encryptionFileUtils,
115 name,
116 walletInfo.type,
117 password,
118 BitcoinCashNetwork.mainnet,
119 );
120 } catch (e) {
121 if (!hasKeysFile) rethrow;
122 }
123
124 final WalletKeysData keysData;
125 // Migrate wallet from the old scheme to then new .keys file scheme
126 if (!hasKeysFile) {
127 keysData =
128 WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
129 } else {
130 keysData = await WalletKeysFile.readKeysFile(
131 name,
132 walletInfo.type,
133 password,
134 encryptionFileUtils,
135 );
136 }
137
138 return BitcoinCashWallet(
139 mnemonic: keysData.mnemonic!,
140 password: password,
141 walletInfo: walletInfo,
142 derivationInfo: await walletInfo.getDerivationInfo(),
143 unspentCoinsInfo: unspentCoinsInfo,
144 initialAddresses: snp?.addresses.map((addr) {
145 try {
146 BitcoinCashAddress(addr.address);
147 return BitcoinAddressRecord(
148 addr.address,
149 index: addr.index,
150 isHidden: addr.isHidden,
151 name: addr.name,
152 type: P2pkhAddressType.p2pkh,
153 network: BitcoinCashNetwork.mainnet,
154 );
155 } catch (_) {
156 return BitcoinAddressRecord(
157 AddressUtils.getCashAddrFormat(addr.address),
158 index: addr.index,
159 isHidden: addr.isHidden,
160 name: addr.name,
161 type: P2pkhAddressType.p2pkh,
162 network: BitcoinCashNetwork.mainnet,
163 );
164 }
165 }).toList(),
166 initialBalance: snp?.balance,
167 seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!, passphrase: keysData.passphrase),
168 encryptionFileUtils: encryptionFileUtils,
169 initialRegularAddressIndex: snp?.regularAddressIndex,
170 initialChangeAddressIndex: snp?.changeAddressIndex,
171 addressPageType: P2pkhAddressType.p2pkh,
172 passphrase: keysData.passphrase,
173 );
174 }
175
176 bitbox.ECPair generateKeyPair({required Bip32Slip10Secp256k1 hd, required int index}) =>
177 bitbox.ECPair.fromPrivateKey(
178 Uint8List.fromList(hd.childKey(Bip32KeyIndex(index)).privateKey.raw),
179 );
180
181 int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
182 int inputsCount = 0;
183 int totalValue = 0;
184
185 for (final input in unspentCoins) {
186 if (input.isSending) {
187 inputsCount++;
188 totalValue += input.value;
189 }
190 if (amount != null && totalValue >= amount) {
191 break;
192 }
193 }
194
195 if (amount != null && totalValue < amount) return 0;
196
197 final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
198
199 return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
200 }
201
202 @override
203 int feeRate(TransactionPriority priority) {
204 if (priority is BitcoinCashTransactionPriority) {
205 switch (priority) {
206 case BitcoinCashTransactionPriority.slow:
207 return 1;
208 case BitcoinCashTransactionPriority.medium:
209 return 5;
210 case BitcoinCashTransactionPriority.fast:
211 return 10;
212 }
213 }
214
215 return 0;
216 }
217
218 @override
219 Future<String> signMessage(String message, {String? address = null}) async {
220 int? index;
221 try {
222 index = address != null
223 ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
224 : null;
225 } catch (_) {}
226 final HD = index == null ? mainHd : mainHd.childKey(Bip32KeyIndex(index));
227 final priv = ECPrivate.fromWif(
228 WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
229 netVersion: network.wifNetVer,
230 );
231 return priv.signMessage(StringUtils.encode(message));
232 }
233 }