mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
BTC
This commit is contained in:
parent
2673ecd45e
commit
1d793ab284
44 changed files with 1503 additions and 225 deletions
17
android/.project
Normal file
17
android/.project
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>android</name>
|
||||||
|
<comment>Project android_ created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
2
android/.settings/org.eclipse.buildship.core.prefs
Normal file
2
android/.settings/org.eclipse.buildship.core.prefs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
connection.project.dir=
|
||||||
|
eclipse.preferences.version=1
|
6
android/app/.classpath
Normal file
6
android/app/.classpath
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
</classpath>
|
23
android/app/.project
Normal file
23
android/app/.project
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>app</name>
|
||||||
|
<comment>Project app created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
2
android/app/.settings/org.eclipse.buildship.core.prefs
Normal file
2
android/app/.settings/org.eclipse.buildship.core.prefs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
connection.project.dir=..
|
||||||
|
eclipse.preferences.version=1
|
6
cw_monero/android/.classpath
Normal file
6
cw_monero/android/.classpath
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
</classpath>
|
23
cw_monero/android/.project
Normal file
23
cw_monero/android/.project
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>cw_monero</name>
|
||||||
|
<comment>Project android created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
13
cw_monero/android/.settings/org.eclipse.buildship.core.prefs
Normal file
13
cw_monero/android/.settings/org.eclipse.buildship.core.prefs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
arguments=
|
||||||
|
auto.sync=false
|
||||||
|
build.scans.enabled=false
|
||||||
|
connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0-20191016123526+0000))
|
||||||
|
connection.project.dir=../../android
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
gradle.user.home=
|
||||||
|
java.home=
|
||||||
|
jvm.arguments=
|
||||||
|
offline.mode=false
|
||||||
|
override.workspace.settings=true
|
||||||
|
show.console.view=true
|
||||||
|
show.executions.view=true
|
|
@ -48,4 +48,9 @@ A new flutter plugin project.
|
||||||
sodium.libraries = 'sodium'
|
sodium.libraries = 'sodium'
|
||||||
sodium.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/libs/sodium/include/**" }
|
sodium.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/libs/sodium/include/**" }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
s.subspec 'lmdb' do |lmdb|
|
||||||
|
lmdb.vendored_libraries = 'External/ios/libs/lmdb/liblmdb.a'
|
||||||
|
lmdb.libraries = 'lmdb'
|
||||||
|
end
|
||||||
end
|
end
|
|
@ -4,24 +4,33 @@ PODS:
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- cw_monero (0.0.2):
|
- cw_monero (0.0.2):
|
||||||
- cw_monero/Boost (= 0.0.2)
|
- cw_monero/Boost (= 0.0.2)
|
||||||
|
- cw_monero/lmdb (= 0.0.2)
|
||||||
- cw_monero/Monero (= 0.0.2)
|
- cw_monero/Monero (= 0.0.2)
|
||||||
- cw_monero/OpenSSL (= 0.0.2)
|
- cw_monero/OpenSSL (= 0.0.2)
|
||||||
- cw_monero/Sodium (= 0.0.2)
|
- cw_monero/Sodium (= 0.0.2)
|
||||||
- Flutter
|
- Flutter
|
||||||
- cw_monero/Boost (0.0.2):
|
- cw_monero/Boost (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- cw_monero/lmdb (0.0.2):
|
||||||
|
- Flutter
|
||||||
- cw_monero/Monero (0.0.2):
|
- cw_monero/Monero (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- cw_monero/OpenSSL (0.0.2):
|
- cw_monero/OpenSSL (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- cw_monero/Sodium (0.0.2):
|
- cw_monero/Sodium (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- devicelocale (0.0.1):
|
||||||
|
- Flutter
|
||||||
- esys_flutter_share (0.0.1):
|
- esys_flutter_share (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_secure_storage (3.3.1):
|
- flutter_secure_storage (3.3.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- local_auth (0.0.1):
|
||||||
|
- Flutter
|
||||||
- MTBBarcodeScanner (5.0.11)
|
- MTBBarcodeScanner (5.0.11)
|
||||||
|
- package_info (0.0.1):
|
||||||
|
- Flutter
|
||||||
- path_provider (0.0.1):
|
- path_provider (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- share (0.5.2):
|
- share (0.5.2):
|
||||||
|
@ -36,9 +45,12 @@ PODS:
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- barcode_scan (from `.symlinks/plugins/barcode_scan/ios`)
|
- barcode_scan (from `.symlinks/plugins/barcode_scan/ios`)
|
||||||
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
|
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
|
||||||
|
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
|
||||||
- esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`)
|
- esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`)
|
||||||
- Flutter (from `.symlinks/flutter/ios-release`)
|
- Flutter (from `.symlinks/flutter/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
- local_auth (from `.symlinks/plugins/local_auth/ios`)
|
||||||
|
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||||
- share (from `.symlinks/plugins/share/ios`)
|
- share (from `.symlinks/plugins/share/ios`)
|
||||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||||
|
@ -54,12 +66,18 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/barcode_scan/ios"
|
:path: ".symlinks/plugins/barcode_scan/ios"
|
||||||
cw_monero:
|
cw_monero:
|
||||||
:path: ".symlinks/plugins/cw_monero/ios"
|
:path: ".symlinks/plugins/cw_monero/ios"
|
||||||
|
devicelocale:
|
||||||
|
:path: ".symlinks/plugins/devicelocale/ios"
|
||||||
esys_flutter_share:
|
esys_flutter_share:
|
||||||
:path: ".symlinks/plugins/esys_flutter_share/ios"
|
:path: ".symlinks/plugins/esys_flutter_share/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: ".symlinks/flutter/ios-release"
|
:path: ".symlinks/flutter/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
local_auth:
|
||||||
|
:path: ".symlinks/plugins/local_auth/ios"
|
||||||
|
package_info:
|
||||||
|
:path: ".symlinks/plugins/package_info/ios"
|
||||||
path_provider:
|
path_provider:
|
||||||
:path: ".symlinks/plugins/path_provider/ios"
|
:path: ".symlinks/plugins/path_provider/ios"
|
||||||
share:
|
share:
|
||||||
|
@ -73,11 +91,14 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
barcode_scan: 33f586d02270046fc6559135038b34b5754eaa4f
|
barcode_scan: 33f586d02270046fc6559135038b34b5754eaa4f
|
||||||
cw_monero: ca40a57b99f7753ed93d3b50af671a637277eb89
|
cw_monero: 2e1f79929880cc2293b5bc1b25e28152e4d84649
|
||||||
|
devicelocale: feebbe5e7a30adb8c4f83185de1b50ff19b44f00
|
||||||
esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4
|
esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4
|
||||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
||||||
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
||||||
|
local_auth: 2571c49920ae469f46d5557435fad8fa473a5e88
|
||||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||||
|
package_info: 48b108e75b8802c2d5e126f208ef540561c98aef
|
||||||
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
|
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
|
||||||
share: bae0a282aab4483288913fc4dc0b935d4b491f2e
|
share: bae0a282aab4483288913fc4dc0b935d4b491f2e
|
||||||
shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01
|
shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01
|
||||||
|
@ -86,4 +107,4 @@ SPEC CHECKSUMS:
|
||||||
|
|
||||||
PODFILE CHECKSUM: f1916a43bb28badbd408be80e8e4b8652a74e93e
|
PODFILE CHECKSUM: f1916a43bb28badbd408be80e8e4b8652a74e93e
|
||||||
|
|
||||||
COCOAPODS: 1.8.4
|
COCOAPODS: 1.9.1
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
21
lib/bitcoin/api.dart
Normal file
21
lib/bitcoin/api.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
|
||||||
|
const blockchainInfoBaseURI = 'https://blockchain.info';
|
||||||
|
const multiAddressURI = '$blockchainInfoBaseURI/multiaddr';
|
||||||
|
|
||||||
|
Future<List<String>> fetchAllAddresses({String xpub}) async {
|
||||||
|
final uri = '$multiAddressURI?active=$xpub';
|
||||||
|
final response = await get(uri);
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
print(responseJSON);
|
||||||
|
|
||||||
|
return (responseJSON['addresses'] as List<dynamic>).map((dynamic row) {
|
||||||
|
if (row is Map<String, Object>) {
|
||||||
|
return row['address'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}).toList();
|
||||||
|
}
|
13
lib/bitcoin/bitcoin_amount_format.dart
Normal file
13
lib/bitcoin/bitcoin_amount_format.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/crypto_amount_format.dart';
|
||||||
|
|
||||||
|
const bitcoinAmountLength = 8;
|
||||||
|
const bitcoinAmountDivider = 100000000;
|
||||||
|
final bitcoinAmountFormat = NumberFormat()
|
||||||
|
..maximumFractionDigits = bitcoinAmountLength
|
||||||
|
..minimumFractionDigits = 1;
|
||||||
|
|
||||||
|
String bitcoinAmountToString({int amount}) =>
|
||||||
|
bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
|
||||||
|
|
||||||
|
double bitcoinAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
|
14
lib/bitcoin/bitcoin_balance.dart
Normal file
14
lib/bitcoin/bitcoin_balance.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/balance.dart';
|
||||||
|
|
||||||
|
class BitcoinBalance extends Balance {
|
||||||
|
BitcoinBalance({@required this.confirmed, @required this.unconfirmed});
|
||||||
|
|
||||||
|
final int confirmed;
|
||||||
|
final int unconfirmed;
|
||||||
|
int get total => confirmed + unconfirmed;
|
||||||
|
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
|
||||||
|
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
|
||||||
|
String get totalFormatted => bitcoinAmountToString(amount: total);
|
||||||
|
}
|
145
lib/bitcoin/bitcoin_transaction_history.dart
Normal file
145
lib/bitcoin/bitcoin_transaction_history.dart
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/file.dart';
|
||||||
|
|
||||||
|
class BitcoinTransactionHistory extends TransactionHistory {
|
||||||
|
BitcoinTransactionHistory(
|
||||||
|
{@required this.eclient,
|
||||||
|
@required this.path,
|
||||||
|
@required String password,
|
||||||
|
@required this.wallet})
|
||||||
|
: _transactions = BehaviorSubject<List<TransactionInfo>>.seeded([]),
|
||||||
|
_password = password,
|
||||||
|
_height = 0;
|
||||||
|
|
||||||
|
final BitcoinWallet wallet;
|
||||||
|
final ElectrumClient eclient;
|
||||||
|
final String path;
|
||||||
|
final String _password;
|
||||||
|
int _height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Observable<List<TransactionInfo>> get transactions => _transactions.stream;
|
||||||
|
List<TransactionInfo> get transactionsAll => _transactions.value;
|
||||||
|
final BehaviorSubject<List<TransactionInfo>> _transactions;
|
||||||
|
bool _isUpdating = false;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
final info = await _read();
|
||||||
|
_height = (info['height'] as int) ?? _height;
|
||||||
|
_transactions.value = info['transactions'] as List<TransactionInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TransactionInfo>> getAll() async => _transactions.value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future update() async {
|
||||||
|
if (_isUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isUpdating = true;
|
||||||
|
final newTransasctions = await fetchTransactions();
|
||||||
|
_transactions.value = _transactions.value + newTransasctions;
|
||||||
|
_updateHeight();
|
||||||
|
await save();
|
||||||
|
_isUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
_isUpdating = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, Object>> fetchTransactionInfo(
|
||||||
|
{@required String hash, @required int height}) async {
|
||||||
|
final rawFetching = eclient.getTransactionRaw(hash: hash);
|
||||||
|
final headerFetching = eclient.getHeader(height: height);
|
||||||
|
final result = await Future.wait([rawFetching, headerFetching]);
|
||||||
|
final raw = result.first as String;
|
||||||
|
final header = result[1] as Map<String, Object>;
|
||||||
|
|
||||||
|
return {'raw': raw, 'header': header};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
||||||
|
final addresses = wallet.getAddresses();
|
||||||
|
final histories =
|
||||||
|
addresses.map((address) => eclient.getHistory(address: address));
|
||||||
|
final _historiesWithDetails = await Future.wait(histories)
|
||||||
|
.then((histories) => histories
|
||||||
|
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
|
||||||
|
.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
|
||||||
|
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
||||||
|
info['raw'] as String, info['header'] as Map<String, Object>,
|
||||||
|
addresses: addresses))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
|
||||||
|
final txs = await getAll()
|
||||||
|
..addAll(transactions);
|
||||||
|
await writeData(
|
||||||
|
path: path,
|
||||||
|
password: _password,
|
||||||
|
data: json
|
||||||
|
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
||||||
|
final txs = await getAll()
|
||||||
|
..add(tx);
|
||||||
|
await writeData(
|
||||||
|
path: path,
|
||||||
|
password: _password,
|
||||||
|
data: json
|
||||||
|
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async => writeData(
|
||||||
|
path: path,
|
||||||
|
password: _password,
|
||||||
|
data: json
|
||||||
|
.encode({'height': _height, 'transactions': _transactions.value}));
|
||||||
|
|
||||||
|
Future<Map<String, Object>> _read() async {
|
||||||
|
try {
|
||||||
|
final content = await read(path: path, password: _password);
|
||||||
|
final jsoned = json.decode(content) as Map<String, Object>;
|
||||||
|
final height = jsoned['height'] as int;
|
||||||
|
final transactions = (jsoned['transactions'] as List<dynamic>)
|
||||||
|
.map((dynamic row) {
|
||||||
|
if (row is Map<String, Object>) {
|
||||||
|
return BitcoinTransactionInfo.fromJson(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.where((el) => el != null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return {'transactions': transactions, 'height': height};
|
||||||
|
} catch (_) {
|
||||||
|
return {'transactions': List<TransactionInfo>(), 'height': 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateHeight() {
|
||||||
|
final int newHeight = _transactions.value
|
||||||
|
.fold(0, (acc, val) => val.height > acc ? val.height : acc);
|
||||||
|
_height = newHeight > _height ? newHeight : _height;
|
||||||
|
}
|
||||||
|
}
|
81
lib/bitcoin/bitcoin_transaction_info.dart
Normal file
81
lib/bitcoin/bitcoin_transaction_info.dart
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
|
|
||||||
|
class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
|
BitcoinTransactionInfo(
|
||||||
|
{@required this.id,
|
||||||
|
@required int height,
|
||||||
|
@required int amount,
|
||||||
|
@required TransactionDirection direction,
|
||||||
|
@required bool isPending,
|
||||||
|
@required DateTime date}) {
|
||||||
|
this.height = height;
|
||||||
|
this.amount = amount;
|
||||||
|
this.direction = direction;
|
||||||
|
this.date = date;
|
||||||
|
this.isPending = isPending;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BitcoinTransactionInfo.fromHexAndHeader(
|
||||||
|
String hex, Map<String, Object> header,
|
||||||
|
{List<String> addresses}) {
|
||||||
|
final tx = bitcoin.Transaction.fromHex(hex);
|
||||||
|
var exist = false;
|
||||||
|
var amount = 0;
|
||||||
|
|
||||||
|
tx.outs.forEach((out) {
|
||||||
|
try {
|
||||||
|
final p2pkh = bitcoin.P2PKH(
|
||||||
|
data: PaymentData(output: out.script), network: bitcoin.bitcoin);
|
||||||
|
exist = addresses.contains(p2pkh.data.address);
|
||||||
|
|
||||||
|
if (exist) {
|
||||||
|
amount += out.value;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: Get transaction is pending
|
||||||
|
return BitcoinTransactionInfo(
|
||||||
|
id: tx.getId(),
|
||||||
|
height: header['block_height'] as int,
|
||||||
|
isPending: false,
|
||||||
|
direction: TransactionDirection.incoming,
|
||||||
|
amount: amount,
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
(header['timestamp'] as int) * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BitcoinTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||||
|
return BitcoinTransactionInfo(
|
||||||
|
id: data['id'] as String,
|
||||||
|
height: data['height'] as int,
|
||||||
|
amount: data['amount'] as int,
|
||||||
|
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||||
|
isPending: data['isPending'] as bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String amountFormatted() => bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fiatAmount() => '';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final m = Map<String, dynamic>();
|
||||||
|
m['id'] = id;
|
||||||
|
m['height'] = height;
|
||||||
|
m['amount'] = amount;
|
||||||
|
m['direction'] = direction.index;
|
||||||
|
m['date'] = date.millisecondsSinceEpoch;
|
||||||
|
m['isPending'] = isPending;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
274
lib/bitcoin/bitcoin_wallet.dart
Normal file
274
lib/bitcoin/bitcoin_wallet.dart
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||||
|
import 'package:cake_wallet/bitcoin/file.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/pending_transaction.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_creation_credentials.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
|
||||||
|
class BitcoinWallet extends Wallet {
|
||||||
|
BitcoinWallet(
|
||||||
|
{@required this.hdwallet,
|
||||||
|
@required this.eclient,
|
||||||
|
@required this.path,
|
||||||
|
@required String password,
|
||||||
|
int accountIndex = 0,
|
||||||
|
this.mnemonic})
|
||||||
|
: _accountIndex = accountIndex,
|
||||||
|
_password = password,
|
||||||
|
_syncStatus = BehaviorSubject<SyncStatus>(),
|
||||||
|
_onBalanceChange = BehaviorSubject<BitcoinBalance>(),
|
||||||
|
_onAddressChange = BehaviorSubject<String>(),
|
||||||
|
_onNameChange = BehaviorSubject<String>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Observable<BitcoinBalance> get onBalanceChange => _onBalanceChange.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Observable<SyncStatus> get syncStatus => _syncStatus.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => path.split('/').last ?? '';
|
||||||
|
@override
|
||||||
|
String get address => hdwallet.address;
|
||||||
|
String get xpub => hdwallet.base58;
|
||||||
|
|
||||||
|
final String path;
|
||||||
|
final bitcoin.HDWallet hdwallet;
|
||||||
|
final ElectrumClient eclient;
|
||||||
|
final String mnemonic;
|
||||||
|
BitcoinTransactionHistory history;
|
||||||
|
|
||||||
|
final BehaviorSubject<SyncStatus> _syncStatus;
|
||||||
|
final BehaviorSubject<BitcoinBalance> _onBalanceChange;
|
||||||
|
final BehaviorSubject<String> _onAddressChange;
|
||||||
|
final BehaviorSubject<String> _onNameChange;
|
||||||
|
BehaviorSubject<Object> _addressUpdatesSubject;
|
||||||
|
StreamSubscription<Object> _addressUpdatesSubscription;
|
||||||
|
final String _password;
|
||||||
|
int _accountIndex;
|
||||||
|
|
||||||
|
static Future<BitcoinWallet> load(
|
||||||
|
{@required String name, @required 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 jsoned = json.decode(walletJSONRaw) as Map<String, Object>;
|
||||||
|
final mnemonic = jsoned['mnemonic'] as String;
|
||||||
|
final accountIndex =
|
||||||
|
(jsoned['account_index'] == "null" || jsoned['account_index'] == null)
|
||||||
|
? 0
|
||||||
|
: int.parse(jsoned['account_index'] as String);
|
||||||
|
|
||||||
|
return await build(
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
password: password,
|
||||||
|
name: name,
|
||||||
|
accountIndex: accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<BitcoinWallet> build(
|
||||||
|
{@required String mnemonic,
|
||||||
|
@required String password,
|
||||||
|
@required String name,
|
||||||
|
int accountIndex = 0}) async {
|
||||||
|
final hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
||||||
|
network: bitcoin.bitcoin);
|
||||||
|
final walletDirPath =
|
||||||
|
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
||||||
|
final walletPath = '$walletDirPath/$name';
|
||||||
|
final historyPath = '$walletDirPath/transactions.json';
|
||||||
|
final eclient = ElectrumClient();
|
||||||
|
final wallet = BitcoinWallet(
|
||||||
|
hdwallet: hd,
|
||||||
|
eclient: eclient,
|
||||||
|
path: walletPath,
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
password: password,
|
||||||
|
accountIndex: accountIndex);
|
||||||
|
final history = BitcoinTransactionHistory(
|
||||||
|
eclient: eclient,
|
||||||
|
path: historyPath,
|
||||||
|
password: password,
|
||||||
|
wallet: wallet);
|
||||||
|
wallet.history = history;
|
||||||
|
await history.init();
|
||||||
|
|
||||||
|
// await wallet.connectToNode(
|
||||||
|
// node: Node(uri: 'https://electrum2.hodlister.co:50002'));
|
||||||
|
|
||||||
|
// final transactions = await history.fetchTransactions();
|
||||||
|
|
||||||
|
// final balance = await wallet.fetchBalance();
|
||||||
|
|
||||||
|
// print('balance\n$balance');
|
||||||
|
|
||||||
|
// transactions.forEach((tx) => print(tx.id));
|
||||||
|
|
||||||
|
await wallet.updateInfo();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getAddresses() => _accountIndex == 0
|
||||||
|
? [address]
|
||||||
|
: List<String>.generate(
|
||||||
|
_accountIndex, (i) => _getAddress(hd: hdwallet, index: i));
|
||||||
|
|
||||||
|
Future<String> newAddress() async {
|
||||||
|
_accountIndex += 1;
|
||||||
|
final address = _getAddress(hd: hdwallet, index: _accountIndex);
|
||||||
|
await save();
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future close() async {
|
||||||
|
await _addressUpdatesSubscription?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future connectToNode(
|
||||||
|
{Node node, bool useSSL = false, bool isLightWallet = false}) async {
|
||||||
|
try {
|
||||||
|
// FIXME: Hardcoded server address
|
||||||
|
// final uri = Uri.parse(node.uri);
|
||||||
|
// https://electrum2.hodlister.co:50002
|
||||||
|
await eclient.connect(host: 'electrum2.hodlister.co', port: 50002);
|
||||||
|
_syncStatus.value = ConnectedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
_syncStatus.value = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingTransaction> createTransaction(
|
||||||
|
TransactionCreationCredentials credentials) {
|
||||||
|
final txb = TransactionBuilder(network: bitcoin.bitcoin);
|
||||||
|
|
||||||
|
// TODO: implement createTransaction
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getAddress() async => address;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> getCurrentHeight() async => 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getFilename() async => path.split('/').last ?? '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getFullBalance() async =>
|
||||||
|
bitcoinAmountToString(amount: _onBalanceChange.value.total);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionHistory getHistory() => history;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, String>> getKeys() async =>
|
||||||
|
{'publicKey': hdwallet.pubKey, 'privateKey': hdwallet.privKey};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getName() async => path.split('/').last ?? '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> getNodeHeight() async => 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getSeed() async => mnemonic;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.bitcoin;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getUnlockedBalance() async =>
|
||||||
|
bitcoinAmountToString(amount: _onBalanceChange.value.total);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isConnected() async => eclient.isConnected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Observable<String> get onAddressChange => _onAddressChange.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Observable<String> get onNameChange => _onNameChange.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future rescan({int restoreHeight = 0}) {
|
||||||
|
// TODO: implement rescan
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future startSync() async {
|
||||||
|
_addressUpdatesSubject = eclient.addressUpdate(address: address);
|
||||||
|
_addressUpdatesSubscription =
|
||||||
|
_addressUpdatesSubject.listen((obj) => print('new obj: $obj'));
|
||||||
|
_onBalanceChange.value = await fetchBalance();
|
||||||
|
getHistory().update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future updateInfo() async {
|
||||||
|
_onNameChange.value = await getName();
|
||||||
|
// _addressUpdatesSubject = eclient.addressUpdate(address: address);
|
||||||
|
// _addressUpdatesSubscription =
|
||||||
|
// _addressUpdatesSubject.listen((obj) => print('new obj: $obj'));
|
||||||
|
_onBalanceChange.value = BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
||||||
|
print(await getKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<BitcoinBalance> fetchBalance() async {
|
||||||
|
final balance = await _fetchBalances();
|
||||||
|
|
||||||
|
return BitcoinBalance(
|
||||||
|
confirmed: balance['confirmed'], unconfirmed: balance['unconfirmed']);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async => await write(
|
||||||
|
path: path,
|
||||||
|
password: _password,
|
||||||
|
obj: {'mnemonic': mnemonic, 'account_index': _accountIndex.toString()});
|
||||||
|
|
||||||
|
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
|
||||||
|
.P2PKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Uint8List.fromList(hd.derive(index).pubKey.codeUnits)))
|
||||||
|
.data
|
||||||
|
.address;
|
||||||
|
|
||||||
|
Future<Map<String, int>> _fetchBalances() async {
|
||||||
|
final balances = await Future.wait(
|
||||||
|
getAddresses().map((address) => eclient.getBalance(address: address)));
|
||||||
|
final balance =
|
||||||
|
balances.fold(Map<String, int>(), (Map<String, int> acc, val) {
|
||||||
|
acc['confirmed'] =
|
||||||
|
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
|
||||||
|
acc['unconfirmed'] =
|
||||||
|
(val['unconfirmed'] as int ?? 0) + (acc['unconfirmed'] ?? 0);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
}
|
59
lib/bitcoin/bitcoin_wallet.manager.dart
Normal file
59
lib/bitcoin/bitcoin_wallet.manager.dart
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_description.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallets_manager.dart';
|
||||||
|
|
||||||
|
class BitcoinWalletManager extends WalletsManager {
|
||||||
|
@override
|
||||||
|
Future<Wallet> create(String name, String password, String language) async {
|
||||||
|
final wallet = await BitcoinWallet.build(
|
||||||
|
mnemonic: bip39.generateMnemonic(), password: password, name: name);
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isWalletExit(String name) async =>
|
||||||
|
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
||||||
|
.existsSync();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Wallet> openWallet(String name, String password) async {
|
||||||
|
return BitcoinWallet.load(
|
||||||
|
name: name, password: password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future remove(WalletDescription wallet) async {
|
||||||
|
final path = await pathForWalletDir(name: wallet.name, type: wallet.type);
|
||||||
|
final f = File(path);
|
||||||
|
|
||||||
|
if (!f.existsSync()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.deleteSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Wallet> restoreFromKeys(String name, String password, String language,
|
||||||
|
int restoreHeight, String address, String viewKey, String spendKey) {
|
||||||
|
// TODO: implement restoreFromKeys
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Wallet> restoreFromSeed(
|
||||||
|
String name, String password, String seed, int restoreHeight) async {
|
||||||
|
final wallet = await BitcoinWallet.build(
|
||||||
|
name: name, password: password, mnemonic: seed);
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
}
|
218
lib/bitcoin/electrum.dart
Normal file
218
lib/bitcoin/electrum.dart
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
String jsonrpcparams(List<Object> params) {
|
||||||
|
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
|
||||||
|
return "[$_params]";
|
||||||
|
}
|
||||||
|
|
||||||
|
String jsonrpc(
|
||||||
|
{String method, List<Object> params, int id, double version = 2.0}) =>
|
||||||
|
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${jsonrpcparams(params)}}\n';
|
||||||
|
|
||||||
|
class SocketTask {
|
||||||
|
SocketTask({this.completer, this.isSubscription, this.subject});
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final BehaviorSubject subject;
|
||||||
|
final bool isSubscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumClient {
|
||||||
|
ElectrumClient()
|
||||||
|
: _id = 0,
|
||||||
|
_isConnected = false,
|
||||||
|
_tasks = {};
|
||||||
|
|
||||||
|
static const connectionTimeout = Duration(seconds: 5);
|
||||||
|
|
||||||
|
bool get isConnected => _isConnected;
|
||||||
|
Socket socket;
|
||||||
|
int _id;
|
||||||
|
final Map<String, SocketTask> _tasks;
|
||||||
|
bool _isConnected;
|
||||||
|
|
||||||
|
Future<void> connect({@required String host, @required int port}) async {
|
||||||
|
if (socket != null) {
|
||||||
|
await socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
final start = DateTime.now();
|
||||||
|
|
||||||
|
socket = await SecureSocket.connect(host, port, timeout: connectionTimeout);
|
||||||
|
|
||||||
|
_isConnected = true;
|
||||||
|
|
||||||
|
socket.listen((List<int> event) {
|
||||||
|
try {
|
||||||
|
final Map<String, Object> jsoned =
|
||||||
|
json.decode(utf8.decode(event)) as Map<String, Object>;
|
||||||
|
final method = jsoned['method'];
|
||||||
|
|
||||||
|
if (method is String) {
|
||||||
|
_methodHandler(method: method, request: jsoned);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final id = jsoned['id'] as String;
|
||||||
|
final params = jsoned['result'];
|
||||||
|
|
||||||
|
_finish(id, params);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}, onError: (Object error) {
|
||||||
|
print('ElectrumClient error: ${error.toString()}');
|
||||||
|
}, onDone: () {
|
||||||
|
final end = DateTime.now();
|
||||||
|
final diff = end.millisecondsSinceEpoch - start.millisecondsSinceEpoch;
|
||||||
|
print('On done: $diff');
|
||||||
|
});
|
||||||
|
|
||||||
|
print('Connected to ${socket.remoteAddress}');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> ping() => call(method: 'server.ping');
|
||||||
|
|
||||||
|
Future<List<String>> version() =>
|
||||||
|
call(method: 'server.version').then((dynamic result) {
|
||||||
|
if (result is List) {
|
||||||
|
return result.map((dynamic val) => val.toString()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<Map<String, Object>> getBalance({String address}) =>
|
||||||
|
call(method: 'blockchain.address.get_balance', params: [address])
|
||||||
|
.then((dynamic result) {
|
||||||
|
if (result is Map<String, Object>) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map<String, Object>();
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getHistory({String address}) =>
|
||||||
|
call(method: 'blockchain.address.get_history', params: [address])
|
||||||
|
.then((dynamic result) {
|
||||||
|
if (result is List) {
|
||||||
|
return result.map((dynamic val) {
|
||||||
|
if (val is Map<String, Object>) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map<String, Object>();
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<String> getTransactionRaw({@required String hash}) async =>
|
||||||
|
call(method: 'blockchain.transaction.get', params: [hash])
|
||||||
|
.then((dynamic result) {
|
||||||
|
if (result is String) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getMerkle(
|
||||||
|
{@required String hash, @required int height}) async =>
|
||||||
|
await call(
|
||||||
|
method: 'blockchain.transaction.get_merkle',
|
||||||
|
params: [hash, height]) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getHeader({@required int height}) async =>
|
||||||
|
await call(method: 'blockchain.block.get_header', params: [height])
|
||||||
|
as Map<String, dynamic>;
|
||||||
|
|
||||||
|
Future<double> estimatefee({@required int p}) =>
|
||||||
|
call(method: 'blockchain.estimatefee', params: [p])
|
||||||
|
.then((dynamic result) {
|
||||||
|
if (result is double) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result is String) {
|
||||||
|
return double.parse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
BehaviorSubject<Object> addressUpdate({@required String address}) =>
|
||||||
|
subscribe<Object>(
|
||||||
|
id: 'blockchain.address.subscribe:$address',
|
||||||
|
method: 'blockchain.address.subscribe',
|
||||||
|
params: [address]);
|
||||||
|
|
||||||
|
BehaviorSubject<T> subscribe<T>(
|
||||||
|
{@required String id,
|
||||||
|
@required String method,
|
||||||
|
List<Object> params = const []}) {
|
||||||
|
final subscription = BehaviorSubject<T>();
|
||||||
|
_regisrySubscription(id, subscription);
|
||||||
|
socket.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> call({String method, List<Object> params = const []}) {
|
||||||
|
final completer = Completer<dynamic>();
|
||||||
|
_id += 1;
|
||||||
|
final id = _id;
|
||||||
|
_regisryTask(id, completer);
|
||||||
|
socket.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
void request({String method, List<Object> params = const []}) {
|
||||||
|
_id += 1;
|
||||||
|
socket.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _regisryTask(int id, Completer completer) => _tasks[id.toString()] =
|
||||||
|
SocketTask(completer: completer, isSubscription: false);
|
||||||
|
|
||||||
|
void _regisrySubscription(String id, BehaviorSubject subject) =>
|
||||||
|
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
|
||||||
|
|
||||||
|
void _finish(String id, Object data) {
|
||||||
|
if (_tasks[id] == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tasks[id]?.completer?.complete(data);
|
||||||
|
|
||||||
|
if (!(_tasks[id]?.isSubscription ?? false)) {
|
||||||
|
_tasks[id] = null;
|
||||||
|
} else {
|
||||||
|
_tasks[id].subject.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _methodHandler(
|
||||||
|
{@required String method, @required Map<String, Object> request}) {
|
||||||
|
switch (method) {
|
||||||
|
case 'blockchain.address.subscribe':
|
||||||
|
final params = request['params'] as List<dynamic>;
|
||||||
|
final address = params.first as String;
|
||||||
|
final id = 'blockchain.address.subscribe:$address';
|
||||||
|
|
||||||
|
if (_tasks[id] != null) {
|
||||||
|
_tasks[id].subject.add(params.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
lib/bitcoin/file.dart
Normal file
43
lib/bitcoin/file.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cake_wallet/bitcoin/key.dart';
|
||||||
|
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
Future<void> write(
|
||||||
|
{@required String path,
|
||||||
|
@required String password,
|
||||||
|
@required Map<String, String> obj}) async {
|
||||||
|
final jsoned = json.encode(obj);
|
||||||
|
final keys = extractKeys(password);
|
||||||
|
final key = encrypt.Key.fromBase64(keys.first);
|
||||||
|
final iv = encrypt.IV.fromBase64(keys.last);
|
||||||
|
final encrypted = await encode(key: key, iv: iv, data: jsoned);
|
||||||
|
final f = File(path);
|
||||||
|
f.writeAsStringSync(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> writeData(
|
||||||
|
{@required String path,
|
||||||
|
@required String password,
|
||||||
|
@required String data}) async {
|
||||||
|
final keys = extractKeys(password);
|
||||||
|
final key = encrypt.Key.fromBase64(keys.first);
|
||||||
|
final iv = encrypt.IV.fromBase64(keys.last);
|
||||||
|
final encrypted = await encode(key: key, iv: iv, data: data);
|
||||||
|
final f = File(path);
|
||||||
|
f.writeAsStringSync(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> read(
|
||||||
|
{@required String path, @required String password}) async {
|
||||||
|
final file = File(path);
|
||||||
|
|
||||||
|
if (!file.existsSync()) {
|
||||||
|
file.createSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
final encrypted = file.readAsStringSync();
|
||||||
|
|
||||||
|
return decode(password: password, data: encrypted);
|
||||||
|
}
|
34
lib/bitcoin/key.dart
Normal file
34
lib/bitcoin/key.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||||
|
|
||||||
|
const ivEncodedStringLength = 12;
|
||||||
|
|
||||||
|
String generateKey() {
|
||||||
|
final key = encrypt.Key.fromSecureRandom(512);
|
||||||
|
final iv = encrypt.IV.fromSecureRandom(8);
|
||||||
|
|
||||||
|
return key.base64 + iv.base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> extractKeys(String key) {
|
||||||
|
final _key = key.substring(0, key.length - ivEncodedStringLength);
|
||||||
|
final iv = key.substring(key.length - ivEncodedStringLength);
|
||||||
|
|
||||||
|
return [_key, iv];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> encode({encrypt.Key key, encrypt.IV iv, String data}) async {
|
||||||
|
final encrypter = encrypt.Encrypter(encrypt.Salsa20(key));
|
||||||
|
final encrypted = encrypter.encrypt(data, iv: iv);
|
||||||
|
|
||||||
|
return encrypted.base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> decode({String password, String data}) async {
|
||||||
|
final keys = extractKeys(password);
|
||||||
|
final key = encrypt.Key.fromBase64(keys.first);
|
||||||
|
final iv = encrypt.IV.fromBase64(keys.last);
|
||||||
|
final encrypter = encrypt.Encrypter(encrypt.Salsa20(key));
|
||||||
|
final encrypted = encrypter.decrypt64(data, iv: iv);
|
||||||
|
|
||||||
|
return encrypted;
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/api.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.manager.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/key.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
@ -36,6 +40,8 @@ import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/language.dart';
|
import 'package:cake_wallet/src/domain/common/language.dart';
|
||||||
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
@ -111,6 +117,42 @@ void main() async {
|
||||||
authenticationStore: authenticationStore,
|
authenticationStore: authenticationStore,
|
||||||
loginStore: loginStore);
|
loginStore: loginStore);
|
||||||
|
|
||||||
|
// final addresses = await fetchAllAddresses(xpub: '6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz');
|
||||||
|
// print(addresses);
|
||||||
|
|
||||||
|
final eclient = ElectrumClient();
|
||||||
|
await eclient.connect(host: "electrum2.hodlister.co", port: 50002);
|
||||||
|
await eclient.getMerkle(hash: '3780302c523831311afaccd883f04c814bc13c3ad7c2c13810bb5bfe2c3fa621', height: 629341);
|
||||||
|
print(await eclient.getHeader(height: 629341));
|
||||||
|
// final version = await eclient.version();
|
||||||
|
// print('version $version');
|
||||||
|
|
||||||
|
// eclient.banner();
|
||||||
|
// eclient.headersSubscribe();
|
||||||
|
|
||||||
|
// final history = await eclient.getHistory(address: '1QEWnc4mSxUoP1fWPs32eZNRtc9zX55TwE');
|
||||||
|
// print('history $history');
|
||||||
|
|
||||||
|
// final balance = await eclient.getBalance(address: '1QEWnc4mSxUoP1fWPs32eZNRtc9zX55TwE');
|
||||||
|
// print('balance $balance');
|
||||||
|
|
||||||
|
// final estimateFee = await eclient.estimatefee(p: 6);
|
||||||
|
// print('estimateFee $estimateFee');
|
||||||
|
|
||||||
|
// final walletManager = BitcoinWalletManager();
|
||||||
|
// final key = "zLEjJ96r4WPzlc8rWbuaP2HgxoNfec6sbWjKAcNwqTjzBfiE62A8/0Wp5P3a8Ryo3GUIs/GDG7KfwkoI1FpyuhzWZNU1P8sMN/fp88sB9ktffU5V4B9GZJU5ufSblQOKsvZxqxLJWA8nhL7iaUGcifr9TkwbpqHxBTZDlQxlZXAf/DlRUFEF2LLwo8EJ0HcCn+iPVsnqeGtgtjmOG6l7puP31AErKzaLX4yEgXaKxrdqo0ljS4g7fn4UUXpipv7ry83ZId9ZhpkcdqMRnzu84Msyg/UGGg3BX7VTtbO/ko7ojIBoyzEaF355Tg+sgbfwYAY0CNvOJqPpIhDwu+sq4mhb5H592JP426rDTcy9KV1JbZWbnbbWcqcb04vE2zvXN0x37bd4WfO77qkdoGN5m1XZB2+F2wzNUxvf25WPp5L/nvPZFk/rJGGFoy6X8mASnmIXcq5bRzwC+F2zkZSbXoRFx3yXxlaRnzltVDjWlLUrh8S01TV2llUJEFQhefzR3Xz7mgmHRXANIqRztb1AmjD7eVZid84OfedhD2Lfg9rzFcXeMTcBlaKR36ChIY5zw+ljpnqAm86pSwcJXOAJVKcQ0fJLT6dYbHYkOQqdiSs4cJQMdr/xshrkFd1raVDyL8CTNznfxSvWqSrCUqxbuvylGfrWgHzJfK5CB0oLZnA=WBZ/TzHfP4A=";
|
||||||
|
// await walletManager.openWallet('name', password)
|
||||||
|
// print(await walletManager.isWalletExit('qwerty'));
|
||||||
|
// final wallet = await walletManager.openWallet('green', key);
|
||||||
|
// final keys = await wallet.getKeys();
|
||||||
|
// final seed = await wallet.getSeed();
|
||||||
|
// final address = await wallet.getAddress();
|
||||||
|
|
||||||
|
// print('key $key');
|
||||||
|
// print('keys $keys');
|
||||||
|
// print('seed $seed');
|
||||||
|
// print('address $address');
|
||||||
|
|
||||||
runApp(MultiProvider(providers: [
|
runApp(MultiProvider(providers: [
|
||||||
Provider(create: (_) => sharedPreferences),
|
Provider(create: (_) => sharedPreferences),
|
||||||
Provider(create: (_) => walletService),
|
Provider(create: (_) => walletService),
|
||||||
|
@ -136,7 +178,7 @@ Future<void> initialSetup(
|
||||||
Box<Node> nodes,
|
Box<Node> nodes,
|
||||||
AuthenticationStore authStore,
|
AuthenticationStore authStore,
|
||||||
int initialMigrationVersion = 1,
|
int initialMigrationVersion = 1,
|
||||||
WalletType initialWalletType = WalletType.monero}) async {
|
WalletType initialWalletType = WalletType.bitcoin}) async {
|
||||||
await walletListService.changeWalletManger(walletType: initialWalletType);
|
await walletListService.changeWalletManger(walletType: initialWalletType);
|
||||||
await defaultSettingsMigration(
|
await defaultSettingsMigration(
|
||||||
version: initialMigrationVersion,
|
version: initialMigrationVersion,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
@ -109,6 +110,9 @@ class Router {
|
||||||
return MaterialPageRoute<void>(builder: (_) => createWelcomePage());
|
return MaterialPageRoute<void>(builder: (_) => createWelcomePage());
|
||||||
|
|
||||||
case Routes.newWalletFromWelcome:
|
case Routes.newWalletFromWelcome:
|
||||||
|
final type = settings.arguments as WalletType;
|
||||||
|
walletListService.changeWalletManger(walletType: type);
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => Provider(
|
builder: (_) => Provider(
|
||||||
create: (_) => UserStore(
|
create: (_) => UserStore(
|
||||||
|
@ -116,10 +120,14 @@ class Router {
|
||||||
secureStorage: FlutterSecureStorage(),
|
secureStorage: FlutterSecureStorage(),
|
||||||
sharedPreferences: sharedPreferences)),
|
sharedPreferences: sharedPreferences)),
|
||||||
child: SetupPinCodePage(
|
child: SetupPinCodePage(
|
||||||
onPinCodeSetup: (context, _) =>
|
onPinCodeSetup: (context, _) => Navigator.pushNamed(
|
||||||
Navigator.pushNamed(context, Routes.newWallet))));
|
context, Routes.newWallet,
|
||||||
|
arguments: type))));
|
||||||
|
|
||||||
case Routes.newWallet:
|
case Routes.newWallet:
|
||||||
|
final type = settings.arguments as WalletType;
|
||||||
|
walletListService.changeWalletManger(walletType: type);
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder:
|
builder:
|
||||||
(_) =>
|
(_) =>
|
||||||
|
@ -152,11 +160,18 @@ class Router {
|
||||||
fullscreenDialog: true);
|
fullscreenDialog: true);
|
||||||
|
|
||||||
case Routes.restoreOptions:
|
case Routes.restoreOptions:
|
||||||
return CupertinoPageRoute<void>(builder: (_) => RestoreOptionsPage());
|
final type = settings.arguments as WalletType;
|
||||||
|
walletListService.changeWalletManger(walletType: type);
|
||||||
|
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
builder: (_) => RestoreOptionsPage(type: type));
|
||||||
|
|
||||||
case Routes.restoreWalletOptions:
|
case Routes.restoreWalletOptions:
|
||||||
|
final type = settings.arguments as WalletType;
|
||||||
|
walletListService.changeWalletManger(walletType: type);
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => RestoreWalletOptionsPage());
|
builder: (_) => RestoreWalletOptionsPage(type: type));
|
||||||
|
|
||||||
case Routes.restoreWalletOptionsFromWelcome:
|
case Routes.restoreWalletOptionsFromWelcome:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
|
@ -177,6 +192,9 @@ class Router {
|
||||||
callback: settings.arguments as void Function()));
|
callback: settings.arguments as void Function()));
|
||||||
|
|
||||||
case Routes.restoreWalletFromSeed:
|
case Routes.restoreWalletFromSeed:
|
||||||
|
final type = settings.arguments as WalletType;
|
||||||
|
walletListService.changeWalletManger(walletType: type);
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) =>
|
builder: (_) =>
|
||||||
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
|
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
|
||||||
|
@ -235,11 +253,15 @@ class Router {
|
||||||
case Routes.receive:
|
case Routes.receive:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_) => MultiProvider(providers: [
|
builder: (_) => MultiProvider(
|
||||||
Provider(
|
providers: walletService.getType() == WalletType.monero
|
||||||
create: (_) =>
|
? [
|
||||||
SubaddressListStore(walletService: walletService))
|
Provider(
|
||||||
], child: ReceivePage()));
|
create: (_) => SubaddressListStore(
|
||||||
|
walletService: walletService))
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
child: ReceivePage()));
|
||||||
|
|
||||||
case Routes.transactionDetails:
|
case Routes.transactionDetails:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
|
|
21
lib/src/domain/common/pathForWallet.dart
Normal file
21
lib/src/domain/common/pathForWallet.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
Future<String> pathForWalletDir({@required String name, @required WalletType type}) async {
|
||||||
|
final root = await getApplicationDocumentsDirectory();
|
||||||
|
final prefix = walletTypeToString(type).toLowerCase();
|
||||||
|
final walletsDir = Directory('${root.path}/wallets');
|
||||||
|
final walletDire = Directory('${walletsDir.path}/$prefix/$name');
|
||||||
|
|
||||||
|
if (!walletDire.existsSync()) {
|
||||||
|
walletDire.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return walletDire.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> pathForWallet({@required String name, @required WalletType type}) async =>
|
||||||
|
await pathForWalletDir(name: name, type: type)
|
||||||
|
.then((path) => path + '/$name');
|
|
@ -4,7 +4,5 @@ import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
abstract class TransactionHistory {
|
abstract class TransactionHistory {
|
||||||
Observable<List<TransactionInfo>> transactions;
|
Observable<List<TransactionInfo>> transactions;
|
||||||
Future<List<TransactionInfo>> getAll();
|
Future<List<TransactionInfo>> getAll();
|
||||||
Future<int> count();
|
|
||||||
Future refresh();
|
|
||||||
Future update();
|
Future update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,11 @@
|
||||||
import 'package:cake_wallet/src/domain/monero/monero_amount_format.dart';
|
|
||||||
import 'package:cw_monero/structs/transaction_info_row.dart';
|
|
||||||
import 'package:cake_wallet/src/domain/common/parseBoolFromString.dart';
|
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/format_amount.dart';
|
|
||||||
|
|
||||||
class TransactionInfo {
|
abstract class TransactionInfo extends Object {
|
||||||
TransactionInfo(this.id, this.height, this.direction, this.date,
|
int amount;
|
||||||
this.isPending, this.amount, this.accountIndex);
|
TransactionDirection direction;
|
||||||
|
bool isPending;
|
||||||
TransactionInfo.fromMap(Map map)
|
DateTime date;
|
||||||
: id = (map['hash'] ?? '') as String,
|
int height;
|
||||||
height = (map['height'] ?? 0) as int,
|
String amountFormatted();
|
||||||
direction =
|
String fiatAmount();
|
||||||
parseTransactionDirectionFromNumber(map['direction'] as String) ??
|
}
|
||||||
TransactionDirection.incoming,
|
|
||||||
date = DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
(int.parse(map['timestamp'] as String) ?? 0) * 1000),
|
|
||||||
isPending = parseBoolFromString(map['isPending'] as String),
|
|
||||||
amount = map['amount'] as int,
|
|
||||||
accountIndex = int.parse(map['accountIndex'] as String);
|
|
||||||
|
|
||||||
TransactionInfo.fromRow(TransactionInfoRow row)
|
|
||||||
: id = row.getHash(),
|
|
||||||
height = row.blockHeight,
|
|
||||||
direction = parseTransactionDirectionFromInt(row.direction) ??
|
|
||||||
TransactionDirection.incoming,
|
|
||||||
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
|
||||||
isPending = row.isPending != 0,
|
|
||||||
amount = row.getAmount(),
|
|
||||||
accountIndex = row.subaddrAccount;
|
|
||||||
|
|
||||||
final String id;
|
|
||||||
final int height;
|
|
||||||
final TransactionDirection direction;
|
|
||||||
final DateTime date;
|
|
||||||
final int accountIndex;
|
|
||||||
final bool isPending;
|
|
||||||
final int amount;
|
|
||||||
String recipientAddress;
|
|
||||||
|
|
||||||
String _fiatAmount;
|
|
||||||
|
|
||||||
String amountFormatted() => '${formatAmount(moneroAmountToString(amount: amount))} XMR';
|
|
||||||
|
|
||||||
String fiatAmount() => _fiatAmount ?? '';
|
|
||||||
|
|
||||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
|
||||||
}
|
|
|
@ -2,13 +2,18 @@ import 'package:hive/hive.dart';
|
||||||
|
|
||||||
part 'wallet_type.g.dart';
|
part 'wallet_type.g.dart';
|
||||||
|
|
||||||
|
const walletTypes = [WalletType.monero, WalletType.bitcoin];
|
||||||
|
|
||||||
@HiveType()
|
@HiveType()
|
||||||
enum WalletType {
|
enum WalletType {
|
||||||
@HiveField(0)
|
@HiveField(0)
|
||||||
monero,
|
monero,
|
||||||
|
|
||||||
@HiveField(1)
|
@HiveField(1)
|
||||||
none
|
none,
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
bitcoin
|
||||||
}
|
}
|
||||||
|
|
||||||
int serializeToInt(WalletType type) {
|
int serializeToInt(WalletType type) {
|
||||||
|
@ -33,7 +38,9 @@ String walletTypeToString(WalletType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return 'Monero';
|
return 'Monero';
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
return 'Bitcoin';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
|
import 'package:cake_wallet/src/domain/monero/monero_transaction_info.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
import 'package:cw_monero/transaction_history.dart'
|
import 'package:cw_monero/transaction_history.dart'
|
||||||
|
@ -6,10 +7,11 @@ import 'package:cw_monero/transaction_history.dart'
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
|
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
|
|
||||||
List<TransactionInfo> _getAllTransactions(dynamic _) => monero_transaction_history
|
List<TransactionInfo> _getAllTransactions(dynamic _) =>
|
||||||
.getAllTransations()
|
monero_transaction_history
|
||||||
.map((row) => TransactionInfo.fromRow(row))
|
.getAllTransations()
|
||||||
.toList();
|
.map((row) => MoneroTransactionInfo.fromRow(row))
|
||||||
|
.toList();
|
||||||
|
|
||||||
class MoneroTransactionHistory extends TransactionHistory {
|
class MoneroTransactionHistory extends TransactionHistory {
|
||||||
MoneroTransactionHistory()
|
MoneroTransactionHistory()
|
||||||
|
@ -31,7 +33,6 @@ class MoneroTransactionHistory extends TransactionHistory {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_isUpdating = true;
|
_isUpdating = true;
|
||||||
await refresh();
|
|
||||||
_transactions.value = await getAll(force: true);
|
_transactions.value = await getAll(force: true);
|
||||||
_isUpdating = false;
|
_isUpdating = false;
|
||||||
|
|
||||||
|
@ -46,13 +47,11 @@ class MoneroTransactionHistory extends TransactionHistory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<TransactionInfo>> getAll({bool force = false}) async =>
|
Future<List<TransactionInfo>> getAll({bool force = false}) async {
|
||||||
_getAllTransactions(null);
|
await refresh();
|
||||||
|
return _getAllTransactions(null);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> count() async => monero_transaction_history.countOfTransactions();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future refresh() async {
|
Future refresh() async {
|
||||||
if (_isRefreshing) {
|
if (_isRefreshing) {
|
||||||
return;
|
return;
|
||||||
|
|
50
lib/src/domain/monero/monero_transaction_info.dart
Normal file
50
lib/src/domain/monero/monero_transaction_info.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/monero/monero_amount_format.dart';
|
||||||
|
import 'package:cw_monero/structs/transaction_info_row.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/parseBoolFromString.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/format_amount.dart';
|
||||||
|
|
||||||
|
class MoneroTransactionInfo extends TransactionInfo {
|
||||||
|
MoneroTransactionInfo(this.id, this.height, this.direction, this.date,
|
||||||
|
this.isPending, this.amount, this.accountIndex);
|
||||||
|
|
||||||
|
MoneroTransactionInfo.fromMap(Map map)
|
||||||
|
: id = (map['hash'] ?? '') as String,
|
||||||
|
height = (map['height'] ?? 0) as int,
|
||||||
|
direction =
|
||||||
|
parseTransactionDirectionFromNumber(map['direction'] as String) ??
|
||||||
|
TransactionDirection.incoming,
|
||||||
|
date = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
(int.parse(map['timestamp'] as String) ?? 0) * 1000),
|
||||||
|
isPending = parseBoolFromString(map['isPending'] as String),
|
||||||
|
amount = map['amount'] as int,
|
||||||
|
accountIndex = int.parse(map['accountIndex'] as String);
|
||||||
|
|
||||||
|
MoneroTransactionInfo.fromRow(TransactionInfoRow row)
|
||||||
|
: id = row.getHash(),
|
||||||
|
height = row.blockHeight,
|
||||||
|
direction = parseTransactionDirectionFromInt(row.direction) ??
|
||||||
|
TransactionDirection.incoming,
|
||||||
|
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
||||||
|
isPending = row.isPending != 0,
|
||||||
|
amount = row.getAmount(),
|
||||||
|
accountIndex = row.subaddrAccount;
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final int height;
|
||||||
|
final TransactionDirection direction;
|
||||||
|
final DateTime date;
|
||||||
|
final int accountIndex;
|
||||||
|
final bool isPending;
|
||||||
|
final int amount;
|
||||||
|
String recipientAddress;
|
||||||
|
|
||||||
|
String _fiatAmount;
|
||||||
|
|
||||||
|
String amountFormatted() => '${formatAmount(moneroAmountToString(amount: amount))} XMR';
|
||||||
|
|
||||||
|
String fiatAmount() => _fiatAmount ?? '';
|
||||||
|
|
||||||
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
@ -11,17 +12,7 @@ import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/monero_wallet.dart';
|
import 'package:cake_wallet/src/domain/monero/monero_wallet.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet_description.dart';
|
import 'package:cake_wallet/src/domain/common/wallet_description.dart';
|
||||||
|
|
||||||
Future<String> pathForWallet({String name}) async {
|
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
|
||||||
final pathDir = directory.path + '/$name';
|
|
||||||
final dir = Directory(pathDir);
|
|
||||||
|
|
||||||
if (!await dir.exists()) {
|
|
||||||
await dir.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathDir + '/$name';
|
|
||||||
}
|
|
||||||
|
|
||||||
class MoneroWalletsManager extends WalletsManager {
|
class MoneroWalletsManager extends WalletsManager {
|
||||||
MoneroWalletsManager({@required this.walletInfoSource});
|
MoneroWalletsManager({@required this.walletInfoSource});
|
||||||
|
@ -34,7 +25,7 @@ class MoneroWalletsManager extends WalletsManager {
|
||||||
Future<Wallet> create(String name, String password, String language) async {
|
Future<Wallet> create(String name, String password, String language) async {
|
||||||
try {
|
try {
|
||||||
const isRecovery = false;
|
const isRecovery = false;
|
||||||
final path = await pathForWallet(name: name);
|
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||||
|
|
||||||
await monero_wallet_manager.createWallet(path: path, password: password, language: language);
|
await monero_wallet_manager.createWallet(path: path, password: password, language: language);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.manager.dart';
|
||||||
|
import 'package:cake_wallet/bitcoin/key.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet_info.dart';
|
import 'package:cake_wallet/src/domain/common/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
@ -29,13 +31,15 @@ class WalletListService {
|
||||||
this.walletInfoSource,
|
this.walletInfoSource,
|
||||||
this.walletsManager,
|
this.walletsManager,
|
||||||
@required this.walletService,
|
@required this.walletService,
|
||||||
@required this.sharedPreferences});
|
@required this.sharedPreferences})
|
||||||
|
: _type = WalletType.monero;
|
||||||
|
|
||||||
final FlutterSecureStorage secureStorage;
|
final FlutterSecureStorage secureStorage;
|
||||||
final WalletService walletService;
|
final WalletService walletService;
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final SharedPreferences sharedPreferences;
|
final SharedPreferences sharedPreferences;
|
||||||
WalletsManager walletsManager;
|
WalletsManager walletsManager;
|
||||||
|
WalletType _type;
|
||||||
|
|
||||||
Future<List<WalletDescription>> getAll() async => walletInfoSource.values
|
Future<List<WalletDescription>> getAll() async => walletInfoSource.values
|
||||||
.map((info) => WalletDescription(name: info.name, type: info.type))
|
.map((info) => WalletDescription(name: info.name, type: info.type))
|
||||||
|
@ -50,7 +54,7 @@ class WalletListService {
|
||||||
await walletService.close();
|
await walletService.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
final password = Uuid().v4();
|
final password = _generatePassword();
|
||||||
await saveWalletPassword(password: password, walletName: name);
|
await saveWalletPassword(password: password, walletName: name);
|
||||||
|
|
||||||
final wallet = await walletsManager.create(name, password, language);
|
final wallet = await walletsManager.create(name, password, language);
|
||||||
|
@ -67,7 +71,7 @@ class WalletListService {
|
||||||
await walletService.close();
|
await walletService.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
final password = Uuid().v4();
|
final password = _generatePassword();
|
||||||
await saveWalletPassword(password: password, walletName: name);
|
await saveWalletPassword(password: password, walletName: name);
|
||||||
|
|
||||||
final wallet = await walletsManager.restoreFromSeed(
|
final wallet = await walletsManager.restoreFromSeed(
|
||||||
|
@ -76,8 +80,8 @@ class WalletListService {
|
||||||
await onWalletChange(wallet);
|
await onWalletChange(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future restoreFromKeys(String name, String language, int restoreHeight, String address,
|
Future restoreFromKeys(String name, String language, int restoreHeight,
|
||||||
String viewKey, String spendKey) async {
|
String address, String viewKey, String spendKey) async {
|
||||||
if (await walletsManager.isWalletExit(name)) {
|
if (await walletsManager.isWalletExit(name)) {
|
||||||
throw WalletIsExistException(name);
|
throw WalletIsExistException(name);
|
||||||
}
|
}
|
||||||
|
@ -86,7 +90,7 @@ class WalletListService {
|
||||||
await walletService.close();
|
await walletService.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
final password = Uuid().v4();
|
final password = _generatePassword();
|
||||||
await saveWalletPassword(password: password, walletName: name);
|
await saveWalletPassword(password: password, walletName: name);
|
||||||
|
|
||||||
final wallet = await walletsManager.restoreFromKeys(
|
final wallet = await walletsManager.restoreFromKeys(
|
||||||
|
@ -107,11 +111,16 @@ class WalletListService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future changeWalletManger({WalletType walletType}) async {
|
Future changeWalletManger({WalletType walletType}) async {
|
||||||
|
_type = walletType;
|
||||||
|
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
walletsManager =
|
walletsManager =
|
||||||
MoneroWalletsManager(walletInfoSource: walletInfoSource);
|
MoneroWalletsManager(walletInfoSource: walletInfoSource);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
walletsManager = BitcoinWalletManager();
|
||||||
|
break;
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
walletsManager = null;
|
walletsManager = null;
|
||||||
break;
|
break;
|
||||||
|
@ -121,6 +130,7 @@ class WalletListService {
|
||||||
Future onWalletChange(Wallet wallet) async {
|
Future onWalletChange(Wallet wallet) async {
|
||||||
walletService.currentWallet = wallet;
|
walletService.currentWallet = wallet;
|
||||||
final walletName = await wallet.getName();
|
final walletName = await wallet.getName();
|
||||||
|
print('walletName $walletName ');
|
||||||
await sharedPreferences.setString('current_wallet_name', walletName);
|
await sharedPreferences.setString('current_wallet_name', walletName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,4 +152,13 @@ class WalletListService {
|
||||||
|
|
||||||
await secureStorage.write(key: key, value: encodedPassword);
|
await secureStorage.write(key: key, value: encodedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _generatePassword() {
|
||||||
|
switch (_type) {
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
return generateKey();
|
||||||
|
default:
|
||||||
|
return Uuid().v4();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ class WalletService extends Wallet {
|
||||||
WalletDescription description;
|
WalletDescription description;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType() => WalletType.monero;
|
WalletType getType() => _currentWallet.getType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getFilename() => _currentWallet.getFilename();
|
Future<String> getFilename() => _currentWallet.getFilename();
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ReceivePage extends BasePage {
|
||||||
splashColor: Colors.transparent,
|
splashColor: Colors.transparent,
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
onPressed: () => Share.text(
|
onPressed: () => Share.text(
|
||||||
'Share address', walletStore.subaddress.address, 'text/plain'),
|
'Share address', walletStore.getAddress, 'text/plain'),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.share,
|
Icons.share,
|
||||||
size: 30.0,
|
size: 30.0,
|
||||||
|
@ -65,7 +65,11 @@ class ReceiveBodyState extends State<ReceiveBody> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final walletStore = Provider.of<WalletStore>(context);
|
final walletStore = Provider.of<WalletStore>(context);
|
||||||
final subaddressListStore = Provider.of<SubaddressListStore>(context);
|
SubaddressListStore subaddressListStore;
|
||||||
|
|
||||||
|
try {
|
||||||
|
subaddressListStore = Provider.of<SubaddressListStore>(context);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
final currentColor = Theme.of(context).selectedRowColor;
|
final currentColor = Theme.of(context).selectedRowColor;
|
||||||
final notCurrentColor = Theme.of(context).scaffoldBackgroundColor;
|
final notCurrentColor = Theme.of(context).scaffoldBackgroundColor;
|
||||||
|
@ -101,7 +105,7 @@ class ReceiveBodyState extends State<ReceiveBody> {
|
||||||
padding: EdgeInsets.all(5),
|
padding: EdgeInsets.all(5),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: QrImage(
|
child: QrImage(
|
||||||
data: walletStore.subaddress.address +
|
data: walletStore.getAddress +
|
||||||
walletStore.amountValue,
|
walletStore.amountValue,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
),
|
),
|
||||||
|
@ -122,8 +126,8 @@ class ReceiveBodyState extends State<ReceiveBody> {
|
||||||
child: Center(
|
child: Center(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Clipboard.setData(ClipboardData(
|
Clipboard.setData(
|
||||||
text: walletStore.subaddress.address));
|
ClipboardData(text: walletStore.getAddress));
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
Scaffold.of(context).showSnackBar(SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
S.of(context).copied_to_clipboard,
|
S.of(context).copied_to_clipboard,
|
||||||
|
@ -133,7 +137,7 @@ class ReceiveBodyState extends State<ReceiveBody> {
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
walletStore.subaddress.address,
|
walletStore.getAddress,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
|
@ -187,78 +191,17 @@ class ReceiveBodyState extends State<ReceiveBody> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
subaddressListStore != null
|
||||||
children: <Widget>[
|
? Row(
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).accentTextTheme.headline.color,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
Expanded(
|
||||||
title: Text(
|
child: Container(
|
||||||
S.of(context).subaddresses,
|
color: Theme.of(context).accentTextTheme.headline.color,
|
||||||
style: TextStyle(
|
child: Column(
|
||||||
fontSize: 16.0,
|
children: <Widget>[
|
||||||
color: Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.headline
|
|
||||||
.color),
|
|
||||||
),
|
|
||||||
trailing: Container(
|
|
||||||
width: 28.0,
|
|
||||||
height: 28.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).selectedRowColor,
|
|
||||||
shape: BoxShape.circle),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => Navigator.of(context)
|
|
||||||
.pushNamed(Routes.newSubaddress),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(14.0)),
|
|
||||||
child: Icon(
|
|
||||||
Icons.add,
|
|
||||||
color: Palette.violet,
|
|
||||||
size: 22.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(
|
|
||||||
color: Theme.of(context).dividerTheme.color,
|
|
||||||
height: 1.0,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Observer(builder: (_) {
|
|
||||||
return ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: subaddressListStore.subaddresses.length,
|
|
||||||
separatorBuilder: (context, i) {
|
|
||||||
return Divider(
|
|
||||||
color: Theme.of(context).dividerTheme.color,
|
|
||||||
height: 1.0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
return Observer(builder: (_) {
|
|
||||||
final subaddress = subaddressListStore.subaddresses[i];
|
|
||||||
final isCurrent =
|
|
||||||
walletStore.subaddress.address == subaddress.address;
|
|
||||||
final label = subaddress.label.isNotEmpty
|
|
||||||
? subaddress.label
|
|
||||||
: subaddress.address;
|
|
||||||
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => walletStore.setSubaddress(subaddress),
|
|
||||||
child: Container(
|
|
||||||
color: isCurrent ? currentColor : notCurrentColor,
|
|
||||||
child: Column(children: <Widget>[
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
label,
|
S.of(context).subaddresses,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
|
@ -266,13 +209,79 @@ class ReceiveBodyState extends State<ReceiveBody> {
|
||||||
.headline
|
.headline
|
||||||
.color),
|
.color),
|
||||||
),
|
),
|
||||||
|
trailing: Container(
|
||||||
|
width: 28.0,
|
||||||
|
height: 28.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).selectedRowColor,
|
||||||
|
shape: BoxShape.circle),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => Navigator.of(context)
|
||||||
|
.pushNamed(Routes.newSubaddress),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.all(Radius.circular(14.0)),
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: Palette.violet,
|
||||||
|
size: 22.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
color: Theme.of(context).dividerTheme.color,
|
||||||
|
height: 1.0,
|
||||||
)
|
)
|
||||||
]),
|
],
|
||||||
),
|
),
|
||||||
);
|
))
|
||||||
});
|
],
|
||||||
});
|
)
|
||||||
})
|
: Container(),
|
||||||
|
subaddressListStore != null
|
||||||
|
? Observer(builder: (_) {
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: subaddressListStore.subaddresses.length,
|
||||||
|
separatorBuilder: (context, i) {
|
||||||
|
return Divider(
|
||||||
|
color: Theme.of(context).dividerTheme.color,
|
||||||
|
height: 1.0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
return Observer(builder: (_) {
|
||||||
|
final subaddress = subaddressListStore.subaddresses[i];
|
||||||
|
final isCurrent =
|
||||||
|
walletStore.getAddress == subaddress.address;
|
||||||
|
final label = subaddress.label.isNotEmpty
|
||||||
|
? subaddress.label
|
||||||
|
: subaddress.address;
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => walletStore.setSubaddress(subaddress),
|
||||||
|
child: Container(
|
||||||
|
color: isCurrent ? currentColor : notCurrentColor,
|
||||||
|
child: Column(children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.headline
|
||||||
|
.color),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
: Container()
|
||||||
],
|
],
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/palette.dart';
|
import 'package:cake_wallet/palette.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
@ -9,7 +10,10 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
class RestoreOptionsPage extends BasePage {
|
class RestoreOptionsPage extends BasePage {
|
||||||
|
RestoreOptionsPage({@required this.type});
|
||||||
|
|
||||||
static const _aspectRatioImage = 2.086;
|
static const _aspectRatioImage = 2.086;
|
||||||
|
final WalletType type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.restore_restore_wallet;
|
String get title => S.current.restore_restore_wallet;
|
||||||
|
|
|
@ -6,12 +6,16 @@ import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
|
||||||
import 'package:cake_wallet/src/screens/restore/widgets/image_widget.dart';
|
import 'package:cake_wallet/src/screens/restore/widgets/image_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/restore/widgets/base_restore_widget.dart';
|
import 'package:cake_wallet/src/screens/restore/widgets/base_restore_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class RestoreWalletOptionsPage extends BasePage {
|
class RestoreWalletOptionsPage extends BasePage {
|
||||||
|
RestoreWalletOptionsPage({@required this.type});
|
||||||
|
|
||||||
static const _aspectRatioImage = 2.086;
|
static const _aspectRatioImage = 2.086;
|
||||||
|
final WalletType type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.restore_seed_keys_restore;
|
String get title => S.current.restore_seed_keys_restore;
|
||||||
|
@ -31,7 +35,7 @@ class RestoreWalletOptionsPage extends BasePage {
|
||||||
firstRestoreButton: RestoreButton(
|
firstRestoreButton: RestoreButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
seedLanguageStore.setCurrentRoute(Routes.restoreWalletFromSeed);
|
seedLanguageStore.setCurrentRoute(Routes.restoreWalletFromSeed);
|
||||||
Navigator.pushNamed(context, Routes.seedLanguage);
|
Navigator.pushNamed(context, Routes.seedLanguage, arguments: type);
|
||||||
},
|
},
|
||||||
imageWidget: ImageWidget(
|
imageWidget: ImageWidget(
|
||||||
image: _imageSeed,
|
image: _imageSeed,
|
||||||
|
@ -46,7 +50,7 @@ class RestoreWalletOptionsPage extends BasePage {
|
||||||
secondRestoreButton: RestoreButton(
|
secondRestoreButton: RestoreButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
seedLanguageStore.setCurrentRoute(Routes.restoreWalletFromKeys);
|
seedLanguageStore.setCurrentRoute(Routes.restoreWalletFromKeys);
|
||||||
Navigator.pushNamed(context, Routes.seedLanguage);
|
Navigator.pushNamed(context, Routes.seedLanguage, arguments: type);
|
||||||
},
|
},
|
||||||
imageWidget: ImageWidget(
|
imageWidget: ImageWidget(
|
||||||
image: _imageKeys,
|
image: _imageKeys,
|
||||||
|
|
|
@ -25,11 +25,19 @@ class ShowKeysPage extends BasePage {
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
final keysMap = {
|
final keysMap = {
|
||||||
S.of(context).view_key_public: walletKeysStore.publicViewKey,
|
S.of(context).view_key_public: walletKeysStore.publicViewKey,
|
||||||
S.of(context).view_key_private: walletKeysStore.privateViewKey,
|
|
||||||
S.of(context).spend_key_public: walletKeysStore.publicSpendKey,
|
|
||||||
S.of(context).spend_key_private: walletKeysStore.privateSpendKey
|
S.of(context).spend_key_private: walletKeysStore.privateSpendKey
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (walletKeysStore.privateViewKey.isNotEmpty) {
|
||||||
|
keysMap[S.of(context).view_key_private] =
|
||||||
|
walletKeysStore.privateViewKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walletKeysStore.publicSpendKey.isNotEmpty) {
|
||||||
|
keysMap[S.of(context).spend_key_public] =
|
||||||
|
walletKeysStore.publicSpendKey;
|
||||||
|
}
|
||||||
|
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
separatorBuilder: (_, __) => Container(
|
separatorBuilder: (_, __) => Container(
|
||||||
padding: EdgeInsets.only(left: 30.0, right: 20.0),
|
padding: EdgeInsets.only(left: 30.0, right: 20.0),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/domain/monero/monero_transaction_info.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
@ -46,31 +47,33 @@ class TransactionDetailsFormState extends State<TransactionDetailsForm> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final _dateFormat = widget.settingsStore.getCurrentDateFormat(
|
final _dateFormat = widget.settingsStore.getCurrentDateFormat(
|
||||||
formatUSA: "yyyy.MM.dd, HH:mm",
|
formatUSA: "yyyy.MM.dd, HH:mm", formatDefault: "dd.MM.yyyy, HH:mm");
|
||||||
formatDefault: "dd.MM.yyyy, HH:mm");
|
final tx = widget.transactionInfo;
|
||||||
final items = [
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_transaction_id,
|
|
||||||
value: widget.transactionInfo.id),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_date,
|
|
||||||
value: _dateFormat.format(widget.transactionInfo.date)),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_height,
|
|
||||||
value: '${widget.transactionInfo.height}'),
|
|
||||||
StandartListItem(
|
|
||||||
title: S.current.transaction_details_amount,
|
|
||||||
value: widget.transactionInfo.amountFormatted())
|
|
||||||
];
|
|
||||||
|
|
||||||
if (widget.settingsStore.shouldSaveRecipientAddress &&
|
if (tx is MoneroTransactionInfo) {
|
||||||
widget.transactionInfo.recipientAddress != null) {
|
final items = [
|
||||||
items.add(StandartListItem(
|
StandartListItem(
|
||||||
title: S.current.transaction_details_recipient_address,
|
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||||
value: widget.transactionInfo.recipientAddress));
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_date,
|
||||||
|
value: _dateFormat.format(tx.date)),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_amount,
|
||||||
|
value: tx.amountFormatted())
|
||||||
|
];
|
||||||
|
|
||||||
|
if (widget.settingsStore.shouldSaveRecipientAddress &&
|
||||||
|
tx.recipientAddress != null) {
|
||||||
|
items.add(StandartListItem(
|
||||||
|
title: S.current.transaction_details_recipient_address,
|
||||||
|
value: tx.recipientAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
_items.addAll(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
_items.addAll(items);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/palette.dart';
|
import 'package:cake_wallet/palette.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
@ -5,14 +7,38 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
|
WalletType _selectedType;
|
||||||
|
|
||||||
class WelcomePage extends BasePage {
|
class WelcomePage extends BasePage {
|
||||||
static const _aspectRatioImage = 1.26;
|
static const _aspectRatioImage = 1.26;
|
||||||
static const _baseWidth = 411.43;
|
static const _baseWidth = 411.43;
|
||||||
final _image = Image.asset('assets/images/welcomeImg.png');
|
final _image = Image.asset('assets/images/welcomeImg.png');
|
||||||
final _cakeLogo = Image.asset('assets/images/cake_logo.png');
|
final _cakeLogo = Image.asset('assets/images/cake_logo.png');
|
||||||
|
final Map<String, WalletType> _picker =
|
||||||
|
walletTypes.fold(Map<String, WalletType>(), (acc, item) {
|
||||||
|
acc[walletTypeToString(item)] = item;
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _selectedType == null
|
||||||
|
? showDialog<void>(
|
||||||
|
builder: (_) => Picker(
|
||||||
|
items: walletTypes
|
||||||
|
.map((item) => walletTypeToString(item))
|
||||||
|
.toList(),
|
||||||
|
selectedAtIndex: -1,
|
||||||
|
title: 'Select wallet type',
|
||||||
|
pickerHeight: 510,
|
||||||
|
onItemSelected: (String item) {
|
||||||
|
print('before $_selectedType');
|
||||||
|
_selectedType = _picker[item];
|
||||||
|
print('after $_selectedType');
|
||||||
|
}),
|
||||||
|
context: context)
|
||||||
|
: null);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).backgroundColor,
|
backgroundColor: Theme.of(context).backgroundColor,
|
||||||
resizeToAvoidBottomPadding: false,
|
resizeToAvoidBottomPadding: false,
|
||||||
|
@ -72,7 +98,8 @@ class WelcomePage extends BasePage {
|
||||||
child: Column(children: <Widget>[
|
child: Column(children: <Widget>[
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pushNamed(context, Routes.newWalletFromWelcome);
|
Navigator.pushNamed(context, Routes.newWalletFromWelcome,
|
||||||
|
arguments: _selectedType);
|
||||||
},
|
},
|
||||||
text: S.of(context).create_new,
|
text: S.of(context).create_new,
|
||||||
color:
|
color:
|
||||||
|
@ -82,7 +109,8 @@ class WelcomePage extends BasePage {
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pushNamed(context, Routes.restoreOptions);
|
Navigator.pushNamed(context, Routes.restoreOptions,
|
||||||
|
arguments: _selectedType);
|
||||||
},
|
},
|
||||||
color: Theme.of(context).accentTextTheme.caption.backgroundColor,
|
color: Theme.of(context).accentTextTheme.caption.backgroundColor,
|
||||||
borderColor:
|
borderColor:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:cake_wallet/src/domain/exchange/trade.dart';
|
import 'package:cake_wallet/src/domain/exchange/trade.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/monero/monero_transaction_info.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -98,10 +99,14 @@ abstract class ActionListBase with Store {
|
||||||
final price = _priceStore.prices[symbol];
|
final price = _priceStore.prices[symbol];
|
||||||
|
|
||||||
_transactions.forEach((item) {
|
_transactions.forEach((item) {
|
||||||
final amount = calculateFiatAmountRaw(
|
final tx = item.transaction;
|
||||||
cryptoAmount: moneroAmountToDouble(amount: item.transaction.amount),
|
|
||||||
price: price);
|
if (tx is MoneroTransactionInfo) {
|
||||||
item.transaction.changeFiatAmount(amount);
|
final amount = calculateFiatAmountRaw(
|
||||||
|
cryptoAmount: moneroAmountToDouble(amount: tx.amount),
|
||||||
|
price: price);
|
||||||
|
tx.changeFiatAmount(amount);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return _transactions;
|
return _transactions;
|
||||||
|
@ -171,7 +176,6 @@ abstract class ActionListBase with Store {
|
||||||
tradesSource.values.map((trade) => TradeListItem(trade: trade)).toList();
|
tradesSource.values.map((trade) => TradeListItem(trade: trade)).toList();
|
||||||
|
|
||||||
Future _updateTransactionsList() async {
|
Future _updateTransactionsList() async {
|
||||||
await _history.refresh();
|
|
||||||
final _transactions = await _history.getAll();
|
final _transactions = await _history.getAll();
|
||||||
await _setTransactions(_transactions);
|
await _setTransactions(_transactions);
|
||||||
}
|
}
|
||||||
|
@ -203,22 +207,29 @@ abstract class ActionListBase with Store {
|
||||||
Future _setTransactions(List<TransactionInfo> transactions) async {
|
Future _setTransactions(List<TransactionInfo> transactions) async {
|
||||||
final wallet = _walletService.currentWallet;
|
final wallet = _walletService.currentWallet;
|
||||||
List<TransactionInfo> sortedTransactions = transactions.map((transaction) {
|
List<TransactionInfo> sortedTransactions = transactions.map((transaction) {
|
||||||
if (transactionDescriptions.values.isNotEmpty) {
|
if (transaction is MoneroTransactionInfo) {
|
||||||
final description = transactionDescriptions.values.firstWhere(
|
if (transactionDescriptions.values.isNotEmpty) {
|
||||||
(desc) => desc.id == transaction.id,
|
final description = transactionDescriptions.values.firstWhere(
|
||||||
orElse: () => null);
|
(desc) => desc.id == transaction.id,
|
||||||
|
orElse: () => null);
|
||||||
|
|
||||||
if (description != null && description.recipientAddress != null) {
|
if (description != null && description.recipientAddress != null) {
|
||||||
transaction.recipientAddress = description.recipientAddress;
|
transaction.recipientAddress = description.recipientAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
if (wallet is MoneroWallet) {
|
if (wallet is MoneroWallet) {
|
||||||
sortedTransactions =
|
sortedTransactions = transactions
|
||||||
transactions.where((tx) => tx.accountIndex == _account.id).toList();
|
.where((tx) => tx is MoneroTransactionInfo
|
||||||
|
? tx.accountIndex == _account.id
|
||||||
|
: false)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._transactions = sortedTransactions
|
this._transactions = sortedTransactions
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||||
|
@ -87,17 +88,30 @@ abstract class BalanceStoreBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _onBalanceChange(Balance balance) async {
|
Future _onBalanceChange(Balance balance) async {
|
||||||
final _balance = balance as MoneroBalance;
|
if (balance is MoneroBalance) {
|
||||||
|
await _onMoneroBalanceChange(balance);
|
||||||
if (this.fullBalance != _balance.fullBalance) {
|
|
||||||
this.fullBalance = _balance.fullBalance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.unlockedBalance != _balance.unlockedBalance) {
|
if (balance is BitcoinBalance) {
|
||||||
this.unlockedBalance = _balance.unlockedBalance;
|
await _onBitcoinBalanceChange(balance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future _onMoneroBalanceChange(MoneroBalance balance) async {
|
||||||
|
if (this.fullBalance != balance.fullBalance) {
|
||||||
|
this.fullBalance = balance.fullBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.unlockedBalance != balance.unlockedBalance) {
|
||||||
|
this.unlockedBalance = balance.unlockedBalance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _onBitcoinBalanceChange(BitcoinBalance balance) async {
|
||||||
|
fullBalance = balance.totalFormatted;
|
||||||
|
unlockedBalance = balance.totalFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
Future _onWalletChanged(Wallet wallet) async {
|
Future _onWalletChanged(Wallet wallet) async {
|
||||||
if (_onBalanceChangeSubscription != null) {
|
if (_onBalanceChangeSubscription != null) {
|
||||||
await _onBalanceChangeSubscription.cancel();
|
await _onBalanceChangeSubscription.cancel();
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||||
|
@ -15,10 +16,17 @@ abstract class WalletKeysStoreBase with Store {
|
||||||
|
|
||||||
if (walletService.currentWallet != null) {
|
if (walletService.currentWallet != null) {
|
||||||
walletService.getKeys().then((keys) {
|
walletService.getKeys().then((keys) {
|
||||||
publicViewKey = keys['publicViewKey'];
|
if (walletService.getType() == WalletType.monero) {
|
||||||
privateViewKey = keys['privateViewKey'];
|
publicViewKey = keys['publicViewKey'];
|
||||||
publicSpendKey = keys['publicSpendKey'];
|
privateViewKey = keys['privateViewKey'];
|
||||||
privateSpendKey = keys['privateSpendKey'];
|
publicSpendKey = keys['publicSpendKey'];
|
||||||
|
privateSpendKey = keys['privateSpendKey'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walletService.getType() == WalletType.bitcoin) {
|
||||||
|
publicViewKey = keys['publicKey'];
|
||||||
|
privateSpendKey = keys['privateKey'];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/account.dart';
|
import 'package:cake_wallet/src/domain/monero/account.dart';
|
||||||
|
@ -119,6 +120,7 @@ abstract class WalletStoreBase with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
address = await wallet.getAddress();
|
||||||
wallet.onNameChange.listen((name) => this.name = name);
|
wallet.onNameChange.listen((name) => this.name = name);
|
||||||
wallet.onAddressChange.listen((address) => this.address = address);
|
wallet.onAddressChange.listen((address) => this.address = address);
|
||||||
|
|
||||||
|
@ -160,4 +162,9 @@ abstract class WalletStoreBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isConnected() async => await _walletService.isConnected();
|
Future<bool> isConnected() async => await _walletService.isConnected();
|
||||||
|
|
||||||
|
WalletType getType() => _walletService.getType();
|
||||||
|
|
||||||
|
String get getAddress =>
|
||||||
|
getType() == WalletType.monero ? subaddress.address : address;
|
||||||
}
|
}
|
||||||
|
|
42
pubspec.lock
42
pubspec.lock
|
@ -57,6 +57,34 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.3"
|
version: "1.9.3"
|
||||||
|
bech32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bech32
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
|
bip32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bip32
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
bip39:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bip39
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
bitcoin_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: bitcoin_flutter
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -64,6 +92,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
bs58check:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bs58check
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
build:
|
build:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -336,6 +371,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
hex:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hex
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
hive:
|
hive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -56,6 +56,7 @@ dependencies:
|
||||||
encrypt: ^4.0.0
|
encrypt: ^4.0.0
|
||||||
password: ^1.0.0
|
password: ^1.0.0
|
||||||
basic_utils: ^1.0.8
|
basic_utils: ^1.0.8
|
||||||
|
bitcoin_flutter: ^2.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue