dev
dart 211 lines 7.44 KB
Raw
1 import 'dart:io';
2 import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
4 import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
5 import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
6 import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
7 import 'package:cw_core/encryption_file_utils.dart';
8 import 'package:cw_core/payjoin_session.dart';
9 import 'package:cw_core/unspent_coins_info.dart';
10 import 'package:cw_core/utils/zpub.dart';
11 import 'package:cw_core/wallet_service.dart';
12 import 'package:cw_bitcoin/bitcoin_wallet.dart';
13 import 'package:cw_core/pathForWallet.dart';
14 import 'package:cw_core/wallet_info.dart';
15 import 'package:cw_core/wallet_type.dart';
16 import 'package:hive/hive.dart';
17 import 'package:bip39/bip39.dart' as bip39;
18
19 class BitcoinWalletService extends WalletService<
20 BitcoinNewWalletCredentials,
21 BitcoinRestoreWalletFromSeedCredentials,
22 BitcoinWalletFromKeysCredentials,
23 BitcoinRestoreWalletFromHardware> {
24 BitcoinWalletService(this.unspentCoinsInfoSource, this.payjoinSessionSource, this.isDirect);
25
26 final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
27 final Box<PayjoinSession> payjoinSessionSource;
28 final bool isDirect;
29
30 @override
31 WalletType getType() => WalletType.bitcoin;
32
33 @override
34 Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
35 final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
36 credentials.walletInfo?.network = network.value;
37
38 final String mnemonic;
39 final derivationInfo = await credentials.walletInfo!.getDerivationInfo();
40 derivationInfo.derivationType =
41 credentials.derivationInfo?.derivationType ?? derivationInfo.derivationType;
42 derivationInfo.derivationPath =
43 credentials.derivationInfo?.derivationPath ?? derivationInfo.derivationPath;
44 derivationInfo.description =
45 credentials.derivationInfo?.description ?? derivationInfo.description;
46 derivationInfo.scriptType = credentials.derivationInfo?.scriptType ?? derivationInfo.scriptType;
47 await derivationInfo.save();
48 switch (derivationInfo.derivationType) {
49 case DerivationType.bip39:
50 final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
51
52 mnemonic = credentials.mnemonic ?? await MnemonicBip39.generate(strength: strength);
53 break;
54 case DerivationType.electrum:
55 default:
56 mnemonic = await generateElectrumMnemonic();
57 break;
58 }
59 await derivationInfo.save();
60
61 final wallet = await BitcoinWalletBase.create(
62 mnemonic: mnemonic,
63 password: credentials.password!,
64 passphrase: credentials.passphrase,
65 walletInfo: credentials.walletInfo!,
66 unspentCoinsInfo: unspentCoinsInfoSource,
67 payjoinBox: payjoinSessionSource,
68 network: network,
69 encryptionFileUtils: encryptionFileUtilsFor(isDirect),
70 );
71
72 await wallet.save();
73 await wallet.init();
74
75 return wallet;
76 }
77
78 @override
79 Future<bool> isWalletExit(String name) async =>
80 File(await pathForWallet(name: name, type: getType())).existsSync();
81
82 @override
83 Future<BitcoinWallet> openWallet(String name, String password) async {
84 final walletInfo = await WalletInfo.get(name, getType());
85 if (walletInfo == null) {
86 throw Exception('Wallet not found');
87 }
88 try {
89 final wallet = await BitcoinWalletBase.open(
90 password: password,
91 name: name,
92 walletInfo: walletInfo,
93 unspentCoinsInfo: unspentCoinsInfoSource,
94 payjoinBox: payjoinSessionSource,
95 encryptionFileUtils: encryptionFileUtilsFor(isDirect),
96 );
97 await wallet.init();
98 saveBackup(name);
99 return wallet;
100 } catch (_) {
101 await restoreWalletFilesFromBackup(name);
102 final wallet = await BitcoinWalletBase.open(
103 password: password,
104 name: name,
105 walletInfo: walletInfo,
106 unspentCoinsInfo: unspentCoinsInfoSource,
107 payjoinBox: payjoinSessionSource,
108 encryptionFileUtils: encryptionFileUtilsFor(isDirect),
109 );
110 await wallet.init();
111 return wallet;
112 }
113 }
114
115 @override
116 Future<void> remove(String wallet) async {
117 File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
118 final walletInfo = await WalletInfo.get(wallet, getType());
119 if (walletInfo == null) {
120 throw Exception('Wallet not found');
121 }
122 await WalletInfo.delete(walletInfo);
123
124 final unspentCoinsToDelete = unspentCoinsInfoSource.values
125 .where((unspentCoin) => unspentCoin.walletId == walletInfo.id)
126 .toList();
127
128 final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList();
129
130 if (keysToDelete.isNotEmpty) {
131 await unspentCoinsInfoSource.deleteAll(keysToDelete);
132 }
133 }
134
135 @override
136 Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
137 {bool? isTestnet}) async {
138 final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
139 credentials.walletInfo?.network = network.value;
140 final derivationInfo = await credentials.walletInfo!.getDerivationInfo();
141 derivationInfo.derivationPath = credentials.hwAccountData.derivationPath;
142
143 final xpub = convertAnyToXpub(credentials.hwAccountData.xpub!);
144
145 await credentials.walletInfo!.save();
146 final wallet = await BitcoinWallet(
147 password: credentials.password!,
148 xpub: xpub,
149 walletInfo: credentials.walletInfo!,
150 derivationInfo: derivationInfo,
151 unspentCoinsInfo: unspentCoinsInfoSource,
152 networkParam: network,
153 encryptionFileUtils: encryptionFileUtilsFor(isDirect),
154 payjoinBox: payjoinSessionSource,
155 useLightning: false,
156 );
157 await wallet.save();
158 await wallet.init();
159 return wallet;
160 }
161
162 @override
163 Future<BitcoinWallet> restoreFromKeys(BitcoinWalletFromKeysCredentials credentials,
164 {bool? isTestnet}) async {
165 final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
166 credentials.walletInfo?.network = network.value;
167
168 final xpub = convertAnyToXpub(credentials.xpub);
169
170 final wallet = await BitcoinWallet(
171 password: credentials.password!,
172 xpub: xpub,
173 walletInfo: credentials.walletInfo!,
174 derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
175 unspentCoinsInfo: unspentCoinsInfoSource,
176 networkParam: network,
177 encryptionFileUtils: encryptionFileUtilsFor(isDirect),
178 payjoinBox: payjoinSessionSource,
179 useLightning: false,
180 );
181
182 await wallet.save();
183 await wallet.init();
184 return wallet;
185 }
186
187 @override
188 Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
189 {bool? isTestnet}) async {
190 if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
191 throw BitcoinMnemonicIsIncorrectException();
192 }
193
194 final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
195 credentials.walletInfo?.network = network.value;
196
197 final wallet = await BitcoinWalletBase.create(
198 password: credentials.password!,
199 passphrase: credentials.passphrase,
200 mnemonic: credentials.mnemonic,
201 walletInfo: credentials.walletInfo!,
202 unspentCoinsInfo: unspentCoinsInfoSource,
203 payjoinBox: payjoinSessionSource,
204 network: network,
205 encryptionFileUtils: encryptionFileUtilsFor(isDirect),
206 );
207 await wallet.save();
208 await wallet.init();
209 return wallet;
210 }
211 }