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);