Reworked electrum wallet. Added Litecoin wallet.
M committed
May 7, 2021 at 10:36 UTC
13309667940b7ebd7c9fd44098ae088a834e0492
71 files changed
+1510
-1078
.gitignore
+2
@@ -96,3 +96,5 @@ vendor/
96
android/app/.cxx/**
97
ios/Flutter/.last_build_id
98
/lib/generated/**
99
+#**#
100
+/**/#**#
\ No newline at end of file
analysis_options.yaml
-1
@@ -25,7 +25,6 @@ linter:
25
- empty_constructor_bodies
26
- empty_statements
27
- hash_and_equals
28
- - implementation_imports
28
- invariant_booleans
29
- iterable_contains_unrelated_type
30
- library_names
assets/bitcoin_electrum_server_list.yml
renamed
assets/images/litecoin_icon.png
Binary files /dev/null and b/assets/images/litecoin_icon.png differ
assets/images/litecoin_menu.png
Binary files /dev/null and b/assets/images/litecoin_menu.png differ
assets/images/monero_menu.png
Binary files a/assets/images/monero_menu.png and b/assets/images/monero_menu.png differ
assets/litecoin_electrum_server_list.yml
new
+2
@@ -0,0 +1,2 @@
1
+-
2
+ uri: 128.199.34.116:50002
\ No newline at end of file
cw_monero/ios/Classes/monero_api.cpp
-4
@@ -179,8 +179,6 @@ extern "C"
179
Monero::SubaddressAccount *m_account;
180
uint64_t m_last_known_wallet_height;
181
uint64_t m_cached_syncing_blockchain_height = 0;
182
- std::mutex store_mutex;
183
-
182
183
void change_current_wallet(Monero::Wallet *wallet)
184
{
@@ -451,9 +449,7 @@ extern "C"
449
450
void store(char *path)
451
{
454
- store_mutex.lock();
452
get_current_wallet()->store(std::string(path));
456
- store_mutex.unlock();
453
}
454
455
bool transaction_create(char *address, char *payment_id, char *amount,
cw_monero/lib/wallet_manager.dart
+2
-2
@@ -1,12 +1,12 @@
1
import 'dart:ffi';
2
-import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
3
-import 'package:cw_monero/wallet.dart';
2
import 'package:ffi/ffi.dart';
3
import 'package:flutter/foundation.dart';
4
import 'package:cw_monero/convert_utf8_to_string.dart';
5
import 'package:cw_monero/signatures.dart';
6
import 'package:cw_monero/types.dart';
7
import 'package:cw_monero/monero_api.dart';
8
+import 'package:cw_monero/wallet.dart';
9
+import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
10
import 'package:cw_monero/exceptions/wallet_creation_exception.dart';
11
import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart';
12
import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart';
cw_monero/pubspec.lock
+20
-20
@@ -7,49 +7,49 @@ packages:
7
name: async
8
url: "https://pub.dartlang.org"
9
source: hosted
10
- version: "2.4.2"
10
+ version: "2.5.0"
11
boolean_selector:
12
dependency: transitive
13
description:
14
name: boolean_selector
15
url: "https://pub.dartlang.org"
16
source: hosted
17
- version: "2.0.0"
17
+ version: "2.1.0"
18
characters:
19
dependency: transitive
20
description:
21
name: characters
22
url: "https://pub.dartlang.org"
23
source: hosted
24
- version: "1.0.0"
24
+ version: "1.1.0"
25
charcode:
26
dependency: transitive
27
description:
28
name: charcode
29
url: "https://pub.dartlang.org"
30
source: hosted
31
- version: "1.1.3"
31
+ version: "1.2.0"
32
clock:
33
dependency: transitive
34
description:
35
name: clock
36
url: "https://pub.dartlang.org"
37
source: hosted
38
- version: "1.0.1"
38
+ version: "1.1.0"
39
collection:
40
dependency: transitive
41
description:
42
name: collection
43
url: "https://pub.dartlang.org"
44
source: hosted
45
- version: "1.14.13"
45
+ version: "1.15.0"
46
fake_async:
47
dependency: transitive
48
description:
49
name: fake_async
50
url: "https://pub.dartlang.org"
51
source: hosted
52
- version: "1.1.0"
52
+ version: "1.2.0"
53
ffi:
54
dependency: "direct main"
55
description:
@@ -73,21 +73,21 @@ packages:
73
name: matcher
74
url: "https://pub.dartlang.org"
75
source: hosted
76
- version: "0.12.8"
76
+ version: "0.12.10"
77
meta:
78
dependency: transitive
79
description:
80
name: meta
81
url: "https://pub.dartlang.org"
82
source: hosted
83
- version: "1.1.8"
83
+ version: "1.3.0"
84
path:
85
dependency: transitive
86
description:
87
name: path
88
url: "https://pub.dartlang.org"
89
source: hosted
90
- version: "1.7.0"
90
+ version: "1.8.0"
91
path_provider:
92
dependency: "direct main"
93
description:
@@ -113,56 +113,56 @@ packages:
113
name: source_span
114
url: "https://pub.dartlang.org"
115
source: hosted
116
- version: "1.7.0"
116
+ version: "1.8.0"
117
stack_trace:
118
dependency: transitive
119
description:
120
name: stack_trace
121
url: "https://pub.dartlang.org"
122
source: hosted
123
- version: "1.9.5"
123
+ version: "1.10.0"
124
stream_channel:
125
dependency: transitive
126
description:
127
name: stream_channel
128
url: "https://pub.dartlang.org"
129
source: hosted
130
- version: "2.0.0"
130
+ version: "2.1.0"
131
string_scanner:
132
dependency: transitive
133
description:
134
name: string_scanner
135
url: "https://pub.dartlang.org"
136
source: hosted
137
- version: "1.0.5"
137
+ version: "1.1.0"
138
term_glyph:
139
dependency: transitive
140
description:
141
name: term_glyph
142
url: "https://pub.dartlang.org"
143
source: hosted
144
- version: "1.1.0"
144
+ version: "1.2.0"
145
test_api:
146
dependency: transitive
147
description:
148
name: test_api
149
url: "https://pub.dartlang.org"
150
source: hosted
151
- version: "0.2.17"
151
+ version: "0.2.19"
152
typed_data:
153
dependency: transitive
154
description:
155
name: typed_data
156
url: "https://pub.dartlang.org"
157
source: hosted
158
- version: "1.2.0"
158
+ version: "1.3.0"
159
vector_math:
160
dependency: transitive
161
description:
162
name: vector_math
163
url: "https://pub.dartlang.org"
164
source: hosted
165
- version: "2.0.8"
165
+ version: "2.1.0"
166
sdks:
167
- dart: ">=2.9.0-14.0.dev <3.0.0"
168
- flutter: ">=0.1.4 <2.0.0"
167
+ dart: ">=2.12.0-0.0 <3.0.0"
168
+ flutter: ">=0.1.4"
lib/bitcoin/address_to_output_script.dart
+10
-6
@@ -1,25 +1,29 @@
1
import 'dart:typed_data';
2
+import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3
import 'package:bs58check/bs58check.dart' as bs58check;
4
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
5
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
6
import 'package:bitcoin_flutter/src/address.dart';
7
7
-
8
Uint8List p2shAddressToOutputScript(String address) {
9
final decodeBase58 = bs58check.decode(address);
10
final hash = decodeBase58.sublist(1);
11
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
12
}
13
14
-Uint8List addressToOutputScript(String address) {
14
+Uint8List addressToOutputScript(
15
+ String address, bitcoin.NetworkType networkType) {
16
try {
17
// FIXME: improve validation for p2sh addresses
17
- if (address.startsWith('3')) {
18
+ // 3 for bitcoin
19
+ // m for litecoin
20
+ if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
21
return p2shAddressToOutputScript(address);
22
}
23
21
- return Address.addressToOutputScript(address);
22
- } catch (_) {
24
+ return Address.addressToOutputScript(address, networkType);
25
+ } catch (err) {
26
+ print(err);
27
return Uint8List(0);
28
}
25
-}
\ No newline at end of file
29
+}
lib/bitcoin/bitcoin_address_record.dart
+7
-4
@@ -1,14 +1,14 @@
1
import 'dart:convert';
2
-import 'package:quiver/core.dart';
2
3
class BitcoinAddressRecord {
5
- BitcoinAddressRecord(this.address, {this.index});
4
+ BitcoinAddressRecord(this.address, {this.index, bool isHidden})
5
+ : _isHidden = isHidden;
6
7
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
8
final decoded = json.decode(jsonSource) as Map;
9
10
return BitcoinAddressRecord(decoded['address'] as String,
11
- index: decoded['index'] as int);
11
+ index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool);
12
}
13
14
@override
@@ -16,10 +16,13 @@ class BitcoinAddressRecord {
16
o is BitcoinAddressRecord && address == o.address;
17
18
final String address;
19
+ bool get isHidden => _isHidden ?? false;
20
int index;
21
+ final bool _isHidden;
22
23
@override
24
int get hashCode => address.hashCode;
25
24
- String toJSON() => json.encode({'address': address, 'index': index});
26
+ String toJSON() =>
27
+ json.encode({'address': address, 'index': index, 'isHidden': isHidden});
28
}
lib/bitcoin/bitcoin_transaction_history.dart
deleted
-192
@@ -1,192 +0,0 @@
1
-import 'dart:convert';
2
-import 'package:flutter/foundation.dart';
3
-import 'package:mobx/mobx.dart';
4
-import 'package:cake_wallet/core/transaction_history.dart';
5
-import 'package:cake_wallet/bitcoin/file.dart';
6
-import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
7
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
8
-import 'package:cake_wallet/bitcoin/electrum.dart';
9
-
10
-part 'bitcoin_transaction_history.g.dart';
11
-
12
-const _transactionsHistoryFileName = 'transactions.json';
13
-
14
-class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
15
- with _$BitcoinTransactionHistory;
16
-
17
-abstract class BitcoinTransactionHistoryBase
18
- extends TransactionHistoryBase<BitcoinTransactionInfo> with Store {
19
- BitcoinTransactionHistoryBase(
20
- {this.eclient, String dirPath, @required String password})
21
- : path = '$dirPath/$_transactionsHistoryFileName',
22
- _password = password,
23
- _height = 0,
24
- _isUpdating = false {
25
- transactions = ObservableMap<String, BitcoinTransactionInfo>();
26
- }
27
-
28
- BitcoinWalletBase wallet;
29
- final ElectrumClient eclient;
30
- final String path;
31
- final String _password;
32
- int _height;
33
- bool _isUpdating;
34
-
35
- Future<void> init() async {
36
- await _load();
37
- }
38
-
39
- @override
40
- Future update() async {
41
- if (_isUpdating) {
42
- return;
43
- }
44
-
45
- try {
46
- _isUpdating = true;
47
- final txs = await fetchTransactions();
48
- await add(txs);
49
- _isUpdating = false;
50
- } catch (_) {
51
- _isUpdating = false;
52
- rethrow;
53
- }
54
- }
55
-
56
- @override
57
- Future<Map<String, BitcoinTransactionInfo>> fetchTransactions() async {
58
- final histories =
59
- wallet.scriptHashes.map((scriptHash) => eclient.getHistory(scriptHash));
60
- final _historiesWithDetails = await Future.wait(histories)
61
- .then((histories) => histories.expand((i) => i).toList())
62
- .then((histories) => histories.map((tx) => fetchTransactionInfo(
63
- hash: tx['tx_hash'] as String, height: tx['height'] as int)));
64
- final historiesWithDetails = await Future.wait(_historiesWithDetails);
65
-
66
- return historiesWithDetails.fold<Map<String, BitcoinTransactionInfo>>(
67
- <String, BitcoinTransactionInfo>{}, (acc, tx) {
68
- acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
69
- return acc;
70
- });
71
- }
72
-
73
- Future<BitcoinTransactionInfo> fetchTransactionInfo(
74
- {@required String hash, @required int height}) async {
75
- final tx = await eclient.getTransactionExpanded(hash: hash);
76
- return BitcoinTransactionInfo.fromElectrumVerbose(tx,
77
- height: height, addresses: wallet.addresses);
78
- }
79
-
80
- Future<void> add(Map<String, BitcoinTransactionInfo> transactionsList) async {
81
- transactionsList.entries.forEach((entry) {
82
- _updateOrInsert(entry.value);
83
-
84
- if (entry.value.height > _height) {
85
- _height = entry.value.height;
86
- }
87
- });
88
-
89
- await save();
90
- }
91
-
92
- Future<void> addOne(BitcoinTransactionInfo tx) async {
93
- _updateOrInsert(tx);
94
-
95
- if (tx.height > _height) {
96
- _height = tx.height;
97
- }
98
-
99
- await save();
100
- }
101
-
102
- BitcoinTransactionInfo get(String id) => transactions[id];
103
-
104
- Future<void> save() async {
105
- try {
106
- final data = json.encode({'height': _height, 'transactions': transactions});
107
- await writeData(path: path, password: _password, data: data);
108
- } catch(e) {
109
- print('Error while save bitcoin transaction history: ${e.toString()}');
110
- }
111
- }
112
-
113
- @override
114
- void updateAsync({void Function() onFinished}) {
115
- fetchTransactionsAsync((transaction) => _updateOrInsert(transaction),
116
- onFinished: onFinished);
117
- }
118
-
119
- @override
120
- void fetchTransactionsAsync(
121
- void Function(BitcoinTransactionInfo transaction) onTransactionLoaded,
122
- {void Function() onFinished}) async {
123
- final histories = await Future.wait(wallet.scriptHashes
124
- .map((scriptHash) async => await eclient.getHistory(scriptHash)));
125
- final transactionsCount =
126
- histories.fold<int>(0, (acc, m) => acc + m.length);
127
- var counter = 0;
128
-
129
- final batches = histories.map((metaList) =>
130
- _fetchBatchOfTransactions(metaList, onTransactionLoaded: (transaction) {
131
- onTransactionLoaded(transaction);
132
- counter += 1;
133
-
134
- if (counter == transactionsCount) {
135
- onFinished?.call();
136
- }
137
- }));
138
-
139
- await Future.wait(batches);
140
- }
141
-
142
- Future<void> _fetchBatchOfTransactions(
143
- Iterable<Map<String, dynamic>> metaList,
144
- {void Function(BitcoinTransactionInfo tranasaction)
145
- onTransactionLoaded}) async =>
146
- metaList.forEach((txMeta) => fetchTransactionInfo(
147
- hash: txMeta['tx_hash'] as String,
148
- height: txMeta['height'] as int)
149
- .then((transaction) => onTransactionLoaded(transaction)));
150
-
151
- Future<Map<String, Object>> _read() async {
152
- final content = await read(path: path, password: _password);
153
- return json.decode(content) as Map<String, Object>;
154
- }
155
-
156
- Future<void> _load() async {
157
- try {
158
- final content = await _read();
159
- final txs = content['transactions'] as Map<String, Object> ?? {};
160
-
161
- txs.entries.forEach((entry) {
162
- final val = entry.value;
163
-
164
- if (val is Map<String, Object>) {
165
- final tx = BitcoinTransactionInfo.fromJson(val);
166
- _updateOrInsert(tx);
167
- }
168
- });
169
-
170
- _height = content['height'] as int;
171
- } catch (e) {
172
- print(e);
173
- }
174
- }
175
-
176
- void _updateOrInsert(BitcoinTransactionInfo transaction) {
177
- if (transaction.id == null) {
178
- return;
179
- }
180
-
181
- if (transactions[transaction.id] == null) {
182
- transactions[transaction.id] = transaction;
183
- } else {
184
- final originalTx = transactions[transaction.id];
185
- originalTx.confirmations = transaction.confirmations;
186
- originalTx.amount = transaction.amount;
187
- originalTx.height = transaction.height;
188
- originalTx.date ??= transaction.date;
189
- originalTx.isPending = transaction.isPending;
190
- }
191
- }
192
-}
lib/bitcoin/bitcoin_unspent.dart
+2
-1
@@ -13,5 +13,6 @@ class BitcoinUnspent {
13
final int value;
14
final int vout;
15
16
- bool get isP2wpkh => address.address.startsWith('bc1');
16
+ bool get isP2wpkh =>
17
+ address.address.startsWith('bc') || address.address.startsWith('ltc');
18
}
lib/bitcoin/bitcoin_wallet.dart
+31
-452
@@ -1,472 +1,51 @@
1
-import 'dart:async';
2
-import 'dart:convert';
3
-import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
4
-import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
5
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
6
-import 'package:cake_wallet/entities/transaction_priority.dart';
1
import 'package:mobx/mobx.dart';
2
import 'package:flutter/foundation.dart';
9
-import 'package:rxdart/rxdart.dart';
3
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
11
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
12
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
13
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
14
-import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
15
-import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
16
-import 'package:cake_wallet/bitcoin/electrum.dart';
17
-import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
18
-import 'package:cake_wallet/bitcoin/script_hash.dart';
4
import 'package:cake_wallet/bitcoin/utils.dart';
20
-import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
21
-import 'package:cake_wallet/entities/sync_status.dart';
5
+import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
6
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
7
import 'package:cake_wallet/entities/wallet_info.dart';
23
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
8
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
25
-import 'package:cake_wallet/bitcoin/file.dart';
26
-import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
27
-import 'package:cake_wallet/entities/node.dart';
28
-import 'package:cake_wallet/core/wallet_base.dart';
9
+import 'package:cake_wallet/bitcoin/electrum_balance.dart';
10
11
part 'bitcoin_wallet.g.dart';
12
13
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
14
34
-abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
35
- BitcoinWalletBase._internal(
36
- {@required this.eclient,
37
- @required this.path,
38
- @required String password,
39
- @required WalletInfo walletInfo,
40
- @required List<BitcoinAddressRecord> initialAddresses,
41
- int accountIndex = 0,
42
- this.transactionHistory,
43
- this.mnemonic,
44
- BitcoinBalance initialBalance})
45
- : balance =
46
- initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0),
47
- hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
48
- network: bitcoin.bitcoin)
49
- .derivePath("m/0'/0"),
50
- addresses = initialAddresses != null
51
- ? ObservableList<BitcoinAddressRecord>.of(initialAddresses.toSet())
52
- : ObservableList<BitcoinAddressRecord>(),
53
- syncStatus = NotConnectedSyncStatus(),
54
- _password = password,
55
- _accountIndex = accountIndex,
56
- _feeRates = <int>[],
57
- super(walletInfo) {
58
- _unspent = [];
59
- _scripthashesUpdateSubject = {};
60
- }
61
-
62
- static BitcoinWallet fromJSON(
63
- {@required String password,
64
- @required String name,
65
- @required String dirPath,
66
- @required WalletInfo walletInfo,
67
- String jsonSource}) {
68
- final data = json.decode(jsonSource) as Map;
69
- final mnemonic = data['mnemonic'] as String;
70
- final accountIndex =
71
- (data['account_index'] == 'null' || data['account_index'] == null)
72
- ? 0
73
- : int.parse(data['account_index'] as String);
74
- final _addresses = data['addresses'] as List ?? <Object>[];
75
- final addresses = <BitcoinAddressRecord>[];
76
- final balance = BitcoinBalance.fromJSON(data['balance'] as String) ??
77
- BitcoinBalance(confirmed: 0, unconfirmed: 0);
78
-
79
- _addresses.forEach((Object el) {
80
- if (el is String) {
81
- addresses.add(BitcoinAddressRecord.fromJSON(el));
82
- }
83
- });
84
-
85
- return BitcoinWalletBase.build(
86
- dirPath: dirPath,
87
- mnemonic: mnemonic,
88
- password: password,
89
- name: name,
90
- accountIndex: accountIndex,
91
- initialAddresses: addresses,
92
- initialBalance: balance,
93
- walletInfo: walletInfo);
94
- }
95
-
96
- static BitcoinWallet build(
15
+abstract class BitcoinWalletBase extends ElectrumWallet with Store {
16
+ BitcoinWalletBase(
17
{@required String mnemonic,
18
@required String password,
99
- @required String name,
100
- @required String dirPath,
19
@required WalletInfo walletInfo,
20
List<BitcoinAddressRecord> initialAddresses,
103
- BitcoinBalance initialBalance,
104
- int accountIndex = 0}) {
105
- final walletPath = '$dirPath/$name';
106
- final eclient = ElectrumClient();
107
- final history = BitcoinTransactionHistory(
108
- eclient: eclient, dirPath: dirPath, password: password);
109
-
110
- return BitcoinWallet._internal(
111
- eclient: eclient,
112
- path: walletPath,
113
- mnemonic: mnemonic,
21
+ ElectrumBalance initialBalance,
22
+ int accountIndex = 0})
23
+ : super(
24
+ mnemonic: mnemonic,
25
+ password: password,
26
+ walletInfo: walletInfo,
27
+ networkType: bitcoin.bitcoin,
28
+ initialAddresses: initialAddresses,
29
+ initialBalance: initialBalance,
30
+ accountIndex: accountIndex);
31
+
32
+ static Future<BitcoinWallet> open({
33
+ @required String name,
34
+ @required WalletInfo walletInfo,
35
+ @required String password,
36
+ }) async {
37
+ final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
38
+ await snp.load();
39
+ return BitcoinWallet(
40
+ mnemonic: snp.mnemonic,
41
password: password,
115
- accountIndex: accountIndex,
116
- initialAddresses: initialAddresses,
117
- initialBalance: initialBalance,
118
- transactionHistory: history,
119
- walletInfo: walletInfo);
120
- }
121
-
122
- static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
123
- inputsCount * 146 + outputsCounts * 33 + 8;
124
-
125
- @override
126
- final BitcoinTransactionHistory transactionHistory;
127
- final String path;
128
- final bitcoin.HDWallet hd;
129
- final ElectrumClient eclient;
130
- final String mnemonic;
131
-
132
- List<BitcoinUnspent> _unspent;
133
-
134
- @override
135
- @observable
136
- String address;
137
-
138
- @override
139
- @observable
140
- BitcoinBalance balance;
141
-
142
- @override
143
- @observable
144
- SyncStatus syncStatus;
145
-
146
- ObservableList<BitcoinAddressRecord> addresses;
147
-
148
- List<String> get scriptHashes =>
149
- addresses.map((addr) => scriptHash(addr.address)).toList();
150
-
151
- String get xpub => hd.base58;
152
-
153
- @override
154
- String get seed => mnemonic;
155
-
156
- @override
157
- BitcoinWalletKeys get keys => BitcoinWalletKeys(
158
- wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
159
-
160
- final String _password;
161
- List<int> _feeRates;
162
- int _accountIndex;
163
- Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
164
-
165
- Future<void> init() async {
166
- if (addresses.isEmpty || addresses.length < 33) {
167
- final addressesCount = 33 - addresses.length;
168
- await generateNewAddresses(addressesCount, startIndex: addresses.length);
169
- }
170
-
171
- address = addresses[_accountIndex].address;
172
- transactionHistory.wallet = this;
173
- await transactionHistory.init();
174
- }
175
-
176
- @action
177
- Future<void> nextAddress() async {
178
- _accountIndex += 1;
179
-
180
- if (_accountIndex >= addresses.length) {
181
- _accountIndex = 0;
182
- }
183
-
184
- address = addresses[_accountIndex].address;
185
-
186
- await save();
187
- }
188
-
189
- Future<BitcoinAddressRecord> generateNewAddress() async {
190
- _accountIndex += 1;
191
- final address = BitcoinAddressRecord(_getAddress(index: _accountIndex),
192
- index: _accountIndex);
193
- addresses.add(address);
194
-
195
- await save();
196
-
197
- return address;
198
- }
199
-
200
- Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
201
- {int startIndex = 0}) async {
202
- final list = <BitcoinAddressRecord>[];
203
-
204
- for (var i = startIndex; i < count + startIndex; i++) {
205
- final address = BitcoinAddressRecord(_getAddress(index: i), index: i);
206
- list.add(address);
207
- }
208
-
209
- addresses.addAll(list);
210
- await save();
211
-
212
- return list;
213
- }
214
-
215
- Future<void> updateAddress(String address) async {
216
- for (final addr in addresses) {
217
- if (addr.address == address) {
218
- await save();
219
- break;
220
- }
221
- }
222
- }
223
-
224
- @action
225
- @override
226
- Future<void> startSync() async {
227
- try {
228
- syncStatus = StartingSyncStatus();
229
- transactionHistory.updateAsync(onFinished: () {
230
- print('transactionHistory update finished!');
231
- transactionHistory.save();
232
- });
233
- _subscribeForUpdates();
234
- await _updateBalance();
235
- await _updateUnspent();
236
- _feeRates = await eclient.feeRates();
237
-
238
- Timer.periodic(const Duration(minutes: 1),
239
- (timer) async => _feeRates = await eclient.feeRates());
240
-
241
- syncStatus = SyncedSyncStatus();
242
- } catch (e) {
243
- print(e.toString());
244
- syncStatus = FailedSyncStatus();
245
- }
42
+ walletInfo: walletInfo,
43
+ initialAddresses: snp.addresses,
44
+ initialBalance: snp.balance,
45
+ accountIndex: snp.accountIndex);
46
}
47
248
- @action
48
@override
250
- Future<void> connectToNode({@required Node node}) async {
251
- try {
252
- syncStatus = ConnectingSyncStatus();
253
- await eclient.connectToUri(node.uri);
254
- eclient.onConnectionStatusChange = (bool isConnected) {
255
- if (!isConnected) {
256
- syncStatus = LostConnectionSyncStatus();
257
- }
258
- };
259
- syncStatus = ConnectedSyncStatus();
260
- } catch (e) {
261
- print(e.toString());
262
- syncStatus = FailedSyncStatus();
263
- }
264
- }
265
-
266
- @override
267
- Future<PendingBitcoinTransaction> createTransaction(
268
- Object credentials) async {
269
- const minAmount = 546;
270
- final transactionCredentials = credentials as BitcoinTransactionCredentials;
271
- final inputs = <BitcoinUnspent>[];
272
- final allAmountFee =
273
- calculateEstimatedFee(transactionCredentials.priority, null);
274
- final allAmount = balance.confirmed - allAmountFee;
275
- var fee = 0;
276
- final credentialsAmount = transactionCredentials.amount != null
277
- ? stringDoubleToBitcoinAmount(transactionCredentials.amount)
278
- : 0;
279
- final amount = transactionCredentials.amount == null ||
280
- allAmount - credentialsAmount < minAmount
281
- ? allAmount
282
- : credentialsAmount;
283
- final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
284
- final changeAddress = address;
285
- var leftAmount = amount;
286
- var totalInputAmount = 0;
287
-
288
- if (_unspent.isEmpty) {
289
- await _updateUnspent();
290
- }
291
-
292
- for (final utx in _unspent) {
293
- leftAmount = leftAmount - utx.value;
294
- totalInputAmount += utx.value;
295
- inputs.add(utx);
296
-
297
- if (leftAmount <= 0) {
298
- break;
299
- }
300
- }
301
-
302
- if (inputs.isEmpty) {
303
- throw BitcoinTransactionNoInputsException();
304
- }
305
-
306
- final totalAmount = amount + fee;
307
- fee = transactionCredentials.amount != null
308
- ? feeAmountForPriority(transactionCredentials.priority, inputs.length,
309
- amount == allAmount ? 1 : 2)
310
- : allAmountFee;
311
-
312
- if (totalAmount > balance.confirmed) {
313
- throw BitcoinTransactionWrongBalanceException();
314
- }
315
-
316
- if (amount <= 0 || totalInputAmount < amount) {
317
- throw BitcoinTransactionWrongBalanceException();
318
- }
319
-
320
- txb.setVersion(1);
321
-
322
- inputs.forEach((input) {
323
- if (input.isP2wpkh) {
324
- final p2wpkh = bitcoin
325
- .P2WPKH(
326
- data: generatePaymentData(hd: hd, index: input.address.index),
327
- network: bitcoin.bitcoin)
328
- .data;
329
-
330
- txb.addInput(input.hash, input.vout, null, p2wpkh.output);
331
- } else {
332
- txb.addInput(input.hash, input.vout);
333
- }
334
- });
335
-
336
- txb.addOutput(
337
- addressToOutputScript(transactionCredentials.address), amount);
338
-
339
- final estimatedSize = estimatedTransactionSize(inputs.length, 2);
340
- final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
341
- final changeValue = totalInputAmount - amount - feeAmount;
342
-
343
- if (changeValue > minAmount) {
344
- txb.addOutput(changeAddress, changeValue);
345
- }
346
-
347
- for (var i = 0; i < inputs.length; i++) {
348
- final input = inputs[i];
349
- final keyPair = generateKeyPair(hd: hd, index: input.address.index);
350
- final witnessValue = input.isP2wpkh ? input.value : null;
351
-
352
- txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
353
- }
354
-
355
- return PendingBitcoinTransaction(txb.build(),
356
- eclient: eclient, amount: amount, fee: fee)
357
- ..addListener((transaction) async {
358
- transactionHistory.addOne(transaction);
359
- await _updateBalance();
360
- });
361
- }
362
-
363
- String toJSON() => json.encode({
364
- 'mnemonic': mnemonic,
365
- 'account_index': _accountIndex.toString(),
366
- 'addresses': addresses.map((addr) => addr.toJSON()).toList(),
367
- 'balance': balance?.toJSON()
368
- });
369
-
370
- int feeRate(TransactionPriority priority) {
371
- if (priority is BitcoinTransactionPriority) {
372
- return _feeRates[priority.raw];
373
- }
374
-
375
- return 0;
376
- }
377
-
378
- int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
379
- int outputsCount) =>
380
- feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
381
-
382
- @override
383
- int calculateEstimatedFee(TransactionPriority priority, int amount) {
384
- if (priority is BitcoinTransactionPriority) {
385
- int inputsCount = 0;
386
-
387
- if (amount != null) {
388
- int totalValue = 0;
389
-
390
- for (final input in _unspent) {
391
- if (totalValue >= amount) {
392
- break;
393
- }
394
-
395
- totalValue += input.value;
396
- inputsCount += 1;
397
- }
398
- } else {
399
- inputsCount = _unspent.length;
400
- }
401
- // If send all, then we have no change value
402
- return feeAmountForPriority(
403
- priority, inputsCount, amount != null ? 2 : 1);
404
- }
405
-
406
- return 0;
407
- }
408
-
409
- @override
410
- Future<void> save() async {
411
- await write(path: path, password: _password, data: toJSON());
412
- await transactionHistory.save();
413
- }
414
-
415
- bitcoin.ECPair keyPairFor({@required int index}) =>
416
- generateKeyPair(hd: hd, index: index);
417
-
418
- @override
419
- Future<void> rescan({int height}) async {
420
- // FIXME: Unimplemented
421
- }
422
-
423
- @override
424
- void close() async {
425
- await eclient.close();
426
- }
427
-
428
- Future<void> _updateUnspent() async {
429
- final unspent = await Future.wait(addresses.map((address) => eclient
430
- .getListUnspentWithAddress(address.address)
431
- .then((unspent) => unspent
432
- .map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
433
- _unspent = unspent.expand((e) => e).toList();
434
- }
435
-
436
- void _subscribeForUpdates() {
437
- scriptHashes.forEach((sh) async {
438
- await _scripthashesUpdateSubject[sh]?.close();
439
- _scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh);
440
- _scripthashesUpdateSubject[sh].listen((event) async {
441
- try {
442
- await _updateBalance();
443
- await _updateUnspent();
444
- transactionHistory.updateAsync();
445
- } catch (e) {
446
- print(e.toString());
447
- }
448
- });
449
- });
450
- }
451
-
452
- Future<BitcoinBalance> _fetchBalances() async {
453
- final balances = await Future.wait(
454
- scriptHashes.map((sHash) => eclient.getBalance(sHash)));
455
- final balance = balances.fold(
456
- BitcoinBalance(confirmed: 0, unconfirmed: 0),
457
- (BitcoinBalance acc, val) => BitcoinBalance(
458
- confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
459
- unconfirmed:
460
- (val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
461
-
462
- return balance;
463
- }
464
-
465
- Future<void> _updateBalance() async {
466
- balance = await _fetchBalances();
467
- await save();
468
- }
469
-
470
- String _getAddress({@required int index}) =>
471
- generateAddress(hd: hd, index: index);
49
+ String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
50
+ generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
51
}
lib/bitcoin/bitcoin_wallet_service.dart
+11
-31
@@ -1,7 +1,6 @@
1
import 'dart:io';
2
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
3
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
4
-import 'package:cake_wallet/bitcoin/file.dart';
4
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
5
import 'package:cake_wallet/core/wallet_base.dart';
6
import 'package:cake_wallet/core/wallet_service.dart';
@@ -19,44 +18,32 @@ class BitcoinWalletService extends WalletService<
18
19
final Box<WalletInfo> walletInfoSource;
20
21
+ @override
22
+ WalletType getType() => WalletType.bitcoin;
23
+
24
@override
25
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
24
- final dirPath = await pathForWalletDir(
25
- type: WalletType.bitcoin, name: credentials.name);
26
- final wallet = BitcoinWalletBase.build(
27
- dirPath: dirPath,
26
+ final wallet = BitcoinWallet(
27
mnemonic: generateMnemonic(),
28
password: credentials.password,
30
- name: credentials.name,
29
walletInfo: credentials.walletInfo);
30
await wallet.save();
31
await wallet.init();
34
-
32
return wallet;
33
}
34
35
@override
36
Future<bool> isWalletExit(String name) async =>
40
- File(await pathForWallet(name: name, type: WalletType.bitcoin))
41
- .existsSync();
37
+ File(await pathForWallet(name: name, type: getType())).existsSync();
38
39
@override
40
Future<BitcoinWallet> openWallet(String name, String password) async {
45
- final walletDirPath =
46
- await pathForWalletDir(name: name, type: WalletType.bitcoin);
47
- final walletPath = '$walletDirPath/$name';
48
- final walletJSONRaw = await read(path: walletPath, password: password);
41
final walletInfo = walletInfoSource.values.firstWhere(
50
- (info) => info.id == WalletBase.idFor(name, WalletType.bitcoin),
42
+ (info) => info.id == WalletBase.idFor(name, getType()),
43
orElse: () => null);
52
- final wallet = BitcoinWalletBase.fromJSON(
53
- password: password,
54
- name: name,
55
- dirPath: walletDirPath,
56
- jsonSource: walletJSONRaw,
57
- walletInfo: walletInfo);
44
+ final wallet = await BitcoinWalletBase.open(
45
+ password: password, name: name, walletInfo: walletInfo);
46
await wallet.init();
59
-
47
return wallet;
48
}
49
@@ -67,10 +54,8 @@ class BitcoinWalletService extends WalletService<
54
55
@override
56
Future<BitcoinWallet> restoreFromKeys(
70
- BitcoinRestoreWalletFromWIFCredentials credentials) async {
71
- // TODO: implement restoreFromKeys
72
- throw UnimplementedError();
73
- }
57
+ BitcoinRestoreWalletFromWIFCredentials credentials) async =>
58
+ throw UnimplementedError();
59
60
@override
61
Future<BitcoinWallet> restoreFromSeed(
@@ -79,17 +64,12 @@ class BitcoinWalletService extends WalletService<
64
throw BitcoinMnemonicIsIncorrectException();
65
}
66
82
- final dirPath = await pathForWalletDir(
83
- type: WalletType.bitcoin, name: credentials.name);
84
- final wallet = BitcoinWalletBase.build(
85
- dirPath: dirPath,
86
- name: credentials.name,
67
+ final wallet = BitcoinWallet(
68
password: credentials.password,
69
mnemonic: credentials.mnemonic,
70
walletInfo: credentials.walletInfo);
71
await wallet.save();
72
await wallet.init();
92
-
73
return wallet;
74
}
75
}
lib/bitcoin/electrum.dart
+25
-12
@@ -2,6 +2,7 @@ import 'dart:async';
2
import 'dart:convert';
3
import 'dart:io';
4
import 'dart:typed_data';
5
+import 'package:bitcoin_flutter/bitcoin_flutter.dart';
6
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
7
import 'package:cake_wallet/bitcoin/script_hash.dart';
8
import 'package:flutter/foundation.dart';
@@ -173,10 +174,11 @@ class ElectrumClient {
174
});
175
176
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
176
- String address) =>
177
+ String address, NetworkType networkType) =>
178
call(
178
- method: 'blockchain.scripthash.listunspent',
179
- params: [scriptHash(address)]).then((dynamic result) {
179
+ method: 'blockchain.scripthash.listunspent',
180
+ params: [scriptHash(address, networkType: networkType)])
181
+ .then((dynamic result) {
182
if (result is List) {
183
return result.map((dynamic val) {
184
if (val is Map<String, Object>) {
@@ -259,7 +261,7 @@ class ElectrumClient {
261
if (result is String) {
262
return result;
263
}
262
- print(result);
264
+
265
return '';
266
});
267
@@ -303,14 +305,25 @@ class ElectrumClient {
305
});
306
307
Future<List<int>> feeRates() async {
306
- final topDoubleString = await estimatefee(p: 1);
307
- final middleDoubleString = await estimatefee(p: 20);
308
- final bottomDoubleString = await estimatefee(p: 150);
309
- final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
310
- final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
311
- final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
312
-
313
- return [bottom, middle, top];
308
+ try {
309
+ final topDoubleString = await estimatefee(p: 1);
310
+ final middleDoubleString = await estimatefee(p: 20);
311
+ final bottomDoubleString = await estimatefee(p: 150);
312
+ final top =
313
+ (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
314
+ .round();
315
+ final middle =
316
+ (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
317
+ .round();
318
+ final bottom =
319
+ (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
320
+ .round();
321
+
322
+ final res = [bottom, middle, top];
323
+ return res;
324
+ } catch (_) {
325
+ return [];
326
+ }
327
}
328
329
BehaviorSubject<Object> scripthashUpdate(String scripthash) {
lib/bitcoin/electrum_balance.dart
renamed
+6
-6
@@ -1,21 +1,20 @@
1
import 'dart:convert';
2
-
2
import 'package:flutter/foundation.dart';
3
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
4
import 'package:cake_wallet/entities/balance.dart';
5
7
-class BitcoinBalance extends Balance {
8
- const BitcoinBalance({@required this.confirmed, @required this.unconfirmed})
6
+class ElectrumBalance extends Balance {
7
+ const ElectrumBalance({@required this.confirmed, @required this.unconfirmed})
8
: super(confirmed, unconfirmed);
9
11
- factory BitcoinBalance.fromJSON(String jsonSource) {
10
+ factory ElectrumBalance.fromJSON(String jsonSource) {
11
if (jsonSource == null) {
12
return null;
13
}
14
15
final decoded = json.decode(jsonSource) as Map;
16
18
- return BitcoinBalance(
17
+ return ElectrumBalance(
18
confirmed: decoded['confirmed'] as int ?? 0,
19
unconfirmed: decoded['unconfirmed'] as int ?? 0);
20
}
@@ -24,7 +23,8 @@ class BitcoinBalance extends Balance {
23
final int unconfirmed;
24
25
@override
27
- String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed);
26
+ String get formattedAvailableBalance =>
27
+ bitcoinAmountToString(amount: confirmed);
28
29
@override
30
String get formattedAdditionalBalance =>
lib/bitcoin/electrum_transaction_history.dart
new
+98
@@ -0,0 +1,98 @@
1
+import 'dart:convert';
2
+import 'package:cake_wallet/entities/pathForWallet.dart';
3
+import 'package:cake_wallet/entities/wallet_info.dart';
4
+import 'package:flutter/foundation.dart';
5
+import 'package:mobx/mobx.dart';
6
+import 'package:cake_wallet/core/transaction_history.dart';
7
+import 'package:cake_wallet/bitcoin/file.dart';
8
+import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
9
+
10
+part 'electrum_transaction_history.g.dart';
11
+
12
+const _transactionsHistoryFileName = 'transactions.json';
13
+
14
+class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
15
+ with _$ElectrumTransactionHistory;
16
+
17
+abstract class ElectrumTransactionHistoryBase
18
+ extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
19
+ ElectrumTransactionHistoryBase(
20
+ {@required this.walletInfo, @required String password})
21
+ : _password = password,
22
+ _height = 0 {
23
+ transactions = ObservableMap<String, ElectrumTransactionInfo>();
24
+ }
25
+
26
+ final WalletInfo walletInfo;
27
+ final String _password;
28
+ int _height;
29
+
30
+ Future<void> init() async => await _load();
31
+
32
+ @override
33
+ void addOne(ElectrumTransactionInfo transaction) =>
34
+ transactions[transaction.id] = transaction;
35
+
36
+ @override
37
+ void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
38
+ this.transactions.addAll(transactions);
39
+
40
+ @override
41
+ Future<void> save() async {
42
+ try {
43
+ final dirPath =
44
+ await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
45
+ final path = '$dirPath/$_transactionsHistoryFileName';
46
+ final data =
47
+ json.encode({'height': _height, 'transactions': transactions});
48
+ await writeData(path: path, password: _password, data: data);
49
+ } catch (e) {
50
+ print('Error while save bitcoin transaction history: ${e.toString()}');
51
+ }
52
+ }
53
+
54
+ Future<Map<String, Object>> _read() async {
55
+ final dirPath =
56
+ await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
57
+ final path = '$dirPath/$_transactionsHistoryFileName';
58
+ final content = await read(path: path, password: _password);
59
+ return json.decode(content) as Map<String, Object>;
60
+ }
61
+
62
+ Future<void> _load() async {
63
+ try {
64
+ final content = await _read();
65
+ final txs = content['transactions'] as Map<String, Object> ?? {};
66
+
67
+ txs.entries.forEach((entry) {
68
+ final val = entry.value;
69
+
70
+ if (val is Map<String, Object>) {
71
+ final tx = ElectrumTransactionInfo.fromJson(val, walletInfo.type);
72
+ _updateOrInsert(tx);
73
+ }
74
+ });
75
+
76
+ _height = content['height'] as int;
77
+ } catch (e) {
78
+ print(e);
79
+ }
80
+ }
81
+
82
+ void _updateOrInsert(ElectrumTransactionInfo transaction) {
83
+ if (transaction.id == null) {
84
+ return;
85
+ }
86
+
87
+ if (transactions[transaction.id] == null) {
88
+ transactions[transaction.id] = transaction;
89
+ } else {
90
+ final originalTx = transactions[transaction.id];
91
+ originalTx.confirmations = transaction.confirmations;
92
+ originalTx.amount = transaction.amount;
93
+ originalTx.height = transaction.height;
94
+ originalTx.date ??= transaction.date;
95
+ originalTx.isPending = transaction.isPending;
96
+ }
97
+ }
98
+}
lib/bitcoin/electrum_transaction_info.dart
renamed
+19
-12
@@ -6,9 +6,10 @@ import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
6
import 'package:cake_wallet/entities/transaction_direction.dart';
7
import 'package:cake_wallet/entities/transaction_info.dart';
8
import 'package:cake_wallet/entities/format_amount.dart';
9
+import 'package:cake_wallet/entities/wallet_type.dart';
10
10
-class BitcoinTransactionInfo extends TransactionInfo {
11
- BitcoinTransactionInfo(
11
+class ElectrumTransactionInfo extends TransactionInfo {
12
+ ElectrumTransactionInfo(this.type,
13
{@required String id,
14
@required int height,
15
@required int amount,
@@ -27,7 +28,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
28
this.confirmations = confirmations;
29
}
30
30
- factory BitcoinTransactionInfo.fromElectrumVerbose(Map<String, Object> obj,
31
+ factory ElectrumTransactionInfo.fromElectrumVerbose(
32
+ Map<String, Object> obj, WalletType type,
33
{@required List<BitcoinAddressRecord> addresses, @required int height}) {
34
final addressesSet = addresses.map((addr) => addr.address).toSet();
35
final id = obj['txid'] as String;
@@ -47,7 +49,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
49
final out = vin['tx']['vout'][vout] as Map;
50
final outAddresses =
51
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
50
- inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
52
+ inputsAmount +=
53
+ stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
54
55
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
56
direction = TransactionDirection.outgoing;
@@ -58,7 +61,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
61
final outAddresses =
62
out['scriptPubKey']['addresses'] as List<Object> ?? [];
63
final ntrs = outAddresses.toSet().intersection(addressesSet);
61
- final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString());
64
+ final value = stringDoubleToBitcoinAmount(
65
+ (out['value'] as double ?? 0.0).toString());
66
totalOutAmount += value;
67
68
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
@@ -69,7 +73,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
73
74
final fee = inputsAmount - totalOutAmount;
75
72
- return BitcoinTransactionInfo(
76
+ return ElectrumTransactionInfo(type,
77
id: id,
78
height: height,
79
isPending: false,
@@ -80,7 +84,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
84
confirmations: confirmations);
85
}
86
83
- factory BitcoinTransactionInfo.fromHexAndHeader(String hex,
87
+ factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
88
{List<String> addresses, int height, int timestamp, int confirmations}) {
89
final tx = bitcoin.Transaction.fromHex(hex);
90
var exist = false;
@@ -104,7 +108,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
108
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
109
: DateTime.now();
110
107
- return BitcoinTransactionInfo(
111
+ return ElectrumTransactionInfo(type,
112
id: tx.getId(),
113
height: height,
114
isPending: false,
@@ -115,8 +119,9 @@ class BitcoinTransactionInfo extends TransactionInfo {
119
confirmations: confirmations);
120
}
121
118
- factory BitcoinTransactionInfo.fromJson(Map<String, dynamic> data) {
119
- return BitcoinTransactionInfo(
122
+ factory ElectrumTransactionInfo.fromJson(
123
+ Map<String, dynamic> data, WalletType type) {
124
+ return ElectrumTransactionInfo(type,
125
id: data['id'] as String,
126
height: data['height'] as int,
127
amount: data['amount'] as int,
@@ -127,6 +132,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
132
confirmations: data['confirmations'] as int);
133
}
134
135
+ final WalletType type;
136
+
137
String _fiatAmount;
138
139
@override
@@ -144,8 +151,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
151
@override
152
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
153
147
- BitcoinTransactionInfo updated(BitcoinTransactionInfo info) {
148
- return BitcoinTransactionInfo(
154
+ ElectrumTransactionInfo updated(ElectrumTransactionInfo info) {
155
+ return ElectrumTransactionInfo(info.type,
156
id: id,
157
height: info.height,
158
amount: info.amount,
lib/bitcoin/electrum_wallet.dart
new
+467
@@ -0,0 +1,467 @@
1
+import 'dart:async';
2
+import 'dart:convert';
3
+import 'package:mobx/mobx.dart';
4
+import 'package:rxdart/subjects.dart';
5
+import 'package:flutter/foundation.dart';
6
+import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
7
+import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
8
+import 'package:cake_wallet/entities/pathForWallet.dart';
9
+import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
10
+import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
11
+import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
12
+import 'package:cake_wallet/bitcoin/electrum_balance.dart';
13
+import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
14
+import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
15
+import 'package:cake_wallet/bitcoin/electrum_transaction_history.dart';
16
+import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
17
+import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
18
+import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
19
+import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
20
+import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
21
+import 'package:cake_wallet/bitcoin/file.dart';
22
+import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
23
+import 'package:cake_wallet/bitcoin/script_hash.dart';
24
+import 'package:cake_wallet/bitcoin/utils.dart';
25
+import 'package:cake_wallet/core/wallet_base.dart';
26
+import 'package:cake_wallet/entities/node.dart';
27
+import 'package:cake_wallet/entities/sync_status.dart';
28
+import 'package:cake_wallet/entities/transaction_priority.dart';
29
+import 'package:cake_wallet/entities/wallet_info.dart';
30
+import 'package:cake_wallet/bitcoin/electrum.dart';
31
+
32
+part 'electrum_wallet.g.dart';
33
+
34
+class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
35
+
36
+abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
37
+ ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
38
+ ElectrumWalletBase(
39
+ {@required String password,
40
+ @required WalletInfo walletInfo,
41
+ @required List<BitcoinAddressRecord> initialAddresses,
42
+ @required this.networkType,
43
+ @required this.mnemonic,
44
+ ElectrumClient electrumClient,
45
+ int accountIndex = 0,
46
+ ElectrumBalance initialBalance})
47
+ : balance = initialBalance ??
48
+ const ElectrumBalance(confirmed: 0, unconfirmed: 0),
49
+ hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
50
+ network: networkType)
51
+ .derivePath("m/0'/0"),
52
+ addresses = ObservableList<BitcoinAddressRecord>.of(
53
+ (initialAddresses ?? []).toSet()),
54
+ syncStatus = NotConnectedSyncStatus(),
55
+ _password = password,
56
+ _accountIndex = accountIndex,
57
+ _feeRates = <int>[],
58
+ _isTransactionUpdating = false,
59
+ super(walletInfo) {
60
+ this.electrumClient = electrumClient ?? ElectrumClient();
61
+ this.walletInfo = walletInfo;
62
+ transactionHistory =
63
+ ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
64
+ _unspent = [];
65
+ _scripthashesUpdateSubject = {};
66
+ }
67
+
68
+ static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
69
+ inputsCount * 146 + outputsCounts * 33 + 8;
70
+
71
+ final bitcoin.HDWallet hd;
72
+ final String mnemonic;
73
+
74
+ ElectrumClient electrumClient;
75
+
76
+ @override
77
+ @observable
78
+ String address;
79
+
80
+ @override
81
+ @observable
82
+ ElectrumBalance balance;
83
+
84
+ @override
85
+ @observable
86
+ SyncStatus syncStatus;
87
+
88
+ ObservableList<BitcoinAddressRecord> addresses;
89
+
90
+ List<String> get scriptHashes => addresses
91
+ .map((addr) => scriptHash(addr.address, networkType: networkType))
92
+ .toList();
93
+
94
+ String get xpub => hd.base58;
95
+
96
+ @override
97
+ String get seed => mnemonic;
98
+
99
+ bitcoin.NetworkType networkType;
100
+
101
+ @override
102
+ BitcoinWalletKeys get keys => BitcoinWalletKeys(
103
+ wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
104
+
105
+ final String _password;
106
+ List<BitcoinUnspent> _unspent;
107
+ List<int> _feeRates;
108
+ int _accountIndex;
109
+ Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
110
+ bool _isTransactionUpdating;
111
+
112
+ Future<void> init() async {
113
+ await generateAddresses();
114
+ address = addresses[_accountIndex].address;
115
+ await transactionHistory.init();
116
+ }
117
+
118
+ @action
119
+ Future<void> nextAddress() async {
120
+ _accountIndex += 1;
121
+
122
+ if (_accountIndex >= addresses.length) {
123
+ _accountIndex = 0;
124
+ }
125
+
126
+ address = addresses[_accountIndex].address;
127
+
128
+ await save();
129
+ }
130
+
131
+ Future<void> generateAddresses() async {
132
+ if (addresses.length < 33) {
133
+ final addressesCount = 33 - addresses.length;
134
+ await generateNewAddresses(addressesCount,
135
+ startIndex: addresses.length, hd: hd);
136
+ }
137
+ }
138
+
139
+ Future<BitcoinAddressRecord> generateNewAddress(
140
+ {bool isHidden = false, bitcoin.HDWallet hd}) async {
141
+ _accountIndex += 1;
142
+ final _hd = hd ?? this.hd;
143
+ final address = BitcoinAddressRecord(
144
+ getAddress(index: _accountIndex, hd: _hd),
145
+ index: _accountIndex,
146
+ isHidden: isHidden);
147
+ addresses.add(address);
148
+ await save();
149
+ return address;
150
+ }
151
+
152
+ Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
153
+ {int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async {
154
+ final list = <BitcoinAddressRecord>[];
155
+
156
+ for (var i = startIndex; i < count + startIndex; i++) {
157
+ final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
158
+ index: i, isHidden: isHidden);
159
+ list.add(address);
160
+ }
161
+
162
+ addresses.addAll(list);
163
+ await save();
164
+ return list;
165
+ }
166
+
167
+ Future<void> updateAddress(String address) async {
168
+ for (final addr in addresses) {
169
+ if (addr.address == address) {
170
+ await save();
171
+ break;
172
+ }
173
+ }
174
+ }
175
+
176
+ @action
177
+ @override
178
+ Future<void> startSync() async {
179
+ try {
180
+ syncStatus = StartingSyncStatus();
181
+ updateTransactions();
182
+ _subscribeForUpdates();
183
+ await _updateBalance();
184
+ await _updateUnspent();
185
+ _feeRates = await electrumClient.feeRates();
186
+
187
+ Timer.periodic(const Duration(minutes: 1),
188
+ (timer) async => _feeRates = await electrumClient.feeRates());
189
+
190
+ syncStatus = SyncedSyncStatus();
191
+ } catch (e) {
192
+ print(e.toString());
193
+ syncStatus = FailedSyncStatus();
194
+ }
195
+ }
196
+
197
+ @action
198
+ @override
199
+ Future<void> connectToNode({@required Node node}) async {
200
+ try {
201
+ syncStatus = ConnectingSyncStatus();
202
+ await electrumClient.connectToUri(node.uri);
203
+ electrumClient.onConnectionStatusChange = (bool isConnected) {
204
+ if (!isConnected) {
205
+ syncStatus = LostConnectionSyncStatus();
206
+ }
207
+ };
208
+ syncStatus = ConnectedSyncStatus();
209
+ } catch (e) {
210
+ print(e.toString());
211
+ syncStatus = FailedSyncStatus();
212
+ }
213
+ }
214
+
215
+ @override
216
+ Future<PendingBitcoinTransaction> createTransaction(
217
+ Object credentials) async {
218
+ const minAmount = 546;
219
+ final transactionCredentials = credentials as BitcoinTransactionCredentials;
220
+ final inputs = <BitcoinUnspent>[];
221
+ final allAmountFee =
222
+ calculateEstimatedFee(transactionCredentials.priority, null);
223
+ final allAmount = balance.confirmed - allAmountFee;
224
+ var fee = 0;
225
+ final credentialsAmount = transactionCredentials.amount != null
226
+ ? stringDoubleToBitcoinAmount(transactionCredentials.amount)
227
+ : 0;
228
+ final amount = transactionCredentials.amount == null ||
229
+ allAmount - credentialsAmount < minAmount
230
+ ? allAmount
231
+ : credentialsAmount;
232
+ final txb = bitcoin.TransactionBuilder(network: networkType);
233
+ final changeAddress = address;
234
+ var leftAmount = amount;
235
+ var totalInputAmount = 0;
236
+
237
+ if (_unspent.isEmpty) {
238
+ await _updateUnspent();
239
+ }
240
+
241
+ for (final utx in _unspent) {
242
+ leftAmount = leftAmount - utx.value;
243
+ totalInputAmount += utx.value;
244
+ inputs.add(utx);
245
+
246
+ if (leftAmount <= 0) {
247
+ break;
248
+ }
249
+ }
250
+
251
+ if (inputs.isEmpty) {
252
+ throw BitcoinTransactionNoInputsException();
253
+ }
254
+
255
+ final totalAmount = amount + fee;
256
+ fee = transactionCredentials.amount != null
257
+ ? feeAmountForPriority(transactionCredentials.priority, inputs.length,
258
+ amount == allAmount ? 1 : 2)
259
+ : allAmountFee;
260
+
261
+ if (totalAmount > balance.confirmed) {
262
+ throw BitcoinTransactionWrongBalanceException();
263
+ }
264
+
265
+ if (amount <= 0 || totalInputAmount < amount) {
266
+ throw BitcoinTransactionWrongBalanceException();
267
+ }
268
+
269
+ txb.setVersion(1);
270
+
271
+ inputs.forEach((input) {
272
+ if (input.isP2wpkh) {
273
+ final p2wpkh = bitcoin
274
+ .P2WPKH(
275
+ data: generatePaymentData(hd: hd, index: input.address.index),
276
+ network: networkType)
277
+ .data;
278
+
279
+ txb.addInput(input.hash, input.vout, null, p2wpkh.output);
280
+ } else {
281
+ txb.addInput(input.hash, input.vout);
282
+ }
283
+ });
284
+
285
+ txb.addOutput(
286
+ addressToOutputScript(transactionCredentials.address, networkType),
287
+ amount);
288
+
289
+ final estimatedSize = estimatedTransactionSize(inputs.length, 2);
290
+ final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
291
+ final changeValue = totalInputAmount - amount - feeAmount;
292
+
293
+ if (changeValue > minAmount) {
294
+ txb.addOutput(changeAddress, changeValue);
295
+ }
296
+
297
+ for (var i = 0; i < inputs.length; i++) {
298
+ final input = inputs[i];
299
+ final keyPair = generateKeyPair(
300
+ hd: hd, index: input.address.index, network: networkType);
301
+ final witnessValue = input.isP2wpkh ? input.value : null;
302
+
303
+ txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
304
+ }
305
+
306
+ return PendingBitcoinTransaction(txb.build(), type,
307
+ electrumClient: electrumClient, amount: amount, fee: fee)
308
+ ..addListener((transaction) async {
309
+ transactionHistory.addOne(transaction);
310
+ await _updateBalance();
311
+ });
312
+ }
313
+
314
+ String toJSON() => json.encode({
315
+ 'mnemonic': mnemonic,
316
+ 'account_index': _accountIndex.toString(),
317
+ 'addresses': addresses.map((addr) => addr.toJSON()).toList(),
318
+ 'balance': balance?.toJSON()
319
+ });
320
+
321
+ int feeRate(TransactionPriority priority) {
322
+ if (priority is BitcoinTransactionPriority) {
323
+ return _feeRates[priority.raw];
324
+ }
325
+
326
+ return 0;
327
+ }
328
+
329
+ int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
330
+ int outputsCount) =>
331
+ feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
332
+
333
+ @override
334
+ int calculateEstimatedFee(TransactionPriority priority, int amount) {
335
+ if (priority is BitcoinTransactionPriority) {
336
+ int inputsCount = 0;
337
+
338
+ if (amount != null) {
339
+ int totalValue = 0;
340
+
341
+ for (final input in _unspent) {
342
+ if (totalValue >= amount) {
343
+ break;
344
+ }
345
+
346
+ totalValue += input.value;
347
+ inputsCount += 1;
348
+ }
349
+ } else {
350
+ inputsCount = _unspent.length;
351
+ }
352
+ // If send all, then we have no change value
353
+ return feeAmountForPriority(
354
+ priority, inputsCount, amount != null ? 2 : 1);
355
+ }
356
+
357
+ return 0;
358
+ }
359
+
360
+ @override
361
+ Future<void> save() async {
362
+ final path = await makePath();
363
+ await write(path: path, password: _password, data: toJSON());
364
+ await transactionHistory.save();
365
+ }
366
+
367
+ bitcoin.ECPair keyPairFor({@required int index}) =>
368
+ generateKeyPair(hd: hd, index: index, network: networkType);
369
+
370
+ @override
371
+ Future<void> rescan({int height}) async => throw UnimplementedError();
372
+
373
+ @override
374
+ Future<void> close() async {
375
+ try {
376
+ await electrumClient?.close();
377
+ } catch (_) {}
378
+ }
379
+
380
+ String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
381
+
382
+ Future<String> makePath() async =>
383
+ pathForWallet(name: walletInfo.name, type: walletInfo.type);
384
+
385
+ Future<void> _updateUnspent() async {
386
+ final unspent = await Future.wait(addresses.map((address) => electrumClient
387
+ .getListUnspentWithAddress(address.address, networkType)
388
+ .then((unspent) => unspent
389
+ .map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
390
+ _unspent = unspent.expand((e) => e).toList();
391
+ }
392
+
393
+ Future<ElectrumTransactionInfo> fetchTransactionInfo(
394
+ {@required String hash, @required int height}) async {
395
+ final tx = await electrumClient.getTransactionExpanded(hash: hash);
396
+ return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type,
397
+ height: height, addresses: addresses);
398
+ }
399
+
400
+ @override
401
+ Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
402
+ final histories =
403
+ scriptHashes.map((scriptHash) => electrumClient.getHistory(scriptHash));
404
+ final _historiesWithDetails = await Future.wait(histories)
405
+ .then((histories) => histories.expand((i) => i).toList())
406
+ .then((histories) => histories.map((tx) => fetchTransactionInfo(
407
+ hash: tx['tx_hash'] as String, height: tx['height'] as int)));
408
+ final historiesWithDetails = await Future.wait(_historiesWithDetails);
409
+
410
+ return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
411
+ <String, ElectrumTransactionInfo>{}, (acc, tx) {
412
+ acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
413
+ return acc;
414
+ });
415
+ }
416
+
417
+ Future<void> updateTransactions() async {
418
+ try {
419
+ if (_isTransactionUpdating) {
420
+ return;
421
+ }
422
+
423
+ _isTransactionUpdating = true;
424
+ final transactions = await fetchTransactions();
425
+ transactionHistory.addMany(transactions);
426
+ await transactionHistory.save();
427
+ _isTransactionUpdating = false;
428
+ } catch (e) {
429
+ print(e);
430
+ _isTransactionUpdating = false;
431
+ }
432
+ }
433
+
434
+ void _subscribeForUpdates() {
435
+ scriptHashes.forEach((sh) async {
436
+ await _scripthashesUpdateSubject[sh]?.close();
437
+ _scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh);
438
+ _scripthashesUpdateSubject[sh].listen((event) async {
439
+ try {
440
+ await _updateBalance();
441
+ await _updateUnspent();
442
+ await updateTransactions();
443
+ } catch (e) {
444
+ print(e.toString());
445
+ }
446
+ });
447
+ });
448
+ }
449
+
450
+ Future<ElectrumBalance> _fetchBalances() async {
451
+ final balances = await Future.wait(
452
+ scriptHashes.map((sh) => electrumClient.getBalance(sh)));
453
+ final balance = balances.fold(
454
+ ElectrumBalance(confirmed: 0, unconfirmed: 0),
455
+ (ElectrumBalance acc, val) => ElectrumBalance(
456
+ confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
457
+ unconfirmed:
458
+ (val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
459
+
460
+ return balance;
461
+ }
462
+
463
+ Future<void> _updateBalance() async {
464
+ balance = await _fetchBalances();
465
+ await save();
466
+ }
467
+}
lib/bitcoin/electrum_wallet_snapshot.dart
new
+42
@@ -0,0 +1,42 @@
1
+import 'dart:convert';
2
+import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
3
+import 'package:cake_wallet/bitcoin/electrum_balance.dart';
4
+import 'package:cake_wallet/bitcoin/file.dart';
5
+import 'package:cake_wallet/entities/pathForWallet.dart';
6
+import 'package:cake_wallet/entities/wallet_type.dart';
7
+
8
+class ElectrumWallletSnapshot {
9
+ ElectrumWallletSnapshot(this.name, this.type, this.password);
10
+
11
+ final String name;
12
+ final String password;
13
+ final WalletType type;
14
+
15
+ String mnemonic;
16
+ List<BitcoinAddressRecord> addresses;
17
+ ElectrumBalance balance;
18
+ int accountIndex;
19
+
20
+ Future<void> load() async {
21
+ try {
22
+ final path = await pathForWallet(name: name, type: type);
23
+ final jsonSource = await read(path: path, password: password);
24
+ final data = json.decode(jsonSource) as Map;
25
+ final addressesTmp = data['addresses'] as List ?? <Object>[];
26
+ mnemonic = data['mnemonic'] as String;
27
+ addresses = addressesTmp
28
+ .whereType<String>()
29
+ .map((addr) => BitcoinAddressRecord.fromJSON(addr))
30
+ .toList();
31
+ balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
32
+ ElectrumBalance(confirmed: 0, unconfirmed: 0);
33
+ accountIndex = 0;
34
+
35
+ try {
36
+ accountIndex = int.parse(data['account_index'] as String);
37
+ } catch (_) {}
38
+ } catch (e) {
39
+ print(e);
40
+ }
41
+ }
42
+}
lib/bitcoin/litecoin_network.dart
new
+9
@@ -0,0 +1,9 @@
1
+import 'package:bitcoin_flutter/bitcoin_flutter.dart';
2
+
3
+final litecoinNetwork = NetworkType(
4
+ messagePrefix: '\x19Litecoin Signed Message:\n',
5
+ bech32: 'ltc',
6
+ bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
7
+ pubKeyHash: 0x30,
8
+ scriptHash: 0x32,
9
+ wif: 0xb0);
lib/bitcoin/litecoin_wallet.dart
new
+88
@@ -0,0 +1,88 @@
1
+import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2
+import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
3
+import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
4
+import 'package:cake_wallet/entities/transaction_priority.dart';
5
+import 'package:flutter/foundation.dart';
6
+import 'package:mobx/mobx.dart';
7
+import 'package:cake_wallet/entities/wallet_info.dart';
8
+import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
9
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
10
+import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
11
+import 'package:cake_wallet/bitcoin/electrum_balance.dart';
12
+import 'package:cake_wallet/bitcoin/litecoin_network.dart';
13
+import 'package:cake_wallet/bitcoin/utils.dart';
14
+
15
+part 'litecoin_wallet.g.dart';
16
+
17
+class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
18
+
19
+abstract class LitecoinWalletBase extends ElectrumWallet with Store {
20
+ LitecoinWalletBase(
21
+ {@required String mnemonic,
22
+ @required String password,
23
+ @required WalletInfo walletInfo,
24
+ List<BitcoinAddressRecord> initialAddresses,
25
+ ElectrumBalance initialBalance,
26
+ int accountIndex = 0})
27
+ : super(
28
+ mnemonic: mnemonic,
29
+ password: password,
30
+ walletInfo: walletInfo,
31
+ networkType: litecoinNetwork,
32
+ initialAddresses: initialAddresses,
33
+ initialBalance: initialBalance,
34
+ accountIndex: accountIndex);
35
+
36
+ static Future<LitecoinWallet> open({
37
+ @required String name,
38
+ @required WalletInfo walletInfo,
39
+ @required String password,
40
+ }) async {
41
+ final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
42
+ await snp.load();
43
+ return LitecoinWallet(
44
+ mnemonic: snp.mnemonic,
45
+ password: password,
46
+ walletInfo: walletInfo,
47
+ initialAddresses: snp.addresses,
48
+ initialBalance: snp.balance,
49
+ accountIndex: snp.accountIndex);
50
+ }
51
+
52
+ @override
53
+ String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
54
+ generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
55
+
56
+ @override
57
+ Future<void> generateAddresses() async {
58
+ if (addresses.length < 33) {
59
+ final addressesCount = 22 - addresses.length;
60
+ await generateNewAddresses(addressesCount,
61
+ hd: hd, startIndex: addresses.length);
62
+
63
+ final changeRoot = bitcoin.HDWallet.fromSeed(
64
+ mnemonicToSeedBytes(mnemonic),
65
+ network: networkType)
66
+ .derivePath("m/0'/1");
67
+
68
+ await generateNewAddresses(11,
69
+ startIndex: 0, hd: changeRoot, isHidden: true);
70
+ }
71
+ }
72
+
73
+ @override
74
+ int feeRate(TransactionPriority priority) {
75
+ if (priority is BitcoinTransactionPriority) {
76
+ switch (priority) {
77
+ case BitcoinTransactionPriority.slow:
78
+ return 1;
79
+ case BitcoinTransactionPriority.medium:
80
+ return 2;
81
+ case BitcoinTransactionPriority.fast:
82
+ return 3;
83
+ }
84
+ }
85
+
86
+ return 0;
87
+ }
88
+}
lib/bitcoin/litecoin_wallet_service.dart
new
+76
@@ -0,0 +1,76 @@
1
+import 'dart:io';
2
+import 'package:hive/hive.dart';
3
+import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
4
+import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
5
+import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
6
+import 'package:cake_wallet/bitcoin/litecoin_wallet.dart';
7
+import 'package:cake_wallet/core/wallet_service.dart';
8
+import 'package:cake_wallet/entities/pathForWallet.dart';
9
+import 'package:cake_wallet/entities/wallet_type.dart';
10
+import 'package:cake_wallet/entities/wallet_info.dart';
11
+import 'package:cake_wallet/core/wallet_base.dart';
12
+
13
+class LitecoinWalletService extends WalletService<
14
+ BitcoinNewWalletCredentials,
15
+ BitcoinRestoreWalletFromSeedCredentials,
16
+ BitcoinRestoreWalletFromWIFCredentials> {
17
+ LitecoinWalletService(this.walletInfoSource);
18
+
19
+ final Box<WalletInfo> walletInfoSource;
20
+
21
+ @override
22
+ WalletType getType() => WalletType.litecoin;
23
+
24
+ @override
25
+ Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
26
+ final wallet = LitecoinWallet(
27
+ mnemonic: generateMnemonic(),
28
+ password: credentials.password,
29
+ walletInfo: credentials.walletInfo);
30
+ await wallet.save();
31
+ await wallet.init();
32
+
33
+ return wallet;
34
+ }
35
+
36
+ @override
37
+ Future<bool> isWalletExit(String name) async =>
38
+ File(await pathForWallet(name: name, type: getType())).existsSync();
39
+
40
+ @override
41
+ Future<LitecoinWallet> openWallet(String name, String password) async {
42
+ final walletInfo = walletInfoSource.values.firstWhere(
43
+ (info) => info.id == WalletBase.idFor(name, getType()),
44
+ orElse: () => null);
45
+ final wallet = await LitecoinWalletBase.open(
46
+ password: password, name: name, walletInfo: walletInfo);
47
+ await wallet.init();
48
+ return wallet;
49
+ }
50
+
51
+ @override
52
+ Future<void> remove(String wallet) async =>
53
+ File(await pathForWalletDir(name: wallet, type: getType()))
54
+ .delete(recursive: true);
55
+
56
+ @override
57
+ Future<LitecoinWallet> restoreFromKeys(
58
+ BitcoinRestoreWalletFromWIFCredentials credentials) async =>
59
+ throw UnimplementedError();
60
+
61
+ @override
62
+ Future<LitecoinWallet> restoreFromSeed(
63
+ BitcoinRestoreWalletFromSeedCredentials credentials) async {
64
+ if (!validateMnemonic(credentials.mnemonic)) {
65
+ throw BitcoinMnemonicIsIncorrectException();
66
+ }
67
+
68
+ final wallet = LitecoinWallet(
69
+ password: credentials.password,
70
+ mnemonic: credentials.mnemonic,
71
+ walletInfo: credentials.walletInfo);
72
+ await wallet.save();
73
+ await wallet.init();
74
+ return wallet;
75
+ }
76
+}
lib/bitcoin/pending_bitcoin_transaction.dart
+17
-12
@@ -1,18 +1,22 @@
1
-import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
2
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
3
-import 'package:cake_wallet/entities/transaction_direction.dart';
1
import 'package:flutter/foundation.dart';
2
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3
import 'package:cake_wallet/core/pending_transaction.dart';
4
import 'package:cake_wallet/bitcoin/electrum.dart';
5
+import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
6
+import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
7
+import 'package:cake_wallet/entities/transaction_direction.dart';
8
+import 'package:cake_wallet/entities/wallet_type.dart';
9
10
class PendingBitcoinTransaction with PendingTransaction {
10
- PendingBitcoinTransaction(this._tx,
11
- {@required this.eclient, @required this.amount, @required this.fee})
12
- : _listeners = <void Function(BitcoinTransactionInfo transaction)>[];
11
+ PendingBitcoinTransaction(this._tx, this.type,
12
+ {@required this.electrumClient,
13
+ @required this.amount,
14
+ @required this.fee})
15
+ : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
16
17
+ final WalletType type;
18
final bitcoin.Transaction _tx;
15
- final ElectrumClient eclient;
19
+ final ElectrumClient electrumClient;
20
final int amount;
21
final int fee;
22
@@ -25,24 +29,25 @@ class PendingBitcoinTransaction with PendingTransaction {
29
@override
30
String get feeFormatted => bitcoinAmountToString(amount: fee);
31
28
- final List<void Function(BitcoinTransactionInfo transaction)> _listeners;
32
+ final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
33
34
@override
35
Future<void> commit() async {
32
- await eclient.broadcastTransaction(transactionRaw: _tx.toHex());
36
+ await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
37
_listeners?.forEach((listener) => listener(transactionInfo()));
38
}
39
40
void addListener(
37
- void Function(BitcoinTransactionInfo transaction) listener) =>
41
+ void Function(ElectrumTransactionInfo transaction) listener) =>
42
_listeners.add(listener);
43
40
- BitcoinTransactionInfo transactionInfo() => BitcoinTransactionInfo(
44
+ ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
45
id: id,
46
height: 0,
47
amount: amount,
48
direction: TransactionDirection.outgoing,
49
date: DateTime.now(),
50
isPending: true,
47
- confirmations: 0);
51
+ confirmations: 0,
52
+ fee: fee);
53
}
lib/bitcoin/script_hash.dart
+9
-7
@@ -1,18 +1,20 @@
1
+import 'package:flutter/foundation.dart';
2
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3
import 'package:crypto/crypto.dart';
4
4
-String scriptHash(String address) {
5
- final outputScript = bitcoin.Address.addressToOutputScript(address);
6
- final splitted = sha256.convert(outputScript).toString().split('');
5
+String scriptHash(String address, {@required bitcoin.NetworkType networkType}) {
6
+ final outputScript =
7
+ bitcoin.Address.addressToOutputScript(address, networkType);
8
+ final parts = sha256.convert(outputScript).toString().split('');
9
var res = '';
10
9
- for (var i = splitted.length - 1; i >= 0; i--) {
10
- final char = splitted[i];
11
+ for (var i = parts.length - 1; i >= 0; i--) {
12
+ final char = parts[i];
13
i--;
12
- final nextChar = splitted[i];
14
+ final nextChar = parts[i];
15
res += nextChar;
16
res += char;
17
}
18
19
return res;
18
-}
\ No newline at end of file
20
+}
lib/bitcoin/utils.dart
+33
-4
@@ -13,14 +13,43 @@ bitcoin.ECPair generateKeyPair(
13
{@required bitcoin.HDWallet hd,
14
@required int index,
15
bitcoin.NetworkType network}) =>
16
- bitcoin.ECPair.fromWIF(hd.derive(index).wif,
17
- network: network ?? bitcoin.bitcoin);
16
+ bitcoin.ECPair.fromWIF(hd.derive(index).wif, network: network);
17
19
-String generateAddress({@required bitcoin.HDWallet hd, @required int index}) =>
18
+String generateP2WPKHAddress(
19
+ {@required bitcoin.HDWallet hd,
20
+ @required int index,
21
+ bitcoin.NetworkType networkType}) =>
22
+ bitcoin
23
+ .P2WPKH(
24
+ data: PaymentData(
25
+ pubkey:
26
+ Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
27
+ network: networkType)
28
+ .data
29
+ .address;
30
+
31
+String generateP2WPKHAddressByPath(
32
+ {@required bitcoin.HDWallet hd,
33
+ @required String path,
34
+ bitcoin.NetworkType networkType}) =>
35
bitcoin
36
.P2WPKH(
37
data: PaymentData(
38
pubkey:
24
- Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))))
39
+ Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey))),
40
+ network: networkType)
41
+ .data
42
+ .address;
43
+
44
+String generateP2PKHAddress(
45
+ {@required bitcoin.HDWallet hd,
46
+ @required int index,
47
+ bitcoin.NetworkType networkType}) =>
48
+ bitcoin
49
+ .P2PKH(
50
+ data: PaymentData(
51
+ pubkey:
52
+ Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
53
+ network: networkType)
54
.data
55
.address;
lib/core/address_validator.dart
+1
-1
@@ -72,7 +72,7 @@ class AddressValidator extends TextValidator {
72
case CryptoCurrency.eth:
73
return [42];
74
case CryptoCurrency.ltc:
75
- return [34];
75
+ return [34, 43];
76
case CryptoCurrency.nano:
77
return [64, 65];
78
case CryptoCurrency.trx:
lib/core/backup_service.dart
-1
@@ -8,7 +8,6 @@ import 'package:path_provider/path_provider.dart';
8
import 'package:cryptography/cryptography.dart';
9
import 'package:shared_preferences/shared_preferences.dart';
10
import 'package:archive/archive_io.dart';
11
-import 'package:cake_wallet/core/auth_service.dart';
11
import 'package:cake_wallet/core/key_service.dart';
12
import 'package:cake_wallet/entities/encrypt.dart';
13
import 'package:cake_wallet/entities/preferences_key.dart';
lib/core/fiat_conversion_service.dart
+5
-6
@@ -3,7 +3,6 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
3
import 'dart:convert';
4
import 'package:flutter/foundation.dart';
5
import 'package:http/http.dart';
6
-import 'package:cake_wallet/entities/currency_formatter.dart';
6
7
const fiatApiAuthority = 'fiat-api.cakewallet.com';
8
const fiatApiPath = '/v1/rates';
@@ -15,9 +14,8 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
14
15
try {
16
final fiatStringified = fiat.toString();
18
- final uri =
19
- Uri.https(fiatApiAuthority, fiatApiPath,
20
- <String, String> {'convert': fiatStringified});
17
+ final uri = Uri.https(fiatApiAuthority, fiatApiPath,
18
+ <String, String>{'convert': fiatStringified});
19
final response = await get(uri.toString());
20
21
if (response.statusCode != 200) {
@@ -28,7 +26,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
26
final data = responseJSON['data'] as List<dynamic>;
27
28
for (final item in data) {
31
- if (item['symbol'] == cryptoToString(crypto)) {
29
+ if (item['symbol'] == crypto.title) {
30
price = item['quote'][fiatStringified]['price'] as double;
31
break;
32
}
@@ -45,6 +43,7 @@ Future<double> _fetchPriceAsync(
43
compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto});
44
45
class FiatConversionService {
48
- static Future<double> fetchPrice(CryptoCurrency crypto, FiatCurrency fiat) async =>
46
+ static Future<double> fetchPrice(
47
+ CryptoCurrency crypto, FiatCurrency fiat) async =>
48
await _fetchPriceAsync(crypto, fiat);
49
}
lib/core/generate_wallet_password.dart
+3
-3
@@ -4,9 +4,9 @@ import 'package:cake_wallet/entities/wallet_type.dart';
4
5
String generateWalletPassword(WalletType type) {
6
switch (type) {
7
- case WalletType.bitcoin:
8
- return generateKey();
9
- default:
7
+ case WalletType.monero:
8
return Uuid().v4();
9
+ default:
10
+ return generateKey();
11
}
12
}
lib/core/node_port_validator.dart
-1
@@ -1,4 +1,3 @@
1
-import 'package:flutter/foundation.dart';
1
import 'package:cake_wallet/generated/i18n.dart';
2
import 'package:cake_wallet/core/validator.dart';
3
lib/core/transaction_history.dart
+42
-35
@@ -3,43 +3,50 @@ import 'package:mobx/mobx.dart';
3
import 'package:cake_wallet/entities/transaction_info.dart';
4
5
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
6
- TransactionHistoryBase() : _isUpdating = false;
6
+ TransactionHistoryBase();
7
+ // : _isUpdating = false;
8
9
@observable
10
ObservableMap<String, TransactionType> transactions;
11
11
- bool _isUpdating;
12
-
13
- @action
14
- Future<void> update() async {
15
- if (_isUpdating) {
16
- return;
17
- }
18
-
19
- try {
20
- _isUpdating = true;
21
- final _transactions = await fetchTransactions();
22
- transactions.keys
23
- .toSet()
24
- .difference(_transactions.keys.toSet())
25
- .forEach((k) => transactions.remove(k));
26
- _transactions.forEach((key, value) => transactions[key] = value);
27
- _isUpdating = false;
28
- } catch (e) {
29
- _isUpdating = false;
30
- rethrow;
31
- }
32
- }
33
-
34
- void updateAsync({void Function() onFinished}) {
35
- fetchTransactionsAsync(
36
- (transaction) => transactions[transaction.id] = transaction,
37
- onFinished: onFinished);
38
- }
39
-
40
- void fetchTransactionsAsync(
41
- void Function(TransactionType transaction) onTransactionLoaded,
42
- {void Function() onFinished});
43
-
44
- Future<Map<String, TransactionType>> fetchTransactions();
12
+ Future<void> save();
13
+
14
+ void addOne(TransactionType transaction);
15
+
16
+ void addMany(Map<String, TransactionType> transactions);
17
+
18
+ // bool _isUpdating;
19
+
20
+ // @action
21
+ // Future<void> update() async {
22
+ // if (_isUpdating) {
23
+ // return;
24
+ // }
25
+
26
+ // try {
27
+ // _isUpdating = true;
28
+ // final _transactions = await fetchTransactions();
29
+ // transactions.keys
30
+ // .toSet()
31
+ // .difference(_transactions.keys.toSet())
32
+ // .forEach((k) => transactions.remove(k));
33
+ // _transactions.forEach((key, value) => transactions[key] = value);
34
+ // _isUpdating = false;
35
+ // } catch (e) {
36
+ // _isUpdating = false;
37
+ // rethrow;
38
+ // }
39
+ // }
40
+
41
+ // void updateAsync({void Function() onFinished}) {
42
+ // fetchTransactionsAsync(
43
+ // (transaction) => transactions[transaction.id] = transaction,
44
+ // onFinished: onFinished);
45
+ // }
46
+
47
+ // void fetchTransactionsAsync(
48
+ // void Function(TransactionType transaction) onTransactionLoaded,
49
+ // {void Function() onFinished});
50
+
51
+ // Future<Map<String, TransactionType>> fetchTransactions();
52
}
lib/core/wallet_base.dart
+12
-2
@@ -1,4 +1,5 @@
1
import 'package:cake_wallet/entities/balance.dart';
2
+import 'package:cake_wallet/entities/transaction_info.dart';
3
import 'package:cake_wallet/entities/transaction_priority.dart';
4
import 'package:flutter/foundation.dart';
5
import 'package:cake_wallet/entities/wallet_info.dart';
@@ -11,7 +12,10 @@ import 'package:cake_wallet/entities/sync_status.dart';
12
import 'package:cake_wallet/entities/node.dart';
13
import 'package:cake_wallet/entities/wallet_type.dart';
14
14
-abstract class WalletBase<BalanceType extends Balance> {
15
+abstract class WalletBase<
16
+ BalanceType extends Balance,
17
+ HistoryType extends TransactionHistoryBase,
18
+ TransactionType extends TransactionInfo> {
19
WalletBase(this.walletInfo);
20
21
static String idFor(String name, WalletType type) =>
@@ -41,7 +45,7 @@ abstract class WalletBase<BalanceType extends Balance> {
45
46
Object get keys;
47
44
- TransactionHistoryBase transactionHistory;
48
+ HistoryType transactionHistory;
49
50
Future<void> connectToNode({@required Node node});
51
@@ -51,6 +55,12 @@ abstract class WalletBase<BalanceType extends Balance> {
55
56
int calculateEstimatedFee(TransactionPriority priority, int amount);
57
58
+ // void fetchTransactionsAsync(
59
+ // void Function(TransactionType transaction) onTransactionLoaded,
60
+ // {void Function() onFinished});
61
+
62
+ Future<Map<String, TransactionType>> fetchTransactions();
63
+
64
Future<void> save();
65
66
Future<void> rescan({int height});
lib/core/wallet_service.dart
+3
@@ -1,8 +1,11 @@
1
import 'package:cake_wallet/core/wallet_base.dart';
2
import 'package:cake_wallet/core/wallet_credentials.dart';
3
+import 'package:cake_wallet/entities/wallet_type.dart';
4
5
abstract class WalletService<N extends WalletCredentials,
6
RFS extends WalletCredentials, RFK extends WalletCredentials> {
7
+ WalletType getType();
8
+
9
Future<WalletBase> create(N credentials);
10
11
Future<WalletBase> restoreFromSeed(RFS credentials);
lib/di.dart
+5
@@ -1,4 +1,5 @@
1
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
2
+import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
3
import 'package:cake_wallet/core/backup_service.dart';
4
import 'package:cake_wallet/core/wallet_service.dart';
5
import 'package:cake_wallet/entities/biometric_auth.dart';
@@ -443,6 +444,8 @@ Future setup(
444
445
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
446
447
+ getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
448
+
449
getIt.registerFactoryParam<WalletService, WalletType, void>(
450
(WalletType param1, __) {
451
switch (param1) {
@@ -450,6 +453,8 @@ Future setup(
453
return getIt.get<MoneroWalletService>();
454
case WalletType.bitcoin:
455
return getIt.get<BitcoinWalletService>();
456
+ case WalletType.litecoin:
457
+ return getIt.get<LitecoinWalletService>();
458
default:
459
return null;
460
}
lib/entities/currency_for_wallet_type.dart
+3
-1
@@ -7,7 +7,9 @@ CryptoCurrency currencyForWalletType(WalletType type) {
7
return CryptoCurrency.btc;
8
case WalletType.monero:
9
return CryptoCurrency.xmr;
10
+ case WalletType.litecoin:
11
+ return CryptoCurrency.ltc;
12
default:
13
return null;
14
}
13
-}
\ No newline at end of file
15
+}
lib/entities/currency_formatter.dart
deleted
-12
@@ -1,12 +0,0 @@
1
-import 'package:cake_wallet/entities/crypto_currency.dart';
2
-
3
-String cryptoToString(CryptoCurrency crypto) {
4
- switch (crypto) {
5
- case CryptoCurrency.xmr:
6
- return 'XMR';
7
- case CryptoCurrency.btc:
8
- return 'BTC';
9
- default:
10
- return '';
11
- }
12
-}
\ No newline at end of file
lib/entities/default_settings_migration.dart
+102
-32
@@ -1,11 +1,7 @@
1
import 'dart:io' show File, Platform;
2
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
3
-import 'package:cake_wallet/core/generate_wallet_password.dart';
4
-import 'package:cake_wallet/core/key_service.dart';
5
-import 'package:cake_wallet/di.dart';
3
import 'package:cake_wallet/entities/pathForWallet.dart';
4
import 'package:cake_wallet/entities/secret_store_key.dart';
8
-import 'package:cake_wallet/monero/monero_wallet_service.dart';
5
import 'package:flutter/foundation.dart';
6
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
7
import 'package:hive/hive.dart';
@@ -24,7 +20,8 @@ import 'package:cake_wallet/exchange/trade.dart';
20
import 'package:encrypt/encrypt.dart' as encrypt;
21
22
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
27
-const cakeWalletElectrumUri = 'electrum.cakewallet.com:50002';
23
+const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
24
+const cakeWalletLitecoinElectrumUri = '128.199.34.116:50002';
25
26
Future defaultSettingsMigration(
27
{@required int version,
@@ -68,6 +65,8 @@ Future defaultSettingsMigration(
65
sharedPreferences: sharedPreferences, nodes: nodes);
66
await changeBitcoinCurrentElectrumServerToDefault(
67
sharedPreferences: sharedPreferences, nodes: nodes);
68
+ await changeLitecoinCurrentElectrumServerToDefault(
69
+ sharedPreferences: sharedPreferences, nodes: nodes);
70
71
break;
72
case 2:
@@ -97,6 +96,7 @@ Future defaultSettingsMigration(
96
case 9:
97
await generateBackupPassword(secureStorage);
98
break;
99
+
100
case 10:
101
await changeTransactionPriorityAndFeeRateKeys(sharedPreferences);
102
break;
@@ -110,7 +110,14 @@ Future defaultSettingsMigration(
110
break;
111
112
case 13:
113
- await resetElectrumServer(nodes, sharedPreferences);
113
+ await resetBitcoinElectrumServer(nodes, sharedPreferences);
114
+ break;
115
+
116
+ case 15:
117
+ await addLitecoinElectrumServerList(nodes: nodes);
118
+ await changeLitecoinCurrentElectrumServerToDefault(
119
+ sharedPreferences: sharedPreferences, nodes: nodes);
120
+ await checkCurrentNodes(nodes, sharedPreferences);
121
break;
122
123
default:
@@ -160,12 +167,21 @@ Future<void> changeMoneroCurrentNodeToDefault(
167
}
168
169
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
163
- return nodes.values
164
- .firstWhere((Node node) => node.uri == cakeWalletElectrumUri, orElse: () => null) ??
170
+ return nodes.values.firstWhere(
171
+ (Node node) => node.uri == cakeWalletBitcoinElectrumUri,
172
+ orElse: () => null) ??
173
nodes.values.firstWhere((node) => node.type == WalletType.bitcoin,
174
orElse: () => null);
175
}
176
177
+Node getLitecoinDefaultElectrumServer({@required Box<Node> nodes}) {
178
+ return nodes.values.firstWhere(
179
+ (Node node) => node.uri == cakeWalletLitecoinElectrumUri,
180
+ orElse: () => null) ??
181
+ nodes.values.firstWhere((node) => node.type == WalletType.litecoin,
182
+ orElse: () => null);
183
+}
184
+
185
Node getMoneroDefaultNode({@required Box<Node> nodes}) {
186
final timeZone = DateTime.now().timeZoneOffset.inHours;
187
var nodeUri = '';
@@ -192,6 +208,15 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
208
await sharedPreferences.setInt('current_node_id_btc', serverId);
209
}
210
211
+Future<void> changeLitecoinCurrentElectrumServerToDefault(
212
+ {@required SharedPreferences sharedPreferences,
213
+ @required Box<Node> nodes}) async {
214
+ final server = getLitecoinDefaultElectrumServer(nodes: nodes);
215
+ final serverId = server?.key as int ?? 0;
216
+
217
+ await sharedPreferences.setInt('current_node_id_ltc', serverId);
218
+}
219
+
220
Future<void> replaceDefaultNode(
221
{@required SharedPreferences sharedPreferences,
222
@required Box<Node> nodes}) async {
@@ -224,7 +249,12 @@ Future<void> updateNodeTypes({@required Box<Node> nodes}) async {
249
}
250
251
Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
227
- final serverList = await loadElectrumServerList();
252
+ final serverList = await loadBitcoinElectrumServerList();
253
+ await nodes.addAll(serverList);
254
+}
255
+
256
+Future<void> addLitecoinElectrumServerList({@required Box<Node> nodes}) async {
257
+ final serverList = await loadLitecoinElectrumServerList();
258
await nodes.addAll(serverList);
259
}
260
@@ -284,57 +314,97 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
314
Future<void> changeDefaultMoneroNode(
315
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
316
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
287
- final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
288
- final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
289
- final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern);
317
+ final currentMoneroNodeId =
318
+ sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
319
+ final currentMoneroNode =
320
+ nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
321
+ final needToReplaceCurrentMoneroNode =
322
+ currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern);
323
324
nodeSource.values.forEach((node) async {
292
- if (node.type == WalletType.monero && node.uri.contains(cakeWalletMoneroNodeUriPattern)) {
325
+ if (node.type == WalletType.monero &&
326
+ node.uri.contains(cakeWalletMoneroNodeUriPattern)) {
327
await node.delete();
328
}
329
});
330
297
- final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
331
+ final newCakeWalletNode =
332
+ Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
333
334
await nodeSource.add(newCakeWalletNode);
335
336
if (needToReplaceCurrentMoneroNode) {
302
- await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
337
+ await sharedPreferences.setInt(
338
+ PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
339
}
340
}
341
306
-Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
307
- final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
308
- final currentElectrumSeverId = await sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
309
- final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId, orElse: () => null);
310
- final currentElectrumServer = nodeSource.values.firstWhere((node) => node.key == currentElectrumSeverId, orElse: () => null);
342
+Future<void> checkCurrentNodes(
343
+ Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
344
+ final currentMoneroNodeId =
345
+ sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
346
+ final currentBitcoinElectrumSeverId =
347
+ sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
348
+ final currentLitecoinElectrumSeverId = sharedPreferences
349
+ .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
350
+ final currentMoneroNode = nodeSource.values.firstWhere(
351
+ (node) => node.key == currentMoneroNodeId,
352
+ orElse: () => null);
353
+ final currentBitcoinElectrumServer = nodeSource.values.firstWhere(
354
+ (node) => node.key == currentBitcoinElectrumSeverId,
355
+ orElse: () => null);
356
+ final currentLitecoinElectrumServer = nodeSource.values.firstWhere(
357
+ (node) => node.key == currentLitecoinElectrumSeverId,
358
+ orElse: () => null);
359
360
if (currentMoneroNode == null) {
313
- final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
361
+ final newCakeWalletNode =
362
+ Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
363
await nodeSource.add(newCakeWalletNode);
315
- await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
364
+ await sharedPreferences.setInt(
365
+ PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
366
}
367
318
- if (currentElectrumServer == null) {
319
- final cakeWalletElectrum = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin);
368
+ if (currentBitcoinElectrumServer == null) {
369
+ final cakeWalletElectrum =
370
+ Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
371
await nodeSource.add(cakeWalletElectrum);
321
- await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
372
+ await sharedPreferences.setInt(
373
+ PreferencesKey.currentBitcoinElectrumSererIdKey,
374
+ cakeWalletElectrum.key as int);
375
}
323
-}
376
377
+ if (currentLitecoinElectrumServer == null) {
378
+ final cakeWalletElectrum =
379
+ Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
380
+ await nodeSource.add(cakeWalletElectrum);
381
+ await sharedPreferences.setInt(
382
+ PreferencesKey.currentLitecoinElectrumSererIdKey,
383
+ cakeWalletElectrum.key as int);
384
+ }
385
+}
386
326
-Future<void> resetElectrumServer(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
327
- final currentElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
328
- final oldElectrumServer = nodeSource.values.firstWhere((node) => node.uri.contains('electrumx.cakewallet.com'), orElse: () => null);
329
- var cakeWalletNode = nodeSource.values.firstWhere((node) => node.uri == cakeWalletElectrumUri, orElse: () => null);
387
+Future<void> resetBitcoinElectrumServer(
388
+ Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
389
+ final currentElectrumSeverId =
390
+ sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
391
+ final oldElectrumServer = nodeSource.values.firstWhere(
392
+ (node) => node.uri.contains('electrumx.cakewallet.com'),
393
+ orElse: () => null);
394
+ var cakeWalletNode = nodeSource.values.firstWhere(
395
+ (node) => node.uri == cakeWalletBitcoinElectrumUri,
396
+ orElse: () => null);
397
398
if (cakeWalletNode == null) {
332
- cakeWalletNode = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin);
399
+ cakeWalletNode =
400
+ Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
401
await nodeSource.add(cakeWalletNode);
402
}
403
404
if (currentElectrumSeverId == oldElectrumServer?.key) {
337
- await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int);
405
+ await sharedPreferences.setInt(
406
+ PreferencesKey.currentBitcoinElectrumSererIdKey,
407
+ cakeWalletNode.key as int);
408
}
409
410
await oldElectrumServer?.delete();
lib/entities/fs_migration.dart
+6
-8
@@ -1,7 +1,10 @@
1
import 'dart:io';
2
import 'dart:convert';
3
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
4
+import 'package:shared_preferences/shared_preferences.dart';
5
+import 'package:hive/hive.dart';
6
+import 'package:path_provider/path_provider.dart';
7
import 'package:cake_wallet/core/key_service.dart';
4
-import 'package:cake_wallet/di.dart';
8
import 'package:cake_wallet/entities/contact.dart';
9
import 'package:cake_wallet/entities/crypto_currency.dart';
10
import 'package:cake_wallet/entities/encrypt.dart';
@@ -14,11 +17,6 @@ import 'package:cake_wallet/entities/wallet_info.dart';
17
import 'package:cake_wallet/entities/wallet_type.dart';
18
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
19
import 'package:cake_wallet/exchange/trade.dart';
17
-import 'package:flutter_secure_storage/flutter_secure_storage.dart';
18
-import 'package:shared_preferences/shared_preferences.dart';
19
-import 'package:flutter/foundation.dart';
20
-import 'package:hive/hive.dart';
21
-import 'package:path_provider/path_provider.dart';
20
import 'package:cake_wallet/.secrets.g.dart' as secrets;
21
22
const reservedNames = ["flutter_assets", "wallets", "db"];
@@ -407,7 +405,7 @@ Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
405
}
406
407
final List<dynamic> addresses =
410
- json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
408
+ json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
409
final contacts = addresses.map((dynamic item) {
410
final _item = item as Map<String, dynamic>;
411
final type = _item["type"] as String;
@@ -420,7 +418,7 @@ Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
418
419
await contactSource.addAll(contacts);
420
await prefs.setBool('ios_migration_address_book_completed', true);
423
- } catch(e) {
421
+ } catch (e) {
422
print(e.toString());
423
}
424
}
lib/entities/node_list.dart
+23
-4
@@ -20,9 +20,9 @@ Future<List<Node>> loadDefaultNodes() async {
20
}).toList();
21
}
22
23
-Future<List<Node>> loadElectrumServerList() async {
23
+Future<List<Node>> loadBitcoinElectrumServerList() async {
24
final serverListRaw =
25
- await rootBundle.loadString('assets/electrum_server_list.yml');
25
+ await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
26
final serverList = loadYaml(serverListRaw) as YamlList;
27
28
return serverList.map((dynamic raw) {
@@ -37,10 +37,29 @@ Future<List<Node>> loadElectrumServerList() async {
37
}).toList();
38
}
39
40
+Future<List<Node>> loadLitecoinElectrumServerList() async {
41
+ final serverListRaw =
42
+ await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
43
+ final serverList = loadYaml(serverListRaw) as YamlList;
44
+
45
+ return serverList.map((dynamic raw) {
46
+ if (raw is Map) {
47
+ final node = Node.fromMap(raw);
48
+ node?.type = WalletType.litecoin;
49
+
50
+ return node;
51
+ }
52
+
53
+ return null;
54
+ }).toList();
55
+}
56
+
57
Future resetToDefault(Box<Node> nodeSource) async {
58
final moneroNodes = await loadDefaultNodes();
42
- final bitcoinElectrumServerList = await loadElectrumServerList();
43
- final nodes = moneroNodes + bitcoinElectrumServerList;
59
+ final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
60
+ final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
61
+ final nodes =
62
+ moneroNodes + bitcoinElectrumServerList + litecoinElectrumServerList;
63
64
await nodeSource.clear();
65
await nodeSource.addAll(nodes);
lib/entities/preferences_key.dart
+6
-4
@@ -1,8 +1,9 @@
1
class PreferencesKey {
2
- static const currentWalletType ='current_wallet_type';
3
- static const currentWalletName ='current_wallet_name';
2
+ static const currentWalletType = 'current_wallet_type';
3
+ static const currentWalletName = 'current_wallet_name';
4
static const currentNodeIdKey = 'current_node_id';
5
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
6
+ static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
7
static const currentFiatCurrencyKey = 'current_fiat_currency';
8
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
9
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
@@ -14,7 +15,8 @@ class PreferencesKey {
15
static const displayActionListModeKey = 'display_list_mode';
16
static const currentPinLength = 'current_pin_length';
17
static const currentLanguageCode = 'language_code';
17
- static const currentDefaultSettingsMigrationVersion = 'current_default_settings_migration_version';
18
+ static const currentDefaultSettingsMigrationVersion =
19
+ 'current_default_settings_migration_version';
20
static const moneroTransactionPriority = 'current_fee_priority_monero';
21
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
20
-}
\ No newline at end of file
22
+}
lib/entities/wallet_type.dart
+19
-2
@@ -3,7 +3,11 @@ import 'package:hive/hive.dart';
3
4
part 'wallet_type.g.dart';
5
6
-const walletTypes = [WalletType.monero, WalletType.bitcoin];
6
+const walletTypes = [
7
+ WalletType.monero,
8
+ WalletType.bitcoin,
9
+ WalletType.litecoin
10
+];
11
const walletTypeTypeId = 5;
12
13
@HiveType(typeId: walletTypeTypeId)
@@ -15,7 +19,10 @@ enum WalletType {
19
none,
20
21
@HiveField(2)
18
- bitcoin
22
+ bitcoin,
23
+
24
+ @HiveField(3)
25
+ litecoin
26
}
27
28
int serializeToInt(WalletType type) {
@@ -24,6 +31,8 @@ int serializeToInt(WalletType type) {
31
return 0;
32
case WalletType.bitcoin:
33
return 1;
34
+ case WalletType.litecoin:
35
+ return 2;
36
default:
37
return -1;
38
}
@@ -35,6 +44,8 @@ WalletType deserializeFromInt(int raw) {
44
return WalletType.monero;
45
case 1:
46
return WalletType.bitcoin;
47
+ case 2:
48
+ return WalletType.litecoin;
49
default:
50
return null;
51
}
@@ -46,6 +57,8 @@ String walletTypeToString(WalletType type) {
57
return 'Monero';
58
case WalletType.bitcoin:
59
return 'Bitcoin';
60
+ case WalletType.litecoin:
61
+ return 'Litecoin';
62
default:
63
return '';
64
}
@@ -57,6 +70,8 @@ String walletTypeToDisplayName(WalletType type) {
70
return 'Monero';
71
case WalletType.bitcoin:
72
return 'Bitcoin (Electrum)';
73
+ case WalletType.litecoin:
74
+ return 'Litecoin';
75
default:
76
return '';
77
}
@@ -68,6 +83,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
83
return CryptoCurrency.xmr;
84
case WalletType.bitcoin:
85
return CryptoCurrency.btc;
86
+ case WalletType.litecoin:
87
+ return CryptoCurrency.ltc;
88
default:
89
return null;
90
}
lib/main.dart
+2
-2
@@ -107,7 +107,7 @@ Future<void> main() async {
107
exchangeTemplates: exchangeTemplates,
108
transactionDescriptions: transactionDescriptions,
109
secureStorage: secureStorage,
110
- initialMigrationVersion: 13);
110
+ initialMigrationVersion: 15);
111
runApp(App());
112
} catch (e) {
113
runApp(MaterialApp(
@@ -135,7 +135,7 @@ Future<void> initialSetup(
135
@required Box<ExchangeTemplate> exchangeTemplates,
136
@required Box<TransactionDescription> transactionDescriptions,
137
FlutterSecureStorage secureStorage,
138
- int initialMigrationVersion = 13}) async {
138
+ int initialMigrationVersion = 15}) async {
139
LanguageService.loadLocaleList();
140
await defaultSettingsMigration(
141
secureStorage: secureStorage,
lib/monero/monero_account_list.dart
+2
-2
@@ -49,13 +49,13 @@ abstract class MoneroAccountListBase with Store {
49
50
Future addAccount({String label}) async {
51
await account_list.addAccount(label: label);
52
- await update();
52
+ update();
53
}
54
55
Future setLabelAccount({int accountIndex, String label}) async {
56
await account_list.setLabelForAccount(
57
accountIndex: accountIndex, label: label);
58
- await update();
58
+ update();
59
}
60
61
void refresh() {
lib/monero/monero_transaction_history.dart
+5
-30
@@ -1,18 +1,10 @@
1
import 'dart:core';
2
import 'package:mobx/mobx.dart';
3
-import 'package:cw_monero/transaction_history.dart'
4
- as monero_transaction_history;
3
import 'package:cake_wallet/core/transaction_history.dart';
4
import 'package:cake_wallet/monero/monero_transaction_info.dart';
5
6
part 'monero_transaction_history.g.dart';
7
10
-List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
11
- monero_transaction_history
12
- .getAllTransations()
13
- .map((row) => MoneroTransactionInfo.fromRow(row))
14
- .toList();
15
-
8
class MoneroTransactionHistory = MoneroTransactionHistoryBase
9
with _$MoneroTransactionHistory;
10
@@ -23,30 +15,13 @@ abstract class MoneroTransactionHistoryBase
15
}
16
17
@override
26
- Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
27
- monero_transaction_history.refreshTransactions();
28
- return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
29
- <String, MoneroTransactionInfo>{},
30
- (Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
31
- acc[tx.id] = tx;
32
- return acc;
33
- });
34
- }
18
+ Future<void> save() async {}
19
20
@override
37
- @action
38
- void updateAsync({void Function() onFinished}) {
39
- fetchTransactionsAsync(
40
- (transaction) => transactions[transaction.id] = transaction,
41
- onFinished: onFinished);
42
- }
21
+ void addOne(MoneroTransactionInfo transaction) =>
22
+ transactions[transaction.id] = transaction;
23
24
@override
45
- void fetchTransactionsAsync(
46
- void Function(MoneroTransactionInfo transaction) onTransactionLoaded,
47
- {void Function() onFinished}) async {
48
- final transactions = await fetchTransactions();
49
- transactions.values.forEach((tx) => onTransactionLoaded(tx));
50
- onFinished?.call();
51
- }
25
+ void addMany(Map<String, MoneroTransactionInfo> transactions) =>
26
+ this.transactions.addAll(transactions);
27
}
lib/monero/monero_transaction_info.dart
+1
@@ -59,6 +59,7 @@ class MoneroTransactionInfo extends TransactionInfo {
59
@override
60
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
61
62
+ @override
63
String feeFormatted() =>
64
'${formatAmount(moneroAmountToString(amount: fee))} XMR';
65
}
lib/monero/monero_wallet.dart
+59
-15
@@ -1,10 +1,12 @@
1
import 'dart:async';
2
-
2
import 'package:cake_wallet/entities/transaction_priority.dart';
3
import 'package:cake_wallet/monero/monero_amount_format.dart';
4
import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart';
5
+import 'package:cake_wallet/monero/monero_transaction_info.dart';
6
import 'package:flutter/foundation.dart';
7
import 'package:mobx/mobx.dart';
8
+import 'package:cw_monero/transaction_history.dart'
9
+ as monero_transaction_history;
10
import 'package:cw_monero/wallet.dart';
11
import 'package:cw_monero/wallet.dart' as monero_wallet;
12
import 'package:cw_monero/transaction_history.dart' as transaction_history;
@@ -30,19 +32,21 @@ const moneroBlockSize = 1000;
32
33
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
34
33
-abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
34
- MoneroWalletBase({String filename, WalletInfo walletInfo})
35
- : transactionHistory = MoneroTransactionHistory(),
36
- accountList = MoneroAccountList(),
35
+abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
36
+ MoneroTransactionHistory, MoneroTransactionInfo> with Store {
37
+ MoneroWalletBase({WalletInfo walletInfo})
38
+ : accountList = MoneroAccountList(),
39
subaddressList = MoneroSubaddressList(),
40
super(walletInfo) {
39
- _filename = filename;
41
+ transactionHistory = MoneroTransactionHistory();
42
balance = MoneroBalance(
43
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
44
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
45
_lastAutosaveTimestamp = 0;
46
+ _lastSaveTimestamp = 0;
47
_isSavingAfterSync = false;
48
_isSavingAfterNewTransaction = false;
49
+ _isTransactionUpdating = false;
50
_onAccountChangeReaction = reaction((_) => account, (Account account) {
51
balance = MoneroBalance(
52
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
@@ -56,9 +60,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
60
61
static const int _autoAfterSyncSaveInterval = 60000;
62
59
- @override
60
- final MoneroTransactionHistory transactionHistory;
61
-
63
@observable
64
Account account;
65
@@ -91,12 +92,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
92
93
final MoneroAccountList accountList;
94
94
- String _filename;
95
SyncListener _listener;
96
ReactionDisposer _onAccountChangeReaction;
97
int _lastAutosaveTimestamp;
98
bool _isSavingAfterSync;
99
bool _isSavingAfterNewTransaction;
100
+ bool _isTransactionUpdating;
101
+ int _lastSaveTimestamp;
102
103
Future<void> init() async {
104
accountList.update();
@@ -111,7 +113,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
113
monero_wallet.getUnlockedBalance(accountIndex: account.id));
114
address = subaddress.address;
115
_setListeners();
114
- await transactionHistory.update();
116
+ await updateTransactions();
117
118
if (walletInfo.isRecovery) {
119
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
@@ -238,6 +240,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
240
241
@override
242
Future<void> save() async {
243
+ final now = DateTime.now().millisecondsSinceEpoch;
244
+
245
+ if (now - _lastSaveTimestamp < Duration(seconds: 10).inMilliseconds) {
246
+ return;
247
+ }
248
+
249
+ _lastSaveTimestamp = now;
250
await monero_wallet.store();
251
}
252
@@ -264,6 +273,40 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
273
await walletInfo.save();
274
}
275
276
+ @override
277
+ Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
278
+ monero_transaction_history.refreshTransactions();
279
+ return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
280
+ <String, MoneroTransactionInfo>{},
281
+ (Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
282
+ acc[tx.id] = tx;
283
+ return acc;
284
+ });
285
+ }
286
+
287
+ Future<void> updateTransactions() async {
288
+ try {
289
+ if (_isTransactionUpdating) {
290
+ return;
291
+ }
292
+
293
+ _isTransactionUpdating = true;
294
+ final transactions = await fetchTransactions();
295
+ transactionHistory.addMany(transactions);
296
+ await transactionHistory.save();
297
+ _isTransactionUpdating = false;
298
+ } catch (e) {
299
+ print(e);
300
+ _isTransactionUpdating = false;
301
+ }
302
+ }
303
+
304
+ List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
305
+ monero_transaction_history
306
+ .getAllTransations()
307
+ .map((row) => MoneroTransactionInfo.fromRow(row))
308
+ .toList();
309
+
310
void _setListeners() {
311
_listener?.stop();
312
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
@@ -315,7 +358,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
358
}
359
360
Future<void> _askForUpdateTransactionHistory() async =>
318
- await transactionHistory.update();
361
+ await updateTransactions();
362
363
int _getFullBalance() =>
364
monero_wallet.getFullBalance(accountIndex: account.id);
@@ -389,11 +432,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
432
}
433
}
434
392
- void _onNewTransaction() {
435
+ void _onNewTransaction() async {
436
try {
394
- _askForUpdateTransactionHistory();
437
+ await _askForUpdateTransactionHistory();
438
_askForUpdateBalance();
396
- Timer(Duration(seconds: 1), () => _afterNewTransactionSave());
439
+ await Future<void>.delayed(Duration(seconds: 1));
440
+ await _afterNewTransactionSave();
441
} catch (e) {
442
print(e.toString());
443
}
lib/monero/monero_wallet_service.dart
+15
-22
@@ -68,18 +68,18 @@ class MoneroWalletService extends WalletService<
68
static bool walletFilesExist(String path) =>
69
!File(path).existsSync() && !File('$path.keys').existsSync();
70
71
+ @override
72
+ WalletType getType() => WalletType.monero;
73
+
74
@override
75
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
76
try {
74
- final path =
75
- await pathForWallet(name: credentials.name, type: WalletType.monero);
77
+ final path = await pathForWallet(name: credentials.name, type: getType());
78
await monero_wallet_manager.createWallet(
79
path: path,
80
password: credentials.password,
81
language: credentials.language);
80
- final wallet = MoneroWallet(
81
- filename: monero_wallet.getFilename(),
82
- walletInfo: credentials.walletInfo);
82
+ final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
83
await wallet.init();
84
85
return wallet;
@@ -93,7 +93,7 @@ class MoneroWalletService extends WalletService<
93
@override
94
Future<bool> isWalletExit(String name) async {
95
try {
96
- final path = await pathForWallet(name: name, type: WalletType.monero);
96
+ final path = await pathForWallet(name: name, type: getType());
97
return monero_wallet_manager.isWalletExist(path: path);
98
} catch (e) {
99
// TODO: Implement Exception for wallet list service.
@@ -105,7 +105,7 @@ class MoneroWalletService extends WalletService<
105
@override
106
Future<MoneroWallet> openWallet(String name, String password) async {
107
try {
108
- final path = await pathForWallet(name: name, type: WalletType.monero);
108
+ final path = await pathForWallet(name: name, type: getType());
109
110
if (walletFilesExist(path)) {
111
await repairOldAndroidWallet(name);
@@ -114,10 +114,9 @@ class MoneroWalletService extends WalletService<
114
await monero_wallet_manager
115
.openWalletAsync({'path': path, 'password': password});
116
final walletInfo = walletInfoSource.values.firstWhere(
117
- (info) => info.id == WalletBase.idFor(name, WalletType.monero),
117
+ (info) => info.id == WalletBase.idFor(name, getType()),
118
orElse: () => null);
119
- final wallet = MoneroWallet(
120
- filename: monero_wallet.getFilename(), walletInfo: walletInfo);
119
+ final wallet = MoneroWallet(walletInfo: walletInfo);
120
final isValid = wallet.validate();
121
122
if (!isValid) {
@@ -146,7 +145,7 @@ class MoneroWalletService extends WalletService<
145
146
@override
147
Future<void> remove(String wallet) async {
149
- final path = await pathForWalletDir(name: wallet, type: WalletType.monero);
148
+ final path = await pathForWalletDir(name: wallet, type: getType());
149
final file = Directory(path);
150
final isExist = file.existsSync();
151
@@ -159,8 +158,7 @@ class MoneroWalletService extends WalletService<
158
Future<MoneroWallet> restoreFromKeys(
159
MoneroRestoreWalletFromKeysCredentials credentials) async {
160
try {
162
- final path =
163
- await pathForWallet(name: credentials.name, type: WalletType.monero);
161
+ final path = await pathForWallet(name: credentials.name, type: getType());
162
await monero_wallet_manager.restoreFromKeys(
163
path: path,
164
password: credentials.password,
@@ -169,9 +167,7 @@ class MoneroWalletService extends WalletService<
167
address: credentials.address,
168
viewKey: credentials.viewKey,
169
spendKey: credentials.spendKey);
172
- final wallet = MoneroWallet(
173
- filename: monero_wallet.getFilename(),
174
- walletInfo: credentials.walletInfo);
170
+ final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
171
await wallet.init();
172
173
return wallet;
@@ -186,16 +182,13 @@ class MoneroWalletService extends WalletService<
182
Future<MoneroWallet> restoreFromSeed(
183
MoneroRestoreWalletFromSeedCredentials credentials) async {
184
try {
189
- final path =
190
- await pathForWallet(name: credentials.name, type: WalletType.monero);
185
+ final path = await pathForWallet(name: credentials.name, type: getType());
186
await monero_wallet_manager.restoreFromSeed(
187
path: path,
188
password: credentials.password,
189
seed: credentials.mnemonic,
190
restoreHeight: credentials.height);
196
- final wallet = MoneroWallet(
197
- filename: monero_wallet.getFilename(),
198
- walletInfo: credentials.walletInfo);
191
+ final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
192
await wallet.init();
193
194
return wallet;
@@ -221,7 +214,7 @@ class MoneroWalletService extends WalletService<
214
}
215
216
final newWalletDirPath =
224
- await pathForWalletDir(name: name, type: WalletType.monero);
217
+ await pathForWalletDir(name: name, type: getType());
218
219
dir.listSync().forEach((f) {
220
final file = File(f.path);
lib/reactions/on_current_wallet_change.dart
+9
-3
@@ -1,4 +1,6 @@
1
+import 'package:cake_wallet/core/transaction_history.dart';
2
import 'package:cake_wallet/entities/balance.dart';
3
+import 'package:cake_wallet/entities/transaction_info.dart';
4
import 'package:mobx/mobx.dart';
5
import 'package:shared_preferences/shared_preferences.dart';
6
import 'package:cake_wallet/di.dart';
@@ -18,9 +20,11 @@ ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction;
20
void startCurrentWalletChangeReaction(AppStore appStore,
21
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
22
_onCurrentWalletChangeReaction?.reaction?.dispose();
23
+ _onCurrentWalletChangeFiatRateUpdateReaction?.reaction?.dispose();
24
22
- _onCurrentWalletChangeReaction =
23
- reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
25
+ _onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
26
+ Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
27
+ wallet) async {
28
try {
29
final node = settingsStore.getCurrentNode(wallet.type);
30
startWalletSyncStatusChangeReaction(wallet);
@@ -45,7 +49,9 @@ void startCurrentWalletChangeReaction(AppStore appStore,
49
});
50
51
_onCurrentWalletChangeFiatRateUpdateReaction =
48
- reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
52
+ reaction((_) => appStore.wallet, (WalletBase<Balance,
53
+ TransactionHistoryBase<TransactionInfo>, TransactionInfo>
54
+ wallet) async {
55
try {
56
fiatConversionStore.prices[wallet.currency] = 0;
57
fiatConversionStore.prices[wallet.currency] =
lib/reactions/on_wallet_sync_status_change.dart
+12
-7
@@ -1,16 +1,21 @@
1
-import 'package:cake_wallet/entities/balance.dart';
1
import 'package:mobx/mobx.dart';
2
+import 'package:cake_wallet/core/transaction_history.dart';
3
import 'package:cake_wallet/core/wallet_base.dart';
4
+import 'package:cake_wallet/entities/balance.dart';
5
+import 'package:cake_wallet/entities/transaction_info.dart';
6
import 'package:cake_wallet/entities/sync_status.dart';
7
8
ReactionDisposer _onWalletSyncStatusChangeReaction;
9
8
-void startWalletSyncStatusChangeReaction(WalletBase<Balance> wallet) {
10
+void startWalletSyncStatusChangeReaction(
11
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
12
+ TransactionInfo>
13
+ wallet) {
14
_onWalletSyncStatusChangeReaction?.reaction?.dispose();
15
_onWalletSyncStatusChangeReaction =
16
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
12
- if (status is ConnectedSyncStatus) {
13
- await wallet.startSync();
14
- }
15
- });
16
-}
\ No newline at end of file
17
+ if (status is ConnectedSyncStatus) {
18
+ await wallet.startSync();
19
+ }
20
+ });
21
+}
lib/src/screens/dashboard/widgets/menu_widget.dart
+4
@@ -21,6 +21,7 @@ class MenuWidget extends StatefulWidget {
21
class MenuWidgetState extends State<MenuWidget> {
22
Image moneroIcon;
23
Image bitcoinIcon;
24
+ Image litecoinIcon;
25
final largeScreen = 731;
26
27
double menuWidth;
@@ -76,6 +77,7 @@ class MenuWidgetState extends State<MenuWidget> {
77
color: Theme.of(context).accentTextTheme.overline.decorationColor);
78
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
79
color: Theme.of(context).accentTextTheme.overline.decorationColor);
80
+ litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
81
82
return Row(
83
mainAxisSize: MainAxisSize.max,
@@ -238,6 +240,8 @@ class MenuWidgetState extends State<MenuWidget> {
240
return moneroIcon;
241
case WalletType.bitcoin:
242
return bitcoinIcon;
243
+ case WalletType.litecoin:
244
+ return litecoinIcon;
245
default:
246
return null;
247
}
lib/src/screens/new_wallet/new_wallet_type_page.dart
+13
-10
@@ -59,6 +59,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
59
Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
60
final bitcoinIcon =
61
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
62
+ final litecoinIcon =
63
+ Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
64
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
65
final walletTypeLightImage =
66
Image.asset('assets/images/wallet_type_light.png');
@@ -69,7 +71,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
71
72
@override
73
void initState() {
72
- types = [WalletType.bitcoin, WalletType.monero];
74
+ types = [WalletType.bitcoin, WalletType.monero, WalletType.litecoin];
75
super.initState();
76
}
77
@@ -84,8 +86,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
86
padding: EdgeInsets.only(left: 12, right: 12),
87
child: AspectRatio(
88
aspectRatio: aspectRatioImage,
87
- child:
88
- FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
89
+ child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
90
),
91
Padding(
92
padding: EdgeInsets.only(top: 48),
@@ -99,13 +100,13 @@ class WalletTypeFormState extends State<WalletTypeForm> {
100
),
101
),
102
...types.map((type) => Padding(
102
- padding: EdgeInsets.only(top: 24),
103
- child: SelectButton(
104
- image: _iconFor(type),
105
- text: walletTypeToDisplayName(type),
106
- isSelected: selected == type,
107
- onTap: () => setState(() => selected = type)),
108
- ))
103
+ padding: EdgeInsets.only(top: 24),
104
+ child: SelectButton(
105
+ image: _iconFor(type),
106
+ text: walletTypeToDisplayName(type),
107
+ isSelected: selected == type,
108
+ onTap: () => setState(() => selected = type)),
109
+ ))
110
],
111
),
112
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
@@ -125,6 +126,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
126
return moneroIcon;
127
case WalletType.bitcoin:
128
return bitcoinIcon;
129
+ case WalletType.litecoin:
130
+ return litecoinIcon;
131
default:
132
return null;
133
}
lib/src/screens/wallet_list/wallet_list_page.dart
+4
@@ -40,6 +40,8 @@ class WalletListBodyState extends State<WalletListBody> {
40
Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
41
final bitcoinIcon =
42
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
43
+ final litecoinIcon =
44
+ Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
45
final scrollController = ScrollController();
46
final double tileHeight = 60;
47
Flushbar<void> _progressBar;
@@ -193,6 +195,8 @@ class WalletListBodyState extends State<WalletListBody> {
195
return bitcoinIcon;
196
case WalletType.monero:
197
return moneroIcon;
198
+ case WalletType.litecoin:
199
+ return litecoinIcon;
200
default:
201
return null;
202
}
lib/store/app_store.dart
+9
-3
@@ -1,6 +1,8 @@
1
-import 'package:cake_wallet/entities/balance.dart';
1
+import 'package:cake_wallet/entities/transaction_info.dart';
2
import 'package:mobx/mobx.dart';
3
+import 'package:cake_wallet/entities/balance.dart';
4
import 'package:cake_wallet/core/wallet_base.dart';
5
+import 'package:cake_wallet/core/transaction_history.dart';
6
import 'package:cake_wallet/store/wallet_list_store.dart';
7
import 'package:cake_wallet/store/authentication_store.dart';
8
import 'package:cake_wallet/store/settings_store.dart';
@@ -20,7 +22,8 @@ abstract class AppStoreBase with Store {
22
AuthenticationStore authenticationStore;
23
24
@observable
23
- WalletBase<Balance> wallet;
25
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
26
+ wallet;
27
28
WalletListStore walletList;
29
@@ -29,7 +32,10 @@ abstract class AppStoreBase with Store {
32
NodeListStore nodeListStore;
33
34
@action
32
- void changeCurrentWallet(WalletBase wallet) {
35
+ void changeCurrentWallet(
36
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
37
+ TransactionInfo>
38
+ wallet) {
39
this.wallet?.close();
40
this.wallet = wallet;
41
}
lib/store/settings_store.dart
+10
-2
@@ -149,7 +149,7 @@ abstract class SettingsStoreBase with Store {
149
150
static Future<SettingsStore> load(
151
{@required Box<Node> nodeSource,
152
- @required bool isBitcoinBuyEnabled,
152
+ @required bool isBitcoinBuyEnabled,
153
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
154
MoneroTransactionPriority initialMoneroTransactionPriority =
155
MoneroTransactionPriority.slow,
@@ -205,15 +205,19 @@ abstract class SettingsStoreBase with Store {
205
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
206
final bitcoinElectrumServerId = sharedPreferences
207
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
208
+ final litecoinElectrumServerId = sharedPreferences
209
+ .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
210
final moneroNode = nodeSource.get(nodeId);
211
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
212
+ final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
213
final packageInfo = await PackageInfo.fromPlatform();
214
215
return SettingsStore(
216
sharedPreferences: sharedPreferences,
217
nodes: {
218
WalletType.monero: moneroNode,
216
- WalletType.bitcoin: bitcoinElectrumServer
219
+ WalletType.bitcoin: bitcoinElectrumServer,
220
+ WalletType.litecoin: litecoinElectrumServer
221
},
222
appVersion: packageInfo.version,
223
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
@@ -263,6 +267,10 @@ abstract class SettingsStoreBase with Store {
267
await _sharedPreferences.setInt(
268
PreferencesKey.currentBitcoinElectrumSererIdKey, node.key as int);
269
break;
270
+ case WalletType.litecoin:
271
+ await _sharedPreferences.setInt(
272
+ PreferencesKey.currentLitecoinElectrumSererIdKey, node.key as int);
273
+ break;
274
case WalletType.monero:
275
await _sharedPreferences.setInt(
276
PreferencesKey.currentNodeIdKey, node.key as int);
lib/view_model/dashboard/balance_view_model.dart
+12
-11
@@ -1,7 +1,9 @@
1
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
2
+import 'package:cake_wallet/core/transaction_history.dart';
3
import 'package:cake_wallet/core/wallet_base.dart';
4
import 'package:cake_wallet/entities/balance.dart';
5
import 'package:cake_wallet/entities/crypto_currency.dart';
6
+import 'package:cake_wallet/entities/transaction_info.dart';
7
import 'package:cake_wallet/entities/wallet_type.dart';
8
import 'package:cake_wallet/generated/i18n.dart';
9
import 'package:cake_wallet/monero/monero_wallet.dart';
@@ -23,11 +25,7 @@ abstract class BalanceViewModelBase with Store {
25
@required this.settingsStore,
26
@required this.fiatConvertationStore}) {
27
isReversing = false;
26
-
28
wallet ??= appStore.wallet;
28
-
29
- _reaction = reaction((_) => appStore.wallet, _onWalletChange);
30
-
29
final _wallet = wallet;
30
31
if (_wallet is MoneroWallet) {
@@ -38,6 +36,8 @@ abstract class BalanceViewModelBase with Store {
36
balance = _wallet.balance;
37
}
38
39
+ reaction((_) => appStore.wallet, _onWalletChange);
40
+
41
_onCurrentWalletChangeReaction =
42
reaction<void>((_) => wallet.balance, (dynamic balance) {
43
if (balance is Balance) {
@@ -59,7 +59,8 @@ abstract class BalanceViewModelBase with Store {
59
Balance balance;
60
61
@observable
62
- WalletBase<Balance> wallet;
62
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
63
+ wallet;
64
65
@computed
66
double get price => fiatConvertationStore.prices[appStore.wallet.currency];
@@ -70,8 +71,8 @@ abstract class BalanceViewModelBase with Store {
71
@computed
72
BalanceDisplayMode get displayMode => isReversing
73
? savedDisplayMode == BalanceDisplayMode.hiddenBalance
73
- ? BalanceDisplayMode.displayableBalance
74
- : savedDisplayMode
74
+ ? BalanceDisplayMode.displayableBalance
75
+ : savedDisplayMode
76
: savedDisplayMode;
77
78
@computed
@@ -153,14 +154,14 @@ abstract class BalanceViewModelBase with Store {
154
CryptoCurrency get currency => appStore.wallet.currency;
155
156
ReactionDisposer _onCurrentWalletChangeReaction;
156
- ReactionDisposer _reaction;
157
158
@action
159
- void _onWalletChange(WalletBase<Balance> wallet) {
159
+ void _onWalletChange(
160
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
161
+ TransactionInfo>
162
+ wallet) {
163
this.wallet = wallet;
161
-
164
balance = wallet.balance;
163
-
165
_onCurrentWalletChangeReaction?.reaction?.dispose();
166
_onCurrentWalletChangeReaction = reaction<Balance>(
167
(_) => wallet.balance, (Balance balance) => this.balance = balance);
lib/view_model/dashboard/dashboard_view_model.dart
+15
-32
@@ -1,23 +1,13 @@
1
-import 'dart:convert';
2
-import 'dart:io';
3
-
4
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
5
-import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
1
+import 'package:cake_wallet/core/transaction_history.dart';
2
import 'package:cake_wallet/entities/balance.dart';
3
import 'package:cake_wallet/entities/order.dart';
8
-import 'package:cake_wallet/entities/transaction_history.dart';
9
-import 'package:cake_wallet/exchange/trade_state.dart';
4
import 'package:cake_wallet/monero/account.dart';
5
import 'package:cake_wallet/monero/monero_balance.dart';
12
-import 'package:cake_wallet/monero/monero_transaction_history.dart';
6
import 'package:cake_wallet/monero/monero_transaction_info.dart';
7
import 'package:cake_wallet/monero/monero_wallet.dart';
8
import 'package:cake_wallet/entities/balance_display_mode.dart';
16
-import 'package:cake_wallet/entities/crypto_currency.dart';
17
-import 'package:cake_wallet/entities/transaction_direction.dart';
9
import 'package:cake_wallet/entities/transaction_info.dart';
10
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
20
-import 'package:cake_wallet/exchange/trade.dart';
11
import 'package:cake_wallet/store/settings_store.dart';
12
import 'package:cake_wallet/store/dashboard/orders_store.dart';
13
import 'package:cake_wallet/utils/mobx.dart';
@@ -27,12 +17,8 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
17
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
18
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
19
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
30
-import 'package:cake_wallet/view_model/dashboard/action_list_display_mode.dart';
20
import 'package:cake_wallet/view_model/wyre_view_model.dart';
32
-import 'package:crypto/crypto.dart';
33
-import 'package:flutter/services.dart';
21
import 'package:hive/hive.dart';
35
-import 'package:http/http.dart';
22
import 'package:mobx/mobx.dart';
23
import 'package:cake_wallet/core/wallet_base.dart';
24
import 'package:cake_wallet/entities/sync_status.dart';
@@ -43,8 +29,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
29
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
30
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
31
import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart';
46
-import 'package:url_launcher/url_launcher.dart';
47
-import 'package:convert/convert.dart';
32
33
part 'dashboard_view_model.g.dart';
34
@@ -100,9 +84,6 @@ abstract class DashboardViewModelBase with Store {
84
name = appStore.wallet?.name;
85
wallet ??= appStore.wallet;
86
type = wallet.type;
103
-
104
- _reaction = reaction((_) => appStore.wallet, _onWalletChange);
105
-
87
final _wallet = wallet;
88
89
if (_wallet is MoneroWallet) {
@@ -133,6 +114,8 @@ abstract class DashboardViewModelBase with Store {
114
settingsStore: appStore.settingsStore)));
115
}
116
117
+ reaction((_) => appStore.wallet, _onWalletChange);
118
+
119
connectMapToListWithTransform(
120
appStore.wallet.transactionHistory.transactions,
121
transactions,
@@ -215,7 +198,8 @@ abstract class DashboardViewModelBase with Store {
198
}
199
200
@observable
218
- WalletBase<Balance> wallet;
201
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
202
+ wallet;
203
204
bool get hasRescan => wallet.type == WalletType.monero;
205
@@ -241,8 +225,6 @@ abstract class DashboardViewModelBase with Store {
225
226
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
227
244
- ReactionDisposer _reaction;
245
-
228
ReactionDisposer _onMoneroAccountChangeReaction;
229
230
ReactionDisposer _onMoneroBalanceChangeReaction;
@@ -253,7 +235,10 @@ abstract class DashboardViewModelBase with Store {
235
}
236
237
@action
256
- void _onWalletChange(WalletBase<Balance> wallet) {
238
+ void _onWalletChange(
239
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
240
+ TransactionInfo>
241
+ wallet) {
242
this.wallet = wallet;
243
type = wallet.type;
244
name = wallet.name;
@@ -286,17 +271,17 @@ abstract class DashboardViewModelBase with Store {
271
connectMapToListWithTransform(
272
appStore.wallet.transactionHistory.transactions,
273
transactions,
289
- (TransactionInfo val) => TransactionListItem(
274
+ (TransactionInfo val) => TransactionListItem(
275
transaction: val,
276
balanceViewModel: balanceViewModel,
277
settingsStore: appStore.settingsStore),
278
filter: (TransactionInfo tx) {
294
- if (tx is MoneroTransactionInfo && wallet is MoneroWallet) {
295
- return tx.accountIndex == wallet.account.id;
296
- }
279
+ if (tx is MoneroTransactionInfo && wallet is MoneroWallet) {
280
+ return tx.accountIndex == wallet.account.id;
281
+ }
282
298
- return true;
299
- });
283
+ return true;
284
+ });
285
}
286
287
@action
@@ -319,6 +304,4 @@ abstract class DashboardViewModelBase with Store {
304
balanceViewModel: balanceViewModel,
305
settingsStore: appStore.settingsStore)));
306
}
322
-
323
-
307
}
lib/view_model/dashboard/transaction_list_item.dart
+3
-3
@@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/transaction_info.dart';
4
import 'package:cake_wallet/store/settings_store.dart';
5
import 'package:cake_wallet/utils/mobx.dart';
6
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
7
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
7
+import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
8
import 'package:cake_wallet/monero/monero_transaction_info.dart';
9
import 'package:cake_wallet/monero/monero_amount_format.dart';
10
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
@@ -42,7 +42,7 @@ class TransactionListItem extends ActionListItem with Keyable {
42
transaction.changeFiatAmount(amount);
43
}
44
45
- if (transaction is BitcoinTransactionInfo) {
45
+ if (transaction is ElectrumTransactionInfo) {
46
final amount = calculateFiatAmountRaw(
47
cryptoAmount: bitcoinAmountToDouble(amount: transaction.amount),
48
price: price);
@@ -56,4 +56,4 @@ class TransactionListItem extends ActionListItem with Keyable {
56
57
@override
58
DateTime get date => transaction.date;
59
-}
\ No newline at end of file
59
+}
lib/view_model/exchange/exchange_view_model.dart
+18
-11
@@ -33,9 +33,7 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
33
abstract class ExchangeViewModelBase with Store {
34
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
35
this.tradesStore, this._settingsStore) {
36
- providerList = [
37
- ChangeNowExchangeProvider()
38
- ];
36
+ providerList = [ChangeNowExchangeProvider()];
37
38
_initialPairBasedOnWallet();
39
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
@@ -57,9 +55,11 @@ abstract class ExchangeViewModelBase with Store {
55
_onPairChange();
56
}
57
});
60
- receiveCurrencies = CryptoCurrency.all.where((cryptoCurrency) =>
61
- (cryptoCurrency != CryptoCurrency.xlm)&&
62
- (cryptoCurrency != CryptoCurrency.xrp)).toList();
58
+ receiveCurrencies = CryptoCurrency.all
59
+ .where((cryptoCurrency) =>
60
+ (cryptoCurrency != CryptoCurrency.xlm) &&
61
+ (cryptoCurrency != CryptoCurrency.xrp))
62
+ .toList();
63
_defineIsReceiveAmountEditable();
64
isFixedRateMode = false;
65
isReceiveAmountEntered = false;
@@ -218,8 +218,10 @@ abstract class ExchangeViewModelBase with Store {
218
limitsState = LimitsIsLoading();
219
220
try {
221
- limits = await provider.fetchLimits(from: depositCurrency,
222
- to: receiveCurrency, isFixedRateMode: isFixedRateMode);
221
+ limits = await provider.fetchLimits(
222
+ from: depositCurrency,
223
+ to: receiveCurrency,
224
+ isFixedRateMode: isFixedRateMode);
225
limitsState = LimitsLoadedSuccessfully(limits: limits);
226
} catch (e) {
227
limitsState = LimitsLoadedFailure(error: e.toString());
@@ -283,8 +285,8 @@ abstract class ExchangeViewModelBase with Store {
285
} else {
286
try {
287
tradeState = TradeIsCreating();
286
- final trade = await provider.createTrade(request: request,
287
- isFixedRateMode: isFixedRateMode);
288
+ final trade = await provider.createTrade(
289
+ request: request, isFixedRateMode: isFixedRateMode);
290
trade.walletId = wallet.id;
291
tradesStore.setTrade(trade);
292
await trades.add(trade);
@@ -320,7 +322,8 @@ abstract class ExchangeViewModelBase with Store {
322
void calculateDepositAllAmount() {
323
if (wallet is BitcoinWallet) {
324
final availableBalance = wallet.balance.available;
323
- final priority = _settingsStore.priority[wallet.type] as BitcoinTransactionPriority;
325
+ final priority =
326
+ _settingsStore.priority[wallet.type] as BitcoinTransactionPriority;
327
final fee = wallet.calculateEstimatedFee(priority, null);
328
329
if (availableBalance < fee || availableBalance == 0) {
@@ -402,6 +405,10 @@ abstract class ExchangeViewModelBase with Store {
405
depositCurrency = CryptoCurrency.btc;
406
receiveCurrency = CryptoCurrency.xmr;
407
break;
408
+ case WalletType.litecoin:
409
+ depositCurrency = CryptoCurrency.ltc;
410
+ receiveCurrency = CryptoCurrency.xmr;
411
+ break;
412
default:
413
break;
414
}
lib/view_model/node_list/node_list_view_model.dart
+3
@@ -39,6 +39,9 @@ abstract class NodeListViewModelBase with Store {
39
case WalletType.monero:
40
node = getMoneroDefaultNode(nodes: _nodeSource);
41
break;
42
+ case WalletType.litecoin:
43
+ node = getLitecoinDefaultElectrumServer(nodes: _nodeSource);
44
+ break;
45
default:
46
break;
47
}
lib/view_model/send/send_view_model.dart
+15
-2
@@ -1,5 +1,6 @@
1
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
2
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
3
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
4
import 'package:cake_wallet/entities/balance_display_mode.dart';
5
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
6
import 'package:cake_wallet/entities/transaction_description.dart';
@@ -90,6 +91,9 @@ abstract class SendViewModelBase with Store {
91
case WalletType.bitcoin:
92
_amount = stringDoubleToBitcoinAmount(_cryptoAmount);
93
break;
94
+ case WalletType.litecoin:
95
+ _amount = stringDoubleToBitcoinAmount(_cryptoAmount);
96
+ break;
97
default:
98
break;
99
}
@@ -102,7 +106,7 @@ abstract class SendViewModelBase with Store {
106
final fee = _wallet.calculateEstimatedFee(
107
_settingsStore.priority[_wallet.type], amount);
108
105
- if (_wallet is BitcoinWallet) {
109
+ if (_wallet is ElectrumWallet) {
110
return bitcoinAmountToDouble(amount: fee);
111
}
112
@@ -304,6 +308,12 @@ abstract class SendViewModelBase with Store {
308
final amount = !sendAll ? _amount : null;
309
final priority = _settingsStore.priority[_wallet.type];
310
311
+ return BitcoinTransactionCredentials(
312
+ address, amount, priority as BitcoinTransactionPriority);
313
+ case WalletType.litecoin:
314
+ final amount = !sendAll ? _amount : null;
315
+ final priority = _settingsStore.priority[_wallet.type];
316
+
317
return BitcoinTransactionCredentials(
318
address, amount, priority as BitcoinTransactionPriority);
319
case WalletType.monero:
@@ -330,6 +340,9 @@ abstract class SendViewModelBase with Store {
340
case WalletType.bitcoin:
341
maximumFractionDigits = 8;
342
break;
343
+ case WalletType.litecoin:
344
+ maximumFractionDigits = 8;
345
+ break;
346
default:
347
break;
348
}
@@ -357,7 +370,7 @@ abstract class SendViewModelBase with Store {
370
final _priority = priority as TransactionPriority;
371
final wallet = _wallet;
372
360
- if (wallet is BitcoinWallet) {
373
+ if (wallet is ElectrumWallet) {
374
final rate = wallet.feeRate(_priority);
375
return '${priority.toString()} ($rate sat/byte)';
376
}
lib/view_model/settings/settings_view_model.dart
+17
-9
@@ -1,10 +1,3 @@
1
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
2
-import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
3
-import 'package:cake_wallet/entities/balance.dart';
4
-import 'package:cake_wallet/entities/transaction_priority.dart';
5
-import 'package:cake_wallet/themes/theme_base.dart';
6
-import 'package:cake_wallet/themes/theme_list.dart';
7
-import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
1
import 'package:flutter/cupertino.dart';
2
import 'package:mobx/mobx.dart';
3
import 'package:package_info/package_info.dart';
@@ -25,6 +18,15 @@ import 'package:cake_wallet/view_model/settings/regular_list_item.dart';
18
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
19
import 'package:cake_wallet/view_model/settings/switcher_list_item.dart';
20
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
21
+import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
22
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
23
+import 'package:cake_wallet/core/transaction_history.dart';
24
+import 'package:cake_wallet/entities/balance.dart';
25
+import 'package:cake_wallet/entities/transaction_info.dart';
26
+import 'package:cake_wallet/entities/transaction_priority.dart';
27
+import 'package:cake_wallet/themes/theme_base.dart';
28
+import 'package:cake_wallet/themes/theme_list.dart';
29
+import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
30
31
part 'settings_view_model.g.dart';
32
@@ -36,13 +38,19 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
38
return MoneroTransactionPriority.all;
39
case WalletType.bitcoin:
40
return BitcoinTransactionPriority.all;
41
+ case WalletType.litecoin:
42
+ return BitcoinTransactionPriority.all;
43
default:
44
return [];
45
}
46
}
47
48
abstract class SettingsViewModelBase with Store {
45
- SettingsViewModelBase(this._settingsStore, WalletBase<Balance> wallet)
49
+ SettingsViewModelBase(
50
+ this._settingsStore,
51
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
52
+ TransactionInfo>
53
+ wallet)
54
: itemHeaders = {},
55
_walletType = wallet.type,
56
_biometricAuth = BiometricAuth() {
@@ -77,7 +85,7 @@ abstract class SettingsViewModelBase with Store {
85
displayItem: (dynamic priority) {
86
final _priority = priority as TransactionPriority;
87
80
- if (wallet is BitcoinWallet) {
88
+ if (wallet is ElectrumWallet) {
89
final rate = wallet.feeRate(_priority);
90
return '${priority.toString()} ($rate sat/byte)';
91
}
lib/view_model/transaction_details_view_model.dart
+42
-14
@@ -1,5 +1,6 @@
1
-import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
1
+import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
2
import 'package:cake_wallet/entities/transaction_info.dart';
3
+import 'package:cake_wallet/entities/wallet_type.dart';
4
import 'package:cake_wallet/monero/monero_transaction_info.dart';
5
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
6
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
@@ -43,12 +44,6 @@ abstract class TransactionDetailsViewModelBase with Store {
44
value: tx.amountFormatted()),
45
StandartListItem(
46
title: S.current.transaction_details_fee, value: tx.feeFormatted()),
46
- BlockExplorerListItem(
47
- title: "View in Block Explorer",
48
- value: "View Transaction on XMRChain.net",
49
- onTap: () {
50
- launch("https://xmrchain.net/search?value=${tx.id}");
51
- })
47
];
48
49
if (tx.key?.isNotEmpty ?? null) {
@@ -59,7 +54,7 @@ abstract class TransactionDetailsViewModelBase with Store {
54
items.addAll(_items);
55
}
56
62
- if (tx is BitcoinTransactionInfo) {
57
+ if (tx is ElectrumTransactionInfo) {
58
final _items = [
59
StandartListItem(
60
title: S.current.transaction_details_transaction_id, value: tx.id),
@@ -78,12 +73,6 @@ abstract class TransactionDetailsViewModelBase with Store {
73
StandartListItem(
74
title: S.current.transaction_details_fee,
75
value: tx.feeFormatted()),
81
- BlockExplorerListItem(
82
- title: "View in Block Explorer",
83
- value: "View Transaction on Blockchain.com",
84
- onTap: () {
85
- launch("https://www.blockchain.com/btc/tx/${tx.id}");
86
- })
76
];
77
78
items.addAll(_items);
@@ -101,6 +90,19 @@ abstract class TransactionDetailsViewModelBase with Store {
90
}
91
}
92
93
+ WalletType type;
94
+
95
+ if (tx is MoneroTransactionInfo) {
96
+ type = WalletType.monero;
97
+ } else if (tx is ElectrumTransactionInfo) {
98
+ type = tx.type;
99
+ }
100
+
101
+ items.add(BlockExplorerListItem(
102
+ title: "View in Block Explorer",
103
+ value: _explorerDescription(type),
104
+ onTap: () => launch(_explorerUrl(type, tx.id))));
105
+
106
final description = transactionDescriptionBox.values.firstWhere(
107
(val) => val.id == transactionInfo.id,
108
orElse: () => TransactionDescription(id: transactionInfo.id));
@@ -125,4 +127,30 @@ abstract class TransactionDetailsViewModelBase with Store {
127
128
final List<TransactionDetailsListItem> items;
129
bool showRecipientAddress;
130
+
131
+ String _explorerUrl(WalletType type, String txId) {
132
+ switch (type) {
133
+ case WalletType.monero:
134
+ return 'https://xmrchain.net/search?value=${txId}';
135
+ case WalletType.bitcoin:
136
+ return 'https://www.blockchain.com/btc/tx/${txId}';
137
+ case WalletType.litecoin:
138
+ return 'https://blockchair.com/litecoin/transaction/${txId}';
139
+ default:
140
+ return '';
141
+ }
142
+ }
143
+
144
+ String _explorerDescription(WalletType type) {
145
+ switch (type) {
146
+ case WalletType.monero:
147
+ return 'View Transaction on XMRChain.net';
148
+ case WalletType.bitcoin:
149
+ return 'View Transaction on Blockchain.com';
150
+ case WalletType.litecoin:
151
+ return 'View Transaction on Blockchair.com';
152
+ default:
153
+ return '';
154
+ }
155
+ }
156
}
lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart
+2
-1
@@ -1,6 +1,7 @@
1
import 'package:flutter/foundation.dart';
2
import 'package:mobx/mobx.dart';
3
import 'package:cake_wallet/core/wallet_base.dart';
4
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
5
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
6
import 'package:cake_wallet/monero/monero_wallet.dart';
7
@@ -62,7 +63,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
63
Future<void> _createNew() async {
64
final wallet = _wallet;
65
65
- if (wallet is BitcoinWallet) {
66
+ if (wallet is ElectrumWallet) {
67
await wallet.generateNewAddress();
68
}
69
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+9
-8
@@ -1,4 +1,6 @@
1
+import 'package:cake_wallet/core/transaction_history.dart';
2
import 'package:cake_wallet/entities/balance.dart';
3
+import 'package:cake_wallet/entities/transaction_info.dart';
4
import 'package:cake_wallet/store/app_store.dart';
5
import 'package:flutter/foundation.dart';
6
import 'package:mobx/mobx.dart';
@@ -56,12 +58,13 @@ class BitcoinURI extends PaymentURI {
58
}
59
60
abstract class WalletAddressListViewModelBase with Store {
59
- WalletAddressListViewModelBase(
60
- {@required AppStore appStore}) {
61
+ WalletAddressListViewModelBase({@required AppStore appStore}) {
62
_appStore = appStore;
63
_wallet = _appStore.wallet;
64
hasAccounts = _wallet?.type == WalletType.monero;
64
- _onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<Balance> wallet) {
65
+ _onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<
66
+ Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
67
+ wallet) {
68
_wallet = wallet;
69
hasAccounts = _wallet.type == WalletType.monero;
70
});
@@ -119,9 +122,7 @@ abstract class WalletAddressListViewModelBase with Store {
122
final isPrimary = addr == primaryAddress;
123
124
return WalletAddressListItem(
122
- isPrimary: isPrimary,
123
- name: null,
124
- address: addr.address);
125
+ isPrimary: isPrimary, name: null, address: addr.address);
126
});
127
addressList.addAll(bitcoinAddresses);
128
}
@@ -147,7 +148,8 @@ abstract class WalletAddressListViewModelBase with Store {
148
bool get hasAddressList => _wallet.type == WalletType.monero;
149
150
@observable
150
- WalletBase<Balance> _wallet;
151
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
152
+ _wallet;
153
154
List<ListItem> _baseItems;
155
@@ -155,7 +157,6 @@ abstract class WalletAddressListViewModelBase with Store {
157
158
ReactionDisposer _onWalletChangeReaction;
159
158
-
160
@action
161
void setAddress(WalletAddressListItem address) =>
162
_wallet.address = address.address;
lib/view_model/wallet_keys_view_model.dart
+3
-4
@@ -2,7 +2,7 @@ import 'package:mobx/mobx.dart';
2
import 'package:cake_wallet/generated/i18n.dart';
3
import 'package:cake_wallet/core/wallet_base.dart';
4
import 'package:cake_wallet/monero/monero_wallet.dart';
5
-import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
5
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
6
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
7
8
part 'wallet_keys_view_model.g.dart';
@@ -24,12 +24,11 @@ abstract class WalletKeysViewModelBase with Store {
24
title: S.current.view_key_public, value: keys.publicViewKey),
25
StandartListItem(
26
title: S.current.view_key_private, value: keys.privateViewKey),
27
- StandartListItem(
28
- title: S.current.wallet_seed, value: wallet.seed),
27
+ StandartListItem(title: S.current.wallet_seed, value: wallet.seed),
28
]);
29
}
30
32
- if (wallet is BitcoinWallet) {
31
+ if (wallet is ElectrumWallet) {
32
final keys = wallet.keys;
33
34
items.addAll([
lib/view_model/wallet_new_vm.dart
+2
@@ -37,6 +37,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
37
name: name, language: options as String);
38
case WalletType.bitcoin:
39
return BitcoinNewWalletCredentials(name: name);
40
+ case WalletType.litecoin:
41
+ return BitcoinNewWalletCredentials(name: name);
42
default:
43
return null;
44
}
pubspec.lock
+11
-7
@@ -67,10 +67,12 @@ packages:
67
bech32:
68
dependency: transitive
69
description:
70
- name: bech32
71
- url: "https://pub.dartlang.org"
72
- source: hosted
73
- version: "0.1.2"
70
+ path: "."
71
+ ref: cake
72
+ resolved-ref: "02fef082f20af13de00b4e64efb93a2c1e5e1cf2"
73
+ url: "git@github.com:cake-tech/bech32.git"
74
+ source: git
75
+ version: "0.2.0"
76
bip32:
77
dependency: transitive
78
description:
@@ -88,9 +90,11 @@ packages:
90
bitcoin_flutter:
91
dependency: "direct main"
92
description:
91
- name: bitcoin_flutter
92
- url: "https://pub.dartlang.org"
93
- source: hosted
93
+ path: "."
94
+ ref: cake
95
+ resolved-ref: b3ab2926c665f0e68b74a4a5f31059f7fcd817b7
96
+ url: "git@github.com:cake-tech/bitcoin_flutter.git"
97
+ source: git
98
version: "2.0.2"
99
boolean_selector:
100
dependency: transitive
pubspec.yaml
+7
-2
@@ -12,6 +12,7 @@ description: Cake Wallet.
12
# Read more about iOS versioning at
13
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14
version: 4.1.4+43
15
+publish_to: none
16
17
environment:
18
sdk: ">=2.7.0 <3.0.0"
@@ -65,7 +66,10 @@ dependencies:
66
crypto: ^2.1.5
67
password: ^1.0.0
68
basic_utils: ^2.0.3
68
- bitcoin_flutter: ^2.0.0
69
+ bitcoin_flutter:
70
+ git:
71
+ url: git@github.com:cake-tech/bitcoin_flutter.git
72
+ ref: cake
73
get_it: ^6.0.0
74
connectivity: ^3.0.3
75
keyboard_actions: ^3.3.0
@@ -105,7 +109,8 @@ flutter:
109
assets:
110
- assets/images/
111
- assets/node_list.yml
108
- - assets/electrum_server_list.yml
112
+ - assets/bitcoin_electrum_server_list.yml
113
+ - assets/litecoin_electrum_server_list.yml
114
- assets/text/
115
- assets/faq/
116