Remove Next Address button and logic for electrum wallets. Add disclaimer for electrum wallets on address screen. Change way for generation of current receive and change addresses.

M committed Jan 24, 2022 at 14:04 UTC 63a0395c2d5167ab813568f4ac7b41b08c5e97c1
21 files changed +142 -142
cw_bitcoin/lib/electrum_transaction_info.dart
+3 -1
@@ -116,7 +116,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
116 factory ElectrumTransactionInfo.fromElectrumBundle(
117 ElectrumTransactionBundle bundle, WalletType type,
118 {@required Set<String> addresses, int height}) {
119 - final date = DateTime.fromMillisecondsSinceEpoch(bundle.time * 1000);
119 + final date = bundle.time != null
120 + ? DateTime.fromMillisecondsSinceEpoch(bundle.time * 1000)
121 + : DateTime.now();
122 var direction = TransactionDirection.incoming;
123 var amount = 0;
124 var inputAmount = 0;
cw_bitcoin/lib/electrum_wallet.dart
+30 -7
@@ -484,7 +484,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
484 final original = bitcoin.Transaction.fromHex(transactionHex);
485 final ins = <bitcoin.Transaction>[];
486 final time = verboseTransaction['time'] as int;
487 - final confirmations = verboseTransaction['time'] as int;
487 + final confirmations = verboseTransaction['confirmations'] as int ?? 0;
488
489 for (final vin in original.ins) {
490 final id = HEX.encode(vin.hash.reversed.toList());
@@ -510,13 +510,35 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
510
511 @override
512 Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
513 + final addressHashes = <String, BitcoinAddressRecord>{};
514 + final normalizedHistories = <Map<String, dynamic>>[];
515 + walletAddresses.addresses.forEach((addressRecord) {
516 + if (addressRecord.isHidden) {
517 + return;
518 + }
519 +
520 + final sh = scriptHash(addressRecord.address, networkType: networkType);
521 + addressHashes[sh] = addressRecord;
522 + });
523 final histories =
514 - publicScriptHashes.map((scriptHash) => electrumClient.getHistory(scriptHash));
515 - final _historiesWithDetails = await Future.wait(histories)
516 - .then((histories) => histories.expand((i) => i).toList())
517 - .then((histories) => histories.map((tx) => fetchTransactionInfo(
518 - hash: tx['tx_hash'] as String, height: tx['height'] as int)));
519 - final historiesWithDetails = await Future.wait(_historiesWithDetails);
524 + addressHashes.keys.map((scriptHash) => electrumClient
525 + .getHistory(scriptHash)
526 + .then((history) => {scriptHash: history}));
527 + final historyResults = await Future.wait(histories);
528 + historyResults.forEach((history) {
529 + history.entries.forEach((historyItem) {
530 + if (historyItem.value.isNotEmpty) {
531 + final address = addressHashes[historyItem.key];
532 + address.setAsUsed();
533 + normalizedHistories.addAll(historyItem.value);
534 + }
535 + });
536 + });
537 + final historiesWithDetails = await Future.wait(
538 + normalizedHistories
539 + .map((transaction) => fetchTransactionInfo(
540 + hash: transaction['tx_hash'] as String,
541 + height: transaction['height'] as int)));
542
543 return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
544 <String, ElectrumTransactionInfo>{}, (acc, tx) {
@@ -534,6 +556,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
556 _isTransactionUpdating = true;
557 final transactions = await fetchTransactions();
558 transactionHistory.addMany(transactions);
559 + walletAddresses.updateReceiveAddresses();
560 await transactionHistory.save();
561 _isTransactionUpdating = false;
562 } catch (e) {
cw_bitcoin/lib/electrum_wallet_addresses.dart
+72 -76
@@ -6,7 +6,6 @@ import 'package:cw_core/wallet_addresses.dart';
6 import 'package:cw_core/wallet_info.dart';
7 import 'package:flutter/foundation.dart';
8 import 'package:mobx/mobx.dart';
9 -import 'dart:math';
9
10 part 'electrum_wallet_addresses.g.dart';
11
@@ -22,44 +21,60 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
21 this.sideHd,
22 this.electrumClient,
23 this.networkType})
25 - : super(walletInfo) {
24 + : addresses = ObservableList<BitcoinAddressRecord>.of(
25 + (initialAddresses ?? []).toSet()),
26 + receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
27 + (initialAddresses ?? [])
28 + .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
29 + .toSet()),
30 + changeAddresses = ObservableList<BitcoinAddressRecord>.of(
31 + (initialAddresses ?? [])
32 + .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
33 + .toSet()),
34 + super(walletInfo) {
35 currentReceiveAddressIndex = initialRegularAddressIndex;
36 currentChangeAddressIndex = initialChangeAddressIndex;
28 - addresses = ObservableList<BitcoinAddressRecord>.of(
29 - (initialAddresses ?? []).toSet());
37 }
38
39 static const defaultReceiveAddressesCount = 22;
40 static const defaultChangeAddressesCount = 17;
41 static const gap = 20;
42
43 + final ObservableList<BitcoinAddressRecord> addresses;
44 + final ObservableList<BitcoinAddressRecord> receiveAddresses;
45 + final ObservableList<BitcoinAddressRecord> changeAddresses;
46 + final ElectrumClient electrumClient;
47 + final bitcoin.NetworkType networkType;
48 + final bitcoin.HDWallet mainHd;
49 + final bitcoin.HDWallet sideHd;
50 +
51 + @override
52 + @computed
53 + String get address => receiveAddresses.first.address;
54 +
55 @override
37 - @observable
38 - String address;
56 + set address(String addr) => null;
57
58 int currentReceiveAddressIndex;
59 int currentChangeAddressIndex;
42 - ElectrumClient electrumClient;
43 - bitcoin.NetworkType networkType;
44 - bitcoin.HDWallet mainHd;
45 - bitcoin.HDWallet sideHd;
46 - ObservableList<BitcoinAddressRecord> addresses;
47 -
48 - List<BitcoinAddressRecord> get receiveAddresses => addresses
49 - .where((addr) => !addr.isHidden && !addr.isUsed)
50 - .toList();
51 -
52 - List<BitcoinAddressRecord> get changeAddresses => addresses
53 - .where((addr) => addr.isHidden && !addr.isUsed)
54 - .toList();
60
56 - List<BitcoinAddressRecord> get totalReceiveAddresses => addresses
57 - .where((addr) => !addr.isHidden)
58 - .toList();
61 + @computed
62 + int get totalCountOfReceiveAddresses =>
63 + addresses.fold(0, (acc, addressRecord) {
64 + if (!addressRecord.isHidden) {
65 + return acc + 1;
66 + }
67 + return acc;
68 + });
69
60 - List<BitcoinAddressRecord> get totalChangeAddresses => addresses
61 - .where((addr) => addr.isHidden)
62 - .toList();
70 + @computed
71 + int get totalCountOfChangeAddresses =>
72 + addresses.fold(0, (acc, addressRecord) {
73 + if (addressRecord.isHidden) {
74 + return acc + 1;
75 + }
76 + return acc;
77 + });
78
79 Future<void> discoverAddresses() async {
80 await _discoverAddresses(mainHd, false);
@@ -70,60 +85,39 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
85 @override
86 Future<void> init() async {
87 await _generateInitialAddresses();
73 -
74 - if (receiveAddresses.isEmpty) {
75 - final newAddresses = await _createNewAddresses(
76 - gap,
77 - hd: mainHd,
78 - startIndex: totalReceiveAddresses.length > 0
79 - ? totalReceiveAddresses.length - 1
80 - : 0,
81 - isHidden: false);
82 - _addAddresses(newAddresses);
83 - } else if (currentReceiveAddressIndex >= receiveAddresses.length) {
84 - currentReceiveAddressIndex = 0;
85 - }
86 -
87 - address = receiveAddresses[currentReceiveAddressIndex].address;
88 + updateReceiveAddresses();
89 + updateChangeAddresses();
90 await updateAddressesInBox();
89 - }
91
91 - @action
92 - Future<void> nextReceiveAddress() async {
93 - if (receiveAddresses.isEmpty) {
94 - final newAddresses = await _createNewAddresses(
95 - gap,
96 - hd: mainHd,
97 - startIndex: totalReceiveAddresses.length > 0
98 - ? totalReceiveAddresses.length - 1
99 - : 0,
100 - isHidden: false);
101 - _addAddresses(newAddresses);
102 - } else if (currentReceiveAddressIndex >= receiveAddresses.length) {
92 + if (currentReceiveAddressIndex >= receiveAddresses.length) {
93 currentReceiveAddressIndex = 0;
94 }
95
106 - address = receiveAddresses[currentReceiveAddressIndex].address;
107 - currentReceiveAddressIndex += 1;
108 - await updateAddressesInBox();
96 + if (currentChangeAddressIndex >= changeAddresses.length) {
97 + currentChangeAddressIndex = 0;
98 + }
99 }
100
101 @action
102 Future<String> getChangeAddress() async {
103 + updateChangeAddresses();
104 +
105 if (changeAddresses.isEmpty) {
106 final newAddresses = await _createNewAddresses(
107 gap,
108 hd: sideHd,
117 - startIndex: totalChangeAddresses.length > 0
118 - ? totalChangeAddresses.length - 1
109 + startIndex: totalCountOfChangeAddresses > 0
110 + ? totalCountOfChangeAddresses - 1
111 : 0,
112 isHidden: true);
113 _addAddresses(newAddresses);
122 - } else if (currentChangeAddressIndex >= changeAddresses.length) {
114 + }
115 +
116 + if (currentChangeAddressIndex >= changeAddresses.length) {
117 currentChangeAddressIndex = 0;
118 }
119
126 -
120 + updateChangeAddresses();
121 final address = changeAddresses[currentChangeAddressIndex].address;
122 currentChangeAddressIndex += 1;
123 return address;
@@ -153,18 +147,20 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
147 }
148 }
149
156 - void randomizeAddress() {
157 - const minCountOfVisibleAddresses = 5;
158 - final random = Random();
159 - var availableAddresses = addresses
160 - .where((addr) => !addr.isHidden)
161 - .toList();
162 -
163 - if (availableAddresses.length < minCountOfVisibleAddresses) {
164 - availableAddresses = addresses;
165 - }
150 + @action
151 + void updateReceiveAddresses() {
152 + receiveAddresses.removeRange(0, receiveAddresses.length);
153 + final newAdresses = addresses
154 + .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
155 + receiveAddresses.addAll(newAdresses);
156 + }
157
167 - address = availableAddresses[random.nextInt(availableAddresses.length)].address;
158 + @action
159 + void updateChangeAddresses() {
160 + changeAddresses.removeRange(0, changeAddresses.length);
161 + final newAdresses = addresses
162 + .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
163 + changeAddresses.addAll(newAdresses);
164 }
165
166 Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
@@ -187,12 +183,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
183
184 while(hasAddrUse) {
185 final addr = addrs.last.address;
190 - hasAddrUse = await _validateAddressUsing(addr);
186 + hasAddrUse = await _hasAddressUsed(addr);
187
188 if (!hasAddrUse) {
189 break;
190 }
195 -
191 +
192 final start = addrs.length;
193 final count = start + gap;
194 final batch = await _createNewAddresses(
@@ -264,9 +260,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
260 this.addresses.addAll(addressesSet);
261 }
262
267 - Future<bool> _validateAddressUsing(String address) async {
263 + Future<bool> _hasAddressUsed(String address) async {
264 final sh = scriptHash(address, networkType: networkType);
269 - final balance = await electrumClient.getBalance(sh);
270 - return balance.isEmpty;
265 + final transactionHistory = await electrumClient.getHistory(sh);
266 + return transactionHistory.isNotEmpty;
267 }
268 }
\ No newline at end of file
lib/bitcoin/cw_bitcoin.dart
-12
@@ -53,18 +53,6 @@ class CWBitcoin extends Bitcoin {
53 final bitcoinWallet = wallet as ElectrumWallet;
54 await bitcoinWallet.walletAddresses.generateNewAddress();
55 }
56 -
57 - @override
58 - Future<void> nextAddress(Object wallet) {
59 - final bitcoinWallet = wallet as ElectrumWallet;
60 - bitcoinWallet.walletAddresses.nextReceiveAddress();
61 - }
62 -
63 - @override
64 - Future<void> randomAddress(Object wallet) {
65 - final bitcoinWallet = wallet as ElectrumWallet;
66 - bitcoinWallet.walletAddresses.randomizeAddress();
67 - }
56
57 @override
58 Object createBitcoinTransactionCredentials(List<Output> outputs, TransactionPriority priority)
lib/src/screens/dashboard/widgets/address_page.dart
+9 -8
@@ -125,14 +125,15 @@ class AddressPage extends StatelessWidget {
125 ),
126 ),
127 )
128 - : PrimaryButton(
129 - onPressed: () => addressListViewModel.nextAddress(),
130 - text: 'Next address',
131 - color: Theme.of(context).buttonColor,
132 - textColor: Theme.of(context)
133 - .accentTextTheme
134 - .display3
135 - .backgroundColor);
128 + : Text(
129 + S.of(context).electrum_address_disclaimer,
130 + textAlign: TextAlign.center,
131 + style: TextStyle(
132 + fontSize: 15,
133 + color: Theme.of(context)
134 + .accentTextTheme
135 + .display2
136 + .backgroundColor));
137 })
138 ],
139 ),
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
-22
@@ -212,26 +212,4 @@ abstract class WalletAddressListViewModelBase with Store {
212
213 _baseItems.add(WalletAddressListHeader());
214 }
215 -
216 - @action
217 - void nextAddress() {
218 - final wallet = _wallet;
219 -
220 - if (wallet.type == WalletType.bitcoin
221 - || wallet.type == WalletType.litecoin) {
222 - bitcoin.nextAddress(wallet);
223 - wallet.save();
224 - }
225 - }
226 -
227 - @action
228 - void generateRandomAddress() {
229 - final wallet = _wallet;
230 -
231 - if (wallet.type == WalletType.bitcoin
232 - || wallet.type == WalletType.litecoin) {
233 - bitcoin.randomAddress(wallet);
234 - wallet.save();
235 - }
236 - }
215 }
res/values/strings_de.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
523 "learn_more" : "Erfahren Sie mehr",
524
525 - "new_template" : "neue Vorlage"
525 + "new_template" : "neue Vorlage",
526 + "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin"
527 }
res/values/strings_en.arb
+2 -1
@@ -523,5 +523,6 @@
523 "third_intro_content" : "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
524 "learn_more" : "Learn More",
525
526 - "new_template" : "New Template"
526 + "new_template" : "New Template",
527 + "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work"
528 }
\ No newline at end of file
res/values/strings_es.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
523 "learn_more" : "Aprende más",
524
525 - "new_template" : "Nueva plantilla"
525 + "new_template" : "Nueva plantilla",
526 + "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando"
527 }
\ No newline at end of file
res/values/strings_hi.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
523 "learn_more" : "और अधिक जानें",
524
525 - "new_template" : "नया टेम्पलेट"
525 + "new_template" : "नया टेम्पलेट",
526 + "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं"
527 }
\ No newline at end of file
res/values/strings_hr.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!",
523 "learn_more" : "Saznajte više",
524
525 - "new_template" : "novi predložak"
525 + "new_template" : "novi predložak",
526 + "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek"
527 }
\ No newline at end of file
res/values/strings_it.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Anche Yats vive fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con un Yat!",
523 "learn_more" : "Impara di più",
524
525 - "new_template" : "Nuovo modello"
525 + "new_template" : "Nuovo modello",
526 + "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare"
527 }
\ No newline at end of file
res/values/strings_ja.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます!",
523 "learn_more" : "もっと詳しく知る",
524
525 - "new_template" : "新しいテンプレート"
525 + "new_template" : "新しいテンプレート",
526 + "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します"
527 }
\ No newline at end of file
res/values/strings_ko.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!",
523 "learn_more" : "더 알아보기",
524
525 - "new_template" : "새 템플릿"
525 + "new_template" : "새 템플릿",
526 + "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다."
527 }
\ No newline at end of file
res/values/strings_nl.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!",
523 "learn_more" : "Kom meer te weten",
524
525 - "new_template" : "Nieuwe sjabloon"
525 + "new_template" : "Nieuwe sjabloon",
526 + "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work"
527 }
\ No newline at end of file
res/values/strings_pl.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!",
523 "learn_more" : "Ucz się więcej",
524
525 - "new_template" : "Nowy szablon"
525 + "new_template" : "Nowy szablon",
526 + "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają"
527 }
\ No newline at end of file
res/values/strings_pt.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!",
523 "learn_more" : "Saber mais",
524
525 - "new_template" : "Novo modelo"
525 + "new_template" : "Novo modelo",
526 + "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando"
527 }
\ No newline at end of file
res/values/strings_ru.arb
+2 -1
@@ -522,5 +522,6 @@
522 "third_intro_content" : "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!",
523 "learn_more" : "Узнать больше",
524
525 - "new_template" : "Новый шаблон"
525 + "new_template" : "Новый шаблон",
526 + "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать."
527 }
\ No newline at end of file
res/values/strings_uk.arb
+2 -1
@@ -521,5 +521,6 @@
521 "third_intro_content" : "Yat знаходиться за межами Cake Wallet. Будь-яку адресу гаманця на землі можна замінити на Yat!",
522 "learn_more" : "Дізнатися більше",
523
524 - "new_template" : "Новий шаблон"
524 + "new_template" : "Новий шаблон",
525 + "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати"
526 }
\ No newline at end of file
res/values/strings_zh.arb
+2 -1
@@ -520,5 +520,6 @@
520 "third_intro_content" : "Yats 也住在 Cake Wallet 之外。 地球上任何一個錢包地址都可以用一個Yat來代替!",
521 "learn_more" : "了解更多",
522
523 - "new_template" : "新模板"
523 + "new_template" : "新模板",
524 + "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效"
525 }
\ No newline at end of file
tool/configure.dart
-2
@@ -74,8 +74,6 @@ abstract class Bitcoin {
74 TransactionPriority deserializeBitcoinTransactionPriority(int raw);
75 int getFeeRate(Object wallet, TransactionPriority priority);
76 Future<void> generateNewAddress(Object wallet);
77 - Future<void> nextAddress(Object wallet);
78 - Future<void> randomAddress(Object wallet);
77 Object createBitcoinTransactionCredentials(List<Output> outputs, TransactionPriority priority);
78
79 List<String> getAddresses(Object wallet);