mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Reworked electrum wallet. Added Litecoin wallet.
This commit is contained in:
parent
c8a521a29f
commit
1330966794
71 changed files with 1503 additions and 1071 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -96,3 +96,5 @@ vendor/
|
||||||
android/app/.cxx/**
|
android/app/.cxx/**
|
||||||
ios/Flutter/.last_build_id
|
ios/Flutter/.last_build_id
|
||||||
/lib/generated/**
|
/lib/generated/**
|
||||||
|
#**#
|
||||||
|
/**/#**#
|
|
@ -25,7 +25,6 @@ linter:
|
||||||
- empty_constructor_bodies
|
- empty_constructor_bodies
|
||||||
- empty_statements
|
- empty_statements
|
||||||
- hash_and_equals
|
- hash_and_equals
|
||||||
- implementation_imports
|
|
||||||
- invariant_booleans
|
- invariant_booleans
|
||||||
- iterable_contains_unrelated_type
|
- iterable_contains_unrelated_type
|
||||||
- library_names
|
- library_names
|
||||||
|
|
BIN
assets/images/litecoin_icon.png
Normal file
BIN
assets/images/litecoin_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/images/litecoin_menu.png
Normal file
BIN
assets/images/litecoin_menu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.1 KiB |
2
assets/litecoin_electrum_server_list.yml
Normal file
2
assets/litecoin_electrum_server_list.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
-
|
||||||
|
uri: 128.199.34.116:50002
|
|
@ -179,8 +179,6 @@ extern "C"
|
||||||
Monero::SubaddressAccount *m_account;
|
Monero::SubaddressAccount *m_account;
|
||||||
uint64_t m_last_known_wallet_height;
|
uint64_t m_last_known_wallet_height;
|
||||||
uint64_t m_cached_syncing_blockchain_height = 0;
|
uint64_t m_cached_syncing_blockchain_height = 0;
|
||||||
std::mutex store_mutex;
|
|
||||||
|
|
||||||
|
|
||||||
void change_current_wallet(Monero::Wallet *wallet)
|
void change_current_wallet(Monero::Wallet *wallet)
|
||||||
{
|
{
|
||||||
|
@ -451,9 +449,7 @@ extern "C"
|
||||||
|
|
||||||
void store(char *path)
|
void store(char *path)
|
||||||
{
|
{
|
||||||
store_mutex.lock();
|
|
||||||
get_current_wallet()->store(std::string(path));
|
get_current_wallet()->store(std::string(path));
|
||||||
store_mutex.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool transaction_create(char *address, char *payment_id, char *amount,
|
bool transaction_create(char *address, char *payment_id, char *amount,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
|
|
||||||
import 'package:cw_monero/wallet.dart';
|
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cw_monero/convert_utf8_to_string.dart';
|
import 'package:cw_monero/convert_utf8_to_string.dart';
|
||||||
import 'package:cw_monero/signatures.dart';
|
import 'package:cw_monero/signatures.dart';
|
||||||
import 'package:cw_monero/types.dart';
|
import 'package:cw_monero/types.dart';
|
||||||
import 'package:cw_monero/monero_api.dart';
|
import 'package:cw_monero/monero_api.dart';
|
||||||
|
import 'package:cw_monero/wallet.dart';
|
||||||
|
import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
|
||||||
import 'package:cw_monero/exceptions/wallet_creation_exception.dart';
|
import 'package:cw_monero/exceptions/wallet_creation_exception.dart';
|
||||||
import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart';
|
import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart';
|
||||||
import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart';
|
import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart';
|
||||||
|
|
|
@ -7,49 +7,49 @@ packages:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.5.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.1.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.1.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: charcode
|
name: charcode
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.2.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.1.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.13"
|
version: "1.15.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -73,21 +73,21 @@ packages:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.8"
|
version: "0.12.10"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.8"
|
version: "1.3.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -113,56 +113,56 @@ packages:
|
||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.5"
|
version: "1.10.0"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.1.0"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.1.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.17"
|
version: "0.2.19"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.9.0-14.0.dev <3.0.0"
|
dart: ">=2.12.0-0.0 <3.0.0"
|
||||||
flutter: ">=0.1.4 <2.0.0"
|
flutter: ">=0.1.4"
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:bs58check/bs58check.dart' as bs58check;
|
import 'package:bs58check/bs58check.dart' as bs58check;
|
||||||
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
|
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
|
||||||
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
|
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
|
||||||
import 'package:bitcoin_flutter/src/address.dart';
|
import 'package:bitcoin_flutter/src/address.dart';
|
||||||
|
|
||||||
|
|
||||||
Uint8List p2shAddressToOutputScript(String address) {
|
Uint8List p2shAddressToOutputScript(String address) {
|
||||||
final decodeBase58 = bs58check.decode(address);
|
final decodeBase58 = bs58check.decode(address);
|
||||||
final hash = decodeBase58.sublist(1);
|
final hash = decodeBase58.sublist(1);
|
||||||
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
|
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List addressToOutputScript(String address) {
|
Uint8List addressToOutputScript(
|
||||||
|
String address, bitcoin.NetworkType networkType) {
|
||||||
try {
|
try {
|
||||||
// FIXME: improve validation for p2sh addresses
|
// FIXME: improve validation for p2sh addresses
|
||||||
if (address.startsWith('3')) {
|
// 3 for bitcoin
|
||||||
|
// m for litecoin
|
||||||
|
if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
|
||||||
return p2shAddressToOutputScript(address);
|
return p2shAddressToOutputScript(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Address.addressToOutputScript(address);
|
return Address.addressToOutputScript(address, networkType);
|
||||||
} catch (_) {
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
return Uint8List(0);
|
return Uint8List(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:quiver/core.dart';
|
|
||||||
|
|
||||||
class BitcoinAddressRecord {
|
class BitcoinAddressRecord {
|
||||||
BitcoinAddressRecord(this.address, {this.index});
|
BitcoinAddressRecord(this.address, {this.index, bool isHidden})
|
||||||
|
: _isHidden = isHidden;
|
||||||
|
|
||||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
return BitcoinAddressRecord(decoded['address'] as String,
|
return BitcoinAddressRecord(decoded['address'] as String,
|
||||||
index: decoded['index'] as int);
|
index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -16,10 +16,13 @@ class BitcoinAddressRecord {
|
||||||
o is BitcoinAddressRecord && address == o.address;
|
o is BitcoinAddressRecord && address == o.address;
|
||||||
|
|
||||||
final String address;
|
final String address;
|
||||||
|
bool get isHidden => _isHidden ?? false;
|
||||||
int index;
|
int index;
|
||||||
|
final bool _isHidden;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => address.hashCode;
|
int get hashCode => address.hashCode;
|
||||||
|
|
||||||
String toJSON() => json.encode({'address': address, 'index': index});
|
String toJSON() =>
|
||||||
|
json.encode({'address': address, 'index': index, 'isHidden': isHidden});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,192 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
import 'package:cake_wallet/core/transaction_history.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/file.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
|
||||||
|
|
||||||
part 'bitcoin_transaction_history.g.dart';
|
|
||||||
|
|
||||||
const _transactionsHistoryFileName = 'transactions.json';
|
|
||||||
|
|
||||||
class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
|
|
||||||
with _$BitcoinTransactionHistory;
|
|
||||||
|
|
||||||
abstract class BitcoinTransactionHistoryBase
|
|
||||||
extends TransactionHistoryBase<BitcoinTransactionInfo> with Store {
|
|
||||||
BitcoinTransactionHistoryBase(
|
|
||||||
{this.eclient, String dirPath, @required String password})
|
|
||||||
: path = '$dirPath/$_transactionsHistoryFileName',
|
|
||||||
_password = password,
|
|
||||||
_height = 0,
|
|
||||||
_isUpdating = false {
|
|
||||||
transactions = ObservableMap<String, BitcoinTransactionInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
BitcoinWalletBase wallet;
|
|
||||||
final ElectrumClient eclient;
|
|
||||||
final String path;
|
|
||||||
final String _password;
|
|
||||||
int _height;
|
|
||||||
bool _isUpdating;
|
|
||||||
|
|
||||||
Future<void> init() async {
|
|
||||||
await _load();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future update() async {
|
|
||||||
if (_isUpdating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
_isUpdating = true;
|
|
||||||
final txs = await fetchTransactions();
|
|
||||||
await add(txs);
|
|
||||||
_isUpdating = false;
|
|
||||||
} catch (_) {
|
|
||||||
_isUpdating = false;
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Map<String, BitcoinTransactionInfo>> fetchTransactions() async {
|
|
||||||
final histories =
|
|
||||||
wallet.scriptHashes.map((scriptHash) => eclient.getHistory(scriptHash));
|
|
||||||
final _historiesWithDetails = await Future.wait(histories)
|
|
||||||
.then((histories) => histories.expand((i) => i).toList())
|
|
||||||
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
|
||||||
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
|
||||||
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
|
||||||
|
|
||||||
return historiesWithDetails.fold<Map<String, BitcoinTransactionInfo>>(
|
|
||||||
<String, BitcoinTransactionInfo>{}, (acc, tx) {
|
|
||||||
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
|
|
||||||
return acc;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<BitcoinTransactionInfo> fetchTransactionInfo(
|
|
||||||
{@required String hash, @required int height}) async {
|
|
||||||
final tx = await eclient.getTransactionExpanded(hash: hash);
|
|
||||||
return BitcoinTransactionInfo.fromElectrumVerbose(tx,
|
|
||||||
height: height, addresses: wallet.addresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> add(Map<String, BitcoinTransactionInfo> transactionsList) async {
|
|
||||||
transactionsList.entries.forEach((entry) {
|
|
||||||
_updateOrInsert(entry.value);
|
|
||||||
|
|
||||||
if (entry.value.height > _height) {
|
|
||||||
_height = entry.value.height;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
|
||||||
_updateOrInsert(tx);
|
|
||||||
|
|
||||||
if (tx.height > _height) {
|
|
||||||
_height = tx.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
await save();
|
|
||||||
}
|
|
||||||
|
|
||||||
BitcoinTransactionInfo get(String id) => transactions[id];
|
|
||||||
|
|
||||||
Future<void> save() async {
|
|
||||||
try {
|
|
||||||
final data = json.encode({'height': _height, 'transactions': transactions});
|
|
||||||
await writeData(path: path, password: _password, data: data);
|
|
||||||
} catch(e) {
|
|
||||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void updateAsync({void Function() onFinished}) {
|
|
||||||
fetchTransactionsAsync((transaction) => _updateOrInsert(transaction),
|
|
||||||
onFinished: onFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void fetchTransactionsAsync(
|
|
||||||
void Function(BitcoinTransactionInfo transaction) onTransactionLoaded,
|
|
||||||
{void Function() onFinished}) async {
|
|
||||||
final histories = await Future.wait(wallet.scriptHashes
|
|
||||||
.map((scriptHash) async => await eclient.getHistory(scriptHash)));
|
|
||||||
final transactionsCount =
|
|
||||||
histories.fold<int>(0, (acc, m) => acc + m.length);
|
|
||||||
var counter = 0;
|
|
||||||
|
|
||||||
final batches = histories.map((metaList) =>
|
|
||||||
_fetchBatchOfTransactions(metaList, onTransactionLoaded: (transaction) {
|
|
||||||
onTransactionLoaded(transaction);
|
|
||||||
counter += 1;
|
|
||||||
|
|
||||||
if (counter == transactionsCount) {
|
|
||||||
onFinished?.call();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Future.wait(batches);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _fetchBatchOfTransactions(
|
|
||||||
Iterable<Map<String, dynamic>> metaList,
|
|
||||||
{void Function(BitcoinTransactionInfo tranasaction)
|
|
||||||
onTransactionLoaded}) async =>
|
|
||||||
metaList.forEach((txMeta) => fetchTransactionInfo(
|
|
||||||
hash: txMeta['tx_hash'] as String,
|
|
||||||
height: txMeta['height'] as int)
|
|
||||||
.then((transaction) => onTransactionLoaded(transaction)));
|
|
||||||
|
|
||||||
Future<Map<String, Object>> _read() async {
|
|
||||||
final content = await read(path: path, password: _password);
|
|
||||||
return json.decode(content) as Map<String, Object>;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _load() async {
|
|
||||||
try {
|
|
||||||
final content = await _read();
|
|
||||||
final txs = content['transactions'] as Map<String, Object> ?? {};
|
|
||||||
|
|
||||||
txs.entries.forEach((entry) {
|
|
||||||
final val = entry.value;
|
|
||||||
|
|
||||||
if (val is Map<String, Object>) {
|
|
||||||
final tx = BitcoinTransactionInfo.fromJson(val);
|
|
||||||
_updateOrInsert(tx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_height = content['height'] as int;
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateOrInsert(BitcoinTransactionInfo transaction) {
|
|
||||||
if (transaction.id == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transactions[transaction.id] == null) {
|
|
||||||
transactions[transaction.id] = transaction;
|
|
||||||
} else {
|
|
||||||
final originalTx = transactions[transaction.id];
|
|
||||||
originalTx.confirmations = transaction.confirmations;
|
|
||||||
originalTx.amount = transaction.amount;
|
|
||||||
originalTx.height = transaction.height;
|
|
||||||
originalTx.date ??= transaction.date;
|
|
||||||
originalTx.isPending = transaction.isPending;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,5 +13,6 @@ class BitcoinUnspent {
|
||||||
final int value;
|
final int value;
|
||||||
final int vout;
|
final int vout;
|
||||||
|
|
||||||
bool get isP2wpkh => address.address.startsWith('bc1');
|
bool get isP2wpkh =>
|
||||||
|
address.address.startsWith('bc') || address.address.startsWith('ltc');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,472 +1,51 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cake_wallet/entities/sync_status.dart';
|
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cake_wallet/bitcoin/file.dart';
|
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
|
||||||
import 'package:cake_wallet/entities/node.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
|
||||||
|
|
||||||
part 'bitcoin_wallet.g.dart';
|
part 'bitcoin_wallet.g.dart';
|
||||||
|
|
||||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||||
|
|
||||||
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
BitcoinWalletBase._internal(
|
BitcoinWalletBase(
|
||||||
{@required this.eclient,
|
|
||||||
@required this.path,
|
|
||||||
@required String password,
|
|
||||||
@required WalletInfo walletInfo,
|
|
||||||
@required List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
int accountIndex = 0,
|
|
||||||
this.transactionHistory,
|
|
||||||
this.mnemonic,
|
|
||||||
BitcoinBalance initialBalance})
|
|
||||||
: balance =
|
|
||||||
initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0),
|
|
||||||
hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
|
|
||||||
network: bitcoin.bitcoin)
|
|
||||||
.derivePath("m/0'/0"),
|
|
||||||
addresses = initialAddresses != null
|
|
||||||
? ObservableList<BitcoinAddressRecord>.of(initialAddresses.toSet())
|
|
||||||
: ObservableList<BitcoinAddressRecord>(),
|
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
|
||||||
_password = password,
|
|
||||||
_accountIndex = accountIndex,
|
|
||||||
_feeRates = <int>[],
|
|
||||||
super(walletInfo) {
|
|
||||||
_unspent = [];
|
|
||||||
_scripthashesUpdateSubject = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
static BitcoinWallet fromJSON(
|
|
||||||
{@required String password,
|
|
||||||
@required String name,
|
|
||||||
@required String dirPath,
|
|
||||||
@required WalletInfo walletInfo,
|
|
||||||
String jsonSource}) {
|
|
||||||
final data = json.decode(jsonSource) as Map;
|
|
||||||
final mnemonic = data['mnemonic'] as String;
|
|
||||||
final accountIndex =
|
|
||||||
(data['account_index'] == 'null' || data['account_index'] == null)
|
|
||||||
? 0
|
|
||||||
: int.parse(data['account_index'] as String);
|
|
||||||
final _addresses = data['addresses'] as List ?? <Object>[];
|
|
||||||
final addresses = <BitcoinAddressRecord>[];
|
|
||||||
final balance = BitcoinBalance.fromJSON(data['balance'] as String) ??
|
|
||||||
BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
|
||||||
|
|
||||||
_addresses.forEach((Object el) {
|
|
||||||
if (el is String) {
|
|
||||||
addresses.add(BitcoinAddressRecord.fromJSON(el));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return BitcoinWalletBase.build(
|
|
||||||
dirPath: dirPath,
|
|
||||||
mnemonic: mnemonic,
|
|
||||||
password: password,
|
|
||||||
name: name,
|
|
||||||
accountIndex: accountIndex,
|
|
||||||
initialAddresses: addresses,
|
|
||||||
initialBalance: balance,
|
|
||||||
walletInfo: walletInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
static BitcoinWallet build(
|
|
||||||
{@required String mnemonic,
|
{@required String mnemonic,
|
||||||
@required String password,
|
@required String password,
|
||||||
@required String name,
|
|
||||||
@required String dirPath,
|
|
||||||
@required WalletInfo walletInfo,
|
@required WalletInfo walletInfo,
|
||||||
List<BitcoinAddressRecord> initialAddresses,
|
List<BitcoinAddressRecord> initialAddresses,
|
||||||
BitcoinBalance initialBalance,
|
ElectrumBalance initialBalance,
|
||||||
int accountIndex = 0}) {
|
int accountIndex = 0})
|
||||||
final walletPath = '$dirPath/$name';
|
: super(
|
||||||
final eclient = ElectrumClient();
|
|
||||||
final history = BitcoinTransactionHistory(
|
|
||||||
eclient: eclient, dirPath: dirPath, password: password);
|
|
||||||
|
|
||||||
return BitcoinWallet._internal(
|
|
||||||
eclient: eclient,
|
|
||||||
path: walletPath,
|
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
accountIndex: accountIndex,
|
walletInfo: walletInfo,
|
||||||
|
networkType: bitcoin.bitcoin,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
transactionHistory: history,
|
accountIndex: accountIndex);
|
||||||
walletInfo: walletInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
static Future<BitcoinWallet> open({
|
||||||
inputsCount * 146 + outputsCounts * 33 + 8;
|
@required String name,
|
||||||
|
@required WalletInfo walletInfo,
|
||||||
@override
|
@required String password,
|
||||||
final BitcoinTransactionHistory transactionHistory;
|
}) async {
|
||||||
final String path;
|
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
||||||
final bitcoin.HDWallet hd;
|
await snp.load();
|
||||||
final ElectrumClient eclient;
|
return BitcoinWallet(
|
||||||
final String mnemonic;
|
mnemonic: snp.mnemonic,
|
||||||
|
password: password,
|
||||||
List<BitcoinUnspent> _unspent;
|
walletInfo: walletInfo,
|
||||||
|
initialAddresses: snp.addresses,
|
||||||
@override
|
initialBalance: snp.balance,
|
||||||
@observable
|
accountIndex: snp.accountIndex);
|
||||||
String address;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
BitcoinBalance balance;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
SyncStatus syncStatus;
|
|
||||||
|
|
||||||
ObservableList<BitcoinAddressRecord> addresses;
|
|
||||||
|
|
||||||
List<String> get scriptHashes =>
|
|
||||||
addresses.map((addr) => scriptHash(addr.address)).toList();
|
|
||||||
|
|
||||||
String get xpub => hd.base58;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get seed => mnemonic;
|
|
||||||
|
|
||||||
@override
|
|
||||||
BitcoinWalletKeys get keys => BitcoinWalletKeys(
|
|
||||||
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
|
|
||||||
|
|
||||||
final String _password;
|
|
||||||
List<int> _feeRates;
|
|
||||||
int _accountIndex;
|
|
||||||
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
|
|
||||||
|
|
||||||
Future<void> init() async {
|
|
||||||
if (addresses.isEmpty || addresses.length < 33) {
|
|
||||||
final addressesCount = 33 - addresses.length;
|
|
||||||
await generateNewAddresses(addressesCount, startIndex: addresses.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
address = addresses[_accountIndex].address;
|
|
||||||
transactionHistory.wallet = this;
|
|
||||||
await transactionHistory.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> nextAddress() async {
|
|
||||||
_accountIndex += 1;
|
|
||||||
|
|
||||||
if (_accountIndex >= addresses.length) {
|
|
||||||
_accountIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
address = addresses[_accountIndex].address;
|
|
||||||
|
|
||||||
await save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<BitcoinAddressRecord> generateNewAddress() async {
|
|
||||||
_accountIndex += 1;
|
|
||||||
final address = BitcoinAddressRecord(_getAddress(index: _accountIndex),
|
|
||||||
index: _accountIndex);
|
|
||||||
addresses.add(address);
|
|
||||||
|
|
||||||
await save();
|
|
||||||
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
|
|
||||||
{int startIndex = 0}) async {
|
|
||||||
final list = <BitcoinAddressRecord>[];
|
|
||||||
|
|
||||||
for (var i = startIndex; i < count + startIndex; i++) {
|
|
||||||
final address = BitcoinAddressRecord(_getAddress(index: i), index: i);
|
|
||||||
list.add(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses.addAll(list);
|
|
||||||
await save();
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateAddress(String address) async {
|
|
||||||
for (final addr in addresses) {
|
|
||||||
if (addr.address == address) {
|
|
||||||
await save();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
@override
|
|
||||||
Future<void> startSync() async {
|
|
||||||
try {
|
|
||||||
syncStatus = StartingSyncStatus();
|
|
||||||
transactionHistory.updateAsync(onFinished: () {
|
|
||||||
print('transactionHistory update finished!');
|
|
||||||
transactionHistory.save();
|
|
||||||
});
|
|
||||||
_subscribeForUpdates();
|
|
||||||
await _updateBalance();
|
|
||||||
await _updateUnspent();
|
|
||||||
_feeRates = await eclient.feeRates();
|
|
||||||
|
|
||||||
Timer.periodic(const Duration(minutes: 1),
|
|
||||||
(timer) async => _feeRates = await eclient.feeRates());
|
|
||||||
|
|
||||||
syncStatus = SyncedSyncStatus();
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
syncStatus = FailedSyncStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
@override
|
|
||||||
Future<void> connectToNode({@required Node node}) async {
|
|
||||||
try {
|
|
||||||
syncStatus = ConnectingSyncStatus();
|
|
||||||
await eclient.connectToUri(node.uri);
|
|
||||||
eclient.onConnectionStatusChange = (bool isConnected) {
|
|
||||||
if (!isConnected) {
|
|
||||||
syncStatus = LostConnectionSyncStatus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
syncStatus = ConnectedSyncStatus();
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
syncStatus = FailedSyncStatus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingBitcoinTransaction> createTransaction(
|
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||||
Object credentials) async {
|
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||||
const minAmount = 546;
|
|
||||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
|
||||||
final inputs = <BitcoinUnspent>[];
|
|
||||||
final allAmountFee =
|
|
||||||
calculateEstimatedFee(transactionCredentials.priority, null);
|
|
||||||
final allAmount = balance.confirmed - allAmountFee;
|
|
||||||
var fee = 0;
|
|
||||||
final credentialsAmount = transactionCredentials.amount != null
|
|
||||||
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
|
|
||||||
: 0;
|
|
||||||
final amount = transactionCredentials.amount == null ||
|
|
||||||
allAmount - credentialsAmount < minAmount
|
|
||||||
? allAmount
|
|
||||||
: credentialsAmount;
|
|
||||||
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
|
|
||||||
final changeAddress = address;
|
|
||||||
var leftAmount = amount;
|
|
||||||
var totalInputAmount = 0;
|
|
||||||
|
|
||||||
if (_unspent.isEmpty) {
|
|
||||||
await _updateUnspent();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final utx in _unspent) {
|
|
||||||
leftAmount = leftAmount - utx.value;
|
|
||||||
totalInputAmount += utx.value;
|
|
||||||
inputs.add(utx);
|
|
||||||
|
|
||||||
if (leftAmount <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputs.isEmpty) {
|
|
||||||
throw BitcoinTransactionNoInputsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final totalAmount = amount + fee;
|
|
||||||
fee = transactionCredentials.amount != null
|
|
||||||
? feeAmountForPriority(transactionCredentials.priority, inputs.length,
|
|
||||||
amount == allAmount ? 1 : 2)
|
|
||||||
: allAmountFee;
|
|
||||||
|
|
||||||
if (totalAmount > balance.confirmed) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount <= 0 || totalInputAmount < amount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException();
|
|
||||||
}
|
|
||||||
|
|
||||||
txb.setVersion(1);
|
|
||||||
|
|
||||||
inputs.forEach((input) {
|
|
||||||
if (input.isP2wpkh) {
|
|
||||||
final p2wpkh = bitcoin
|
|
||||||
.P2WPKH(
|
|
||||||
data: generatePaymentData(hd: hd, index: input.address.index),
|
|
||||||
network: bitcoin.bitcoin)
|
|
||||||
.data;
|
|
||||||
|
|
||||||
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
|
|
||||||
} else {
|
|
||||||
txb.addInput(input.hash, input.vout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
txb.addOutput(
|
|
||||||
addressToOutputScript(transactionCredentials.address), amount);
|
|
||||||
|
|
||||||
final estimatedSize = estimatedTransactionSize(inputs.length, 2);
|
|
||||||
final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
|
|
||||||
final changeValue = totalInputAmount - amount - feeAmount;
|
|
||||||
|
|
||||||
if (changeValue > minAmount) {
|
|
||||||
txb.addOutput(changeAddress, changeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
|
||||||
final input = inputs[i];
|
|
||||||
final keyPair = generateKeyPair(hd: hd, index: input.address.index);
|
|
||||||
final witnessValue = input.isP2wpkh ? input.value : null;
|
|
||||||
|
|
||||||
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PendingBitcoinTransaction(txb.build(),
|
|
||||||
eclient: eclient, amount: amount, fee: fee)
|
|
||||||
..addListener((transaction) async {
|
|
||||||
transactionHistory.addOne(transaction);
|
|
||||||
await _updateBalance();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
|
||||||
'mnemonic': mnemonic,
|
|
||||||
'account_index': _accountIndex.toString(),
|
|
||||||
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
|
|
||||||
'balance': balance?.toJSON()
|
|
||||||
});
|
|
||||||
|
|
||||||
int feeRate(TransactionPriority priority) {
|
|
||||||
if (priority is BitcoinTransactionPriority) {
|
|
||||||
return _feeRates[priority.raw];
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
|
|
||||||
int outputsCount) =>
|
|
||||||
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int amount) {
|
|
||||||
if (priority is BitcoinTransactionPriority) {
|
|
||||||
int inputsCount = 0;
|
|
||||||
|
|
||||||
if (amount != null) {
|
|
||||||
int totalValue = 0;
|
|
||||||
|
|
||||||
for (final input in _unspent) {
|
|
||||||
if (totalValue >= amount) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalValue += input.value;
|
|
||||||
inputsCount += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
inputsCount = _unspent.length;
|
|
||||||
}
|
|
||||||
// If send all, then we have no change value
|
|
||||||
return feeAmountForPriority(
|
|
||||||
priority, inputsCount, amount != null ? 2 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> save() async {
|
|
||||||
await write(path: path, password: _password, data: toJSON());
|
|
||||||
await transactionHistory.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
bitcoin.ECPair keyPairFor({@required int index}) =>
|
|
||||||
generateKeyPair(hd: hd, index: index);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> rescan({int height}) async {
|
|
||||||
// FIXME: Unimplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void close() async {
|
|
||||||
await eclient.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateUnspent() async {
|
|
||||||
final unspent = await Future.wait(addresses.map((address) => eclient
|
|
||||||
.getListUnspentWithAddress(address.address)
|
|
||||||
.then((unspent) => unspent
|
|
||||||
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
|
|
||||||
_unspent = unspent.expand((e) => e).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _subscribeForUpdates() {
|
|
||||||
scriptHashes.forEach((sh) async {
|
|
||||||
await _scripthashesUpdateSubject[sh]?.close();
|
|
||||||
_scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh);
|
|
||||||
_scripthashesUpdateSubject[sh].listen((event) async {
|
|
||||||
try {
|
|
||||||
await _updateBalance();
|
|
||||||
await _updateUnspent();
|
|
||||||
transactionHistory.updateAsync();
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<BitcoinBalance> _fetchBalances() async {
|
|
||||||
final balances = await Future.wait(
|
|
||||||
scriptHashes.map((sHash) => eclient.getBalance(sHash)));
|
|
||||||
final balance = balances.fold(
|
|
||||||
BitcoinBalance(confirmed: 0, unconfirmed: 0),
|
|
||||||
(BitcoinBalance acc, val) => BitcoinBalance(
|
|
||||||
confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
|
|
||||||
unconfirmed:
|
|
||||||
(val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
|
|
||||||
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateBalance() async {
|
|
||||||
balance = await _fetchBalances();
|
|
||||||
await save();
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getAddress({@required int index}) =>
|
|
||||||
generateAddress(hd: hd, index: index);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
||||||
import 'package:cake_wallet/bitcoin/file.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
import 'package:cake_wallet/core/wallet_service.dart';
|
||||||
|
@ -19,44 +18,32 @@ class BitcoinWalletService extends WalletService<
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.bitcoin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
||||||
final dirPath = await pathForWalletDir(
|
final wallet = BitcoinWallet(
|
||||||
type: WalletType.bitcoin, name: credentials.name);
|
|
||||||
final wallet = BitcoinWalletBase.build(
|
|
||||||
dirPath: dirPath,
|
|
||||||
mnemonic: generateMnemonic(),
|
mnemonic: generateMnemonic(),
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
name: credentials.name,
|
|
||||||
walletInfo: credentials.walletInfo);
|
walletInfo: credentials.walletInfo);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isWalletExit(String name) async =>
|
Future<bool> isWalletExit(String name) async =>
|
||||||
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||||
.existsSync();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> openWallet(String name, String password) async {
|
Future<BitcoinWallet> openWallet(String name, String password) async {
|
||||||
final walletDirPath =
|
|
||||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
|
||||||
final walletPath = '$walletDirPath/$name';
|
|
||||||
final walletJSONRaw = await read(path: walletPath, password: password);
|
|
||||||
final walletInfo = walletInfoSource.values.firstWhere(
|
final walletInfo = walletInfoSource.values.firstWhere(
|
||||||
(info) => info.id == WalletBase.idFor(name, WalletType.bitcoin),
|
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||||
orElse: () => null);
|
orElse: () => null);
|
||||||
final wallet = BitcoinWalletBase.fromJSON(
|
final wallet = await BitcoinWalletBase.open(
|
||||||
password: password,
|
password: password, name: name, walletInfo: walletInfo);
|
||||||
name: name,
|
|
||||||
dirPath: walletDirPath,
|
|
||||||
jsonSource: walletJSONRaw,
|
|
||||||
walletInfo: walletInfo);
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,10 +54,8 @@ class BitcoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromKeys(
|
Future<BitcoinWallet> restoreFromKeys(
|
||||||
BitcoinRestoreWalletFromWIFCredentials credentials) async {
|
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
||||||
// TODO: implement restoreFromKeys
|
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromSeed(
|
Future<BitcoinWallet> restoreFromSeed(
|
||||||
|
@ -79,17 +64,12 @@ class BitcoinWalletService extends WalletService<
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
throw BitcoinMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
||||||
final dirPath = await pathForWalletDir(
|
final wallet = BitcoinWallet(
|
||||||
type: WalletType.bitcoin, name: credentials.name);
|
|
||||||
final wallet = BitcoinWalletBase.build(
|
|
||||||
dirPath: dirPath,
|
|
||||||
name: credentials.name,
|
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo);
|
walletInfo: credentials.walletInfo);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -173,10 +174,11 @@ class ElectrumClient {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
||||||
String address) =>
|
String address, NetworkType networkType) =>
|
||||||
call(
|
call(
|
||||||
method: 'blockchain.scripthash.listunspent',
|
method: 'blockchain.scripthash.listunspent',
|
||||||
params: [scriptHash(address)]).then((dynamic result) {
|
params: [scriptHash(address, networkType: networkType)])
|
||||||
|
.then((dynamic result) {
|
||||||
if (result is List) {
|
if (result is List) {
|
||||||
return result.map((dynamic val) {
|
return result.map((dynamic val) {
|
||||||
if (val is Map<String, Object>) {
|
if (val is Map<String, Object>) {
|
||||||
|
@ -259,7 +261,7 @@ class ElectrumClient {
|
||||||
if (result is String) {
|
if (result is String) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
print(result);
|
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -303,14 +305,25 @@ class ElectrumClient {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<List<int>> feeRates() async {
|
Future<List<int>> feeRates() async {
|
||||||
|
try {
|
||||||
final topDoubleString = await estimatefee(p: 1);
|
final topDoubleString = await estimatefee(p: 1);
|
||||||
final middleDoubleString = await estimatefee(p: 20);
|
final middleDoubleString = await estimatefee(p: 20);
|
||||||
final bottomDoubleString = await estimatefee(p: 150);
|
final bottomDoubleString = await estimatefee(p: 150);
|
||||||
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
final top =
|
||||||
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
|
||||||
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
.round();
|
||||||
|
final middle =
|
||||||
|
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
|
||||||
|
.round();
|
||||||
|
final bottom =
|
||||||
|
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
|
||||||
|
.round();
|
||||||
|
|
||||||
return [bottom, middle, top];
|
final res = [bottom, middle, top];
|
||||||
|
return res;
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BehaviorSubject<Object> scripthashUpdate(String scripthash) {
|
BehaviorSubject<Object> scripthashUpdate(String scripthash) {
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
|
|
||||||
class BitcoinBalance extends Balance {
|
class ElectrumBalance extends Balance {
|
||||||
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed})
|
const ElectrumBalance({@required this.confirmed, @required this.unconfirmed})
|
||||||
: super(confirmed, unconfirmed);
|
: super(confirmed, unconfirmed);
|
||||||
|
|
||||||
factory BitcoinBalance.fromJSON(String jsonSource) {
|
factory ElectrumBalance.fromJSON(String jsonSource) {
|
||||||
if (jsonSource == null) {
|
if (jsonSource == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
return BitcoinBalance(
|
return ElectrumBalance(
|
||||||
confirmed: decoded['confirmed'] as int ?? 0,
|
confirmed: decoded['confirmed'] as int ?? 0,
|
||||||
unconfirmed: decoded['unconfirmed'] as int ?? 0);
|
unconfirmed: decoded['unconfirmed'] as int ?? 0);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +23,8 @@ class BitcoinBalance extends Balance {
|
||||||
final int unconfirmed;
|
final int unconfirmed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed);
|
String get formattedAvailableBalance =>
|
||||||
|
bitcoinAmountToString(amount: confirmed);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedAdditionalBalance =>
|
String get formattedAdditionalBalance =>
|
98
lib/bitcoin/electrum_transaction_history.dart
Normal file
98
lib/bitcoin/electrum_transaction_history.dart
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/file.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
||||||
|
|
||||||
|
part 'electrum_transaction_history.g.dart';
|
||||||
|
|
||||||
|
const _transactionsHistoryFileName = 'transactions.json';
|
||||||
|
|
||||||
|
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
|
||||||
|
with _$ElectrumTransactionHistory;
|
||||||
|
|
||||||
|
abstract class ElectrumTransactionHistoryBase
|
||||||
|
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
||||||
|
ElectrumTransactionHistoryBase(
|
||||||
|
{@required this.walletInfo, @required String password})
|
||||||
|
: _password = password,
|
||||||
|
_height = 0 {
|
||||||
|
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletInfo walletInfo;
|
||||||
|
final String _password;
|
||||||
|
int _height;
|
||||||
|
|
||||||
|
Future<void> init() async => await _load();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addOne(ElectrumTransactionInfo transaction) =>
|
||||||
|
transactions[transaction.id] = transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
|
||||||
|
this.transactions.addAll(transactions);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
try {
|
||||||
|
final dirPath =
|
||||||
|
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$_transactionsHistoryFileName';
|
||||||
|
final data =
|
||||||
|
json.encode({'height': _height, 'transactions': transactions});
|
||||||
|
await writeData(path: path, password: _password, data: data);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error while save bitcoin transaction history: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, Object>> _read() async {
|
||||||
|
final dirPath =
|
||||||
|
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$_transactionsHistoryFileName';
|
||||||
|
final content = await read(path: path, password: _password);
|
||||||
|
return json.decode(content) as Map<String, Object>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
try {
|
||||||
|
final content = await _read();
|
||||||
|
final txs = content['transactions'] as Map<String, Object> ?? {};
|
||||||
|
|
||||||
|
txs.entries.forEach((entry) {
|
||||||
|
final val = entry.value;
|
||||||
|
|
||||||
|
if (val is Map<String, Object>) {
|
||||||
|
final tx = ElectrumTransactionInfo.fromJson(val, walletInfo.type);
|
||||||
|
_updateOrInsert(tx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_height = content['height'] as int;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateOrInsert(ElectrumTransactionInfo transaction) {
|
||||||
|
if (transaction.id == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactions[transaction.id] == null) {
|
||||||
|
transactions[transaction.id] = transaction;
|
||||||
|
} else {
|
||||||
|
final originalTx = transactions[transaction.id];
|
||||||
|
originalTx.confirmations = transaction.confirmations;
|
||||||
|
originalTx.amount = transaction.amount;
|
||||||
|
originalTx.height = transaction.height;
|
||||||
|
originalTx.date ??= transaction.date;
|
||||||
|
originalTx.isPending = transaction.isPending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,10 @@ import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_direction.dart';
|
import 'package:cake_wallet/entities/transaction_direction.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/entities/format_amount.dart';
|
import 'package:cake_wallet/entities/format_amount.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
|
||||||
class BitcoinTransactionInfo extends TransactionInfo {
|
class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
BitcoinTransactionInfo(
|
ElectrumTransactionInfo(this.type,
|
||||||
{@required String id,
|
{@required String id,
|
||||||
@required int height,
|
@required int height,
|
||||||
@required int amount,
|
@required int amount,
|
||||||
|
@ -27,7 +28,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
this.confirmations = confirmations;
|
this.confirmations = confirmations;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory BitcoinTransactionInfo.fromElectrumVerbose(Map<String, Object> obj,
|
factory ElectrumTransactionInfo.fromElectrumVerbose(
|
||||||
|
Map<String, Object> obj, WalletType type,
|
||||||
{@required List<BitcoinAddressRecord> addresses, @required int height}) {
|
{@required List<BitcoinAddressRecord> addresses, @required int height}) {
|
||||||
final addressesSet = addresses.map((addr) => addr.address).toSet();
|
final addressesSet = addresses.map((addr) => addr.address).toSet();
|
||||||
final id = obj['txid'] as String;
|
final id = obj['txid'] as String;
|
||||||
|
@ -47,7 +49,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
final out = vin['tx']['vout'][vout] as Map;
|
final out = vin['tx']['vout'][vout] as Map;
|
||||||
final outAddresses =
|
final outAddresses =
|
||||||
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
|
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
|
||||||
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
|
inputsAmount +=
|
||||||
|
stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
|
||||||
|
|
||||||
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
|
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
|
||||||
direction = TransactionDirection.outgoing;
|
direction = TransactionDirection.outgoing;
|
||||||
|
@ -58,7 +61,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
final outAddresses =
|
final outAddresses =
|
||||||
out['scriptPubKey']['addresses'] as List<Object> ?? [];
|
out['scriptPubKey']['addresses'] as List<Object> ?? [];
|
||||||
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
||||||
final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString());
|
final value = stringDoubleToBitcoinAmount(
|
||||||
|
(out['value'] as double ?? 0.0).toString());
|
||||||
totalOutAmount += value;
|
totalOutAmount += value;
|
||||||
|
|
||||||
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
||||||
|
@ -69,7 +73,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
|
|
||||||
final fee = inputsAmount - totalOutAmount;
|
final fee = inputsAmount - totalOutAmount;
|
||||||
|
|
||||||
return BitcoinTransactionInfo(
|
return ElectrumTransactionInfo(type,
|
||||||
id: id,
|
id: id,
|
||||||
height: height,
|
height: height,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
|
@ -80,7 +84,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
confirmations: confirmations);
|
confirmations: confirmations);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory BitcoinTransactionInfo.fromHexAndHeader(String hex,
|
factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
|
||||||
{List<String> addresses, int height, int timestamp, int confirmations}) {
|
{List<String> addresses, int height, int timestamp, int confirmations}) {
|
||||||
final tx = bitcoin.Transaction.fromHex(hex);
|
final tx = bitcoin.Transaction.fromHex(hex);
|
||||||
var exist = false;
|
var exist = false;
|
||||||
|
@ -104,7 +108,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
|
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
|
||||||
: DateTime.now();
|
: DateTime.now();
|
||||||
|
|
||||||
return BitcoinTransactionInfo(
|
return ElectrumTransactionInfo(type,
|
||||||
id: tx.getId(),
|
id: tx.getId(),
|
||||||
height: height,
|
height: height,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
|
@ -115,8 +119,9 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
confirmations: confirmations);
|
confirmations: confirmations);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory BitcoinTransactionInfo.fromJson(Map<String, dynamic> data) {
|
factory ElectrumTransactionInfo.fromJson(
|
||||||
return BitcoinTransactionInfo(
|
Map<String, dynamic> data, WalletType type) {
|
||||||
|
return ElectrumTransactionInfo(type,
|
||||||
id: data['id'] as String,
|
id: data['id'] as String,
|
||||||
height: data['height'] as int,
|
height: data['height'] as int,
|
||||||
amount: data['amount'] as int,
|
amount: data['amount'] as int,
|
||||||
|
@ -127,6 +132,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
confirmations: data['confirmations'] as int);
|
confirmations: data['confirmations'] as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final WalletType type;
|
||||||
|
|
||||||
String _fiatAmount;
|
String _fiatAmount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -144,8 +151,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
@override
|
@override
|
||||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
|
||||||
BitcoinTransactionInfo updated(BitcoinTransactionInfo info) {
|
ElectrumTransactionInfo updated(ElectrumTransactionInfo info) {
|
||||||
return BitcoinTransactionInfo(
|
return ElectrumTransactionInfo(info.type,
|
||||||
id: id,
|
id: id,
|
||||||
height: info.height,
|
height: info.height,
|
||||||
amount: info.amount,
|
amount: info.amount,
|
467
lib/bitcoin/electrum_wallet.dart
Normal file
467
lib/bitcoin/electrum_wallet.dart
Normal file
|
@ -0,0 +1,467 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:rxdart/subjects.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_transaction_history.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/file.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
|
import 'package:cake_wallet/entities/node.dart';
|
||||||
|
import 'package:cake_wallet/entities/sync_status.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
|
|
||||||
|
part 'electrum_wallet.g.dart';
|
||||||
|
|
||||||
|
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
|
||||||
|
|
||||||
|
abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
|
ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
|
||||||
|
ElectrumWalletBase(
|
||||||
|
{@required String password,
|
||||||
|
@required WalletInfo walletInfo,
|
||||||
|
@required List<BitcoinAddressRecord> initialAddresses,
|
||||||
|
@required this.networkType,
|
||||||
|
@required this.mnemonic,
|
||||||
|
ElectrumClient electrumClient,
|
||||||
|
int accountIndex = 0,
|
||||||
|
ElectrumBalance initialBalance})
|
||||||
|
: balance = initialBalance ??
|
||||||
|
const ElectrumBalance(confirmed: 0, unconfirmed: 0),
|
||||||
|
hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
|
||||||
|
network: networkType)
|
||||||
|
.derivePath("m/0'/0"),
|
||||||
|
addresses = ObservableList<BitcoinAddressRecord>.of(
|
||||||
|
(initialAddresses ?? []).toSet()),
|
||||||
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
|
_password = password,
|
||||||
|
_accountIndex = accountIndex,
|
||||||
|
_feeRates = <int>[],
|
||||||
|
_isTransactionUpdating = false,
|
||||||
|
super(walletInfo) {
|
||||||
|
this.electrumClient = electrumClient ?? ElectrumClient();
|
||||||
|
this.walletInfo = walletInfo;
|
||||||
|
transactionHistory =
|
||||||
|
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
||||||
|
_unspent = [];
|
||||||
|
_scripthashesUpdateSubject = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
||||||
|
inputsCount * 146 + outputsCounts * 33 + 8;
|
||||||
|
|
||||||
|
final bitcoin.HDWallet hd;
|
||||||
|
final String mnemonic;
|
||||||
|
|
||||||
|
ElectrumClient electrumClient;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
String address;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
ElectrumBalance balance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
|
ObservableList<BitcoinAddressRecord> addresses;
|
||||||
|
|
||||||
|
List<String> get scriptHashes => addresses
|
||||||
|
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
String get xpub => hd.base58;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get seed => mnemonic;
|
||||||
|
|
||||||
|
bitcoin.NetworkType networkType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BitcoinWalletKeys get keys => BitcoinWalletKeys(
|
||||||
|
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
|
||||||
|
|
||||||
|
final String _password;
|
||||||
|
List<BitcoinUnspent> _unspent;
|
||||||
|
List<int> _feeRates;
|
||||||
|
int _accountIndex;
|
||||||
|
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
|
||||||
|
bool _isTransactionUpdating;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
await generateAddresses();
|
||||||
|
address = addresses[_accountIndex].address;
|
||||||
|
await transactionHistory.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> nextAddress() async {
|
||||||
|
_accountIndex += 1;
|
||||||
|
|
||||||
|
if (_accountIndex >= addresses.length) {
|
||||||
|
_accountIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
address = addresses[_accountIndex].address;
|
||||||
|
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> generateAddresses() async {
|
||||||
|
if (addresses.length < 33) {
|
||||||
|
final addressesCount = 33 - addresses.length;
|
||||||
|
await generateNewAddresses(addressesCount,
|
||||||
|
startIndex: addresses.length, hd: hd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<BitcoinAddressRecord> generateNewAddress(
|
||||||
|
{bool isHidden = false, bitcoin.HDWallet hd}) async {
|
||||||
|
_accountIndex += 1;
|
||||||
|
final _hd = hd ?? this.hd;
|
||||||
|
final address = BitcoinAddressRecord(
|
||||||
|
getAddress(index: _accountIndex, hd: _hd),
|
||||||
|
index: _accountIndex,
|
||||||
|
isHidden: isHidden);
|
||||||
|
addresses.add(address);
|
||||||
|
await save();
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
|
||||||
|
{int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async {
|
||||||
|
final list = <BitcoinAddressRecord>[];
|
||||||
|
|
||||||
|
for (var i = startIndex; i < count + startIndex; i++) {
|
||||||
|
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
|
||||||
|
index: i, isHidden: isHidden);
|
||||||
|
list.add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses.addAll(list);
|
||||||
|
await save();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateAddress(String address) async {
|
||||||
|
for (final addr in addresses) {
|
||||||
|
if (addr.address == address) {
|
||||||
|
await save();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> startSync() async {
|
||||||
|
try {
|
||||||
|
syncStatus = StartingSyncStatus();
|
||||||
|
updateTransactions();
|
||||||
|
_subscribeForUpdates();
|
||||||
|
await _updateBalance();
|
||||||
|
await _updateUnspent();
|
||||||
|
_feeRates = await electrumClient.feeRates();
|
||||||
|
|
||||||
|
Timer.periodic(const Duration(minutes: 1),
|
||||||
|
(timer) async => _feeRates = await electrumClient.feeRates());
|
||||||
|
|
||||||
|
syncStatus = SyncedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> connectToNode({@required Node node}) async {
|
||||||
|
try {
|
||||||
|
syncStatus = ConnectingSyncStatus();
|
||||||
|
await electrumClient.connectToUri(node.uri);
|
||||||
|
electrumClient.onConnectionStatusChange = (bool isConnected) {
|
||||||
|
if (!isConnected) {
|
||||||
|
syncStatus = LostConnectionSyncStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
syncStatus = ConnectedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingBitcoinTransaction> createTransaction(
|
||||||
|
Object credentials) async {
|
||||||
|
const minAmount = 546;
|
||||||
|
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||||
|
final inputs = <BitcoinUnspent>[];
|
||||||
|
final allAmountFee =
|
||||||
|
calculateEstimatedFee(transactionCredentials.priority, null);
|
||||||
|
final allAmount = balance.confirmed - allAmountFee;
|
||||||
|
var fee = 0;
|
||||||
|
final credentialsAmount = transactionCredentials.amount != null
|
||||||
|
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
|
||||||
|
: 0;
|
||||||
|
final amount = transactionCredentials.amount == null ||
|
||||||
|
allAmount - credentialsAmount < minAmount
|
||||||
|
? allAmount
|
||||||
|
: credentialsAmount;
|
||||||
|
final txb = bitcoin.TransactionBuilder(network: networkType);
|
||||||
|
final changeAddress = address;
|
||||||
|
var leftAmount = amount;
|
||||||
|
var totalInputAmount = 0;
|
||||||
|
|
||||||
|
if (_unspent.isEmpty) {
|
||||||
|
await _updateUnspent();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final utx in _unspent) {
|
||||||
|
leftAmount = leftAmount - utx.value;
|
||||||
|
totalInputAmount += utx.value;
|
||||||
|
inputs.add(utx);
|
||||||
|
|
||||||
|
if (leftAmount <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs.isEmpty) {
|
||||||
|
throw BitcoinTransactionNoInputsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalAmount = amount + fee;
|
||||||
|
fee = transactionCredentials.amount != null
|
||||||
|
? feeAmountForPriority(transactionCredentials.priority, inputs.length,
|
||||||
|
amount == allAmount ? 1 : 2)
|
||||||
|
: allAmountFee;
|
||||||
|
|
||||||
|
if (totalAmount > balance.confirmed) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0 || totalInputAmount < amount) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
txb.setVersion(1);
|
||||||
|
|
||||||
|
inputs.forEach((input) {
|
||||||
|
if (input.isP2wpkh) {
|
||||||
|
final p2wpkh = bitcoin
|
||||||
|
.P2WPKH(
|
||||||
|
data: generatePaymentData(hd: hd, index: input.address.index),
|
||||||
|
network: networkType)
|
||||||
|
.data;
|
||||||
|
|
||||||
|
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
|
||||||
|
} else {
|
||||||
|
txb.addInput(input.hash, input.vout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
txb.addOutput(
|
||||||
|
addressToOutputScript(transactionCredentials.address, networkType),
|
||||||
|
amount);
|
||||||
|
|
||||||
|
final estimatedSize = estimatedTransactionSize(inputs.length, 2);
|
||||||
|
final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
|
||||||
|
final changeValue = totalInputAmount - amount - feeAmount;
|
||||||
|
|
||||||
|
if (changeValue > minAmount) {
|
||||||
|
txb.addOutput(changeAddress, changeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
final input = inputs[i];
|
||||||
|
final keyPair = generateKeyPair(
|
||||||
|
hd: hd, index: input.address.index, network: networkType);
|
||||||
|
final witnessValue = input.isP2wpkh ? input.value : null;
|
||||||
|
|
||||||
|
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingBitcoinTransaction(txb.build(), type,
|
||||||
|
electrumClient: electrumClient, amount: amount, fee: fee)
|
||||||
|
..addListener((transaction) async {
|
||||||
|
transactionHistory.addOne(transaction);
|
||||||
|
await _updateBalance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJSON() => json.encode({
|
||||||
|
'mnemonic': mnemonic,
|
||||||
|
'account_index': _accountIndex.toString(),
|
||||||
|
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
|
||||||
|
'balance': balance?.toJSON()
|
||||||
|
});
|
||||||
|
|
||||||
|
int feeRate(TransactionPriority priority) {
|
||||||
|
if (priority is BitcoinTransactionPriority) {
|
||||||
|
return _feeRates[priority.raw];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
|
||||||
|
int outputsCount) =>
|
||||||
|
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int calculateEstimatedFee(TransactionPriority priority, int amount) {
|
||||||
|
if (priority is BitcoinTransactionPriority) {
|
||||||
|
int inputsCount = 0;
|
||||||
|
|
||||||
|
if (amount != null) {
|
||||||
|
int totalValue = 0;
|
||||||
|
|
||||||
|
for (final input in _unspent) {
|
||||||
|
if (totalValue >= amount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalValue += input.value;
|
||||||
|
inputsCount += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputsCount = _unspent.length;
|
||||||
|
}
|
||||||
|
// If send all, then we have no change value
|
||||||
|
return feeAmountForPriority(
|
||||||
|
priority, inputsCount, amount != null ? 2 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
final path = await makePath();
|
||||||
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
await transactionHistory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
bitcoin.ECPair keyPairFor({@required int index}) =>
|
||||||
|
generateKeyPair(hd: hd, index: index, network: networkType);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rescan({int height}) async => throw UnimplementedError();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
try {
|
||||||
|
await electrumClient?.close();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
|
||||||
|
|
||||||
|
Future<String> makePath() async =>
|
||||||
|
pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
|
Future<void> _updateUnspent() async {
|
||||||
|
final unspent = await Future.wait(addresses.map((address) => electrumClient
|
||||||
|
.getListUnspentWithAddress(address.address, networkType)
|
||||||
|
.then((unspent) => unspent
|
||||||
|
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
|
||||||
|
_unspent = unspent.expand((e) => e).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ElectrumTransactionInfo> fetchTransactionInfo(
|
||||||
|
{@required String hash, @required int height}) async {
|
||||||
|
final tx = await electrumClient.getTransactionExpanded(hash: hash);
|
||||||
|
return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type,
|
||||||
|
height: height, addresses: addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
|
final histories =
|
||||||
|
scriptHashes.map((scriptHash) => electrumClient.getHistory(scriptHash));
|
||||||
|
final _historiesWithDetails = await Future.wait(histories)
|
||||||
|
.then((histories) => histories.expand((i) => i).toList())
|
||||||
|
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
||||||
|
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
||||||
|
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
||||||
|
|
||||||
|
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
|
||||||
|
<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
||||||
|
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateTransactions() async {
|
||||||
|
try {
|
||||||
|
if (_isTransactionUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTransactionUpdating = true;
|
||||||
|
final transactions = await fetchTransactions();
|
||||||
|
transactionHistory.addMany(transactions);
|
||||||
|
await transactionHistory.save();
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _subscribeForUpdates() {
|
||||||
|
scriptHashes.forEach((sh) async {
|
||||||
|
await _scripthashesUpdateSubject[sh]?.close();
|
||||||
|
_scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh);
|
||||||
|
_scripthashesUpdateSubject[sh].listen((event) async {
|
||||||
|
try {
|
||||||
|
await _updateBalance();
|
||||||
|
await _updateUnspent();
|
||||||
|
await updateTransactions();
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ElectrumBalance> _fetchBalances() async {
|
||||||
|
final balances = await Future.wait(
|
||||||
|
scriptHashes.map((sh) => electrumClient.getBalance(sh)));
|
||||||
|
final balance = balances.fold(
|
||||||
|
ElectrumBalance(confirmed: 0, unconfirmed: 0),
|
||||||
|
(ElectrumBalance acc, val) => ElectrumBalance(
|
||||||
|
confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
|
||||||
|
unconfirmed:
|
||||||
|
(val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
|
||||||
|
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateBalance() async {
|
||||||
|
balance = await _fetchBalances();
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
}
|
42
lib/bitcoin/electrum_wallet_snapshot.dart
Normal file
42
lib/bitcoin/electrum_wallet_snapshot.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/file.dart';
|
||||||
|
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
|
||||||
|
class ElectrumWallletSnapshot {
|
||||||
|
ElectrumWallletSnapshot(this.name, this.type, this.password);
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
final String password;
|
||||||
|
final WalletType type;
|
||||||
|
|
||||||
|
String mnemonic;
|
||||||
|
List<BitcoinAddressRecord> addresses;
|
||||||
|
ElectrumBalance balance;
|
||||||
|
int accountIndex;
|
||||||
|
|
||||||
|
Future<void> load() async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: name, type: type);
|
||||||
|
final jsonSource = await read(path: path, password: password);
|
||||||
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
final addressesTmp = data['addresses'] as List ?? <Object>[];
|
||||||
|
mnemonic = data['mnemonic'] as String;
|
||||||
|
addresses = addressesTmp
|
||||||
|
.whereType<String>()
|
||||||
|
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
|
||||||
|
.toList();
|
||||||
|
balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
|
||||||
|
ElectrumBalance(confirmed: 0, unconfirmed: 0);
|
||||||
|
accountIndex = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
accountIndex = int.parse(data['account_index'] as String);
|
||||||
|
} catch (_) {}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
lib/bitcoin/litecoin_network.dart
Normal file
9
lib/bitcoin/litecoin_network.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||||
|
|
||||||
|
final litecoinNetwork = NetworkType(
|
||||||
|
messagePrefix: '\x19Litecoin Signed Message:\n',
|
||||||
|
bech32: 'ltc',
|
||||||
|
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
|
||||||
|
pubKeyHash: 0x30,
|
||||||
|
scriptHash: 0x32,
|
||||||
|
wif: 0xb0);
|
88
lib/bitcoin/litecoin_wallet.dart
Normal file
88
lib/bitcoin/litecoin_wallet.dart
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/litecoin_network.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||||
|
|
||||||
|
part 'litecoin_wallet.g.dart';
|
||||||
|
|
||||||
|
class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
|
||||||
|
|
||||||
|
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
LitecoinWalletBase(
|
||||||
|
{@required String mnemonic,
|
||||||
|
@required String password,
|
||||||
|
@required WalletInfo walletInfo,
|
||||||
|
List<BitcoinAddressRecord> initialAddresses,
|
||||||
|
ElectrumBalance initialBalance,
|
||||||
|
int accountIndex = 0})
|
||||||
|
: super(
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
password: password,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
networkType: litecoinNetwork,
|
||||||
|
initialAddresses: initialAddresses,
|
||||||
|
initialBalance: initialBalance,
|
||||||
|
accountIndex: accountIndex);
|
||||||
|
|
||||||
|
static Future<LitecoinWallet> open({
|
||||||
|
@required String name,
|
||||||
|
@required WalletInfo walletInfo,
|
||||||
|
@required String password,
|
||||||
|
}) async {
|
||||||
|
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
||||||
|
await snp.load();
|
||||||
|
return LitecoinWallet(
|
||||||
|
mnemonic: snp.mnemonic,
|
||||||
|
password: password,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
initialAddresses: snp.addresses,
|
||||||
|
initialBalance: snp.balance,
|
||||||
|
accountIndex: snp.accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||||
|
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> generateAddresses() async {
|
||||||
|
if (addresses.length < 33) {
|
||||||
|
final addressesCount = 22 - addresses.length;
|
||||||
|
await generateNewAddresses(addressesCount,
|
||||||
|
hd: hd, startIndex: addresses.length);
|
||||||
|
|
||||||
|
final changeRoot = bitcoin.HDWallet.fromSeed(
|
||||||
|
mnemonicToSeedBytes(mnemonic),
|
||||||
|
network: networkType)
|
||||||
|
.derivePath("m/0'/1");
|
||||||
|
|
||||||
|
await generateNewAddresses(11,
|
||||||
|
startIndex: 0, hd: changeRoot, isHidden: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int feeRate(TransactionPriority priority) {
|
||||||
|
if (priority is BitcoinTransactionPriority) {
|
||||||
|
switch (priority) {
|
||||||
|
case BitcoinTransactionPriority.slow:
|
||||||
|
return 1;
|
||||||
|
case BitcoinTransactionPriority.medium:
|
||||||
|
return 2;
|
||||||
|
case BitcoinTransactionPriority.fast:
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
76
lib/bitcoin/litecoin_wallet_service.dart
Normal file
76
lib/bitcoin/litecoin_wallet_service.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/litecoin_wallet.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_service.dart';
|
||||||
|
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
|
|
||||||
|
class LitecoinWalletService extends WalletService<
|
||||||
|
BitcoinNewWalletCredentials,
|
||||||
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
|
BitcoinRestoreWalletFromWIFCredentials> {
|
||||||
|
LitecoinWalletService(this.walletInfoSource);
|
||||||
|
|
||||||
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.litecoin;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
||||||
|
final wallet = LitecoinWallet(
|
||||||
|
mnemonic: generateMnemonic(),
|
||||||
|
password: credentials.password,
|
||||||
|
walletInfo: credentials.walletInfo);
|
||||||
|
await wallet.save();
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isWalletExit(String name) async =>
|
||||||
|
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LitecoinWallet> openWallet(String name, String password) async {
|
||||||
|
final walletInfo = walletInfoSource.values.firstWhere(
|
||||||
|
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||||
|
orElse: () => null);
|
||||||
|
final wallet = await LitecoinWalletBase.open(
|
||||||
|
password: password, name: name, walletInfo: walletInfo);
|
||||||
|
await wallet.init();
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> remove(String wallet) async =>
|
||||||
|
File(await pathForWalletDir(name: wallet, type: getType()))
|
||||||
|
.delete(recursive: true);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LitecoinWallet> restoreFromKeys(
|
||||||
|
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
||||||
|
throw UnimplementedError();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LitecoinWallet> restoreFromSeed(
|
||||||
|
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||||
|
if (!validateMnemonic(credentials.mnemonic)) {
|
||||||
|
throw BitcoinMnemonicIsIncorrectException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final wallet = LitecoinWallet(
|
||||||
|
password: credentials.password,
|
||||||
|
mnemonic: credentials.mnemonic,
|
||||||
|
walletInfo: credentials.walletInfo);
|
||||||
|
await wallet.save();
|
||||||
|
await wallet.init();
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,22 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_direction.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:cake_wallet/core/pending_transaction.dart';
|
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_direction.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
|
||||||
class PendingBitcoinTransaction with PendingTransaction {
|
class PendingBitcoinTransaction with PendingTransaction {
|
||||||
PendingBitcoinTransaction(this._tx,
|
PendingBitcoinTransaction(this._tx, this.type,
|
||||||
{@required this.eclient, @required this.amount, @required this.fee})
|
{@required this.electrumClient,
|
||||||
: _listeners = <void Function(BitcoinTransactionInfo transaction)>[];
|
@required this.amount,
|
||||||
|
@required this.fee})
|
||||||
|
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||||
|
|
||||||
|
final WalletType type;
|
||||||
final bitcoin.Transaction _tx;
|
final bitcoin.Transaction _tx;
|
||||||
final ElectrumClient eclient;
|
final ElectrumClient electrumClient;
|
||||||
final int amount;
|
final int amount;
|
||||||
final int fee;
|
final int fee;
|
||||||
|
|
||||||
|
@ -25,24 +29,25 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
@override
|
@override
|
||||||
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
||||||
|
|
||||||
final List<void Function(BitcoinTransactionInfo transaction)> _listeners;
|
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> commit() async {
|
Future<void> commit() async {
|
||||||
await eclient.broadcastTransaction(transactionRaw: _tx.toHex());
|
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
|
||||||
_listeners?.forEach((listener) => listener(transactionInfo()));
|
_listeners?.forEach((listener) => listener(transactionInfo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addListener(
|
void addListener(
|
||||||
void Function(BitcoinTransactionInfo transaction) listener) =>
|
void Function(ElectrumTransactionInfo transaction) listener) =>
|
||||||
_listeners.add(listener);
|
_listeners.add(listener);
|
||||||
|
|
||||||
BitcoinTransactionInfo transactionInfo() => BitcoinTransactionInfo(
|
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
||||||
id: id,
|
id: id,
|
||||||
height: 0,
|
height: 0,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
direction: TransactionDirection.outgoing,
|
direction: TransactionDirection.outgoing,
|
||||||
date: DateTime.now(),
|
date: DateTime.now(),
|
||||||
isPending: true,
|
isPending: true,
|
||||||
confirmations: 0);
|
confirmations: 0,
|
||||||
|
fee: fee);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
String scriptHash(String address) {
|
String scriptHash(String address, {@required bitcoin.NetworkType networkType}) {
|
||||||
final outputScript = bitcoin.Address.addressToOutputScript(address);
|
final outputScript =
|
||||||
final splitted = sha256.convert(outputScript).toString().split('');
|
bitcoin.Address.addressToOutputScript(address, networkType);
|
||||||
|
final parts = sha256.convert(outputScript).toString().split('');
|
||||||
var res = '';
|
var res = '';
|
||||||
|
|
||||||
for (var i = splitted.length - 1; i >= 0; i--) {
|
for (var i = parts.length - 1; i >= 0; i--) {
|
||||||
final char = splitted[i];
|
final char = parts[i];
|
||||||
i--;
|
i--;
|
||||||
final nextChar = splitted[i];
|
final nextChar = parts[i];
|
||||||
res += nextChar;
|
res += nextChar;
|
||||||
res += char;
|
res += char;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,43 @@ bitcoin.ECPair generateKeyPair(
|
||||||
{@required bitcoin.HDWallet hd,
|
{@required bitcoin.HDWallet hd,
|
||||||
@required int index,
|
@required int index,
|
||||||
bitcoin.NetworkType network}) =>
|
bitcoin.NetworkType network}) =>
|
||||||
bitcoin.ECPair.fromWIF(hd.derive(index).wif,
|
bitcoin.ECPair.fromWIF(hd.derive(index).wif, network: network);
|
||||||
network: network ?? bitcoin.bitcoin);
|
|
||||||
|
|
||||||
String generateAddress({@required bitcoin.HDWallet hd, @required int index}) =>
|
String generateP2WPKHAddress(
|
||||||
|
{@required bitcoin.HDWallet hd,
|
||||||
|
@required int index,
|
||||||
|
bitcoin.NetworkType networkType}) =>
|
||||||
bitcoin
|
bitcoin
|
||||||
.P2WPKH(
|
.P2WPKH(
|
||||||
data: PaymentData(
|
data: PaymentData(
|
||||||
pubkey:
|
pubkey:
|
||||||
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))))
|
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
|
||||||
|
network: networkType)
|
||||||
|
.data
|
||||||
|
.address;
|
||||||
|
|
||||||
|
String generateP2WPKHAddressByPath(
|
||||||
|
{@required bitcoin.HDWallet hd,
|
||||||
|
@required String path,
|
||||||
|
bitcoin.NetworkType networkType}) =>
|
||||||
|
bitcoin
|
||||||
|
.P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey:
|
||||||
|
Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey))),
|
||||||
|
network: networkType)
|
||||||
|
.data
|
||||||
|
.address;
|
||||||
|
|
||||||
|
String generateP2PKHAddress(
|
||||||
|
{@required bitcoin.HDWallet hd,
|
||||||
|
@required int index,
|
||||||
|
bitcoin.NetworkType networkType}) =>
|
||||||
|
bitcoin
|
||||||
|
.P2PKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey:
|
||||||
|
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
|
||||||
|
network: networkType)
|
||||||
.data
|
.data
|
||||||
.address;
|
.address;
|
||||||
|
|
|
@ -72,7 +72,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.eth:
|
case CryptoCurrency.eth:
|
||||||
return [42];
|
return [42];
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
return [34];
|
return [34, 43];
|
||||||
case CryptoCurrency.nano:
|
case CryptoCurrency.nano:
|
||||||
return [64, 65];
|
return [64, 65];
|
||||||
case CryptoCurrency.trx:
|
case CryptoCurrency.trx:
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/entities/encrypt.dart';
|
import 'package:cake_wallet/entities/encrypt.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:cake_wallet/entities/currency_formatter.dart';
|
|
||||||
|
|
||||||
const fiatApiAuthority = 'fiat-api.cakewallet.com';
|
const fiatApiAuthority = 'fiat-api.cakewallet.com';
|
||||||
const fiatApiPath = '/v1/rates';
|
const fiatApiPath = '/v1/rates';
|
||||||
|
@ -15,8 +14,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final fiatStringified = fiat.toString();
|
final fiatStringified = fiat.toString();
|
||||||
final uri =
|
final uri = Uri.https(fiatApiAuthority, fiatApiPath,
|
||||||
Uri.https(fiatApiAuthority, fiatApiPath,
|
|
||||||
<String, String>{'convert': fiatStringified});
|
<String, String>{'convert': fiatStringified});
|
||||||
final response = await get(uri.toString());
|
final response = await get(uri.toString());
|
||||||
|
|
||||||
|
@ -28,7 +26,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
||||||
final data = responseJSON['data'] as List<dynamic>;
|
final data = responseJSON['data'] as List<dynamic>;
|
||||||
|
|
||||||
for (final item in data) {
|
for (final item in data) {
|
||||||
if (item['symbol'] == cryptoToString(crypto)) {
|
if (item['symbol'] == crypto.title) {
|
||||||
price = item['quote'][fiatStringified]['price'] as double;
|
price = item['quote'][fiatStringified]['price'] as double;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +43,7 @@ Future<double> _fetchPriceAsync(
|
||||||
compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto});
|
compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto});
|
||||||
|
|
||||||
class FiatConversionService {
|
class FiatConversionService {
|
||||||
static Future<double> fetchPrice(CryptoCurrency crypto, FiatCurrency fiat) async =>
|
static Future<double> fetchPrice(
|
||||||
|
CryptoCurrency crypto, FiatCurrency fiat) async =>
|
||||||
await _fetchPriceAsync(crypto, fiat);
|
await _fetchPriceAsync(crypto, fiat);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
|
||||||
String generateWalletPassword(WalletType type) {
|
String generateWalletPassword(WalletType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.monero:
|
||||||
return generateKey();
|
|
||||||
default:
|
|
||||||
return Uuid().v4();
|
return Uuid().v4();
|
||||||
|
default:
|
||||||
|
return generateKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
|
|
||||||
|
|
|
@ -3,43 +3,50 @@ import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
|
|
||||||
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
|
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
|
||||||
TransactionHistoryBase() : _isUpdating = false;
|
TransactionHistoryBase();
|
||||||
|
// : _isUpdating = false;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
ObservableMap<String, TransactionType> transactions;
|
ObservableMap<String, TransactionType> transactions;
|
||||||
|
|
||||||
bool _isUpdating;
|
Future<void> save();
|
||||||
|
|
||||||
@action
|
void addOne(TransactionType transaction);
|
||||||
Future<void> update() async {
|
|
||||||
if (_isUpdating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
void addMany(Map<String, TransactionType> transactions);
|
||||||
_isUpdating = true;
|
|
||||||
final _transactions = await fetchTransactions();
|
|
||||||
transactions.keys
|
|
||||||
.toSet()
|
|
||||||
.difference(_transactions.keys.toSet())
|
|
||||||
.forEach((k) => transactions.remove(k));
|
|
||||||
_transactions.forEach((key, value) => transactions[key] = value);
|
|
||||||
_isUpdating = false;
|
|
||||||
} catch (e) {
|
|
||||||
_isUpdating = false;
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateAsync({void Function() onFinished}) {
|
// bool _isUpdating;
|
||||||
fetchTransactionsAsync(
|
|
||||||
(transaction) => transactions[transaction.id] = transaction,
|
|
||||||
onFinished: onFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchTransactionsAsync(
|
// @action
|
||||||
void Function(TransactionType transaction) onTransactionLoaded,
|
// Future<void> update() async {
|
||||||
{void Function() onFinished});
|
// if (_isUpdating) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
Future<Map<String, TransactionType>> fetchTransactions();
|
// try {
|
||||||
|
// _isUpdating = true;
|
||||||
|
// final _transactions = await fetchTransactions();
|
||||||
|
// transactions.keys
|
||||||
|
// .toSet()
|
||||||
|
// .difference(_transactions.keys.toSet())
|
||||||
|
// .forEach((k) => transactions.remove(k));
|
||||||
|
// _transactions.forEach((key, value) => transactions[key] = value);
|
||||||
|
// _isUpdating = false;
|
||||||
|
// } catch (e) {
|
||||||
|
// _isUpdating = false;
|
||||||
|
// rethrow;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void updateAsync({void Function() onFinished}) {
|
||||||
|
// fetchTransactionsAsync(
|
||||||
|
// (transaction) => transactions[transaction.id] = transaction,
|
||||||
|
// onFinished: onFinished);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void fetchTransactionsAsync(
|
||||||
|
// void Function(TransactionType transaction) onTransactionLoaded,
|
||||||
|
// {void Function() onFinished});
|
||||||
|
|
||||||
|
// Future<Map<String, TransactionType>> fetchTransactions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||||
|
@ -11,7 +12,10 @@ import 'package:cake_wallet/entities/sync_status.dart';
|
||||||
import 'package:cake_wallet/entities/node.dart';
|
import 'package:cake_wallet/entities/node.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
|
||||||
abstract class WalletBase<BalanceType extends Balance> {
|
abstract class WalletBase<
|
||||||
|
BalanceType extends Balance,
|
||||||
|
HistoryType extends TransactionHistoryBase,
|
||||||
|
TransactionType extends TransactionInfo> {
|
||||||
WalletBase(this.walletInfo);
|
WalletBase(this.walletInfo);
|
||||||
|
|
||||||
static String idFor(String name, WalletType type) =>
|
static String idFor(String name, WalletType type) =>
|
||||||
|
@ -41,7 +45,7 @@ abstract class WalletBase<BalanceType extends Balance> {
|
||||||
|
|
||||||
Object get keys;
|
Object get keys;
|
||||||
|
|
||||||
TransactionHistoryBase transactionHistory;
|
HistoryType transactionHistory;
|
||||||
|
|
||||||
Future<void> connectToNode({@required Node node});
|
Future<void> connectToNode({@required Node node});
|
||||||
|
|
||||||
|
@ -51,6 +55,12 @@ abstract class WalletBase<BalanceType extends Balance> {
|
||||||
|
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int amount);
|
int calculateEstimatedFee(TransactionPriority priority, int amount);
|
||||||
|
|
||||||
|
// void fetchTransactionsAsync(
|
||||||
|
// void Function(TransactionType transaction) onTransactionLoaded,
|
||||||
|
// {void Function() onFinished});
|
||||||
|
|
||||||
|
Future<Map<String, TransactionType>> fetchTransactions();
|
||||||
|
|
||||||
Future<void> save();
|
Future<void> save();
|
||||||
|
|
||||||
Future<void> rescan({int height});
|
Future<void> rescan({int height});
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
|
||||||
abstract class WalletService<N extends WalletCredentials,
|
abstract class WalletService<N extends WalletCredentials,
|
||||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
||||||
|
WalletType getType();
|
||||||
|
|
||||||
Future<WalletBase> create(N credentials);
|
Future<WalletBase> create(N credentials);
|
||||||
|
|
||||||
Future<WalletBase> restoreFromSeed(RFS credentials);
|
Future<WalletBase> restoreFromSeed(RFS credentials);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
|
||||||
import 'package:cake_wallet/core/backup_service.dart';
|
import 'package:cake_wallet/core/backup_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
import 'package:cake_wallet/core/wallet_service.dart';
|
||||||
import 'package:cake_wallet/entities/biometric_auth.dart';
|
import 'package:cake_wallet/entities/biometric_auth.dart';
|
||||||
|
@ -443,6 +444,8 @@ Future setup(
|
||||||
|
|
||||||
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
|
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
|
||||||
|
|
||||||
|
getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletService, WalletType, void>(
|
getIt.registerFactoryParam<WalletService, WalletType, void>(
|
||||||
(WalletType param1, __) {
|
(WalletType param1, __) {
|
||||||
switch (param1) {
|
switch (param1) {
|
||||||
|
@ -450,6 +453,8 @@ Future setup(
|
||||||
return getIt.get<MoneroWalletService>();
|
return getIt.get<MoneroWalletService>();
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return getIt.get<BitcoinWalletService>();
|
return getIt.get<BitcoinWalletService>();
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return getIt.get<LitecoinWalletService>();
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
|
||||||
return CryptoCurrency.btc;
|
return CryptoCurrency.btc;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return CryptoCurrency.xmr;
|
return CryptoCurrency.xmr;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return CryptoCurrency.ltc;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
|
||||||
|
|
||||||
String cryptoToString(CryptoCurrency crypto) {
|
|
||||||
switch (crypto) {
|
|
||||||
case CryptoCurrency.xmr:
|
|
||||||
return 'XMR';
|
|
||||||
case CryptoCurrency.btc:
|
|
||||||
return 'BTC';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,7 @@
|
||||||
import 'dart:io' show File, Platform;
|
import 'dart:io' show File, Platform;
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
|
||||||
import 'package:cake_wallet/di.dart';
|
|
||||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
@ -24,7 +20,8 @@ import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||||
|
|
||||||
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
||||||
const cakeWalletElectrumUri = 'electrum.cakewallet.com:50002';
|
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
||||||
|
const cakeWalletLitecoinElectrumUri = '128.199.34.116:50002';
|
||||||
|
|
||||||
Future defaultSettingsMigration(
|
Future defaultSettingsMigration(
|
||||||
{@required int version,
|
{@required int version,
|
||||||
|
@ -68,6 +65,8 @@ Future defaultSettingsMigration(
|
||||||
sharedPreferences: sharedPreferences, nodes: nodes);
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
await changeBitcoinCurrentElectrumServerToDefault(
|
await changeBitcoinCurrentElectrumServerToDefault(
|
||||||
sharedPreferences: sharedPreferences, nodes: nodes);
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
|
await changeLitecoinCurrentElectrumServerToDefault(
|
||||||
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -97,6 +96,7 @@ Future defaultSettingsMigration(
|
||||||
case 9:
|
case 9:
|
||||||
await generateBackupPassword(secureStorage);
|
await generateBackupPassword(secureStorage);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 10:
|
case 10:
|
||||||
await changeTransactionPriorityAndFeeRateKeys(sharedPreferences);
|
await changeTransactionPriorityAndFeeRateKeys(sharedPreferences);
|
||||||
break;
|
break;
|
||||||
|
@ -110,7 +110,14 @@ Future defaultSettingsMigration(
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 13:
|
case 13:
|
||||||
await resetElectrumServer(nodes, sharedPreferences);
|
await resetBitcoinElectrumServer(nodes, sharedPreferences);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 15:
|
||||||
|
await addLitecoinElectrumServerList(nodes: nodes);
|
||||||
|
await changeLitecoinCurrentElectrumServerToDefault(
|
||||||
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
|
await checkCurrentNodes(nodes, sharedPreferences);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -160,12 +167,21 @@ Future<void> changeMoneroCurrentNodeToDefault(
|
||||||
}
|
}
|
||||||
|
|
||||||
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
|
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
|
||||||
return nodes.values
|
return nodes.values.firstWhere(
|
||||||
.firstWhere((Node node) => node.uri == cakeWalletElectrumUri, orElse: () => null) ??
|
(Node node) => node.uri == cakeWalletBitcoinElectrumUri,
|
||||||
|
orElse: () => null) ??
|
||||||
nodes.values.firstWhere((node) => node.type == WalletType.bitcoin,
|
nodes.values.firstWhere((node) => node.type == WalletType.bitcoin,
|
||||||
orElse: () => null);
|
orElse: () => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node getLitecoinDefaultElectrumServer({@required Box<Node> nodes}) {
|
||||||
|
return nodes.values.firstWhere(
|
||||||
|
(Node node) => node.uri == cakeWalletLitecoinElectrumUri,
|
||||||
|
orElse: () => null) ??
|
||||||
|
nodes.values.firstWhere((node) => node.type == WalletType.litecoin,
|
||||||
|
orElse: () => null);
|
||||||
|
}
|
||||||
|
|
||||||
Node getMoneroDefaultNode({@required Box<Node> nodes}) {
|
Node getMoneroDefaultNode({@required Box<Node> nodes}) {
|
||||||
final timeZone = DateTime.now().timeZoneOffset.inHours;
|
final timeZone = DateTime.now().timeZoneOffset.inHours;
|
||||||
var nodeUri = '';
|
var nodeUri = '';
|
||||||
|
@ -192,6 +208,15 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
|
||||||
await sharedPreferences.setInt('current_node_id_btc', serverId);
|
await sharedPreferences.setInt('current_node_id_btc', serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> changeLitecoinCurrentElectrumServerToDefault(
|
||||||
|
{@required SharedPreferences sharedPreferences,
|
||||||
|
@required Box<Node> nodes}) async {
|
||||||
|
final server = getLitecoinDefaultElectrumServer(nodes: nodes);
|
||||||
|
final serverId = server?.key as int ?? 0;
|
||||||
|
|
||||||
|
await sharedPreferences.setInt('current_node_id_ltc', serverId);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> replaceDefaultNode(
|
Future<void> replaceDefaultNode(
|
||||||
{@required SharedPreferences sharedPreferences,
|
{@required SharedPreferences sharedPreferences,
|
||||||
@required Box<Node> nodes}) async {
|
@required Box<Node> nodes}) async {
|
||||||
|
@ -224,7 +249,12 @@ Future<void> updateNodeTypes({@required Box<Node> nodes}) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
|
Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
|
||||||
final serverList = await loadElectrumServerList();
|
final serverList = await loadBitcoinElectrumServerList();
|
||||||
|
await nodes.addAll(serverList);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addLitecoinElectrumServerList({@required Box<Node> nodes}) async {
|
||||||
|
final serverList = await loadLitecoinElectrumServerList();
|
||||||
await nodes.addAll(serverList);
|
await nodes.addAll(serverList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,57 +314,97 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
|
||||||
Future<void> changeDefaultMoneroNode(
|
Future<void> changeDefaultMoneroNode(
|
||||||
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||||
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
|
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
|
||||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
final currentMoneroNodeId =
|
||||||
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
|
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern);
|
final currentMoneroNode =
|
||||||
|
nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
|
||||||
|
final needToReplaceCurrentMoneroNode =
|
||||||
|
currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern);
|
||||||
|
|
||||||
nodeSource.values.forEach((node) async {
|
nodeSource.values.forEach((node) async {
|
||||||
if (node.type == WalletType.monero && node.uri.contains(cakeWalletMoneroNodeUriPattern)) {
|
if (node.type == WalletType.monero &&
|
||||||
|
node.uri.contains(cakeWalletMoneroNodeUriPattern)) {
|
||||||
await node.delete();
|
await node.delete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
final newCakeWalletNode =
|
||||||
|
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
||||||
|
|
||||||
await nodeSource.add(newCakeWalletNode);
|
await nodeSource.add(newCakeWalletNode);
|
||||||
|
|
||||||
if (needToReplaceCurrentMoneroNode) {
|
if (needToReplaceCurrentMoneroNode) {
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
Future<void> checkCurrentNodes(
|
||||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||||
final currentElectrumSeverId = await sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
final currentMoneroNodeId =
|
||||||
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId, orElse: () => null);
|
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
final currentElectrumServer = nodeSource.values.firstWhere((node) => node.key == currentElectrumSeverId, orElse: () => null);
|
final currentBitcoinElectrumSeverId =
|
||||||
|
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||||
|
final currentLitecoinElectrumSeverId = sharedPreferences
|
||||||
|
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||||
|
final currentMoneroNode = nodeSource.values.firstWhere(
|
||||||
|
(node) => node.key == currentMoneroNodeId,
|
||||||
|
orElse: () => null);
|
||||||
|
final currentBitcoinElectrumServer = nodeSource.values.firstWhere(
|
||||||
|
(node) => node.key == currentBitcoinElectrumSeverId,
|
||||||
|
orElse: () => null);
|
||||||
|
final currentLitecoinElectrumServer = nodeSource.values.firstWhere(
|
||||||
|
(node) => node.key == currentLitecoinElectrumSeverId,
|
||||||
|
orElse: () => null);
|
||||||
|
|
||||||
if (currentMoneroNode == null) {
|
if (currentMoneroNode == null) {
|
||||||
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
final newCakeWalletNode =
|
||||||
|
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
||||||
await nodeSource.add(newCakeWalletNode);
|
await nodeSource.add(newCakeWalletNode);
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentElectrumServer == null) {
|
if (currentBitcoinElectrumServer == null) {
|
||||||
final cakeWalletElectrum = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin);
|
final cakeWalletElectrum =
|
||||||
|
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
|
||||||
await nodeSource.add(cakeWalletElectrum);
|
await nodeSource.add(cakeWalletElectrum);
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentBitcoinElectrumSererIdKey,
|
||||||
|
cakeWalletElectrum.key as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLitecoinElectrumServer == null) {
|
||||||
|
final cakeWalletElectrum =
|
||||||
|
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
|
||||||
|
await nodeSource.add(cakeWalletElectrum);
|
||||||
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentLitecoinElectrumSererIdKey,
|
||||||
|
cakeWalletElectrum.key as int);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> resetBitcoinElectrumServer(
|
||||||
Future<void> resetElectrumServer(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||||
final currentElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
final currentElectrumSeverId =
|
||||||
final oldElectrumServer = nodeSource.values.firstWhere((node) => node.uri.contains('electrumx.cakewallet.com'), orElse: () => null);
|
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||||
var cakeWalletNode = nodeSource.values.firstWhere((node) => node.uri == cakeWalletElectrumUri, orElse: () => null);
|
final oldElectrumServer = nodeSource.values.firstWhere(
|
||||||
|
(node) => node.uri.contains('electrumx.cakewallet.com'),
|
||||||
|
orElse: () => null);
|
||||||
|
var cakeWalletNode = nodeSource.values.firstWhere(
|
||||||
|
(node) => node.uri == cakeWalletBitcoinElectrumUri,
|
||||||
|
orElse: () => null);
|
||||||
|
|
||||||
if (cakeWalletNode == null) {
|
if (cakeWalletNode == null) {
|
||||||
cakeWalletNode = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin);
|
cakeWalletNode =
|
||||||
|
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
|
||||||
await nodeSource.add(cakeWalletNode);
|
await nodeSource.add(cakeWalletNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentElectrumSeverId == oldElectrumServer?.key) {
|
if (currentElectrumSeverId == oldElectrumServer?.key) {
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int);
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentBitcoinElectrumSererIdKey,
|
||||||
|
cakeWalletNode.key as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
await oldElectrumServer?.delete();
|
await oldElectrumServer?.delete();
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||||
import 'package:cake_wallet/entities/encrypt.dart';
|
import 'package:cake_wallet/entities/encrypt.dart';
|
||||||
|
@ -14,11 +17,6 @@ import 'package:cake_wallet/entities/wallet_info.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
const reservedNames = ["flutter_assets", "wallets", "db"];
|
const reservedNames = ["flutter_assets", "wallets", "db"];
|
||||||
|
|
|
@ -20,9 +20,9 @@ Future<List<Node>> loadDefaultNodes() async {
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Node>> loadElectrumServerList() async {
|
Future<List<Node>> loadBitcoinElectrumServerList() async {
|
||||||
final serverListRaw =
|
final serverListRaw =
|
||||||
await rootBundle.loadString('assets/electrum_server_list.yml');
|
await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
|
||||||
final serverList = loadYaml(serverListRaw) as YamlList;
|
final serverList = loadYaml(serverListRaw) as YamlList;
|
||||||
|
|
||||||
return serverList.map((dynamic raw) {
|
return serverList.map((dynamic raw) {
|
||||||
|
@ -37,10 +37,29 @@ Future<List<Node>> loadElectrumServerList() async {
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Node>> loadLitecoinElectrumServerList() async {
|
||||||
|
final serverListRaw =
|
||||||
|
await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
|
||||||
|
final serverList = loadYaml(serverListRaw) as YamlList;
|
||||||
|
|
||||||
|
return serverList.map((dynamic raw) {
|
||||||
|
if (raw is Map) {
|
||||||
|
final node = Node.fromMap(raw);
|
||||||
|
node?.type = WalletType.litecoin;
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
Future resetToDefault(Box<Node> nodeSource) async {
|
Future resetToDefault(Box<Node> nodeSource) async {
|
||||||
final moneroNodes = await loadDefaultNodes();
|
final moneroNodes = await loadDefaultNodes();
|
||||||
final bitcoinElectrumServerList = await loadElectrumServerList();
|
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
||||||
final nodes = moneroNodes + bitcoinElectrumServerList;
|
final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
|
||||||
|
final nodes =
|
||||||
|
moneroNodes + bitcoinElectrumServerList + litecoinElectrumServerList;
|
||||||
|
|
||||||
await nodeSource.clear();
|
await nodeSource.clear();
|
||||||
await nodeSource.addAll(nodes);
|
await nodeSource.addAll(nodes);
|
||||||
|
|
|
@ -3,6 +3,7 @@ class PreferencesKey {
|
||||||
static const currentWalletName = 'current_wallet_name';
|
static const currentWalletName = 'current_wallet_name';
|
||||||
static const currentNodeIdKey = 'current_node_id';
|
static const currentNodeIdKey = 'current_node_id';
|
||||||
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
|
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
|
||||||
|
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
|
||||||
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
||||||
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
||||||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||||
|
@ -14,7 +15,8 @@ class PreferencesKey {
|
||||||
static const displayActionListModeKey = 'display_list_mode';
|
static const displayActionListModeKey = 'display_list_mode';
|
||||||
static const currentPinLength = 'current_pin_length';
|
static const currentPinLength = 'current_pin_length';
|
||||||
static const currentLanguageCode = 'language_code';
|
static const currentLanguageCode = 'language_code';
|
||||||
static const currentDefaultSettingsMigrationVersion = 'current_default_settings_migration_version';
|
static const currentDefaultSettingsMigrationVersion =
|
||||||
|
'current_default_settings_migration_version';
|
||||||
static const moneroTransactionPriority = 'current_fee_priority_monero';
|
static const moneroTransactionPriority = 'current_fee_priority_monero';
|
||||||
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
|
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
|
||||||
}
|
}
|
|
@ -3,7 +3,11 @@ import 'package:hive/hive.dart';
|
||||||
|
|
||||||
part 'wallet_type.g.dart';
|
part 'wallet_type.g.dart';
|
||||||
|
|
||||||
const walletTypes = [WalletType.monero, WalletType.bitcoin];
|
const walletTypes = [
|
||||||
|
WalletType.monero,
|
||||||
|
WalletType.bitcoin,
|
||||||
|
WalletType.litecoin
|
||||||
|
];
|
||||||
const walletTypeTypeId = 5;
|
const walletTypeTypeId = 5;
|
||||||
|
|
||||||
@HiveType(typeId: walletTypeTypeId)
|
@HiveType(typeId: walletTypeTypeId)
|
||||||
|
@ -15,7 +19,10 @@ enum WalletType {
|
||||||
none,
|
none,
|
||||||
|
|
||||||
@HiveField(2)
|
@HiveField(2)
|
||||||
bitcoin
|
bitcoin,
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
litecoin
|
||||||
}
|
}
|
||||||
|
|
||||||
int serializeToInt(WalletType type) {
|
int serializeToInt(WalletType type) {
|
||||||
|
@ -24,6 +31,8 @@ int serializeToInt(WalletType type) {
|
||||||
return 0;
|
return 0;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return 1;
|
return 1;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return 2;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +44,8 @@ WalletType deserializeFromInt(int raw) {
|
||||||
return WalletType.monero;
|
return WalletType.monero;
|
||||||
case 1:
|
case 1:
|
||||||
return WalletType.bitcoin;
|
return WalletType.bitcoin;
|
||||||
|
case 2:
|
||||||
|
return WalletType.litecoin;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +57,8 @@ String walletTypeToString(WalletType type) {
|
||||||
return 'Monero';
|
return 'Monero';
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return 'Bitcoin';
|
return 'Bitcoin';
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return 'Litecoin';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -57,6 +70,8 @@ String walletTypeToDisplayName(WalletType type) {
|
||||||
return 'Monero';
|
return 'Monero';
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return 'Bitcoin (Electrum)';
|
return 'Bitcoin (Electrum)';
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return 'Litecoin';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -68,6 +83,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
|
||||||
return CryptoCurrency.xmr;
|
return CryptoCurrency.xmr;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return CryptoCurrency.btc;
|
return CryptoCurrency.btc;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return CryptoCurrency.ltc;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ Future<void> main() async {
|
||||||
exchangeTemplates: exchangeTemplates,
|
exchangeTemplates: exchangeTemplates,
|
||||||
transactionDescriptions: transactionDescriptions,
|
transactionDescriptions: transactionDescriptions,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
initialMigrationVersion: 13);
|
initialMigrationVersion: 15);
|
||||||
runApp(App());
|
runApp(App());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
runApp(MaterialApp(
|
runApp(MaterialApp(
|
||||||
|
@ -135,7 +135,7 @@ Future<void> initialSetup(
|
||||||
@required Box<ExchangeTemplate> exchangeTemplates,
|
@required Box<ExchangeTemplate> exchangeTemplates,
|
||||||
@required Box<TransactionDescription> transactionDescriptions,
|
@required Box<TransactionDescription> transactionDescriptions,
|
||||||
FlutterSecureStorage secureStorage,
|
FlutterSecureStorage secureStorage,
|
||||||
int initialMigrationVersion = 13}) async {
|
int initialMigrationVersion = 15}) async {
|
||||||
LanguageService.loadLocaleList();
|
LanguageService.loadLocaleList();
|
||||||
await defaultSettingsMigration(
|
await defaultSettingsMigration(
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
|
|
|
@ -49,13 +49,13 @@ abstract class MoneroAccountListBase with Store {
|
||||||
|
|
||||||
Future addAccount({String label}) async {
|
Future addAccount({String label}) async {
|
||||||
await account_list.addAccount(label: label);
|
await account_list.addAccount(label: label);
|
||||||
await update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future setLabelAccount({int accountIndex, String label}) async {
|
Future setLabelAccount({int accountIndex, String label}) async {
|
||||||
await account_list.setLabelForAccount(
|
await account_list.setLabelForAccount(
|
||||||
accountIndex: accountIndex, label: label);
|
accountIndex: accountIndex, label: label);
|
||||||
await update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh() {
|
void refresh() {
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_monero/transaction_history.dart'
|
|
||||||
as monero_transaction_history;
|
|
||||||
import 'package:cake_wallet/core/transaction_history.dart';
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||||
|
|
||||||
part 'monero_transaction_history.g.dart';
|
part 'monero_transaction_history.g.dart';
|
||||||
|
|
||||||
List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
|
|
||||||
monero_transaction_history
|
|
||||||
.getAllTransations()
|
|
||||||
.map((row) => MoneroTransactionInfo.fromRow(row))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
class MoneroTransactionHistory = MoneroTransactionHistoryBase
|
class MoneroTransactionHistory = MoneroTransactionHistoryBase
|
||||||
with _$MoneroTransactionHistory;
|
with _$MoneroTransactionHistory;
|
||||||
|
|
||||||
|
@ -23,30 +15,13 @@ abstract class MoneroTransactionHistoryBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
Future<void> save() async {}
|
||||||
monero_transaction_history.refreshTransactions();
|
|
||||||
return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
|
|
||||||
<String, MoneroTransactionInfo>{},
|
|
||||||
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
|
||||||
acc[tx.id] = tx;
|
|
||||||
return acc;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@action
|
void addOne(MoneroTransactionInfo transaction) =>
|
||||||
void updateAsync({void Function() onFinished}) {
|
transactions[transaction.id] = transaction;
|
||||||
fetchTransactionsAsync(
|
|
||||||
(transaction) => transactions[transaction.id] = transaction,
|
|
||||||
onFinished: onFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void fetchTransactionsAsync(
|
void addMany(Map<String, MoneroTransactionInfo> transactions) =>
|
||||||
void Function(MoneroTransactionInfo transaction) onTransactionLoaded,
|
this.transactions.addAll(transactions);
|
||||||
{void Function() onFinished}) async {
|
|
||||||
final transactions = await fetchTransactions();
|
|
||||||
transactions.values.forEach((tx) => onTransactionLoaded(tx));
|
|
||||||
onFinished?.call();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ class MoneroTransactionInfo extends TransactionInfo {
|
||||||
@override
|
@override
|
||||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
|
||||||
|
@override
|
||||||
String feeFormatted() =>
|
String feeFormatted() =>
|
||||||
'${formatAmount(moneroAmountToString(amount: fee))} XMR';
|
'${formatAmount(moneroAmountToString(amount: fee))} XMR';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||||
import 'package:cake_wallet/monero/monero_amount_format.dart';
|
import 'package:cake_wallet/monero/monero_amount_format.dart';
|
||||||
import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart';
|
import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart';
|
||||||
|
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_monero/transaction_history.dart'
|
||||||
|
as monero_transaction_history;
|
||||||
import 'package:cw_monero/wallet.dart';
|
import 'package:cw_monero/wallet.dart';
|
||||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||||
import 'package:cw_monero/transaction_history.dart' as transaction_history;
|
import 'package:cw_monero/transaction_history.dart' as transaction_history;
|
||||||
|
@ -30,19 +32,21 @@ const moneroBlockSize = 1000;
|
||||||
|
|
||||||
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
||||||
|
|
||||||
abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
MoneroWalletBase({String filename, WalletInfo walletInfo})
|
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
|
||||||
: transactionHistory = MoneroTransactionHistory(),
|
MoneroWalletBase({WalletInfo walletInfo})
|
||||||
accountList = MoneroAccountList(),
|
: accountList = MoneroAccountList(),
|
||||||
subaddressList = MoneroSubaddressList(),
|
subaddressList = MoneroSubaddressList(),
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
_filename = filename;
|
transactionHistory = MoneroTransactionHistory();
|
||||||
balance = MoneroBalance(
|
balance = MoneroBalance(
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
||||||
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
|
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
|
||||||
_lastAutosaveTimestamp = 0;
|
_lastAutosaveTimestamp = 0;
|
||||||
|
_lastSaveTimestamp = 0;
|
||||||
_isSavingAfterSync = false;
|
_isSavingAfterSync = false;
|
||||||
_isSavingAfterNewTransaction = false;
|
_isSavingAfterNewTransaction = false;
|
||||||
|
_isTransactionUpdating = false;
|
||||||
_onAccountChangeReaction = reaction((_) => account, (Account account) {
|
_onAccountChangeReaction = reaction((_) => account, (Account account) {
|
||||||
balance = MoneroBalance(
|
balance = MoneroBalance(
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||||
|
@ -56,9 +60,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
|
|
||||||
static const int _autoAfterSyncSaveInterval = 60000;
|
static const int _autoAfterSyncSaveInterval = 60000;
|
||||||
|
|
||||||
@override
|
|
||||||
final MoneroTransactionHistory transactionHistory;
|
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
Account account;
|
Account account;
|
||||||
|
|
||||||
|
@ -91,12 +92,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
|
|
||||||
final MoneroAccountList accountList;
|
final MoneroAccountList accountList;
|
||||||
|
|
||||||
String _filename;
|
|
||||||
SyncListener _listener;
|
SyncListener _listener;
|
||||||
ReactionDisposer _onAccountChangeReaction;
|
ReactionDisposer _onAccountChangeReaction;
|
||||||
int _lastAutosaveTimestamp;
|
int _lastAutosaveTimestamp;
|
||||||
bool _isSavingAfterSync;
|
bool _isSavingAfterSync;
|
||||||
bool _isSavingAfterNewTransaction;
|
bool _isSavingAfterNewTransaction;
|
||||||
|
bool _isTransactionUpdating;
|
||||||
|
int _lastSaveTimestamp;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
accountList.update();
|
accountList.update();
|
||||||
|
@ -111,7 +113,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
monero_wallet.getUnlockedBalance(accountIndex: account.id));
|
monero_wallet.getUnlockedBalance(accountIndex: account.id));
|
||||||
address = subaddress.address;
|
address = subaddress.address;
|
||||||
_setListeners();
|
_setListeners();
|
||||||
await transactionHistory.update();
|
await updateTransactions();
|
||||||
|
|
||||||
if (walletInfo.isRecovery) {
|
if (walletInfo.isRecovery) {
|
||||||
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
||||||
|
@ -238,6 +240,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
if (now - _lastSaveTimestamp < Duration(seconds: 10).inMilliseconds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastSaveTimestamp = now;
|
||||||
await monero_wallet.store();
|
await monero_wallet.store();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +273,40 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
await walletInfo.save();
|
await walletInfo.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
||||||
|
monero_transaction_history.refreshTransactions();
|
||||||
|
return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
|
||||||
|
<String, MoneroTransactionInfo>{},
|
||||||
|
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
||||||
|
acc[tx.id] = tx;
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateTransactions() async {
|
||||||
|
try {
|
||||||
|
if (_isTransactionUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTransactionUpdating = true;
|
||||||
|
final transactions = await fetchTransactions();
|
||||||
|
transactionHistory.addMany(transactions);
|
||||||
|
await transactionHistory.save();
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
|
||||||
|
monero_transaction_history
|
||||||
|
.getAllTransations()
|
||||||
|
.map((row) => MoneroTransactionInfo.fromRow(row))
|
||||||
|
.toList();
|
||||||
|
|
||||||
void _setListeners() {
|
void _setListeners() {
|
||||||
_listener?.stop();
|
_listener?.stop();
|
||||||
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
||||||
|
@ -315,7 +358,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _askForUpdateTransactionHistory() async =>
|
Future<void> _askForUpdateTransactionHistory() async =>
|
||||||
await transactionHistory.update();
|
await updateTransactions();
|
||||||
|
|
||||||
int _getFullBalance() =>
|
int _getFullBalance() =>
|
||||||
monero_wallet.getFullBalance(accountIndex: account.id);
|
monero_wallet.getFullBalance(accountIndex: account.id);
|
||||||
|
@ -389,11 +432,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onNewTransaction() {
|
void _onNewTransaction() async {
|
||||||
try {
|
try {
|
||||||
_askForUpdateTransactionHistory();
|
await _askForUpdateTransactionHistory();
|
||||||
_askForUpdateBalance();
|
_askForUpdateBalance();
|
||||||
Timer(Duration(seconds: 1), () => _afterNewTransactionSave());
|
await Future<void>.delayed(Duration(seconds: 1));
|
||||||
|
await _afterNewTransactionSave();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,18 +68,18 @@ class MoneroWalletService extends WalletService<
|
||||||
static bool walletFilesExist(String path) =>
|
static bool walletFilesExist(String path) =>
|
||||||
!File(path).existsSync() && !File('$path.keys').existsSync();
|
!File(path).existsSync() && !File('$path.keys').existsSync();
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.monero;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
|
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final path =
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await pathForWallet(name: credentials.name, type: WalletType.monero);
|
|
||||||
await monero_wallet_manager.createWallet(
|
await monero_wallet_manager.createWallet(
|
||||||
path: path,
|
path: path,
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
language: credentials.language);
|
language: credentials.language);
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
|
||||||
filename: monero_wallet.getFilename(),
|
|
||||||
walletInfo: credentials.walletInfo);
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -93,7 +93,7 @@ class MoneroWalletService extends WalletService<
|
||||||
@override
|
@override
|
||||||
Future<bool> isWalletExit(String name) async {
|
Future<bool> isWalletExit(String name) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
final path = await pathForWallet(name: name, type: getType());
|
||||||
return monero_wallet_manager.isWalletExist(path: path);
|
return monero_wallet_manager.isWalletExist(path: path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception for wallet list service.
|
// TODO: Implement Exception for wallet list service.
|
||||||
|
@ -105,7 +105,7 @@ class MoneroWalletService extends WalletService<
|
||||||
@override
|
@override
|
||||||
Future<MoneroWallet> openWallet(String name, String password) async {
|
Future<MoneroWallet> openWallet(String name, String password) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
final path = await pathForWallet(name: name, type: getType());
|
||||||
|
|
||||||
if (walletFilesExist(path)) {
|
if (walletFilesExist(path)) {
|
||||||
await repairOldAndroidWallet(name);
|
await repairOldAndroidWallet(name);
|
||||||
|
@ -114,10 +114,9 @@ class MoneroWalletService extends WalletService<
|
||||||
await monero_wallet_manager
|
await monero_wallet_manager
|
||||||
.openWalletAsync({'path': path, 'password': password});
|
.openWalletAsync({'path': path, 'password': password});
|
||||||
final walletInfo = walletInfoSource.values.firstWhere(
|
final walletInfo = walletInfoSource.values.firstWhere(
|
||||||
(info) => info.id == WalletBase.idFor(name, WalletType.monero),
|
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||||
orElse: () => null);
|
orElse: () => null);
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(walletInfo: walletInfo);
|
||||||
filename: monero_wallet.getFilename(), walletInfo: walletInfo);
|
|
||||||
final isValid = wallet.validate();
|
final isValid = wallet.validate();
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
|
@ -146,7 +145,7 @@ class MoneroWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> remove(String wallet) async {
|
Future<void> remove(String wallet) async {
|
||||||
final path = await pathForWalletDir(name: wallet, type: WalletType.monero);
|
final path = await pathForWalletDir(name: wallet, type: getType());
|
||||||
final file = Directory(path);
|
final file = Directory(path);
|
||||||
final isExist = file.existsSync();
|
final isExist = file.existsSync();
|
||||||
|
|
||||||
|
@ -159,8 +158,7 @@ class MoneroWalletService extends WalletService<
|
||||||
Future<MoneroWallet> restoreFromKeys(
|
Future<MoneroWallet> restoreFromKeys(
|
||||||
MoneroRestoreWalletFromKeysCredentials credentials) async {
|
MoneroRestoreWalletFromKeysCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final path =
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await pathForWallet(name: credentials.name, type: WalletType.monero);
|
|
||||||
await monero_wallet_manager.restoreFromKeys(
|
await monero_wallet_manager.restoreFromKeys(
|
||||||
path: path,
|
path: path,
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
|
@ -169,9 +167,7 @@ class MoneroWalletService extends WalletService<
|
||||||
address: credentials.address,
|
address: credentials.address,
|
||||||
viewKey: credentials.viewKey,
|
viewKey: credentials.viewKey,
|
||||||
spendKey: credentials.spendKey);
|
spendKey: credentials.spendKey);
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
|
||||||
filename: monero_wallet.getFilename(),
|
|
||||||
walletInfo: credentials.walletInfo);
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -186,16 +182,13 @@ class MoneroWalletService extends WalletService<
|
||||||
Future<MoneroWallet> restoreFromSeed(
|
Future<MoneroWallet> restoreFromSeed(
|
||||||
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final path =
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await pathForWallet(name: credentials.name, type: WalletType.monero);
|
|
||||||
await monero_wallet_manager.restoreFromSeed(
|
await monero_wallet_manager.restoreFromSeed(
|
||||||
path: path,
|
path: path,
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
seed: credentials.mnemonic,
|
seed: credentials.mnemonic,
|
||||||
restoreHeight: credentials.height);
|
restoreHeight: credentials.height);
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
|
||||||
filename: monero_wallet.getFilename(),
|
|
||||||
walletInfo: credentials.walletInfo);
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -221,7 +214,7 @@ class MoneroWalletService extends WalletService<
|
||||||
}
|
}
|
||||||
|
|
||||||
final newWalletDirPath =
|
final newWalletDirPath =
|
||||||
await pathForWalletDir(name: name, type: WalletType.monero);
|
await pathForWalletDir(name: name, type: getType());
|
||||||
|
|
||||||
dir.listSync().forEach((f) {
|
dir.listSync().forEach((f) {
|
||||||
final file = File(f.path);
|
final file = File(f.path);
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
|
@ -18,9 +20,11 @@ ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction;
|
||||||
void startCurrentWalletChangeReaction(AppStore appStore,
|
void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
||||||
_onCurrentWalletChangeReaction?.reaction?.dispose();
|
_onCurrentWalletChangeReaction?.reaction?.dispose();
|
||||||
|
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction?.dispose();
|
||||||
|
|
||||||
_onCurrentWalletChangeReaction =
|
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
|
||||||
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
|
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
wallet) async {
|
||||||
try {
|
try {
|
||||||
final node = settingsStore.getCurrentNode(wallet.type);
|
final node = settingsStore.getCurrentNode(wallet.type);
|
||||||
startWalletSyncStatusChangeReaction(wallet);
|
startWalletSyncStatusChangeReaction(wallet);
|
||||||
|
@ -45,7 +49,9 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
});
|
});
|
||||||
|
|
||||||
_onCurrentWalletChangeFiatRateUpdateReaction =
|
_onCurrentWalletChangeFiatRateUpdateReaction =
|
||||||
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
|
reaction((_) => appStore.wallet, (WalletBase<Balance,
|
||||||
|
TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
wallet) async {
|
||||||
try {
|
try {
|
||||||
fiatConversionStore.prices[wallet.currency] = 0;
|
fiatConversionStore.prices[wallet.currency] = 0;
|
||||||
fiatConversionStore.prices[wallet.currency] =
|
fiatConversionStore.prices[wallet.currency] =
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/entities/sync_status.dart';
|
import 'package:cake_wallet/entities/sync_status.dart';
|
||||||
|
|
||||||
ReactionDisposer _onWalletSyncStatusChangeReaction;
|
ReactionDisposer _onWalletSyncStatusChangeReaction;
|
||||||
|
|
||||||
void startWalletSyncStatusChangeReaction(WalletBase<Balance> wallet) {
|
void startWalletSyncStatusChangeReaction(
|
||||||
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||||
|
TransactionInfo>
|
||||||
|
wallet) {
|
||||||
_onWalletSyncStatusChangeReaction?.reaction?.dispose();
|
_onWalletSyncStatusChangeReaction?.reaction?.dispose();
|
||||||
_onWalletSyncStatusChangeReaction =
|
_onWalletSyncStatusChangeReaction =
|
||||||
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
||||||
|
|
|
@ -21,6 +21,7 @@ class MenuWidget extends StatefulWidget {
|
||||||
class MenuWidgetState extends State<MenuWidget> {
|
class MenuWidgetState extends State<MenuWidget> {
|
||||||
Image moneroIcon;
|
Image moneroIcon;
|
||||||
Image bitcoinIcon;
|
Image bitcoinIcon;
|
||||||
|
Image litecoinIcon;
|
||||||
final largeScreen = 731;
|
final largeScreen = 731;
|
||||||
|
|
||||||
double menuWidth;
|
double menuWidth;
|
||||||
|
@ -76,6 +77,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
color: Theme.of(context).accentTextTheme.overline.decorationColor);
|
color: Theme.of(context).accentTextTheme.overline.decorationColor);
|
||||||
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
|
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
|
||||||
color: Theme.of(context).accentTextTheme.overline.decorationColor);
|
color: Theme.of(context).accentTextTheme.overline.decorationColor);
|
||||||
|
litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
@ -238,6 +240,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
return moneroIcon;
|
return moneroIcon;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return bitcoinIcon;
|
return bitcoinIcon;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return litecoinIcon;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
||||||
final bitcoinIcon =
|
final bitcoinIcon =
|
||||||
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||||
|
final litecoinIcon =
|
||||||
|
Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||||
final walletTypeLightImage =
|
final walletTypeLightImage =
|
||||||
Image.asset('assets/images/wallet_type_light.png');
|
Image.asset('assets/images/wallet_type_light.png');
|
||||||
|
@ -69,7 +71,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
types = [WalletType.bitcoin, WalletType.monero];
|
types = [WalletType.bitcoin, WalletType.monero, WalletType.litecoin];
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +86,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
padding: EdgeInsets.only(left: 12, right: 12),
|
padding: EdgeInsets.only(left: 12, right: 12),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: aspectRatioImage,
|
aspectRatio: aspectRatioImage,
|
||||||
child:
|
child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
|
||||||
FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
|
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 48),
|
padding: EdgeInsets.only(top: 48),
|
||||||
|
@ -125,6 +126,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
return moneroIcon;
|
return moneroIcon;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return bitcoinIcon;
|
return bitcoinIcon;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return litecoinIcon;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
||||||
final bitcoinIcon =
|
final bitcoinIcon =
|
||||||
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||||
|
final litecoinIcon =
|
||||||
|
Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
final double tileHeight = 60;
|
final double tileHeight = 60;
|
||||||
Flushbar<void> _progressBar;
|
Flushbar<void> _progressBar;
|
||||||
|
@ -193,6 +195,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
return bitcoinIcon;
|
return bitcoinIcon;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return moneroIcon;
|
return moneroIcon;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return litecoinIcon;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||||
import 'package:cake_wallet/store/authentication_store.dart';
|
import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
@ -20,7 +22,8 @@ abstract class AppStoreBase with Store {
|
||||||
AuthenticationStore authenticationStore;
|
AuthenticationStore authenticationStore;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance> wallet;
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
wallet;
|
||||||
|
|
||||||
WalletListStore walletList;
|
WalletListStore walletList;
|
||||||
|
|
||||||
|
@ -29,7 +32,10 @@ abstract class AppStoreBase with Store {
|
||||||
NodeListStore nodeListStore;
|
NodeListStore nodeListStore;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void changeCurrentWallet(WalletBase wallet) {
|
void changeCurrentWallet(
|
||||||
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||||
|
TransactionInfo>
|
||||||
|
wallet) {
|
||||||
this.wallet?.close();
|
this.wallet?.close();
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,15 +205,19 @@ abstract class SettingsStoreBase with Store {
|
||||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
final bitcoinElectrumServerId = sharedPreferences
|
final bitcoinElectrumServerId = sharedPreferences
|
||||||
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||||
|
final litecoinElectrumServerId = sharedPreferences
|
||||||
|
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
|
||||||
final moneroNode = nodeSource.get(nodeId);
|
final moneroNode = nodeSource.get(nodeId);
|
||||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||||
|
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
return SettingsStore(
|
return SettingsStore(
|
||||||
sharedPreferences: sharedPreferences,
|
sharedPreferences: sharedPreferences,
|
||||||
nodes: {
|
nodes: {
|
||||||
WalletType.monero: moneroNode,
|
WalletType.monero: moneroNode,
|
||||||
WalletType.bitcoin: bitcoinElectrumServer
|
WalletType.bitcoin: bitcoinElectrumServer,
|
||||||
|
WalletType.litecoin: litecoinElectrumServer
|
||||||
},
|
},
|
||||||
appVersion: packageInfo.version,
|
appVersion: packageInfo.version,
|
||||||
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
||||||
|
@ -263,6 +267,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
await _sharedPreferences.setInt(
|
await _sharedPreferences.setInt(
|
||||||
PreferencesKey.currentBitcoinElectrumSererIdKey, node.key as int);
|
PreferencesKey.currentBitcoinElectrumSererIdKey, node.key as int);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
await _sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentLitecoinElectrumSererIdKey, node.key as int);
|
||||||
|
break;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
await _sharedPreferences.setInt(
|
await _sharedPreferences.setInt(
|
||||||
PreferencesKey.currentNodeIdKey, node.key as int);
|
PreferencesKey.currentNodeIdKey, node.key as int);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||||
|
@ -23,11 +25,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
@required this.settingsStore,
|
@required this.settingsStore,
|
||||||
@required this.fiatConvertationStore}) {
|
@required this.fiatConvertationStore}) {
|
||||||
isReversing = false;
|
isReversing = false;
|
||||||
|
|
||||||
wallet ??= appStore.wallet;
|
wallet ??= appStore.wallet;
|
||||||
|
|
||||||
_reaction = reaction((_) => appStore.wallet, _onWalletChange);
|
|
||||||
|
|
||||||
final _wallet = wallet;
|
final _wallet = wallet;
|
||||||
|
|
||||||
if (_wallet is MoneroWallet) {
|
if (_wallet is MoneroWallet) {
|
||||||
|
@ -38,6 +36,8 @@ abstract class BalanceViewModelBase with Store {
|
||||||
balance = _wallet.balance;
|
balance = _wallet.balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reaction((_) => appStore.wallet, _onWalletChange);
|
||||||
|
|
||||||
_onCurrentWalletChangeReaction =
|
_onCurrentWalletChangeReaction =
|
||||||
reaction<void>((_) => wallet.balance, (dynamic balance) {
|
reaction<void>((_) => wallet.balance, (dynamic balance) {
|
||||||
if (balance is Balance) {
|
if (balance is Balance) {
|
||||||
|
@ -59,7 +59,8 @@ abstract class BalanceViewModelBase with Store {
|
||||||
Balance balance;
|
Balance balance;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance> wallet;
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
wallet;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
double get price => fiatConvertationStore.prices[appStore.wallet.currency];
|
double get price => fiatConvertationStore.prices[appStore.wallet.currency];
|
||||||
|
@ -153,14 +154,14 @@ abstract class BalanceViewModelBase with Store {
|
||||||
CryptoCurrency get currency => appStore.wallet.currency;
|
CryptoCurrency get currency => appStore.wallet.currency;
|
||||||
|
|
||||||
ReactionDisposer _onCurrentWalletChangeReaction;
|
ReactionDisposer _onCurrentWalletChangeReaction;
|
||||||
ReactionDisposer _reaction;
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _onWalletChange(WalletBase<Balance> wallet) {
|
void _onWalletChange(
|
||||||
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||||
|
TransactionInfo>
|
||||||
|
wallet) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
|
|
||||||
balance = wallet.balance;
|
balance = wallet.balance;
|
||||||
|
|
||||||
_onCurrentWalletChangeReaction?.reaction?.dispose();
|
_onCurrentWalletChangeReaction?.reaction?.dispose();
|
||||||
_onCurrentWalletChangeReaction = reaction<Balance>(
|
_onCurrentWalletChangeReaction = reaction<Balance>(
|
||||||
(_) => wallet.balance, (Balance balance) => this.balance = balance);
|
(_) => wallet.balance, (Balance balance) => this.balance = balance);
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
import 'dart:convert';
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
import 'package:cake_wallet/entities/order.dart';
|
import 'package:cake_wallet/entities/order.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_history.dart';
|
|
||||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
|
||||||
import 'package:cake_wallet/monero/account.dart';
|
import 'package:cake_wallet/monero/account.dart';
|
||||||
import 'package:cake_wallet/monero/monero_balance.dart';
|
import 'package:cake_wallet/monero/monero_balance.dart';
|
||||||
import 'package:cake_wallet/monero/monero_transaction_history.dart';
|
|
||||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_direction.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||||
import 'package:cake_wallet/utils/mobx.dart';
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
|
@ -27,12 +17,8 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/action_list_display_mode.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/entities/sync_status.dart';
|
import 'package:cake_wallet/entities/sync_status.dart';
|
||||||
|
@ -43,8 +29,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
|
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
|
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart';
|
import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import 'package:convert/convert.dart';
|
|
||||||
|
|
||||||
part 'dashboard_view_model.g.dart';
|
part 'dashboard_view_model.g.dart';
|
||||||
|
|
||||||
|
@ -100,9 +84,6 @@ abstract class DashboardViewModelBase with Store {
|
||||||
name = appStore.wallet?.name;
|
name = appStore.wallet?.name;
|
||||||
wallet ??= appStore.wallet;
|
wallet ??= appStore.wallet;
|
||||||
type = wallet.type;
|
type = wallet.type;
|
||||||
|
|
||||||
_reaction = reaction((_) => appStore.wallet, _onWalletChange);
|
|
||||||
|
|
||||||
final _wallet = wallet;
|
final _wallet = wallet;
|
||||||
|
|
||||||
if (_wallet is MoneroWallet) {
|
if (_wallet is MoneroWallet) {
|
||||||
|
@ -133,6 +114,8 @@ abstract class DashboardViewModelBase with Store {
|
||||||
settingsStore: appStore.settingsStore)));
|
settingsStore: appStore.settingsStore)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reaction((_) => appStore.wallet, _onWalletChange);
|
||||||
|
|
||||||
connectMapToListWithTransform(
|
connectMapToListWithTransform(
|
||||||
appStore.wallet.transactionHistory.transactions,
|
appStore.wallet.transactionHistory.transactions,
|
||||||
transactions,
|
transactions,
|
||||||
|
@ -215,7 +198,8 @@ abstract class DashboardViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance> wallet;
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
wallet;
|
||||||
|
|
||||||
bool get hasRescan => wallet.type == WalletType.monero;
|
bool get hasRescan => wallet.type == WalletType.monero;
|
||||||
|
|
||||||
|
@ -241,8 +225,6 @@ abstract class DashboardViewModelBase with Store {
|
||||||
|
|
||||||
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
||||||
|
|
||||||
ReactionDisposer _reaction;
|
|
||||||
|
|
||||||
ReactionDisposer _onMoneroAccountChangeReaction;
|
ReactionDisposer _onMoneroAccountChangeReaction;
|
||||||
|
|
||||||
ReactionDisposer _onMoneroBalanceChangeReaction;
|
ReactionDisposer _onMoneroBalanceChangeReaction;
|
||||||
|
@ -253,7 +235,10 @@ abstract class DashboardViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _onWalletChange(WalletBase<Balance> wallet) {
|
void _onWalletChange(
|
||||||
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||||
|
TransactionInfo>
|
||||||
|
wallet) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
type = wallet.type;
|
type = wallet.type;
|
||||||
name = wallet.name;
|
name = wallet.name;
|
||||||
|
@ -319,6 +304,4 @@ abstract class DashboardViewModelBase with Store {
|
||||||
balanceViewModel: balanceViewModel,
|
balanceViewModel: balanceViewModel,
|
||||||
settingsStore: appStore.settingsStore)));
|
settingsStore: appStore.settingsStore)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/utils/mobx.dart';
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||||
import 'package:cake_wallet/monero/monero_amount_format.dart';
|
import 'package:cake_wallet/monero/monero_amount_format.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
@ -42,7 +42,7 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||||
transaction.changeFiatAmount(amount);
|
transaction.changeFiatAmount(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transaction is BitcoinTransactionInfo) {
|
if (transaction is ElectrumTransactionInfo) {
|
||||||
final amount = calculateFiatAmountRaw(
|
final amount = calculateFiatAmountRaw(
|
||||||
cryptoAmount: bitcoinAmountToDouble(amount: transaction.amount),
|
cryptoAmount: bitcoinAmountToDouble(amount: transaction.amount),
|
||||||
price: price);
|
price: price);
|
||||||
|
|
|
@ -33,9 +33,7 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
|
||||||
abstract class ExchangeViewModelBase with Store {
|
abstract class ExchangeViewModelBase with Store {
|
||||||
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
|
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
|
||||||
this.tradesStore, this._settingsStore) {
|
this.tradesStore, this._settingsStore) {
|
||||||
providerList = [
|
providerList = [ChangeNowExchangeProvider()];
|
||||||
ChangeNowExchangeProvider()
|
|
||||||
];
|
|
||||||
|
|
||||||
_initialPairBasedOnWallet();
|
_initialPairBasedOnWallet();
|
||||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||||
|
@ -57,9 +55,11 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
_onPairChange();
|
_onPairChange();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
receiveCurrencies = CryptoCurrency.all.where((cryptoCurrency) =>
|
receiveCurrencies = CryptoCurrency.all
|
||||||
|
.where((cryptoCurrency) =>
|
||||||
(cryptoCurrency != CryptoCurrency.xlm) &&
|
(cryptoCurrency != CryptoCurrency.xlm) &&
|
||||||
(cryptoCurrency != CryptoCurrency.xrp)).toList();
|
(cryptoCurrency != CryptoCurrency.xrp))
|
||||||
|
.toList();
|
||||||
_defineIsReceiveAmountEditable();
|
_defineIsReceiveAmountEditable();
|
||||||
isFixedRateMode = false;
|
isFixedRateMode = false;
|
||||||
isReceiveAmountEntered = false;
|
isReceiveAmountEntered = false;
|
||||||
|
@ -218,8 +218,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
limitsState = LimitsIsLoading();
|
limitsState = LimitsIsLoading();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
limits = await provider.fetchLimits(from: depositCurrency,
|
limits = await provider.fetchLimits(
|
||||||
to: receiveCurrency, isFixedRateMode: isFixedRateMode);
|
from: depositCurrency,
|
||||||
|
to: receiveCurrency,
|
||||||
|
isFixedRateMode: isFixedRateMode);
|
||||||
limitsState = LimitsLoadedSuccessfully(limits: limits);
|
limitsState = LimitsLoadedSuccessfully(limits: limits);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
limitsState = LimitsLoadedFailure(error: e.toString());
|
limitsState = LimitsLoadedFailure(error: e.toString());
|
||||||
|
@ -283,8 +285,8 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
tradeState = TradeIsCreating();
|
tradeState = TradeIsCreating();
|
||||||
final trade = await provider.createTrade(request: request,
|
final trade = await provider.createTrade(
|
||||||
isFixedRateMode: isFixedRateMode);
|
request: request, isFixedRateMode: isFixedRateMode);
|
||||||
trade.walletId = wallet.id;
|
trade.walletId = wallet.id;
|
||||||
tradesStore.setTrade(trade);
|
tradesStore.setTrade(trade);
|
||||||
await trades.add(trade);
|
await trades.add(trade);
|
||||||
|
@ -320,7 +322,8 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
void calculateDepositAllAmount() {
|
void calculateDepositAllAmount() {
|
||||||
if (wallet is BitcoinWallet) {
|
if (wallet is BitcoinWallet) {
|
||||||
final availableBalance = wallet.balance.available;
|
final availableBalance = wallet.balance.available;
|
||||||
final priority = _settingsStore.priority[wallet.type] as BitcoinTransactionPriority;
|
final priority =
|
||||||
|
_settingsStore.priority[wallet.type] as BitcoinTransactionPriority;
|
||||||
final fee = wallet.calculateEstimatedFee(priority, null);
|
final fee = wallet.calculateEstimatedFee(priority, null);
|
||||||
|
|
||||||
if (availableBalance < fee || availableBalance == 0) {
|
if (availableBalance < fee || availableBalance == 0) {
|
||||||
|
@ -402,6 +405,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
depositCurrency = CryptoCurrency.btc;
|
depositCurrency = CryptoCurrency.btc;
|
||||||
receiveCurrency = CryptoCurrency.xmr;
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
depositCurrency = CryptoCurrency.ltc;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,9 @@ abstract class NodeListViewModelBase with Store {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
node = getMoneroDefaultNode(nodes: _nodeSource);
|
node = getMoneroDefaultNode(nodes: _nodeSource);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
node = getLitecoinDefaultElectrumServer(nodes: _nodeSource);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||||
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
|
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
|
@ -90,6 +91,9 @@ abstract class SendViewModelBase with Store {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
_amount = stringDoubleToBitcoinAmount(_cryptoAmount);
|
_amount = stringDoubleToBitcoinAmount(_cryptoAmount);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
_amount = stringDoubleToBitcoinAmount(_cryptoAmount);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +106,7 @@ abstract class SendViewModelBase with Store {
|
||||||
final fee = _wallet.calculateEstimatedFee(
|
final fee = _wallet.calculateEstimatedFee(
|
||||||
_settingsStore.priority[_wallet.type], amount);
|
_settingsStore.priority[_wallet.type], amount);
|
||||||
|
|
||||||
if (_wallet is BitcoinWallet) {
|
if (_wallet is ElectrumWallet) {
|
||||||
return bitcoinAmountToDouble(amount: fee);
|
return bitcoinAmountToDouble(amount: fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,6 +308,12 @@ abstract class SendViewModelBase with Store {
|
||||||
final amount = !sendAll ? _amount : null;
|
final amount = !sendAll ? _amount : null;
|
||||||
final priority = _settingsStore.priority[_wallet.type];
|
final priority = _settingsStore.priority[_wallet.type];
|
||||||
|
|
||||||
|
return BitcoinTransactionCredentials(
|
||||||
|
address, amount, priority as BitcoinTransactionPriority);
|
||||||
|
case WalletType.litecoin:
|
||||||
|
final amount = !sendAll ? _amount : null;
|
||||||
|
final priority = _settingsStore.priority[_wallet.type];
|
||||||
|
|
||||||
return BitcoinTransactionCredentials(
|
return BitcoinTransactionCredentials(
|
||||||
address, amount, priority as BitcoinTransactionPriority);
|
address, amount, priority as BitcoinTransactionPriority);
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
|
@ -330,6 +340,9 @@ abstract class SendViewModelBase with Store {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
maximumFractionDigits = 8;
|
maximumFractionDigits = 8;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
maximumFractionDigits = 8;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -357,7 +370,7 @@ abstract class SendViewModelBase with Store {
|
||||||
final _priority = priority as TransactionPriority;
|
final _priority = priority as TransactionPriority;
|
||||||
final wallet = _wallet;
|
final wallet = _wallet;
|
||||||
|
|
||||||
if (wallet is BitcoinWallet) {
|
if (wallet is ElectrumWallet) {
|
||||||
final rate = wallet.feeRate(_priority);
|
final rate = wallet.feeRate(_priority);
|
||||||
return '${priority.toString()} ($rate sat/byte)';
|
return '${priority.toString()} ($rate sat/byte)';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/themes/theme_base.dart';
|
|
||||||
import 'package:cake_wallet/themes/theme_list.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:package_info/package_info.dart';
|
import 'package:package_info/package_info.dart';
|
||||||
|
@ -25,6 +18,15 @@ import 'package:cake_wallet/view_model/settings/regular_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
|
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/switcher_list_item.dart';
|
import 'package:cake_wallet/view_model/settings/switcher_list_item.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||||
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
|
import 'package:cake_wallet/themes/theme_list.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||||
|
|
||||||
part 'settings_view_model.g.dart';
|
part 'settings_view_model.g.dart';
|
||||||
|
|
||||||
|
@ -36,13 +38,19 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
|
||||||
return MoneroTransactionPriority.all;
|
return MoneroTransactionPriority.all;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return BitcoinTransactionPriority.all;
|
return BitcoinTransactionPriority.all;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return BitcoinTransactionPriority.all;
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SettingsViewModelBase with Store {
|
abstract class SettingsViewModelBase with Store {
|
||||||
SettingsViewModelBase(this._settingsStore, WalletBase<Balance> wallet)
|
SettingsViewModelBase(
|
||||||
|
this._settingsStore,
|
||||||
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||||
|
TransactionInfo>
|
||||||
|
wallet)
|
||||||
: itemHeaders = {},
|
: itemHeaders = {},
|
||||||
_walletType = wallet.type,
|
_walletType = wallet.type,
|
||||||
_biometricAuth = BiometricAuth() {
|
_biometricAuth = BiometricAuth() {
|
||||||
|
@ -77,7 +85,7 @@ abstract class SettingsViewModelBase with Store {
|
||||||
displayItem: (dynamic priority) {
|
displayItem: (dynamic priority) {
|
||||||
final _priority = priority as TransactionPriority;
|
final _priority = priority as TransactionPriority;
|
||||||
|
|
||||||
if (wallet is BitcoinWallet) {
|
if (wallet is ElectrumWallet) {
|
||||||
final rate = wallet.feeRate(_priority);
|
final rate = wallet.feeRate(_priority);
|
||||||
return '${priority.toString()} ($rate sat/byte)';
|
return '${priority.toString()} ($rate sat/byte)';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||||
|
@ -43,12 +44,6 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
value: tx.amountFormatted()),
|
value: tx.amountFormatted()),
|
||||||
StandartListItem(
|
StandartListItem(
|
||||||
title: S.current.transaction_details_fee, value: tx.feeFormatted()),
|
title: S.current.transaction_details_fee, value: tx.feeFormatted()),
|
||||||
BlockExplorerListItem(
|
|
||||||
title: "View in Block Explorer",
|
|
||||||
value: "View Transaction on XMRChain.net",
|
|
||||||
onTap: () {
|
|
||||||
launch("https://xmrchain.net/search?value=${tx.id}");
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (tx.key?.isNotEmpty ?? null) {
|
if (tx.key?.isNotEmpty ?? null) {
|
||||||
|
@ -59,7 +54,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
items.addAll(_items);
|
items.addAll(_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tx is BitcoinTransactionInfo) {
|
if (tx is ElectrumTransactionInfo) {
|
||||||
final _items = [
|
final _items = [
|
||||||
StandartListItem(
|
StandartListItem(
|
||||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||||
|
@ -78,12 +73,6 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
StandartListItem(
|
StandartListItem(
|
||||||
title: S.current.transaction_details_fee,
|
title: S.current.transaction_details_fee,
|
||||||
value: tx.feeFormatted()),
|
value: tx.feeFormatted()),
|
||||||
BlockExplorerListItem(
|
|
||||||
title: "View in Block Explorer",
|
|
||||||
value: "View Transaction on Blockchain.com",
|
|
||||||
onTap: () {
|
|
||||||
launch("https://www.blockchain.com/btc/tx/${tx.id}");
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
items.addAll(_items);
|
items.addAll(_items);
|
||||||
|
@ -101,6 +90,19 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WalletType type;
|
||||||
|
|
||||||
|
if (tx is MoneroTransactionInfo) {
|
||||||
|
type = WalletType.monero;
|
||||||
|
} else if (tx is ElectrumTransactionInfo) {
|
||||||
|
type = tx.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(BlockExplorerListItem(
|
||||||
|
title: "View in Block Explorer",
|
||||||
|
value: _explorerDescription(type),
|
||||||
|
onTap: () => launch(_explorerUrl(type, tx.id))));
|
||||||
|
|
||||||
final description = transactionDescriptionBox.values.firstWhere(
|
final description = transactionDescriptionBox.values.firstWhere(
|
||||||
(val) => val.id == transactionInfo.id,
|
(val) => val.id == transactionInfo.id,
|
||||||
orElse: () => TransactionDescription(id: transactionInfo.id));
|
orElse: () => TransactionDescription(id: transactionInfo.id));
|
||||||
|
@ -125,4 +127,30 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
|
|
||||||
final List<TransactionDetailsListItem> items;
|
final List<TransactionDetailsListItem> items;
|
||||||
bool showRecipientAddress;
|
bool showRecipientAddress;
|
||||||
|
|
||||||
|
String _explorerUrl(WalletType type, String txId) {
|
||||||
|
switch (type) {
|
||||||
|
case WalletType.monero:
|
||||||
|
return 'https://xmrchain.net/search?value=${txId}';
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
return 'https://www.blockchain.com/btc/tx/${txId}';
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return 'https://blockchair.com/litecoin/transaction/${txId}';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _explorerDescription(WalletType type) {
|
||||||
|
switch (type) {
|
||||||
|
case WalletType.monero:
|
||||||
|
return 'View Transaction on XMRChain.net';
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
return 'View Transaction on Blockchain.com';
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return 'View Transaction on Blockchair.com';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
||||||
Future<void> _createNew() async {
|
Future<void> _createNew() async {
|
||||||
final wallet = _wallet;
|
final wallet = _wallet;
|
||||||
|
|
||||||
if (wallet is BitcoinWallet) {
|
if (wallet is ElectrumWallet) {
|
||||||
await wallet.generateNewAddress();
|
await wallet.generateNewAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -56,12 +58,13 @@ class BitcoinURI extends PaymentURI {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class WalletAddressListViewModelBase with Store {
|
abstract class WalletAddressListViewModelBase with Store {
|
||||||
WalletAddressListViewModelBase(
|
WalletAddressListViewModelBase({@required AppStore appStore}) {
|
||||||
{@required AppStore appStore}) {
|
|
||||||
_appStore = appStore;
|
_appStore = appStore;
|
||||||
_wallet = _appStore.wallet;
|
_wallet = _appStore.wallet;
|
||||||
hasAccounts = _wallet?.type == WalletType.monero;
|
hasAccounts = _wallet?.type == WalletType.monero;
|
||||||
_onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<Balance> wallet) {
|
_onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<
|
||||||
|
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
wallet) {
|
||||||
_wallet = wallet;
|
_wallet = wallet;
|
||||||
hasAccounts = _wallet.type == WalletType.monero;
|
hasAccounts = _wallet.type == WalletType.monero;
|
||||||
});
|
});
|
||||||
|
@ -119,9 +122,7 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
final isPrimary = addr == primaryAddress;
|
final isPrimary = addr == primaryAddress;
|
||||||
|
|
||||||
return WalletAddressListItem(
|
return WalletAddressListItem(
|
||||||
isPrimary: isPrimary,
|
isPrimary: isPrimary, name: null, address: addr.address);
|
||||||
name: null,
|
|
||||||
address: addr.address);
|
|
||||||
});
|
});
|
||||||
addressList.addAll(bitcoinAddresses);
|
addressList.addAll(bitcoinAddresses);
|
||||||
}
|
}
|
||||||
|
@ -147,7 +148,8 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
bool get hasAddressList => _wallet.type == WalletType.monero;
|
bool get hasAddressList => _wallet.type == WalletType.monero;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance> _wallet;
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
_wallet;
|
||||||
|
|
||||||
List<ListItem> _baseItems;
|
List<ListItem> _baseItems;
|
||||||
|
|
||||||
|
@ -155,7 +157,6 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
|
|
||||||
ReactionDisposer _onWalletChangeReaction;
|
ReactionDisposer _onWalletChangeReaction;
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setAddress(WalletAddressListItem address) =>
|
void setAddress(WalletAddressListItem address) =>
|
||||||
_wallet.address = address.address;
|
_wallet.address = address.address;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||||
|
|
||||||
part 'wallet_keys_view_model.g.dart';
|
part 'wallet_keys_view_model.g.dart';
|
||||||
|
@ -24,12 +24,11 @@ abstract class WalletKeysViewModelBase with Store {
|
||||||
title: S.current.view_key_public, value: keys.publicViewKey),
|
title: S.current.view_key_public, value: keys.publicViewKey),
|
||||||
StandartListItem(
|
StandartListItem(
|
||||||
title: S.current.view_key_private, value: keys.privateViewKey),
|
title: S.current.view_key_private, value: keys.privateViewKey),
|
||||||
StandartListItem(
|
StandartListItem(title: S.current.wallet_seed, value: wallet.seed),
|
||||||
title: S.current.wallet_seed, value: wallet.seed),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wallet is BitcoinWallet) {
|
if (wallet is ElectrumWallet) {
|
||||||
final keys = wallet.keys;
|
final keys = wallet.keys;
|
||||||
|
|
||||||
items.addAll([
|
items.addAll([
|
||||||
|
|
|
@ -37,6 +37,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||||
name: name, language: options as String);
|
name: name, language: options as String);
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return BitcoinNewWalletCredentials(name: name);
|
return BitcoinNewWalletCredentials(name: name);
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return BitcoinNewWalletCredentials(name: name);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
18
pubspec.lock
18
pubspec.lock
|
@ -67,10 +67,12 @@ packages:
|
||||||
bech32:
|
bech32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: bech32
|
path: "."
|
||||||
url: "https://pub.dartlang.org"
|
ref: cake
|
||||||
source: hosted
|
resolved-ref: "02fef082f20af13de00b4e64efb93a2c1e5e1cf2"
|
||||||
version: "0.1.2"
|
url: "git@github.com:cake-tech/bech32.git"
|
||||||
|
source: git
|
||||||
|
version: "0.2.0"
|
||||||
bip32:
|
bip32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -88,9 +90,11 @@ packages:
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: bitcoin_flutter
|
path: "."
|
||||||
url: "https://pub.dartlang.org"
|
ref: cake
|
||||||
source: hosted
|
resolved-ref: b3ab2926c665f0e68b74a4a5f31059f7fcd817b7
|
||||||
|
url: "git@github.com:cake-tech/bitcoin_flutter.git"
|
||||||
|
source: git
|
||||||
version: "2.0.2"
|
version: "2.0.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
|
|
|
@ -12,6 +12,7 @@ description: Cake Wallet.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 4.1.4+43
|
version: 4.1.4+43
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.7.0 <3.0.0"
|
sdk: ">=2.7.0 <3.0.0"
|
||||||
|
@ -65,7 +66,10 @@ dependencies:
|
||||||
crypto: ^2.1.5
|
crypto: ^2.1.5
|
||||||
password: ^1.0.0
|
password: ^1.0.0
|
||||||
basic_utils: ^2.0.3
|
basic_utils: ^2.0.3
|
||||||
bitcoin_flutter: ^2.0.0
|
bitcoin_flutter:
|
||||||
|
git:
|
||||||
|
url: git@github.com:cake-tech/bitcoin_flutter.git
|
||||||
|
ref: cake
|
||||||
get_it: ^6.0.0
|
get_it: ^6.0.0
|
||||||
connectivity: ^3.0.3
|
connectivity: ^3.0.3
|
||||||
keyboard_actions: ^3.3.0
|
keyboard_actions: ^3.3.0
|
||||||
|
@ -105,7 +109,8 @@ flutter:
|
||||||
assets:
|
assets:
|
||||||
- assets/images/
|
- assets/images/
|
||||||
- assets/node_list.yml
|
- assets/node_list.yml
|
||||||
- assets/electrum_server_list.yml
|
- assets/bitcoin_electrum_server_list.yml
|
||||||
|
- assets/litecoin_electrum_server_list.yml
|
||||||
- assets/text/
|
- assets/text/
|
||||||
- assets/faq/
|
- assets/faq/
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue