Merge branch 'main' into CAKE-306-add-moonpay-option-for-btc-buying

# Conflicts:
#	lib/buy/wyre/wyre_buy_provider.dart
#	lib/di.dart
#	lib/src/screens/dashboard/dashboard_page.dart
#	lib/view_model/dashboard/dashboard_view_model.dart
#	res/values/strings_de.arb
#	res/values/strings_en.arb
#	res/values/strings_es.arb
#	res/values/strings_hi.arb
#	res/values/strings_ja.arb
#	res/values/strings_ko.arb
#	res/values/strings_nl.arb
#	res/values/strings_pl.arb
#	res/values/strings_pt.arb
#	res/values/strings_ru.arb
#	res/values/strings_uk.arb
#	res/values/strings_zh.arb
This commit is contained in:
OleksandrSobol 2021-05-28 12:06:50 +03:00
commit 1fccb0b546
136 changed files with 3144 additions and 1479 deletions

2
.gitignore vendored
View file

@ -96,3 +96,5 @@ vendor/
android/app/.cxx/** android/app/.cxx/**
ios/Flutter/.last_build_id ios/Flutter/.last_build_id
/lib/generated/** /lib/generated/**
#**#
/**/#**#

View file

@ -25,7 +25,6 @@ linter:
- empty_constructor_bodies - empty_constructor_bodies
- empty_statements - empty_statements
- hash_and_equals - hash_and_equals
- implementation_imports
- invariant_booleans - invariant_booleans
- iterable_contains_unrelated_type - iterable_contains_unrelated_type
- library_names - library_names

View file

@ -1,15 +1,49 @@
package com.cakewallet.cake_wallet; package com.cakewallet.cake_wallet;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterFragmentActivity; import io.flutter.embedding.android.FlutterFragmentActivity;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant; import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import java.security.SecureRandom;
public class MainActivity extends FlutterFragmentActivity { public class MainActivity extends FlutterFragmentActivity {
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
@Override @Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine); GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel utilsChannel =
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),
UTILS_CHANNEL);
utilsChannel.setMethodCallHandler(this::handle);
}
private void handle(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
Handler handler = new Handler(Looper.getMainLooper());
try {
if (call.method.equals("sec_random")) {
int count = call.argument("count");
SecureRandom random = new SecureRandom();
byte bytes[] = new byte[count];
random.nextBytes(bytes);
handler.post(() -> result.success(bytes));
} else {
handler.post(() -> result.notImplemented());
}
} catch (Exception e) {
handler.post(() -> result.error("UNCAUGHT_ERROR", e.getMessage(), null));
}
} }
} }

View file

@ -1,13 +0,0 @@
package com.cakewallet.cake_wallet
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine){
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/white" />
<foreground android:drawable="@drawable/ic_launcher" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,2 @@
-
uri: ltc-electrum.cakewallet.com:50002

View file

@ -179,8 +179,6 @@ extern "C"
Monero::SubaddressAccount *m_account; Monero::SubaddressAccount *m_account;
uint64_t m_last_known_wallet_height; uint64_t m_last_known_wallet_height;
uint64_t m_cached_syncing_blockchain_height = 0; uint64_t m_cached_syncing_blockchain_height = 0;
std::mutex store_mutex;
void change_current_wallet(Monero::Wallet *wallet) void change_current_wallet(Monero::Wallet *wallet)
{ {
@ -451,9 +449,7 @@ extern "C"
void store(char *path) void store(char *path)
{ {
store_mutex.lock();
get_current_wallet()->store(std::string(path)); get_current_wallet()->store(std::string(path));
store_mutex.unlock();
} }
bool transaction_create(char *address, char *payment_id, char *amount, bool transaction_create(char *address, char *payment_id, char *amount,

View file

@ -5,20 +5,18 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'cw_monero' s.name = 'cw_monero'
s.version = '0.0.2' s.version = '0.0.2'
s.summary = 'A new flutter plugin project.' s.summary = 'CW Monero'
s.description = <<-DESC s.description = 'Cake Wallet wrapper over Monero project.'
A new flutter plugin project. s.homepage = 'http://cakewallet.com'
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' } s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' } s.author = { 'CakeWallet' => 'support@cakewallet.com' }
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h, Classes/*.h, External/ios/libs/monero/include/src/**/*.h, External/ios/libs/monero/include/contrib/**/*.h, External/ios/libs/monero/include/External/ios/**/*.h' s.public_header_files = 'Classes/**/*.h, Classes/*.h, External/ios/libs/monero/include/src/**/*.h, External/ios/libs/monero/include/contrib/**/*.h, External/ios/libs/monero/include/External/ios/**/*.h'
s.dependency 'Flutter' s.dependency 'Flutter'
s.platform = :ios, '9.0' s.platform = :ios, '9.0'
s.swift_version = '4.0' s.swift_version = '4.0'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'arm64' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'arm64', 'ENABLE_BITCODE' => 'NO' }
s.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/Classes/*.h" } s.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/Classes/*.h" }
s.subspec 'OpenSSL' do |openssl| s.subspec 'OpenSSL' do |openssl|
@ -53,4 +51,4 @@ A new flutter plugin project.
lmdb.vendored_libraries = 'External/ios/libs/lmdb/liblmdb.a' lmdb.vendored_libraries = 'External/ios/libs/lmdb/liblmdb.a'
lmdb.libraries = 'lmdb' lmdb.libraries = 'lmdb'
end end
end end

View file

@ -5,6 +5,7 @@ import 'package:cw_monero/types.dart';
import 'package:cw_monero/monero_api.dart'; import 'package:cw_monero/monero_api.dart';
import 'package:cw_monero/structs/account_row.dart'; import 'package:cw_monero/structs/account_row.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cw_monero/wallet.dart';
final accountSizeNative = moneroApi final accountSizeNative = moneroApi
.lookup<NativeFunction<account_size>>('account_size') .lookup<NativeFunction<account_size>>('account_size')
@ -70,8 +71,13 @@ void _setLabelForAccount(Map<String, dynamic> args) {
setLabelForAccountSync(label: label, accountIndex: accountIndex); setLabelForAccountSync(label: label, accountIndex: accountIndex);
} }
Future<void> addAccount({String label}) async => compute(_addAccount, label); Future<void> addAccount({String label}) async {
await compute(_addAccount, label);
await store();
}
Future<void> setLabelForAccount({int accountIndex, String label}) async => Future<void> setLabelForAccount({int accountIndex, String label}) async {
compute( await compute(
_setLabelForAccount, {'accountIndex': accountIndex, 'label': label}); _setLabelForAccount, {'accountIndex': accountIndex, 'label': label});
await store();
}

View file

@ -5,6 +5,7 @@ import 'package:cw_monero/signatures.dart';
import 'package:cw_monero/types.dart'; import 'package:cw_monero/types.dart';
import 'package:cw_monero/monero_api.dart'; import 'package:cw_monero/monero_api.dart';
import 'package:cw_monero/structs/subaddress_row.dart'; import 'package:cw_monero/structs/subaddress_row.dart';
import 'package:cw_monero/wallet.dart';
final subaddressSizeNative = moneroApi final subaddressSizeNative = moneroApi
.lookup<NativeFunction<subaddrress_size>>('subaddrress_size') .lookup<NativeFunction<subaddrress_size>>('subaddrress_size')
@ -79,14 +80,18 @@ void _setLabelForSubaddress(Map<String, dynamic> args) {
accountIndex: accountIndex, addressIndex: addressIndex, label: label); accountIndex: accountIndex, addressIndex: addressIndex, label: label);
} }
Future addSubaddress({int accountIndex, String label}) async => Future addSubaddress({int accountIndex, String label}) async {
compute<Map<String, Object>, void>( await compute<Map<String, Object>, void>(
_addSubaddress, {'accountIndex': accountIndex, 'label': label}); _addSubaddress, {'accountIndex': accountIndex, 'label': label});
await store();
}
Future setLabelForSubaddress( Future setLabelForSubaddress(
{int accountIndex, int addressIndex, String label}) => {int accountIndex, int addressIndex, String label}) async {
compute<Map<String, Object>, void>(_setLabelForSubaddress, { await compute<Map<String, Object>, void>(_setLabelForSubaddress, {
'accountIndex': accountIndex, 'accountIndex': accountIndex,
'addressIndex': addressIndex, 'addressIndex': addressIndex,
'label': label 'label': label
}); });
await store();
}

View file

@ -1,12 +1,12 @@
import 'dart:ffi'; import 'dart:ffi';
import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/wallet.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cw_monero/convert_utf8_to_string.dart'; import 'package:cw_monero/convert_utf8_to_string.dart';
import 'package:cw_monero/signatures.dart'; import 'package:cw_monero/signatures.dart';
import 'package:cw_monero/types.dart'; import 'package:cw_monero/types.dart';
import 'package:cw_monero/monero_api.dart'; import 'package:cw_monero/monero_api.dart';
import 'package:cw_monero/wallet.dart';
import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/exceptions/wallet_creation_exception.dart'; import 'package:cw_monero/exceptions/wallet_creation_exception.dart';
import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart';
import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart'; import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart';

View file

@ -7,49 +7,49 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.1.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.3" version: "1.2.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.1.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.14.13" version: "1.15.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
ffi: ffi:
dependency: "direct main" dependency: "direct main"
description: description:
@ -73,21 +73,21 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.8" version: "0.12.10"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.8" version: "1.3.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -113,56 +113,56 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.5" version: "1.10.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.1.0"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.5" version: "1.1.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.17" version: "0.2.19"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.1.0"
sdks: sdks:
dart: ">=2.9.0-14.0.dev <3.0.0" dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=0.1.4 <2.0.0" flutter: ">=0.1.4"

View file

@ -8,7 +8,7 @@ The following are the system requirements to build CakeWallet for your Android d
Ubuntu >= 16.04 Ubuntu >= 16.04
Android SDK 28 Android SDK 28
Android NDK 17c Android NDK 17c
Flutter 1.22.6 Flutter 2 or above
``` ```
## Building CakeWallet on Android ## Building CakeWallet on Android
@ -51,9 +51,9 @@ You may download and install the latest version of Android Studio [here](https:/
### 3. Installing Flutter ### 3. Installing Flutter
CakeWallet requires **EXACTLY** Flutter version `1.22.6` to build properly. The easiest way to install Flutter is by using the `snap` package manager. Other means of installing Flutter on your system can be found [here](https://flutter.dev/docs/get-started/install/linux).
To install this version of Flutter on your Ubuntu system, please use [these instructions](https://flutter.dev/docs/get-started/install/linux#install-flutter-manually). `$ sudo snap install flutter --classic`
### 4. Verify Installations ### 4. Verify Installations
@ -64,7 +64,7 @@ Verify that the Android toolchain, Flutter, and Android Studio have been correct
The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding. The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding.
``` ```
Doctor summary (to see all details, run flutter doctor -v): Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.22.6, on Linux, locale en_US.UTF-8) [✓] Flutter (Channel stable, 2.0.2, on Linux, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 28) [✓] Android toolchain - develop for Android devices (Android SDK version 28)
[✓] Android Studio (version 4.0) [✓] Android Studio (version 4.0)
``` ```
@ -87,7 +87,7 @@ $ cd /opt/android
..and download the source code into that directory. ..and download the source code into that directory.
`$ git clone https://github.com/cake-tech/cake_wallet.git --branch deploy` `$ git clone https://github.com/cake-tech/cake_wallet.git --branch main`
Proceed into the source code before proceeding with the next steps: Proceed into the source code before proceeding with the next steps:

View file

@ -0,0 +1,12 @@
import Foundation
func secRandom(count: Int) -> Data? {
var bytes = [Int8](repeating: 0, count: count)
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
if status == errSecSuccess {
return Data(bytes: bytes, count: bytes.count)
}
return nil
}

View file

@ -72,7 +72,7 @@ PODS:
- Flutter - Flutter
- path_provider (0.0.1): - path_provider (0.0.1):
- Flutter - Flutter
- "permission_handler (5.0.1+1)": - "permission_handler (5.1.0+2)":
- Flutter - Flutter
- Reachability (3.2) - Reachability (3.2)
- SDWebImage (5.9.1): - SDWebImage (5.9.1):
@ -157,19 +157,19 @@ SPEC CHECKSUMS:
barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060 CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060
cw_monero: 2e1f79929880cc2293b5bc1b25e28152e4d84649 cw_monero: 78f369253cc913efc23db9cf6be81a11eaf40fe1
devicelocale: feebbe5e7a30adb8c4f83185de1b50ff19b44f00 devicelocale: b22617f40038496deffba44747101255cee005b0
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4 esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
permission_handler: eac8e15b4a1a3fba55b761d19f3f4e6b005d15b6 permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5 SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5
share: 0b2c3e82132f5888bccca3351c504d0003b3b410 share: 0b2c3e82132f5888bccca3351c504d0003b3b410
@ -177,7 +177,7 @@ SPEC CHECKSUMS:
SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699 SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
webview_flutter: d2b4d6c66968ad042ad94cbb791f5b72b4678a96 webview_flutter: 9f491a9b5a66f2573946a389b2677987b0ff8c0b
PODFILE CHECKSUM: 5b5f101b119a1b6eb857c967d462832a9062dec4 PODFILE CHECKSUM: 5b5f101b119a1b6eb857c967d462832a9062dec4

View file

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44A7192518EF8000B570ED /* decrypt.swift */; }; 0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44A7192518EF8000B570ED /* decrypt.swift */; };
0C9D68C9264854B60011B691 /* secRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9D68C8264854B60011B691 /* secRandom.swift */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
20ED0868E1BD7E12278C0CB3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */; }; 20ED0868E1BD7E12278C0CB3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
@ -21,6 +22,7 @@
0C400E0F25B21ABB0025E469 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; }; 0C400E0F25B21ABB0025E469 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
0C44A7192518EF8000B570ED /* decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = decrypt.swift; sourceTree = "<group>"; }; 0C44A7192518EF8000B570ED /* decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = decrypt.swift; sourceTree = "<group>"; };
0C9986A3251A932F00D566FD /* CryptoSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CryptoSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0C9986A3251A932F00D566FD /* CryptoSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CryptoSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0C9D68C8264854B60011B691 /* secRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = secRandom.swift; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
20F67A1B2C2FCB2A3BB048C1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; 20F67A1B2C2FCB2A3BB048C1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
@ -65,6 +67,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0C44A7192518EF8000B570ED /* decrypt.swift */, 0C44A7192518EF8000B570ED /* decrypt.swift */,
0C9D68C8264854B60011B691 /* secRandom.swift */,
); );
path = CakeWallet; path = CakeWallet;
sourceTree = "<group>"; sourceTree = "<group>";
@ -274,6 +277,7 @@
files = ( files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
0C9D68C9264854B60011B691 /* secRandom.swift in Sources */,
0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */, 0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -331,6 +335,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -357,7 +362,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 31; CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = 32J6BB6VUS; DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -374,7 +379,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 4.1.3; MARKETING_VERSION = 4.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -415,6 +420,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -470,6 +476,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -498,7 +505,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 31; CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = 32J6BB6VUS; DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -515,7 +522,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 4.1.3; MARKETING_VERSION = 4.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -533,7 +540,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 31; CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = 32J6BB6VUS; DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -550,7 +557,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
MARKETING_VERSION = 4.1.3; MARKETING_VERSION = 4.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View file

@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "self:">
</FileRef> </FileRef>
</Workspace> </Workspace>

View file

@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"

View file

@ -8,9 +8,10 @@ import Flutter
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "com.cakewallet.cakewallet/legacy_wallet_migration", let legacyMigrationChannel = FlutterMethodChannel(
binaryMessenger: controller.binaryMessenger) name: "com.cakewallet.cakewallet/legacy_wallet_migration",
batteryChannel.setMethodCallHandler({ binaryMessenger: controller.binaryMessenger)
legacyMigrationChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch call.method { switch call.method {
@ -52,6 +53,24 @@ import Flutter
} }
}) })
let utilsChannel = FlutterMethodChannel(
name: "com.cake_wallet/native_utils",
binaryMessenger: controller.binaryMessenger)
utilsChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch call.method {
case "sec_random":
guard let args = call.arguments as? Dictionary<String, Any>,
let count = args["count"] as? Int else {
result(nil)
return
}
result(secRandom(count: count))
default:
result(FlutterMethodNotImplemented)
}
})
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }

View file

@ -2,100 +2,100 @@
"images" : [ "images" : [
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "20x20", "scale" : "2x",
"scale" : "2x" "size" : "20x20"
}, },
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "20x20", "scale" : "3x",
"scale" : "3x" "size" : "20x20"
}, },
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "29x29", "scale" : "2x",
"scale" : "2x" "size" : "29x29"
}, },
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "29x29", "scale" : "3x",
"scale" : "3x" "size" : "29x29"
}, },
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "40x40", "scale" : "2x",
"scale" : "2x" "size" : "40x40"
}, },
{ {
"idiom" : "iphone", "idiom" : "iphone",
"size" : "40x40", "scale" : "3x",
"scale" : "3x" "size" : "40x40"
}, },
{ {
"size" : "60x60", "filename" : "app_icon_120.png",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "cake_xmr_120.png", "scale" : "2x",
"scale" : "2x" "size" : "60x60"
}, },
{ {
"size" : "60x60", "filename" : "app_icon_180.png",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "cake_xmr_180.png", "scale" : "3x",
"scale" : "3x" "size" : "60x60"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "20x20", "scale" : "1x",
"scale" : "1x" "size" : "20x20"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "20x20", "scale" : "2x",
"scale" : "2x" "size" : "20x20"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "29x29", "scale" : "1x",
"scale" : "1x" "size" : "29x29"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "29x29", "scale" : "2x",
"scale" : "2x" "size" : "29x29"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "40x40", "scale" : "1x",
"scale" : "1x" "size" : "40x40"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "40x40", "scale" : "2x",
"scale" : "2x" "size" : "40x40"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "76x76", "scale" : "1x",
"scale" : "1x" "size" : "76x76"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "76x76", "scale" : "2x",
"scale" : "2x" "size" : "76x76"
}, },
{ {
"idiom" : "ipad", "idiom" : "ipad",
"size" : "83.5x83.5", "scale" : "2x",
"scale" : "2x" "size" : "83.5x83.5"
}, },
{ {
"size" : "1024x1024", "filename" : "app_icon_1024.png",
"idiom" : "ios-marketing", "idiom" : "ios-marketing",
"filename" : "cake_xmr_1024.png", "scale" : "1x",
"scale" : "1x" "size" : "1024x1024"
} }
], ],
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -24,6 +24,11 @@
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Used for scan QR code</string> <string>Used for scan QR code</string>
<key>NSDocumentsFolderUsageDescription</key> <key>NSDocumentsFolderUsageDescription</key>

View file

@ -1,25 +1,29 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bs58check/bs58check.dart' as bs58check; import 'package:bs58check/bs58check.dart' as bs58check;
import 'package:bitcoin_flutter/src/utils/constants/op.dart'; import 'package:bitcoin_flutter/src/utils/constants/op.dart';
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript; import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
import 'package:bitcoin_flutter/src/address.dart'; import 'package:bitcoin_flutter/src/address.dart';
Uint8List p2shAddressToOutputScript(String address) { Uint8List p2shAddressToOutputScript(String address) {
final decodeBase58 = bs58check.decode(address); final decodeBase58 = bs58check.decode(address);
final hash = decodeBase58.sublist(1); final hash = decodeBase58.sublist(1);
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]); return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
} }
Uint8List addressToOutputScript(String address) { Uint8List addressToOutputScript(
String address, bitcoin.NetworkType networkType) {
try { try {
// FIXME: improve validation for p2sh addresses // FIXME: improve validation for p2sh addresses
if (address.startsWith('3')) { // 3 for bitcoin
// m for litecoin
if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
return p2shAddressToOutputScript(address); return p2shAddressToOutputScript(address);
} }
return Address.addressToOutputScript(address); return Address.addressToOutputScript(address, networkType);
} catch (_) { } catch (err) {
print(err);
return Uint8List(0); return Uint8List(0);
} }
} }

View file

@ -1,14 +1,14 @@
import 'dart:convert'; import 'dart:convert';
import 'package:quiver/core.dart';
class BitcoinAddressRecord { class BitcoinAddressRecord {
BitcoinAddressRecord(this.address, {this.index}); BitcoinAddressRecord(this.address, {this.index, bool isHidden})
: _isHidden = isHidden;
factory BitcoinAddressRecord.fromJSON(String jsonSource) { factory BitcoinAddressRecord.fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(decoded['address'] as String, return BitcoinAddressRecord(decoded['address'] as String,
index: decoded['index'] as int); index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool);
} }
@override @override
@ -16,10 +16,13 @@ class BitcoinAddressRecord {
o is BitcoinAddressRecord && address == o.address; o is BitcoinAddressRecord && address == o.address;
final String address; final String address;
bool get isHidden => _isHidden ?? false;
int index; int index;
final bool _isHidden;
@override @override
int get hashCode => address.hashCode; int get hashCode => address.hashCode;
String toJSON() => json.encode({'address': address, 'index': index}); String toJSON() =>
json.encode({'address': address, 'index': index, 'isHidden': isHidden});
} }

View file

@ -4,23 +4,11 @@ import 'dart:typed_data';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:unorm_dart/unorm_dart.dart' as unorm; import 'package:unorm_dart/unorm_dart.dart' as unorm;
import 'package:cryptography/cryptography.dart' as cryptography; import 'package:cryptography/cryptography.dart' as cryptography;
import 'package:cake_wallet/core/sec_random_native.dart';
const segwit = '100'; const segwit = '100';
final wordlist = englishWordlist; final wordlist = englishWordlist;
Uint8List randomBytes(int length, {bool secure = false}) {
assert(length > 0);
final random = secure ? Random.secure() : Random();
final ret = Uint8List(length);
for (var i = 0; i < length; i++) {
ret[i] = random.nextInt(256);
}
return ret;
}
double logBase(num x, num base) => log(x) / log(base); double logBase(num x, num base) => log(x) / log(base);
String mnemonicEncode(int i) { String mnemonicEncode(int i) {
@ -102,14 +90,15 @@ List<bool> prefixMatches(String source, List<String> prefixes) {
return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList(); return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList();
} }
String generateMnemonic({int strength = 132, String prefix = segwit}) { Future<String> generateMnemonic(
{int strength = 264, String prefix = segwit}) async {
final wordBitlen = logBase(wordlist.length, 2).ceil(); final wordBitlen = logBase(wordlist.length, 2).ceil();
final wordCount = strength / wordBitlen; final wordCount = strength / wordBitlen;
final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil(); final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil();
var result = ''; var result = '';
do { do {
final bytes = randomBytes(byteCount); final bytes = await secRandom(byteCount);
maskBytes(bytes, strength); maskBytes(bytes, strength);
result = encode(bytes); result = encode(bytes);
} while (!prefixMatches(result, [prefix]).first); } while (!prefixMatches(result, [prefix]).first);
@ -134,7 +123,7 @@ bool matchesAnyPrefix(String mnemonic) =>
bool validateMnemonic(String mnemonic, {String prefix = segwit}) { bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
try { try {
return matchesAnyPrefix(mnemonic); return matchesAnyPrefix(mnemonic);
} catch(e) { } catch (e) {
return false; return false;
} }
} }

View file

@ -1,5 +1,5 @@
class BitcoinMnemonicIsIncorrectException implements Exception { class BitcoinMnemonicIsIncorrectException implements Exception {
@override @override
String toString() => String toString() =>
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 words separated by space.'; 'Bitcoin mnemonic has incorrect format. Mnemonic should contain 24 words separated by space.';
} }

View file

@ -1,192 +0,0 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/bitcoin/electrum.dart';
part 'bitcoin_transaction_history.g.dart';
const _transactionsHistoryFileName = 'transactions.json';
class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
with _$BitcoinTransactionHistory;
abstract class BitcoinTransactionHistoryBase
extends TransactionHistoryBase<BitcoinTransactionInfo> with Store {
BitcoinTransactionHistoryBase(
{this.eclient, String dirPath, @required String password})
: path = '$dirPath/$_transactionsHistoryFileName',
_password = password,
_height = 0,
_isUpdating = false {
transactions = ObservableMap<String, BitcoinTransactionInfo>();
}
BitcoinWalletBase wallet;
final ElectrumClient eclient;
final String path;
final String _password;
int _height;
bool _isUpdating;
Future<void> init() async {
await _load();
}
@override
Future update() async {
if (_isUpdating) {
return;
}
try {
_isUpdating = true;
final txs = await fetchTransactions();
await add(txs);
_isUpdating = false;
} catch (_) {
_isUpdating = false;
rethrow;
}
}
@override
Future<Map<String, BitcoinTransactionInfo>> fetchTransactions() async {
final histories =
wallet.scriptHashes.map((scriptHash) => eclient.getHistory(scriptHash));
final _historiesWithDetails = await Future.wait(histories)
.then((histories) => histories.expand((i) => i).toList())
.then((histories) => histories.map((tx) => fetchTransactionInfo(
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
final historiesWithDetails = await Future.wait(_historiesWithDetails);
return historiesWithDetails.fold<Map<String, BitcoinTransactionInfo>>(
<String, BitcoinTransactionInfo>{}, (acc, tx) {
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
return acc;
});
}
Future<BitcoinTransactionInfo> fetchTransactionInfo(
{@required String hash, @required int height}) async {
final tx = await eclient.getTransactionExpanded(hash: hash);
return BitcoinTransactionInfo.fromElectrumVerbose(tx,
height: height, addresses: wallet.addresses);
}
Future<void> add(Map<String, BitcoinTransactionInfo> transactionsList) async {
transactionsList.entries.forEach((entry) {
_updateOrInsert(entry.value);
if (entry.value.height > _height) {
_height = entry.value.height;
}
});
await save();
}
Future<void> addOne(BitcoinTransactionInfo tx) async {
_updateOrInsert(tx);
if (tx.height > _height) {
_height = tx.height;
}
await save();
}
BitcoinTransactionInfo get(String id) => transactions[id];
Future<void> save() async {
try {
final data = json.encode({'height': _height, 'transactions': transactions});
await writeData(path: path, password: _password, data: data);
} catch(e) {
print('Error while save bitcoin transaction history: ${e.toString()}');
}
}
@override
void updateAsync({void Function() onFinished}) {
fetchTransactionsAsync((transaction) => _updateOrInsert(transaction),
onFinished: onFinished);
}
@override
void fetchTransactionsAsync(
void Function(BitcoinTransactionInfo transaction) onTransactionLoaded,
{void Function() onFinished}) async {
final histories = await Future.wait(wallet.scriptHashes
.map((scriptHash) async => await eclient.getHistory(scriptHash)));
final transactionsCount =
histories.fold<int>(0, (acc, m) => acc + m.length);
var counter = 0;
final batches = histories.map((metaList) =>
_fetchBatchOfTransactions(metaList, onTransactionLoaded: (transaction) {
onTransactionLoaded(transaction);
counter += 1;
if (counter == transactionsCount) {
onFinished?.call();
}
}));
await Future.wait(batches);
}
Future<void> _fetchBatchOfTransactions(
Iterable<Map<String, dynamic>> metaList,
{void Function(BitcoinTransactionInfo tranasaction)
onTransactionLoaded}) async =>
metaList.forEach((txMeta) => fetchTransactionInfo(
hash: txMeta['tx_hash'] as String,
height: txMeta['height'] as int)
.then((transaction) => onTransactionLoaded(transaction)));
Future<Map<String, Object>> _read() async {
final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, Object>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, Object> ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, Object>) {
final tx = BitcoinTransactionInfo.fromJson(val);
_updateOrInsert(tx);
}
});
_height = content['height'] as int;
} catch (e) {
print(e);
}
}
void _updateOrInsert(BitcoinTransactionInfo transaction) {
if (transaction.id == null) {
return;
}
if (transactions[transaction.id] == null) {
transactions[transaction.id] = transaction;
} else {
final originalTx = transactions[transaction.id];
originalTx.confirmations = transaction.confirmations;
originalTx.amount = transaction.amount;
originalTx.height = transaction.height;
originalTx.date ??= transaction.date;
originalTx.isPending = transaction.isPending;
}
}
}

View file

@ -26,13 +26,15 @@ class BitcoinTransactionPriority extends TransactionPriority {
} }
} }
String get units => 'sat';
@override @override
String toString() { String toString() {
var label = ''; var label = '';
switch (this) { switch (this) {
case BitcoinTransactionPriority.slow: case BitcoinTransactionPriority.slow:
label = S.current.transaction_priority_slow; label = '${S.current.transaction_priority_slow} ~24hrs';
break; break;
case BitcoinTransactionPriority.medium: case BitcoinTransactionPriority.medium:
label = S.current.transaction_priority_medium; label = S.current.transaction_priority_medium;
@ -46,4 +48,56 @@ class BitcoinTransactionPriority extends TransactionPriority {
return label; return label;
} }
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
}
class LitecoinTransactionPriority extends BitcoinTransactionPriority {
const LitecoinTransactionPriority({String title, int raw})
: super(title: title, raw: raw);
static const List<LitecoinTransactionPriority> all = [fast, medium, slow];
static const LitecoinTransactionPriority slow =
LitecoinTransactionPriority(title: 'Slow', raw: 0);
static const LitecoinTransactionPriority medium =
LitecoinTransactionPriority(title: 'Medium', raw: 1);
static const LitecoinTransactionPriority fast =
LitecoinTransactionPriority(title: 'Fast', raw: 2);
static LitecoinTransactionPriority deserialize({int raw}) {
switch (raw) {
case 0:
return slow;
case 1:
return medium;
case 2:
return fast;
default:
return null;
}
}
@override
String get units => 'Latoshi';
@override
String toString() {
var label = '';
switch (this) {
case LitecoinTransactionPriority.slow:
label = S.current.transaction_priority_slow;
break;
case LitecoinTransactionPriority.medium:
label = S.current.transaction_priority_medium;
break;
case LitecoinTransactionPriority.fast:
label = S.current.transaction_priority_fast;
break;
default:
break;
}
return label;
}
} }

View file

@ -13,5 +13,6 @@ class BitcoinUnspent {
final int value; final int value;
final int vout; final int vout;
bool get isP2wpkh => address.address.startsWith('bc1'); bool get isP2wpkh =>
address.address.startsWith('bc') || address.address.startsWith('ltc');
} }

View file

@ -1,472 +1,51 @@
import 'dart:async';
import 'dart:convert';
import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
import 'package:cake_wallet/bitcoin/electrum.dart';
import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
import 'package:cake_wallet/bitcoin/script_hash.dart';
import 'package:cake_wallet/bitcoin/utils.dart'; import 'package:cake_wallet/bitcoin/utils.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
import 'package:cake_wallet/entities/sync_status.dart'; import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
import 'package:cake_wallet/entities/wallet_info.dart'; import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart'; import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/bitcoin/file.dart'; import 'package:cake_wallet/bitcoin/electrum_balance.dart';
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/core/wallet_base.dart';
part 'bitcoin_wallet.g.dart'; part 'bitcoin_wallet.g.dart';
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet; class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store { abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinWalletBase._internal( BitcoinWalletBase(
{@required this.eclient,
@required this.path,
@required String password,
@required WalletInfo walletInfo,
@required List<BitcoinAddressRecord> initialAddresses,
int accountIndex = 0,
this.transactionHistory,
this.mnemonic,
BitcoinBalance initialBalance})
: balance =
initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0),
hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
network: bitcoin.bitcoin)
.derivePath("m/0'/0"),
addresses = initialAddresses != null
? ObservableList<BitcoinAddressRecord>.of(initialAddresses.toSet())
: ObservableList<BitcoinAddressRecord>(),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_accountIndex = accountIndex,
_feeRates = <int>[],
super(walletInfo) {
_unspent = [];
_scripthashesUpdateSubject = {};
}
static BitcoinWallet fromJSON(
{@required String password,
@required String name,
@required String dirPath,
@required WalletInfo walletInfo,
String jsonSource}) {
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
final accountIndex =
(data['account_index'] == 'null' || data['account_index'] == null)
? 0
: int.parse(data['account_index'] as String);
final _addresses = data['addresses'] as List ?? <Object>[];
final addresses = <BitcoinAddressRecord>[];
final balance = BitcoinBalance.fromJSON(data['balance'] as String) ??
BitcoinBalance(confirmed: 0, unconfirmed: 0);
_addresses.forEach((Object el) {
if (el is String) {
addresses.add(BitcoinAddressRecord.fromJSON(el));
}
});
return BitcoinWalletBase.build(
dirPath: dirPath,
mnemonic: mnemonic,
password: password,
name: name,
accountIndex: accountIndex,
initialAddresses: addresses,
initialBalance: balance,
walletInfo: walletInfo);
}
static BitcoinWallet build(
{@required String mnemonic, {@required String mnemonic,
@required String password, @required String password,
@required String name,
@required String dirPath,
@required WalletInfo walletInfo, @required WalletInfo walletInfo,
List<BitcoinAddressRecord> initialAddresses, List<BitcoinAddressRecord> initialAddresses,
BitcoinBalance initialBalance, ElectrumBalance initialBalance,
int accountIndex = 0}) { int accountIndex = 0})
final walletPath = '$dirPath/$name'; : super(
final eclient = ElectrumClient(); mnemonic: mnemonic,
final history = BitcoinTransactionHistory( password: password,
eclient: eclient, dirPath: dirPath, password: password); walletInfo: walletInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
accountIndex: accountIndex);
return BitcoinWallet._internal( static Future<BitcoinWallet> open({
eclient: eclient, @required String name,
path: walletPath, @required WalletInfo walletInfo,
mnemonic: mnemonic, @required String password,
}) async {
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
await snp.load();
return BitcoinWallet(
mnemonic: snp.mnemonic,
password: password, password: password,
accountIndex: accountIndex, walletInfo: walletInfo,
initialAddresses: initialAddresses, initialAddresses: snp.addresses,
initialBalance: initialBalance, initialBalance: snp.balance,
transactionHistory: history, accountIndex: snp.accountIndex);
walletInfo: walletInfo);
}
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 146 + outputsCounts * 33 + 8;
@override
final BitcoinTransactionHistory transactionHistory;
final String path;
final bitcoin.HDWallet hd;
final ElectrumClient eclient;
final String mnemonic;
List<BitcoinUnspent> _unspent;
@override
@observable
String address;
@override
@observable
BitcoinBalance balance;
@override
@observable
SyncStatus syncStatus;
ObservableList<BitcoinAddressRecord> addresses;
List<String> get scriptHashes =>
addresses.map((addr) => scriptHash(addr.address)).toList();
String get xpub => hd.base58;
@override
String get seed => mnemonic;
@override
BitcoinWalletKeys get keys => BitcoinWalletKeys(
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
final String _password;
List<int> _feeRates;
int _accountIndex;
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
Future<void> init() async {
if (addresses.isEmpty || addresses.length < 33) {
final addressesCount = 33 - addresses.length;
await generateNewAddresses(addressesCount, startIndex: addresses.length);
}
address = addresses[_accountIndex].address;
transactionHistory.wallet = this;
await transactionHistory.init();
}
@action
Future<void> nextAddress() async {
_accountIndex += 1;
if (_accountIndex >= addresses.length) {
_accountIndex = 0;
}
address = addresses[_accountIndex].address;
await save();
}
Future<BitcoinAddressRecord> generateNewAddress() async {
_accountIndex += 1;
final address = BitcoinAddressRecord(_getAddress(index: _accountIndex),
index: _accountIndex);
addresses.add(address);
await save();
return address;
}
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
{int startIndex = 0}) async {
final list = <BitcoinAddressRecord>[];
for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord(_getAddress(index: i), index: i);
list.add(address);
}
addresses.addAll(list);
await save();
return list;
}
Future<void> updateAddress(String address) async {
for (final addr in addresses) {
if (addr.address == address) {
await save();
break;
}
}
}
@action
@override
Future<void> startSync() async {
try {
syncStatus = StartingSyncStatus();
transactionHistory.updateAsync(onFinished: () {
print('transactionHistory update finished!');
transactionHistory.save();
});
_subscribeForUpdates();
await _updateBalance();
await _updateUnspent();
_feeRates = await eclient.feeRates();
Timer.periodic(const Duration(minutes: 1),
(timer) async => _feeRates = await eclient.feeRates());
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e.toString());
syncStatus = FailedSyncStatus();
}
}
@action
@override
Future<void> connectToNode({@required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
await eclient.connectToUri(node.uri);
eclient.onConnectionStatusChange = (bool isConnected) {
if (!isConnected) {
syncStatus = LostConnectionSyncStatus();
}
};
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e.toString());
syncStatus = FailedSyncStatus();
}
} }
@override @override
Future<PendingBitcoinTransaction> createTransaction( String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
Object credentials) async { generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final allAmountFee =
calculateEstimatedFee(transactionCredentials.priority, null);
final allAmount = balance.confirmed - allAmountFee;
var fee = 0;
final credentialsAmount = transactionCredentials.amount != null
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: 0;
final amount = transactionCredentials.amount == null ||
allAmount - credentialsAmount < minAmount
? allAmount
: credentialsAmount;
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
final changeAddress = address;
var leftAmount = amount;
var totalInputAmount = 0;
if (_unspent.isEmpty) {
await _updateUnspent();
}
for (final utx in _unspent) {
leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value;
inputs.add(utx);
if (leftAmount <= 0) {
break;
}
}
if (inputs.isEmpty) {
throw BitcoinTransactionNoInputsException();
}
final totalAmount = amount + fee;
fee = transactionCredentials.amount != null
? feeAmountForPriority(transactionCredentials.priority, inputs.length,
amount == allAmount ? 1 : 2)
: allAmountFee;
if (totalAmount > balance.confirmed) {
throw BitcoinTransactionWrongBalanceException();
}
if (amount <= 0 || totalInputAmount < amount) {
throw BitcoinTransactionWrongBalanceException();
}
txb.setVersion(1);
inputs.forEach((input) {
if (input.isP2wpkh) {
final p2wpkh = bitcoin
.P2WPKH(
data: generatePaymentData(hd: hd, index: input.address.index),
network: bitcoin.bitcoin)
.data;
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
} else {
txb.addInput(input.hash, input.vout);
}
});
txb.addOutput(
addressToOutputScript(transactionCredentials.address), amount);
final estimatedSize = estimatedTransactionSize(inputs.length, 2);
final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
final changeValue = totalInputAmount - amount - feeAmount;
if (changeValue > minAmount) {
txb.addOutput(changeAddress, changeValue);
}
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
final keyPair = generateKeyPair(hd: hd, index: input.address.index);
final witnessValue = input.isP2wpkh ? input.value : null;
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
}
return PendingBitcoinTransaction(txb.build(),
eclient: eclient, amount: amount, fee: fee)
..addListener((transaction) async {
transactionHistory.addOne(transaction);
await _updateBalance();
});
}
String toJSON() => json.encode({
'mnemonic': mnemonic,
'account_index': _accountIndex.toString(),
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance?.toJSON()
});
int feeRate(TransactionPriority priority) {
if (priority is BitcoinTransactionPriority) {
return _feeRates[priority.raw];
}
return 0;
}
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
int outputsCount) =>
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
@override
int calculateEstimatedFee(TransactionPriority priority, int amount) {
if (priority is BitcoinTransactionPriority) {
int inputsCount = 0;
if (amount != null) {
int totalValue = 0;
for (final input in _unspent) {
if (totalValue >= amount) {
break;
}
totalValue += input.value;
inputsCount += 1;
}
} else {
inputsCount = _unspent.length;
}
// If send all, then we have no change value
return feeAmountForPriority(
priority, inputsCount, amount != null ? 2 : 1);
}
return 0;
}
@override
Future<void> save() async {
await write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
}
bitcoin.ECPair keyPairFor({@required int index}) =>
generateKeyPair(hd: hd, index: index);
@override
Future<void> rescan({int height}) async {
// FIXME: Unimplemented
}
@override
void close() async {
await eclient.close();
}
Future<void> _updateUnspent() async {
final unspent = await Future.wait(addresses.map((address) => eclient
.getListUnspentWithAddress(address.address)
.then((unspent) => unspent
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
_unspent = unspent.expand((e) => e).toList();
}
void _subscribeForUpdates() {
scriptHashes.forEach((sh) async {
await _scripthashesUpdateSubject[sh]?.close();
_scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh].listen((event) async {
try {
await _updateBalance();
await _updateUnspent();
transactionHistory.updateAsync();
} catch (e) {
print(e.toString());
}
});
});
}
Future<BitcoinBalance> _fetchBalances() async {
final balances = await Future.wait(
scriptHashes.map((sHash) => eclient.getBalance(sHash)));
final balance = balances.fold(
BitcoinBalance(confirmed: 0, unconfirmed: 0),
(BitcoinBalance acc, val) => BitcoinBalance(
confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
unconfirmed:
(val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
return balance;
}
Future<void> _updateBalance() async {
balance = await _fetchBalances();
await save();
}
String _getAddress({@required int index}) =>
generateAddress(hd: hd, index: index);
} }

View file

@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/core/wallet_service.dart';
@ -19,44 +18,32 @@ class BitcoinWalletService extends WalletService<
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
@override
WalletType getType() => WalletType.bitcoin;
@override @override
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async { Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
final dirPath = await pathForWalletDir( final wallet = BitcoinWallet(
type: WalletType.bitcoin, name: credentials.name); mnemonic: await generateMnemonic(),
final wallet = BitcoinWalletBase.build(
dirPath: dirPath,
mnemonic: generateMnemonic(),
password: credentials.password, password: credentials.password,
name: credentials.name,
walletInfo: credentials.walletInfo); walletInfo: credentials.walletInfo);
await wallet.save(); await wallet.save();
await wallet.init(); await wallet.init();
return wallet; return wallet;
} }
@override @override
Future<bool> isWalletExit(String name) async => Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: WalletType.bitcoin)) File(await pathForWallet(name: name, type: getType())).existsSync();
.existsSync();
@override @override
Future<BitcoinWallet> openWallet(String name, String password) async { Future<BitcoinWallet> openWallet(String name, String password) async {
final walletDirPath =
await pathForWalletDir(name: name, type: WalletType.bitcoin);
final walletPath = '$walletDirPath/$name';
final walletJSONRaw = await read(path: walletPath, password: password);
final walletInfo = walletInfoSource.values.firstWhere( final walletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(name, WalletType.bitcoin), (info) => info.id == WalletBase.idFor(name, getType()),
orElse: () => null); orElse: () => null);
final wallet = BitcoinWalletBase.fromJSON( final wallet = await BitcoinWalletBase.open(
password: password, password: password, name: name, walletInfo: walletInfo);
name: name,
dirPath: walletDirPath,
jsonSource: walletJSONRaw,
walletInfo: walletInfo);
await wallet.init(); await wallet.init();
return wallet; return wallet;
} }
@ -67,10 +54,8 @@ class BitcoinWalletService extends WalletService<
@override @override
Future<BitcoinWallet> restoreFromKeys( Future<BitcoinWallet> restoreFromKeys(
BitcoinRestoreWalletFromWIFCredentials credentials) async { BitcoinRestoreWalletFromWIFCredentials credentials) async =>
// TODO: implement restoreFromKeys throw UnimplementedError();
throw UnimplementedError();
}
@override @override
Future<BitcoinWallet> restoreFromSeed( Future<BitcoinWallet> restoreFromSeed(
@ -79,17 +64,12 @@ class BitcoinWalletService extends WalletService<
throw BitcoinMnemonicIsIncorrectException(); throw BitcoinMnemonicIsIncorrectException();
} }
final dirPath = await pathForWalletDir( final wallet = BitcoinWallet(
type: WalletType.bitcoin, name: credentials.name);
final wallet = BitcoinWalletBase.build(
dirPath: dirPath,
name: credentials.name,
password: credentials.password, password: credentials.password,
mnemonic: credentials.mnemonic, mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo); walletInfo: credentials.walletInfo);
await wallet.save(); await wallet.save();
await wallet.init(); await wallet.init();
return wallet; return wallet;
} }
} }

View file

@ -2,21 +2,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/script_hash.dart'; import 'package:cake_wallet/bitcoin/script_hash.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
class UriParseException implements Exception {
UriParseException(this.uri);
final String uri;
@override
String toString() =>
'Cannot parse host and port from uri. Invalid uri format. Uri: $uri';
}
String jsonrpcparams(List<Object> params) { String jsonrpcparams(List<Object> params) {
final _params = params?.map((val) => '"${val.toString()}"')?.join(','); final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
return '[$_params]'; return '[$_params]';
@ -53,17 +44,8 @@ class ElectrumClient {
Timer _aliveTimer; Timer _aliveTimer;
String unterminatedString; String unterminatedString;
Future<void> connectToUri(String uri) async { Future<void> connectToUri(Uri uri) async =>
final splittedUri = uri.split(':'); await connect(host: uri.host, port: uri.port);
if (splittedUri.length != 2) {
throw UriParseException(uri);
}
final host = splittedUri.first;
final port = int.parse(splittedUri.last);
await connect(host: host, port: port);
}
Future<void> connect({@required String host, @required int port}) async { Future<void> connect({@required String host, @required int port}) async {
try { try {
@ -173,10 +155,11 @@ class ElectrumClient {
}); });
Future<List<Map<String, dynamic>>> getListUnspentWithAddress( Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address) => String address, NetworkType networkType) =>
call( call(
method: 'blockchain.scripthash.listunspent', method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address)]).then((dynamic result) { params: [scriptHash(address, networkType: networkType)])
.then((dynamic result) {
if (result is List) { if (result is List) {
return result.map((dynamic val) { return result.map((dynamic val) {
if (val is Map<String, Object>) { if (val is Map<String, Object>) {
@ -259,7 +242,7 @@ class ElectrumClient {
if (result is String) { if (result is String) {
return result; return result;
} }
print(result);
return ''; return '';
}); });
@ -303,14 +286,24 @@ class ElectrumClient {
}); });
Future<List<int>> feeRates() async { Future<List<int>> feeRates() async {
final topDoubleString = await estimatefee(p: 1); try {
final middleDoubleString = await estimatefee(p: 20); final topDoubleString = await estimatefee(p: 1);
final bottomDoubleString = await estimatefee(p: 150); final middleDoubleString = await estimatefee(p: 20);
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); final bottomDoubleString = await estimatefee(p: 100);
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); final top =
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
.round();
final middle =
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
.round();
final bottom =
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
.round();
return [bottom, middle, top]; return [bottom, middle, top];
} catch (_) {
return [];
}
} }
BehaviorSubject<Object> scripthashUpdate(String scripthash) { BehaviorSubject<Object> scripthashUpdate(String scripthash) {

View file

@ -1,21 +1,20 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/entities/balance.dart';
class BitcoinBalance extends Balance { class ElectrumBalance extends Balance {
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) const ElectrumBalance({@required this.confirmed, @required this.unconfirmed})
: super(confirmed, unconfirmed); : super(confirmed, unconfirmed);
factory BitcoinBalance.fromJSON(String jsonSource) { factory ElectrumBalance.fromJSON(String jsonSource) {
if (jsonSource == null) { if (jsonSource == null) {
return null; return null;
} }
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return BitcoinBalance( return ElectrumBalance(
confirmed: decoded['confirmed'] as int ?? 0, confirmed: decoded['confirmed'] as int ?? 0,
unconfirmed: decoded['unconfirmed'] as int ?? 0); unconfirmed: decoded['unconfirmed'] as int ?? 0);
} }
@ -24,7 +23,8 @@ class BitcoinBalance extends Balance {
final int unconfirmed; final int unconfirmed;
@override @override
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed); String get formattedAvailableBalance =>
bitcoinAmountToString(amount: confirmed);
@override @override
String get formattedAdditionalBalance => String get formattedAdditionalBalance =>

View file

@ -0,0 +1,98 @@
import 'dart:convert';
import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
part 'electrum_transaction_history.g.dart';
const _transactionsHistoryFileName = 'transactions.json';
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
with _$ElectrumTransactionHistory;
abstract class ElectrumTransactionHistoryBase
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
ElectrumTransactionHistoryBase(
{@required this.walletInfo, @required String password})
: _password = password,
_height = 0 {
transactions = ObservableMap<String, ElectrumTransactionInfo>();
}
final WalletInfo walletInfo;
final String _password;
int _height;
Future<void> init() async => await _load();
@override
void addOne(ElectrumTransactionInfo transaction) =>
transactions[transaction.id] = transaction;
@override
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
@override
Future<void> save() async {
try {
final dirPath =
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$_transactionsHistoryFileName';
final data =
json.encode({'height': _height, 'transactions': transactions});
await writeData(path: path, password: _password, data: data);
} catch (e) {
print('Error while save bitcoin transaction history: ${e.toString()}');
}
}
Future<Map<String, Object>> _read() async {
final dirPath =
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$_transactionsHistoryFileName';
final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, Object>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, Object> ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, Object>) {
final tx = ElectrumTransactionInfo.fromJson(val, walletInfo.type);
_updateOrInsert(tx);
}
});
_height = content['height'] as int;
} catch (e) {
print(e);
}
}
void _updateOrInsert(ElectrumTransactionInfo transaction) {
if (transaction.id == null) {
return;
}
if (transactions[transaction.id] == null) {
transactions[transaction.id] = transaction;
} else {
final originalTx = transactions[transaction.id];
originalTx.confirmations = transaction.confirmations;
originalTx.amount = transaction.amount;
originalTx.height = transaction.height;
originalTx.date ??= transaction.date;
originalTx.isPending = transaction.isPending;
}
}
}

View file

@ -6,9 +6,10 @@ import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/entities/transaction_direction.dart'; import 'package:cake_wallet/entities/transaction_direction.dart';
import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/entities/format_amount.dart'; import 'package:cake_wallet/entities/format_amount.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
class BitcoinTransactionInfo extends TransactionInfo { class ElectrumTransactionInfo extends TransactionInfo {
BitcoinTransactionInfo( ElectrumTransactionInfo(this.type,
{@required String id, {@required String id,
@required int height, @required int height,
@required int amount, @required int amount,
@ -27,7 +28,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
this.confirmations = confirmations; this.confirmations = confirmations;
} }
factory BitcoinTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, factory ElectrumTransactionInfo.fromElectrumVerbose(
Map<String, Object> obj, WalletType type,
{@required List<BitcoinAddressRecord> addresses, @required int height}) { {@required List<BitcoinAddressRecord> addresses, @required int height}) {
final addressesSet = addresses.map((addr) => addr.address).toSet(); final addressesSet = addresses.map((addr) => addr.address).toSet();
final id = obj['txid'] as String; final id = obj['txid'] as String;
@ -47,7 +49,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
final out = vin['tx']['vout'][vout] as Map; final out = vin['tx']['vout'][vout] as Map;
final outAddresses = final outAddresses =
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet(); (out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString()); inputsAmount +=
stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) { if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
direction = TransactionDirection.outgoing; direction = TransactionDirection.outgoing;
@ -58,7 +61,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
final outAddresses = final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? []; out['scriptPubKey']['addresses'] as List<Object> ?? [];
final ntrs = outAddresses.toSet().intersection(addressesSet); final ntrs = outAddresses.toSet().intersection(addressesSet);
final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString()); final value = stringDoubleToBitcoinAmount(
(out['value'] as double ?? 0.0).toString());
totalOutAmount += value; totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
@ -69,7 +73,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final fee = inputsAmount - totalOutAmount; final fee = inputsAmount - totalOutAmount;
return BitcoinTransactionInfo( return ElectrumTransactionInfo(type,
id: id, id: id,
height: height, height: height,
isPending: false, isPending: false,
@ -80,7 +84,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
confirmations: confirmations); confirmations: confirmations);
} }
factory BitcoinTransactionInfo.fromHexAndHeader(String hex, factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
{List<String> addresses, int height, int timestamp, int confirmations}) { {List<String> addresses, int height, int timestamp, int confirmations}) {
final tx = bitcoin.Transaction.fromHex(hex); final tx = bitcoin.Transaction.fromHex(hex);
var exist = false; var exist = false;
@ -104,7 +108,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
: DateTime.now(); : DateTime.now();
return BitcoinTransactionInfo( return ElectrumTransactionInfo(type,
id: tx.getId(), id: tx.getId(),
height: height, height: height,
isPending: false, isPending: false,
@ -115,8 +119,9 @@ class BitcoinTransactionInfo extends TransactionInfo {
confirmations: confirmations); confirmations: confirmations);
} }
factory BitcoinTransactionInfo.fromJson(Map<String, dynamic> data) { factory ElectrumTransactionInfo.fromJson(
return BitcoinTransactionInfo( Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type,
id: data['id'] as String, id: data['id'] as String,
height: data['height'] as int, height: data['height'] as int,
amount: data['amount'] as int, amount: data['amount'] as int,
@ -127,15 +132,17 @@ class BitcoinTransactionInfo extends TransactionInfo {
confirmations: data['confirmations'] as int); confirmations: data['confirmations'] as int);
} }
final WalletType type;
String _fiatAmount; String _fiatAmount;
@override @override
String amountFormatted() => String amountFormatted() =>
'${formatAmount(bitcoinAmountToString(amount: amount))} BTC'; '${formatAmount(bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}';
@override @override
String feeFormatted() => fee != null String feeFormatted() => fee != null
? '${formatAmount(bitcoinAmountToString(amount: fee))} BTC' ? '${formatAmount(bitcoinAmountToString(amount: fee))} ${walletTypeToCryptoCurrency(type).title}'
: ''; : '';
@override @override
@ -144,8 +151,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
@override @override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
BitcoinTransactionInfo updated(BitcoinTransactionInfo info) { ElectrumTransactionInfo updated(ElectrumTransactionInfo info) {
return BitcoinTransactionInfo( return ElectrumTransactionInfo(info.type,
id: id, id: id,
height: info.height, height: info.height,
amount: info.amount, amount: info.amount,

View file

@ -0,0 +1,467 @@
import 'dart:async';
import 'dart:convert';
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cake_wallet/bitcoin/electrum_transaction_history.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
import 'package:cake_wallet/bitcoin/script_hash.dart';
import 'package:cake_wallet/bitcoin/utils.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/entities/sync_status.dart';
import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/bitcoin/electrum.dart';
part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
ElectrumWalletBase(
{@required String password,
@required WalletInfo walletInfo,
@required List<BitcoinAddressRecord> initialAddresses,
@required this.networkType,
@required this.mnemonic,
ElectrumClient electrumClient,
int accountIndex = 0,
ElectrumBalance initialBalance})
: balance = initialBalance ??
const ElectrumBalance(confirmed: 0, unconfirmed: 0),
hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
network: networkType)
.derivePath("m/0'/0"),
addresses = ObservableList<BitcoinAddressRecord>.of(
(initialAddresses ?? []).toSet()),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_accountIndex = accountIndex,
_feeRates = <int>[],
_isTransactionUpdating = false,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo;
transactionHistory =
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
_unspent = [];
_scripthashesUpdateSubject = {};
}
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 146 + outputsCounts * 33 + 8;
final bitcoin.HDWallet hd;
final String mnemonic;
ElectrumClient electrumClient;
@override
@observable
String address;
@override
@observable
ElectrumBalance balance;
@override
@observable
SyncStatus syncStatus;
ObservableList<BitcoinAddressRecord> addresses;
List<String> get scriptHashes => addresses
.map((addr) => scriptHash(addr.address, networkType: networkType))
.toList();
String get xpub => hd.base58;
@override
String get seed => mnemonic;
bitcoin.NetworkType networkType;
@override
BitcoinWalletKeys get keys => BitcoinWalletKeys(
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
final String _password;
List<BitcoinUnspent> _unspent;
List<int> _feeRates;
int _accountIndex;
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
bool _isTransactionUpdating;
Future<void> init() async {
await generateAddresses();
address = addresses[_accountIndex].address;
await transactionHistory.init();
}
@action
Future<void> nextAddress() async {
_accountIndex += 1;
if (_accountIndex >= addresses.length) {
_accountIndex = 0;
}
address = addresses[_accountIndex].address;
await save();
}
Future<void> generateAddresses() async {
if (addresses.length < 33) {
final addressesCount = 33 - addresses.length;
await generateNewAddresses(addressesCount,
startIndex: addresses.length, hd: hd);
}
}
Future<BitcoinAddressRecord> generateNewAddress(
{bool isHidden = false, bitcoin.HDWallet hd}) async {
_accountIndex += 1;
final _hd = hd ?? this.hd;
final address = BitcoinAddressRecord(
getAddress(index: _accountIndex, hd: _hd),
index: _accountIndex,
isHidden: isHidden);
addresses.add(address);
await save();
return address;
}
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
{int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async {
final list = <BitcoinAddressRecord>[];
for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
index: i, isHidden: isHidden);
list.add(address);
}
addresses.addAll(list);
await save();
return list;
}
Future<void> updateAddress(String address) async {
for (final addr in addresses) {
if (addr.address == address) {
await save();
break;
}
}
}
@action
@override
Future<void> startSync() async {
try {
syncStatus = StartingSyncStatus();
updateTransactions();
_subscribeForUpdates();
await _updateBalance();
await _updateUnspent();
_feeRates = await electrumClient.feeRates();
Timer.periodic(const Duration(minutes: 1),
(timer) async => _feeRates = await electrumClient.feeRates());
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e.toString());
syncStatus = FailedSyncStatus();
}
}
@action
@override
Future<void> connectToNode({@required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
await electrumClient.connectToUri(node.uri);
electrumClient.onConnectionStatusChange = (bool isConnected) {
if (!isConnected) {
syncStatus = LostConnectionSyncStatus();
}
};
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e.toString());
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingBitcoinTransaction> createTransaction(
Object credentials) async {
const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final allAmountFee =
calculateEstimatedFee(transactionCredentials.priority, null);
final allAmount = balance.confirmed - allAmountFee;
var fee = 0;
final credentialsAmount = transactionCredentials.amount != null
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: 0;
final amount = transactionCredentials.amount == null ||
allAmount - credentialsAmount < minAmount
? allAmount
: credentialsAmount;
final txb = bitcoin.TransactionBuilder(network: networkType);
final changeAddress = address;
var leftAmount = amount;
var totalInputAmount = 0;
if (_unspent.isEmpty) {
await _updateUnspent();
}
for (final utx in _unspent) {
leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value;
inputs.add(utx);
if (leftAmount <= 0) {
break;
}
}
if (inputs.isEmpty) {
throw BitcoinTransactionNoInputsException();
}
final totalAmount = amount + fee;
fee = transactionCredentials.amount != null
? feeAmountForPriority(transactionCredentials.priority, inputs.length,
amount == allAmount ? 1 : 2)
: allAmountFee;
if (totalAmount > balance.confirmed) {
throw BitcoinTransactionWrongBalanceException();
}
if (amount <= 0 || totalInputAmount < amount) {
throw BitcoinTransactionWrongBalanceException();
}
txb.setVersion(1);
inputs.forEach((input) {
if (input.isP2wpkh) {
final p2wpkh = bitcoin
.P2WPKH(
data: generatePaymentData(hd: hd, index: input.address.index),
network: networkType)
.data;
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
} else {
txb.addInput(input.hash, input.vout);
}
});
txb.addOutput(
addressToOutputScript(transactionCredentials.address, networkType),
amount);
final estimatedSize = estimatedTransactionSize(inputs.length, 2);
final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
final changeValue = totalInputAmount - amount - feeAmount;
if (changeValue > minAmount) {
txb.addOutput(changeAddress, changeValue);
}
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
final keyPair = generateKeyPair(
hd: hd, index: input.address.index, network: networkType);
final witnessValue = input.isP2wpkh ? input.value : null;
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
}
return PendingBitcoinTransaction(txb.build(), type,
electrumClient: electrumClient, amount: amount, fee: fee)
..addListener((transaction) async {
transactionHistory.addOne(transaction);
await _updateBalance();
});
}
String toJSON() => json.encode({
'mnemonic': mnemonic,
'account_index': _accountIndex.toString(),
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance?.toJSON()
});
int feeRate(TransactionPriority priority) {
if (priority is BitcoinTransactionPriority) {
return _feeRates[priority.raw];
}
return 0;
}
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
int outputsCount) =>
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
@override
int calculateEstimatedFee(TransactionPriority priority, int amount) {
if (priority is BitcoinTransactionPriority) {
int inputsCount = 0;
if (amount != null) {
int totalValue = 0;
for (final input in _unspent) {
if (totalValue >= amount) {
break;
}
totalValue += input.value;
inputsCount += 1;
}
} else {
inputsCount = _unspent.length;
}
// If send all, then we have no change value
return feeAmountForPriority(
priority, inputsCount, amount != null ? 2 : 1);
}
return 0;
}
@override
Future<void> save() async {
final path = await makePath();
await write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
}
bitcoin.ECPair keyPairFor({@required int index}) =>
generateKeyPair(hd: hd, index: index, network: networkType);
@override
Future<void> rescan({int height}) async => throw UnimplementedError();
@override
Future<void> close() async {
try {
await electrumClient?.close();
} catch (_) {}
}
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
Future<String> makePath() async =>
pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<void> _updateUnspent() async {
final unspent = await Future.wait(addresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
_unspent = unspent.expand((e) => e).toList();
}
Future<ElectrumTransactionInfo> fetchTransactionInfo(
{@required String hash, @required int height}) async {
final tx = await electrumClient.getTransactionExpanded(hash: hash);
return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type,
height: height, addresses: addresses);
}
@override
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
final histories =
scriptHashes.map((scriptHash) => electrumClient.getHistory(scriptHash));
final _historiesWithDetails = await Future.wait(histories)
.then((histories) => histories.expand((i) => i).toList())
.then((histories) => histories.map((tx) => fetchTransactionInfo(
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
final historiesWithDetails = await Future.wait(_historiesWithDetails);
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
<String, ElectrumTransactionInfo>{}, (acc, tx) {
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
return acc;
});
}
Future<void> updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (e) {
print(e);
_isTransactionUpdating = false;
}
}
void _subscribeForUpdates() {
scriptHashes.forEach((sh) async {
await _scripthashesUpdateSubject[sh]?.close();
_scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh].listen((event) async {
try {
await _updateBalance();
await _updateUnspent();
await updateTransactions();
} catch (e) {
print(e.toString());
}
});
});
}
Future<ElectrumBalance> _fetchBalances() async {
final balances = await Future.wait(
scriptHashes.map((sh) => electrumClient.getBalance(sh)));
final balance = balances.fold(
ElectrumBalance(confirmed: 0, unconfirmed: 0),
(ElectrumBalance acc, val) => ElectrumBalance(
confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
unconfirmed:
(val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
return balance;
}
Future<void> _updateBalance() async {
balance = await _fetchBalances();
await save();
}
}

View file

@ -0,0 +1,42 @@
import 'dart:convert';
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
class ElectrumWallletSnapshot {
ElectrumWallletSnapshot(this.name, this.type, this.password);
final String name;
final String password;
final WalletType type;
String mnemonic;
List<BitcoinAddressRecord> addresses;
ElectrumBalance balance;
int accountIndex;
Future<void> load() async {
try {
final path = await pathForWallet(name: name, type: type);
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final addressesTmp = data['addresses'] as List ?? <Object>[];
mnemonic = data['mnemonic'] as String;
addresses = addressesTmp
.whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
.toList();
balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0);
accountIndex = 0;
try {
accountIndex = int.parse(data['account_index'] as String);
} catch (_) {}
} catch (e) {
print(e);
}
}
}

View file

@ -0,0 +1,9 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
final litecoinNetwork = NetworkType(
messagePrefix: '\x19Litecoin Signed Message:\n',
bech32: 'ltc',
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0);

View file

@ -0,0 +1,88 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
import 'package:cake_wallet/bitcoin/litecoin_network.dart';
import 'package:cake_wallet/bitcoin/utils.dart';
part 'litecoin_wallet.g.dart';
class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
LitecoinWalletBase(
{@required String mnemonic,
@required String password,
@required WalletInfo walletInfo,
List<BitcoinAddressRecord> initialAddresses,
ElectrumBalance initialBalance,
int accountIndex = 0})
: super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
networkType: litecoinNetwork,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
accountIndex: accountIndex);
static Future<LitecoinWallet> open({
@required String name,
@required WalletInfo walletInfo,
@required String password,
}) async {
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
await snp.load();
return LitecoinWallet(
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
accountIndex: snp.accountIndex);
}
@override
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
@override
Future<void> generateAddresses() async {
if (addresses.length < 33) {
final addressesCount = 22 - addresses.length;
await generateNewAddresses(addressesCount,
hd: hd, startIndex: addresses.length);
final changeRoot = bitcoin.HDWallet.fromSeed(
mnemonicToSeedBytes(mnemonic),
network: networkType)
.derivePath("m/0'/1");
await generateNewAddresses(11,
startIndex: 0, hd: changeRoot, isHidden: true);
}
}
@override
int feeRate(TransactionPriority priority) {
if (priority is LitecoinTransactionPriority) {
switch (priority) {
case LitecoinTransactionPriority.slow:
return 1;
case LitecoinTransactionPriority.medium:
return 2;
case LitecoinTransactionPriority.fast:
return 3;
}
}
return 0;
}
}

View file

@ -0,0 +1,76 @@
import 'dart:io';
import 'package:hive/hive.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cake_wallet/bitcoin/litecoin_wallet.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/core/wallet_base.dart';
class LitecoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials> {
LitecoinWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@override
WalletType getType() => WalletType.litecoin;
@override
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
final wallet = LitecoinWallet(
mnemonic: await generateMnemonic(),
password: credentials.password,
walletInfo: credentials.walletInfo);
await wallet.save();
await wallet.init();
return wallet;
}
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<LitecoinWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(name, getType()),
orElse: () => null);
final wallet = await LitecoinWalletBase.open(
password: password, name: name, walletInfo: walletInfo);
await wallet.init();
return wallet;
}
@override
Future<void> remove(String wallet) async =>
File(await pathForWalletDir(name: wallet, type: getType()))
.delete(recursive: true);
@override
Future<LitecoinWallet> restoreFromKeys(
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
throw UnimplementedError();
@override
Future<LitecoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}
final wallet = LitecoinWallet(
password: credentials.password,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo);
await wallet.save();
await wallet.init();
return wallet;
}
}

View file

@ -1,18 +1,22 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/entities/transaction_direction.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cake_wallet/core/pending_transaction.dart'; import 'package:cake_wallet/core/pending_transaction.dart';
import 'package:cake_wallet/bitcoin/electrum.dart'; import 'package:cake_wallet/bitcoin/electrum.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
import 'package:cake_wallet/entities/transaction_direction.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
class PendingBitcoinTransaction with PendingTransaction { class PendingBitcoinTransaction with PendingTransaction {
PendingBitcoinTransaction(this._tx, PendingBitcoinTransaction(this._tx, this.type,
{@required this.eclient, @required this.amount, @required this.fee}) {@required this.electrumClient,
: _listeners = <void Function(BitcoinTransactionInfo transaction)>[]; @required this.amount,
@required this.fee})
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
final bitcoin.Transaction _tx; final bitcoin.Transaction _tx;
final ElectrumClient eclient; final ElectrumClient electrumClient;
final int amount; final int amount;
final int fee; final int fee;
@ -25,24 +29,25 @@ class PendingBitcoinTransaction with PendingTransaction {
@override @override
String get feeFormatted => bitcoinAmountToString(amount: fee); String get feeFormatted => bitcoinAmountToString(amount: fee);
final List<void Function(BitcoinTransactionInfo transaction)> _listeners; final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
@override @override
Future<void> commit() async { Future<void> commit() async {
await eclient.broadcastTransaction(transactionRaw: _tx.toHex()); await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
_listeners?.forEach((listener) => listener(transactionInfo())); _listeners?.forEach((listener) => listener(transactionInfo()));
} }
void addListener( void addListener(
void Function(BitcoinTransactionInfo transaction) listener) => void Function(ElectrumTransactionInfo transaction) listener) =>
_listeners.add(listener); _listeners.add(listener);
BitcoinTransactionInfo transactionInfo() => BitcoinTransactionInfo( ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
id: id, id: id,
height: 0, height: 0,
amount: amount, amount: amount,
direction: TransactionDirection.outgoing, direction: TransactionDirection.outgoing,
date: DateTime.now(), date: DateTime.now(),
isPending: true, isPending: true,
confirmations: 0); confirmations: 0,
fee: fee);
} }

View file

@ -1,18 +1,20 @@
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
String scriptHash(String address) { String scriptHash(String address, {@required bitcoin.NetworkType networkType}) {
final outputScript = bitcoin.Address.addressToOutputScript(address); final outputScript =
final splitted = sha256.convert(outputScript).toString().split(''); bitcoin.Address.addressToOutputScript(address, networkType);
final parts = sha256.convert(outputScript).toString().split('');
var res = ''; var res = '';
for (var i = splitted.length - 1; i >= 0; i--) { for (var i = parts.length - 1; i >= 0; i--) {
final char = splitted[i]; final char = parts[i];
i--; i--;
final nextChar = splitted[i]; final nextChar = parts[i];
res += nextChar; res += nextChar;
res += char; res += char;
} }
return res; return res;
} }

View file

@ -13,14 +13,43 @@ bitcoin.ECPair generateKeyPair(
{@required bitcoin.HDWallet hd, {@required bitcoin.HDWallet hd,
@required int index, @required int index,
bitcoin.NetworkType network}) => bitcoin.NetworkType network}) =>
bitcoin.ECPair.fromWIF(hd.derive(index).wif, bitcoin.ECPair.fromWIF(hd.derive(index).wif, network: network);
network: network ?? bitcoin.bitcoin);
String generateAddress({@required bitcoin.HDWallet hd, @required int index}) => String generateP2WPKHAddress(
{@required bitcoin.HDWallet hd,
@required int index,
bitcoin.NetworkType networkType}) =>
bitcoin bitcoin
.P2WPKH( .P2WPKH(
data: PaymentData( data: PaymentData(
pubkey: pubkey:
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey)))) Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
network: networkType)
.data
.address;
String generateP2WPKHAddressByPath(
{@required bitcoin.HDWallet hd,
@required String path,
bitcoin.NetworkType networkType}) =>
bitcoin
.P2WPKH(
data: PaymentData(
pubkey:
Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey))),
network: networkType)
.data
.address;
String generateP2PKHAddress(
{@required bitcoin.HDWallet hd,
@required int index,
bitcoin.NetworkType networkType}) =>
bitcoin
.P2PKH(
data: PaymentData(
pubkey:
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
network: networkType)
.data .data
.address; .address;

View file

@ -72,7 +72,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.eth: case CryptoCurrency.eth:
return [42]; return [42];
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
return [34]; return [34, 43];
case CryptoCurrency.nano: case CryptoCurrency.nano:
return [64, 65]; return [64, 65];
case CryptoCurrency.trx: case CryptoCurrency.trx:

View file

@ -8,7 +8,6 @@ import 'package:path_provider/path_provider.dart';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:archive/archive_io.dart'; import 'package:archive/archive_io.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/encrypt.dart'; import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';

View file

@ -3,7 +3,6 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:cake_wallet/entities/currency_formatter.dart';
const fiatApiAuthority = 'fiat-api.cakewallet.com'; const fiatApiAuthority = 'fiat-api.cakewallet.com';
const fiatApiPath = '/v1/rates'; const fiatApiPath = '/v1/rates';
@ -15,9 +14,8 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
try { try {
final fiatStringified = fiat.toString(); final fiatStringified = fiat.toString();
final uri = final uri = Uri.https(fiatApiAuthority, fiatApiPath,
Uri.https(fiatApiAuthority, fiatApiPath, <String, String>{'convert': fiatStringified});
<String, String> {'convert': fiatStringified});
final response = await get(uri.toString()); final response = await get(uri.toString());
if (response.statusCode != 200) { if (response.statusCode != 200) {
@ -28,7 +26,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
final data = responseJSON['data'] as List<dynamic>; final data = responseJSON['data'] as List<dynamic>;
for (final item in data) { for (final item in data) {
if (item['symbol'] == cryptoToString(crypto)) { if (item['symbol'] == crypto.title) {
price = item['quote'][fiatStringified]['price'] as double; price = item['quote'][fiatStringified]['price'] as double;
break; break;
} }
@ -45,6 +43,7 @@ Future<double> _fetchPriceAsync(
compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto}); compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto});
class FiatConversionService { class FiatConversionService {
static Future<double> fetchPrice(CryptoCurrency crypto, FiatCurrency fiat) async => static Future<double> fetchPrice(
CryptoCurrency crypto, FiatCurrency fiat) async =>
await _fetchPriceAsync(crypto, fiat); await _fetchPriceAsync(crypto, fiat);
} }

View file

@ -4,9 +4,9 @@ import 'package:cake_wallet/entities/wallet_type.dart';
String generateWalletPassword(WalletType type) { String generateWalletPassword(WalletType type) {
switch (type) { switch (type) {
case WalletType.bitcoin: case WalletType.monero:
return generateKey();
default:
return Uuid().v4(); return Uuid().v4();
default:
return generateKey();
} }
} }

View file

@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/core/validator.dart';

View file

@ -0,0 +1,13 @@
import 'dart:typed_data';
import 'package:flutter/services.dart';
const utils = const MethodChannel('com.cake_wallet/native_utils');
Future<Uint8List> secRandom(int count) async {
try {
return await utils.invokeMethod<Uint8List>('sec_random', {'count': count});
} on PlatformException catch (_) {
return Uint8List.fromList([]);
}
}

View file

@ -24,6 +24,8 @@ class SeedValidator extends Validator<MnemonicItem> {
switch (type) { switch (type) {
case WalletType.bitcoin: case WalletType.bitcoin:
return getBitcoinWordList(language); return getBitcoinWordList(language);
case WalletType.litecoin:
return getBitcoinWordList(language);
case WalletType.monero: case WalletType.monero:
return getMoneroWordList(language); return getMoneroWordList(language);
default: default:

View file

@ -3,43 +3,50 @@ import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/entities/transaction_info.dart';
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> { abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
TransactionHistoryBase() : _isUpdating = false; TransactionHistoryBase();
// : _isUpdating = false;
@observable @observable
ObservableMap<String, TransactionType> transactions; ObservableMap<String, TransactionType> transactions;
bool _isUpdating; Future<void> save();
@action void addOne(TransactionType transaction);
Future<void> update() async {
if (_isUpdating) {
return;
}
try { void addMany(Map<String, TransactionType> transactions);
_isUpdating = true;
final _transactions = await fetchTransactions();
transactions.keys
.toSet()
.difference(_transactions.keys.toSet())
.forEach((k) => transactions.remove(k));
_transactions.forEach((key, value) => transactions[key] = value);
_isUpdating = false;
} catch (e) {
_isUpdating = false;
rethrow;
}
}
void updateAsync({void Function() onFinished}) { // bool _isUpdating;
fetchTransactionsAsync(
(transaction) => transactions[transaction.id] = transaction,
onFinished: onFinished);
}
void fetchTransactionsAsync( // @action
void Function(TransactionType transaction) onTransactionLoaded, // Future<void> update() async {
{void Function() onFinished}); // if (_isUpdating) {
// return;
// }
Future<Map<String, TransactionType>> fetchTransactions(); // try {
// _isUpdating = true;
// final _transactions = await fetchTransactions();
// transactions.keys
// .toSet()
// .difference(_transactions.keys.toSet())
// .forEach((k) => transactions.remove(k));
// _transactions.forEach((key, value) => transactions[key] = value);
// _isUpdating = false;
// } catch (e) {
// _isUpdating = false;
// rethrow;
// }
// }
// void updateAsync({void Function() onFinished}) {
// fetchTransactionsAsync(
// (transaction) => transactions[transaction.id] = transaction,
// onFinished: onFinished);
// }
// void fetchTransactionsAsync(
// void Function(TransactionType transaction) onTransactionLoaded,
// {void Function() onFinished});
// Future<Map<String, TransactionType>> fetchTransactions();
} }

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cake_wallet/entities/wallet_info.dart'; import 'package:cake_wallet/entities/wallet_info.dart';
@ -11,7 +12,10 @@ import 'package:cake_wallet/entities/sync_status.dart';
import 'package:cake_wallet/entities/node.dart'; import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/entities/wallet_type.dart';
abstract class WalletBase<BalanceType extends Balance> { abstract class WalletBase<
BalanceType extends Balance,
HistoryType extends TransactionHistoryBase,
TransactionType extends TransactionInfo> {
WalletBase(this.walletInfo); WalletBase(this.walletInfo);
static String idFor(String name, WalletType type) => static String idFor(String name, WalletType type) =>
@ -41,7 +45,7 @@ abstract class WalletBase<BalanceType extends Balance> {
Object get keys; Object get keys;
TransactionHistoryBase transactionHistory; HistoryType transactionHistory;
Future<void> connectToNode({@required Node node}); Future<void> connectToNode({@required Node node});
@ -51,6 +55,12 @@ abstract class WalletBase<BalanceType extends Balance> {
int calculateEstimatedFee(TransactionPriority priority, int amount); int calculateEstimatedFee(TransactionPriority priority, int amount);
// void fetchTransactionsAsync(
// void Function(TransactionType transaction) onTransactionLoaded,
// {void Function() onFinished});
Future<Map<String, TransactionType>> fetchTransactions();
Future<void> save(); Future<void> save();
Future<void> rescan({int height}); Future<void> rescan({int height});

View file

@ -1,8 +1,11 @@
import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_credentials.dart'; import 'package:cake_wallet/core/wallet_credentials.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
abstract class WalletService<N extends WalletCredentials, abstract class WalletService<N extends WalletCredentials,
RFS extends WalletCredentials, RFK extends WalletCredentials> { RFS extends WalletCredentials, RFK extends WalletCredentials> {
WalletType getType();
Future<WalletBase> create(N credentials); Future<WalletBase> create(N credentials);
Future<WalletBase> restoreFromSeed(RFS credentials); Future<WalletBase> restoreFromSeed(RFS credentials);

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/entities/biometric_auth.dart'; import 'package:cake_wallet/entities/biometric_auth.dart';
@ -441,6 +442,8 @@ Future setup(
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource)); getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
getIt.registerFactoryParam<WalletService, WalletType, void>( getIt.registerFactoryParam<WalletService, WalletType, void>(
(WalletType param1, __) { (WalletType param1, __) {
switch (param1) { switch (param1) {
@ -448,6 +451,8 @@ Future setup(
return getIt.get<MoneroWalletService>(); return getIt.get<MoneroWalletService>();
case WalletType.bitcoin: case WalletType.bitcoin:
return getIt.get<BitcoinWalletService>(); return getIt.get<BitcoinWalletService>();
case WalletType.litecoin:
return getIt.get<LitecoinWalletService>();
default: default:
return null; return null;
} }

View file

@ -4,11 +4,12 @@ String calculateFiatAmount({double price, String cryptoAmount}) {
} }
final _amount = double.parse(cryptoAmount); final _amount = double.parse(cryptoAmount);
final result = price * _amount; final _result = price * _amount;
final result = _result < 0 ? _result * -1 : _result;
if (result == 0.0) { if (result == 0.0) {
return '0.00'; return '0.00';
} }
return result > 0.01 ? result.toStringAsFixed(2) : '< 0.01'; return result > 0.01 ? result.toStringAsFixed(2) : '< 0.01';
} }

View file

@ -19,7 +19,6 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
CryptoCurrency.eos, CryptoCurrency.eos,
CryptoCurrency.eth, CryptoCurrency.eth,
CryptoCurrency.ltc, CryptoCurrency.ltc,
CryptoCurrency.nano,
CryptoCurrency.trx, CryptoCurrency.trx,
CryptoCurrency.usdt, CryptoCurrency.usdt,
CryptoCurrency.usdterc20, CryptoCurrency.usdterc20,
@ -29,7 +28,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
static const xmr = CryptoCurrency(title: 'XMR', raw: 0); static const xmr = CryptoCurrency(title: 'XMR', raw: 0);
static const ada = CryptoCurrency(title: 'ADA', raw: 1); static const ada = CryptoCurrency(title: 'ADA', raw: 1);
static const bch = CryptoCurrency(title: 'BCH', raw: 2); static const bch = CryptoCurrency(title: 'BCH', raw: 2);
static const bnb = CryptoCurrency(title: 'BNB', raw: 3); static const bnb = CryptoCurrency(title: 'BNB BEP2', raw: 3);
static const btc = CryptoCurrency(title: 'BTC', raw: 4); static const btc = CryptoCurrency(title: 'BTC', raw: 4);
static const dai = CryptoCurrency(title: 'DAI', raw: 5); static const dai = CryptoCurrency(title: 'DAI', raw: 5);
static const dash = CryptoCurrency(title: 'DASH', raw: 6); static const dash = CryptoCurrency(title: 'DASH', raw: 6);
@ -90,7 +89,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
return CryptoCurrency.ada; return CryptoCurrency.ada;
case 'bch': case 'bch':
return CryptoCurrency.bch; return CryptoCurrency.bch;
case 'bnb': case 'bnbmainnet':
return CryptoCurrency.bnb; return CryptoCurrency.bnb;
case 'btc': case 'btc':
return CryptoCurrency.btc; return CryptoCurrency.btc;

View file

@ -7,7 +7,9 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.btc; return CryptoCurrency.btc;
case WalletType.monero: case WalletType.monero:
return CryptoCurrency.xmr; return CryptoCurrency.xmr;
case WalletType.litecoin:
return CryptoCurrency.ltc;
default: default:
return null; return null;
} }
} }

View file

@ -1,12 +0,0 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
String cryptoToString(CryptoCurrency crypto) {
switch (crypto) {
case CryptoCurrency.xmr:
return 'XMR';
case CryptoCurrency.btc:
return 'BTC';
default:
return '';
}
}

View file

@ -1,11 +1,7 @@
import 'dart:io' show File, Platform; import 'dart:io' show File, Platform;
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/pathForWallet.dart'; import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -24,7 +20,8 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:encrypt/encrypt.dart' as encrypt; import 'package:encrypt/encrypt.dart' as encrypt;
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
const cakeWalletElectrumUri = 'electrum.cakewallet.com:50002'; const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
Future defaultSettingsMigration( Future defaultSettingsMigration(
{@required int version, {@required int version,
@ -68,6 +65,8 @@ Future defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
await changeBitcoinCurrentElectrumServerToDefault( await changeBitcoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 2: case 2:
@ -97,6 +96,7 @@ Future defaultSettingsMigration(
case 9: case 9:
await generateBackupPassword(secureStorage); await generateBackupPassword(secureStorage);
break; break;
case 10: case 10:
await changeTransactionPriorityAndFeeRateKeys(sharedPreferences); await changeTransactionPriorityAndFeeRateKeys(sharedPreferences);
break; break;
@ -110,7 +110,14 @@ Future defaultSettingsMigration(
break; break;
case 13: case 13:
await resetElectrumServer(nodes, sharedPreferences); await resetBitcoinElectrumServer(nodes, sharedPreferences);
break;
case 15:
await addLitecoinElectrumServerList(nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences);
break; break;
default: default:
@ -142,7 +149,7 @@ Future<void> replaceNodesMigration({@required Box<Node> nodes}) async {
final nodeToReplace = replaceNodes[node.uri]; final nodeToReplace = replaceNodes[node.uri];
if (nodeToReplace != null) { if (nodeToReplace != null) {
node.uri = nodeToReplace.uri; node.uriRaw = nodeToReplace.uriRaw;
node.login = nodeToReplace.login; node.login = nodeToReplace.login;
node.password = nodeToReplace.password; node.password = nodeToReplace.password;
await node.save(); await node.save();
@ -160,12 +167,21 @@ Future<void> changeMoneroCurrentNodeToDefault(
} }
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) { Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
return nodes.values return nodes.values.firstWhere(
.firstWhere((Node node) => node.uri == cakeWalletElectrumUri, orElse: () => null) ?? (Node node) => node.uri == cakeWalletBitcoinElectrumUri,
orElse: () => null) ??
nodes.values.firstWhere((node) => node.type == WalletType.bitcoin, nodes.values.firstWhere((node) => node.type == WalletType.bitcoin,
orElse: () => null); orElse: () => null);
} }
Node getLitecoinDefaultElectrumServer({@required Box<Node> nodes}) {
return nodes.values.firstWhere(
(Node node) => node.uri == cakeWalletLitecoinElectrumUri,
orElse: () => null) ??
nodes.values.firstWhere((node) => node.type == WalletType.litecoin,
orElse: () => null);
}
Node getMoneroDefaultNode({@required Box<Node> nodes}) { Node getMoneroDefaultNode({@required Box<Node> nodes}) {
final timeZone = DateTime.now().timeZoneOffset.inHours; final timeZone = DateTime.now().timeZoneOffset.inHours;
var nodeUri = ''; var nodeUri = '';
@ -192,6 +208,15 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
await sharedPreferences.setInt('current_node_id_btc', serverId); await sharedPreferences.setInt('current_node_id_btc', serverId);
} }
Future<void> changeLitecoinCurrentElectrumServerToDefault(
{@required SharedPreferences sharedPreferences,
@required Box<Node> nodes}) async {
final server = getLitecoinDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int ?? 0;
await sharedPreferences.setInt('current_node_id_ltc', serverId);
}
Future<void> replaceDefaultNode( Future<void> replaceDefaultNode(
{@required SharedPreferences sharedPreferences, {@required SharedPreferences sharedPreferences,
@required Box<Node> nodes}) async { @required Box<Node> nodes}) async {
@ -224,7 +249,12 @@ Future<void> updateNodeTypes({@required Box<Node> nodes}) async {
} }
Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async { Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
final serverList = await loadElectrumServerList(); final serverList = await loadBitcoinElectrumServerList();
await nodes.addAll(serverList);
}
Future<void> addLitecoinElectrumServerList({@required Box<Node> nodes}) async {
final serverList = await loadLitecoinElectrumServerList();
await nodes.addAll(serverList); await nodes.addAll(serverList);
} }
@ -284,57 +314,97 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
Future<void> changeDefaultMoneroNode( Future<void> changeDefaultMoneroNode(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async { Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com'; const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentMoneroNodeId =
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId); sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern); final currentMoneroNode =
nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode =
currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
nodeSource.values.forEach((node) async { nodeSource.values.forEach((node) async {
if (node.type == WalletType.monero && node.uri.contains(cakeWalletMoneroNodeUriPattern)) { if (node.type == WalletType.monero &&
node.uri.toString().contains(cakeWalletMoneroNodeUriPattern)) {
await node.delete(); await node.delete();
} }
}); });
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode); await nodeSource.add(newCakeWalletNode);
if (needToReplaceCurrentMoneroNode) { if (needToReplaceCurrentMoneroNode) {
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int); await sharedPreferences.setInt(
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
} }
} }
Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPreferences) async { Future<void> checkCurrentNodes(
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentElectrumSeverId = await sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); final currentMoneroNodeId =
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId, orElse: () => null); sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentElectrumServer = nodeSource.values.firstWhere((node) => node.key == currentElectrumSeverId, orElse: () => null); final currentBitcoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentLitecoinElectrumSeverId = sharedPreferences
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentMoneroNode = nodeSource.values.firstWhere(
(node) => node.key == currentMoneroNodeId,
orElse: () => null);
final currentBitcoinElectrumServer = nodeSource.values.firstWhere(
(node) => node.key == currentBitcoinElectrumSeverId,
orElse: () => null);
final currentLitecoinElectrumServer = nodeSource.values.firstWhere(
(node) => node.key == currentLitecoinElectrumSeverId,
orElse: () => null);
if (currentMoneroNode == null) { if (currentMoneroNode == null) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode); await nodeSource.add(newCakeWalletNode);
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int); await sharedPreferences.setInt(
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
} }
if (currentElectrumServer == null) { if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin); final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletElectrum); await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int); await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
cakeWalletElectrum.key as int);
}
if (currentLitecoinElectrumServer == null) {
final cakeWalletElectrum =
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt(
PreferencesKey.currentLitecoinElectrumSererIdKey,
cakeWalletElectrum.key as int);
} }
} }
Future<void> resetBitcoinElectrumServer(
Future<void> resetElectrumServer(Box<Node> nodeSource, SharedPreferences sharedPreferences) async { Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); final currentElectrumSeverId =
final oldElectrumServer = nodeSource.values.firstWhere((node) => node.uri.contains('electrumx.cakewallet.com'), orElse: () => null); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
var cakeWalletNode = nodeSource.values.firstWhere((node) => node.uri == cakeWalletElectrumUri, orElse: () => null); final oldElectrumServer = nodeSource.values.firstWhere(
(node) => node.uri.toString().contains('electrumx.cakewallet.com'),
orElse: () => null);
var cakeWalletNode = nodeSource.values.firstWhere(
(node) => node.uri.toString() == cakeWalletBitcoinElectrumUri,
orElse: () => null);
if (cakeWalletNode == null) { if (cakeWalletNode == null) {
cakeWalletNode = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin); cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletNode); await nodeSource.add(cakeWalletNode);
} }
if (currentElectrumSeverId == oldElectrumServer?.key) { if (currentElectrumSeverId == oldElectrumServer?.key) {
await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int); await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
cakeWalletNode.key as int);
} }
await oldElectrumServer?.delete(); await oldElectrumServer?.delete();

View file

@ -1,7 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/encrypt.dart'; import 'package:cake_wallet/entities/encrypt.dart';
@ -14,11 +17,6 @@ import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
const reservedNames = ["flutter_assets", "wallets", "db"]; const reservedNames = ["flutter_assets", "wallets", "db"];
@ -407,7 +405,7 @@ Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
} }
final List<dynamic> addresses = final List<dynamic> addresses =
json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>; json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
final contacts = addresses.map((dynamic item) { final contacts = addresses.map((dynamic item) {
final _item = item as Map<String, dynamic>; final _item = item as Map<String, dynamic>;
final type = _item["type"] as String; final type = _item["type"] as String;
@ -420,7 +418,7 @@ Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
await contactSource.addAll(contacts); await contactSource.addAll(contacts);
await prefs.setBool('ios_migration_address_book_completed', true); await prefs.setBool('ios_migration_address_book_completed', true);
} catch(e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
} }

View file

@ -15,7 +15,9 @@ class LanguageService {
'pt': 'Português (Portuguese)', 'pt': 'Português (Portuguese)',
'ru': 'Русский (Russian)', 'ru': 'Русский (Russian)',
'uk': 'Українська (Ukrainian)', 'uk': 'Українська (Ukrainian)',
'zh': '中文 (Chinese)' 'zh': '中文 (Chinese)',
'hr': 'Hrvatski (Croatian)',
'it': 'Italiano (Italian)'
}; };
static final list = <String, String> {}; static final list = <String, String> {};

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/utils/mobx.dart'; import 'package:cake_wallet/utils/mobx.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'dart:convert'; import 'dart:convert';
@ -8,19 +10,23 @@ import 'package:cake_wallet/entities/digest_request.dart';
part 'node.g.dart'; part 'node.g.dart';
Uri createUriFromElectrumAddress(String address) =>
Uri.tryParse('tcp://$address');
@HiveType(typeId: Node.typeId) @HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable { class Node extends HiveObject with Keyable {
Node( Node(
{@required this.uri, {@required String uri,
@required WalletType type, @required WalletType type,
this.login, this.login,
this.password, this.password,
this.useSSL}) { this.useSSL}) {
uriRaw = uri;
this.type = type; this.type = type;
} }
Node.fromMap(Map map) Node.fromMap(Map map)
: uri = map['uri'] as String ?? '', : uriRaw = map['uri'] as String ?? '',
login = map['login'] as String, login = map['login'] as String,
password = map['password'] as String, password = map['password'] as String,
typeRaw = map['typeRaw'] as int, typeRaw = map['typeRaw'] as int,
@ -30,7 +36,7 @@ class Node extends HiveObject with Keyable {
static const boxName = 'Nodes'; static const boxName = 'Nodes';
@HiveField(0) @HiveField(0)
String uri; String uriRaw;
@HiveField(1) @HiveField(1)
String login; String login;
@ -46,6 +52,19 @@ class Node extends HiveObject with Keyable {
bool get isSSL => useSSL ?? false; bool get isSSL => useSSL ?? false;
Uri get uri {
switch (type) {
case WalletType.monero:
return Uri.http(uriRaw, '');
case WalletType.bitcoin:
return createUriFromElectrumAddress(uriRaw);
case WalletType.litecoin:
return createUriFromElectrumAddress(uriRaw);
default:
return null;
}
}
@override @override
dynamic get keyIndex { dynamic get keyIndex {
_keyIndex ??= key; _keyIndex ??= key;
@ -64,7 +83,9 @@ class Node extends HiveObject with Keyable {
case WalletType.monero: case WalletType.monero:
return requestMoneroNode(); return requestMoneroNode();
case WalletType.bitcoin: case WalletType.bitcoin:
return requestBitcoinElectrumServer(); return requestElectrumServer();
case WalletType.litecoin:
return requestElectrumServer();
default: default:
return false; return false;
} }
@ -80,15 +101,15 @@ class Node extends HiveObject with Keyable {
if (login != null && password != null) { if (login != null && password != null) {
final digestRequest = DigestRequest(); final digestRequest = DigestRequest();
final response = await digestRequest.request( final response = await digestRequest.request(
uri: uri, login: login, password: password); uri: uri.toString(), login: login, password: password);
resBody = response.data as Map<String, dynamic>; resBody = response.data as Map<String, dynamic>;
} else { } else {
final url = Uri.http(uri, '/json_rpc'); final rpcUri = Uri.http(uri.authority, '/json_rpc');
final headers = {'Content-type': 'application/json'}; final headers = {'Content-type': 'application/json'};
final body = final body =
json.encode({'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'}); json.encode({'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'});
final response = final response =
await http.post(url.toString(), headers: headers, body: body); await http.post(rpcUri.toString(), headers: headers, body: body);
resBody = json.decode(response.body) as Map<String, dynamic>; resBody = json.decode(response.body) as Map<String, dynamic>;
} }
@ -98,8 +119,13 @@ class Node extends HiveObject with Keyable {
} }
} }
Future<bool> requestBitcoinElectrumServer() async { Future<bool> requestElectrumServer() async {
// FIXME: IMPLEMENT ME try {
return true; await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
return true;
} catch (_) {
return false;
}
} }
} }

View file

@ -20,9 +20,9 @@ Future<List<Node>> loadDefaultNodes() async {
}).toList(); }).toList();
} }
Future<List<Node>> loadElectrumServerList() async { Future<List<Node>> loadBitcoinElectrumServerList() async {
final serverListRaw = final serverListRaw =
await rootBundle.loadString('assets/electrum_server_list.yml'); await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final serverList = loadYaml(serverListRaw) as YamlList; final serverList = loadYaml(serverListRaw) as YamlList;
return serverList.map((dynamic raw) { return serverList.map((dynamic raw) {
@ -37,10 +37,29 @@ Future<List<Node>> loadElectrumServerList() async {
}).toList(); }).toList();
} }
Future<List<Node>> loadLitecoinElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final serverList = loadYaml(serverListRaw) as YamlList;
return serverList.map((dynamic raw) {
if (raw is Map) {
final node = Node.fromMap(raw);
node?.type = WalletType.litecoin;
return node;
}
return null;
}).toList();
}
Future resetToDefault(Box<Node> nodeSource) async { Future resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes(); final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadElectrumServerList(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final nodes = moneroNodes + bitcoinElectrumServerList; final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final nodes =
moneroNodes + bitcoinElectrumServerList + litecoinElectrumServerList;
await nodeSource.clear(); await nodeSource.clear();
await nodeSource.addAll(nodes); await nodeSource.addAll(nodes);

View file

@ -1,8 +1,9 @@
class PreferencesKey { class PreferencesKey {
static const currentWalletType ='current_wallet_type'; static const currentWalletType = 'current_wallet_type';
static const currentWalletName ='current_wallet_name'; static const currentWalletName = 'current_wallet_name';
static const currentNodeIdKey = 'current_node_id'; static const currentNodeIdKey = 'current_node_id';
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc'; static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentFiatCurrencyKey = 'current_fiat_currency'; static const currentFiatCurrencyKey = 'current_fiat_currency';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
@ -14,7 +15,8 @@ class PreferencesKey {
static const displayActionListModeKey = 'display_list_mode'; static const displayActionListModeKey = 'display_list_mode';
static const currentPinLength = 'current_pin_length'; static const currentPinLength = 'current_pin_length';
static const currentLanguageCode = 'language_code'; static const currentLanguageCode = 'language_code';
static const currentDefaultSettingsMigrationVersion = 'current_default_settings_migration_version'; static const currentDefaultSettingsMigrationVersion =
'current_default_settings_migration_version';
static const moneroTransactionPriority = 'current_fee_priority_monero'; static const moneroTransactionPriority = 'current_fee_priority_monero';
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin'; static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
} }

View file

@ -3,7 +3,11 @@ import 'package:hive/hive.dart';
part 'wallet_type.g.dart'; part 'wallet_type.g.dart';
const walletTypes = [WalletType.monero, WalletType.bitcoin]; const walletTypes = [
WalletType.monero,
WalletType.bitcoin,
WalletType.litecoin
];
const walletTypeTypeId = 5; const walletTypeTypeId = 5;
@HiveType(typeId: walletTypeTypeId) @HiveType(typeId: walletTypeTypeId)
@ -15,7 +19,10 @@ enum WalletType {
none, none,
@HiveField(2) @HiveField(2)
bitcoin bitcoin,
@HiveField(3)
litecoin
} }
int serializeToInt(WalletType type) { int serializeToInt(WalletType type) {
@ -24,6 +31,8 @@ int serializeToInt(WalletType type) {
return 0; return 0;
case WalletType.bitcoin: case WalletType.bitcoin:
return 1; return 1;
case WalletType.litecoin:
return 2;
default: default:
return -1; return -1;
} }
@ -35,6 +44,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.monero; return WalletType.monero;
case 1: case 1:
return WalletType.bitcoin; return WalletType.bitcoin;
case 2:
return WalletType.litecoin;
default: default:
return null; return null;
} }
@ -46,6 +57,8 @@ String walletTypeToString(WalletType type) {
return 'Monero'; return 'Monero';
case WalletType.bitcoin: case WalletType.bitcoin:
return 'Bitcoin'; return 'Bitcoin';
case WalletType.litecoin:
return 'Litecoin';
default: default:
return ''; return '';
} }
@ -57,6 +70,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Monero'; return 'Monero';
case WalletType.bitcoin: case WalletType.bitcoin:
return 'Bitcoin (Electrum)'; return 'Bitcoin (Electrum)';
case WalletType.litecoin:
return 'Litecoin';
default: default:
return ''; return '';
} }
@ -68,6 +83,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
return CryptoCurrency.xmr; return CryptoCurrency.xmr;
case WalletType.bitcoin: case WalletType.bitcoin:
return CryptoCurrency.btc; return CryptoCurrency.btc;
case WalletType.litecoin:
return CryptoCurrency.ltc;
default: default:
return null; return null;
} }

View file

@ -48,7 +48,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
@override @override
Future<Limits> fetchLimits({CryptoCurrency from, CryptoCurrency to, Future<Limits> fetchLimits({CryptoCurrency from, CryptoCurrency to,
bool isFixedRateMode}) async { bool isFixedRateMode}) async {
final symbol = from.toString() + '_' + to.toString(); final fromTitle = defineCurrencyTitle(from);
final toTitle = defineCurrencyTitle(to);
final symbol = fromTitle + '_' + toTitle;
final url = isFixedRateMode final url = isFixedRateMode
? apiUri + _marketInfoUriSufix + _fixedRateUriSufix + apiKey ? apiUri + _marketInfoUriSufix + _fixedRateUriSufix + apiKey
: apiUri + _minAmountUriSufix + symbol; : apiUri + _minAmountUriSufix + symbol;
@ -61,8 +63,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final elemFrom = elem["from"] as String; final elemFrom = elem["from"] as String;
final elemTo = elem["to"] as String; final elemTo = elem["to"] as String;
if ((elemFrom == from.toString().toLowerCase()) && if ((elemFrom == fromTitle) && (elemTo == toTitle)) {
(elemTo == to.toString().toLowerCase())) {
final min = elem["min"] as double; final min = elem["min"] as double;
final max = elem["max"] as double; final max = elem["max"] as double;
@ -84,9 +85,11 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
? apiUri + _transactionsUriSufix + _fixedRateUriSufix + apiKey ? apiUri + _transactionsUriSufix + _fixedRateUriSufix + apiKey
: apiUri + _transactionsUriSufix + apiKey; : apiUri + _transactionsUriSufix + apiKey;
final _request = request as ChangeNowRequest; final _request = request as ChangeNowRequest;
final fromTitle = defineCurrencyTitle(_request.from);
final toTitle = defineCurrencyTitle(_request.to);
final body = { final body = {
'from': _request.from.toString(), 'from': fromTitle,
'to': _request.to.toString(), 'to': toTitle,
'address': _request.address, 'address': _request.address,
'amount': _request.amount, 'amount': _request.amount,
'refundAddress': _request.refundAddress 'refundAddress': _request.refundAddress
@ -182,6 +185,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final url = apiUri + _marketInfoUriSufix + _fixedRateUriSufix + apiKey; final url = apiUri + _marketInfoUriSufix + _fixedRateUriSufix + apiKey;
final response = await get(url); final response = await get(url);
final responseJSON = json.decode(response.body) as List<dynamic>; final responseJSON = json.decode(response.body) as List<dynamic>;
final fromTitle = defineCurrencyTitle(from);
final toTitle = defineCurrencyTitle(to);
var rate = 0.0; var rate = 0.0;
var fee = 0.0; var fee = 0.0;
@ -189,8 +194,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final elemFrom = elem["from"] as String; final elemFrom = elem["from"] as String;
final elemTo = elem["to"] as String; final elemTo = elem["to"] as String;
if ((elemFrom == to.toString().toLowerCase()) && if ((elemFrom == toTitle) && (elemTo == fromTitle)) {
(elemTo == from.toString().toLowerCase())) {
rate = elem["rate"] as double; rate = elem["rate"] as double;
fee = elem["minerFee"] as double; fee = elem["minerFee"] as double;
break; break;
@ -216,22 +220,32 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
CryptoCurrency to, CryptoCurrency to,
double amount, double amount,
bool isFixedRateMode) { bool isFixedRateMode) {
final fromTitle = defineCurrencyTitle(from);
final toTitle = defineCurrencyTitle(to);
return isFixedRateMode return isFixedRateMode
? apiUri + ? apiUri +
_exchangeAmountUriSufix + _exchangeAmountUriSufix +
_fixedRateUriSufix + _fixedRateUriSufix +
amount.toString() + amount.toString() +
'/' + '/' +
from.toString() + fromTitle +
'_' + '_' +
to.toString() + toTitle +
'?api_key=' + apiKey '?api_key=' + apiKey
: apiUri + : apiUri +
_exchangeAmountUriSufix + _exchangeAmountUriSufix +
amount.toString() + amount.toString() +
'/' + '/' +
from.toString() + fromTitle +
'_' + '_' +
to.toString(); toTitle;
}
static String defineCurrencyTitle(CryptoCurrency currency) {
const bnbTitle = 'bnbmainnet';
final currencyTitle = currency == CryptoCurrency.bnb
? bnbTitle : currency.title.toLowerCase();
return currencyTitle;
} }
} }

View file

@ -107,7 +107,7 @@ Future<void> main() async {
exchangeTemplates: exchangeTemplates, exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
initialMigrationVersion: 13); initialMigrationVersion: 15);
runApp(App()); runApp(App());
} catch (e) { } catch (e) {
runApp(MaterialApp( runApp(MaterialApp(
@ -135,7 +135,7 @@ Future<void> initialSetup(
@required Box<ExchangeTemplate> exchangeTemplates, @required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions, @required Box<TransactionDescription> transactionDescriptions,
FlutterSecureStorage secureStorage, FlutterSecureStorage secureStorage,
int initialMigrationVersion = 13}) async { int initialMigrationVersion = 15}) async {
LanguageService.loadLocaleList(); LanguageService.loadLocaleList();
await defaultSettingsMigration( await defaultSettingsMigration(
secureStorage: secureStorage, secureStorage: secureStorage,

View file

@ -49,13 +49,13 @@ abstract class MoneroAccountListBase with Store {
Future addAccount({String label}) async { Future addAccount({String label}) async {
await account_list.addAccount(label: label); await account_list.addAccount(label: label);
await update(); update();
} }
Future setLabelAccount({int accountIndex, String label}) async { Future setLabelAccount({int accountIndex, String label}) async {
await account_list.setLabelForAccount( await account_list.setLabelForAccount(
accountIndex: accountIndex, label: label); accountIndex: accountIndex, label: label);
await update(); update();
} }
void refresh() { void refresh() {

View file

@ -1,18 +1,10 @@
import 'dart:core'; import 'dart:core';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cw_monero/transaction_history.dart'
as monero_transaction_history;
import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/monero/monero_transaction_info.dart'; import 'package:cake_wallet/monero/monero_transaction_info.dart';
part 'monero_transaction_history.g.dart'; part 'monero_transaction_history.g.dart';
List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
monero_transaction_history
.getAllTransations()
.map((row) => MoneroTransactionInfo.fromRow(row))
.toList();
class MoneroTransactionHistory = MoneroTransactionHistoryBase class MoneroTransactionHistory = MoneroTransactionHistoryBase
with _$MoneroTransactionHistory; with _$MoneroTransactionHistory;
@ -23,30 +15,13 @@ abstract class MoneroTransactionHistoryBase
} }
@override @override
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async { Future<void> save() async {}
monero_transaction_history.refreshTransactions();
return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
<String, MoneroTransactionInfo>{},
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
acc[tx.id] = tx;
return acc;
});
}
@override @override
@action void addOne(MoneroTransactionInfo transaction) =>
void updateAsync({void Function() onFinished}) { transactions[transaction.id] = transaction;
fetchTransactionsAsync(
(transaction) => transactions[transaction.id] = transaction,
onFinished: onFinished);
}
@override @override
void fetchTransactionsAsync( void addMany(Map<String, MoneroTransactionInfo> transactions) =>
void Function(MoneroTransactionInfo transaction) onTransactionLoaded, this.transactions.addAll(transactions);
{void Function() onFinished}) async {
final transactions = await fetchTransactions();
transactions.values.forEach((tx) => onTransactionLoaded(tx));
onFinished?.call();
}
} }

View file

@ -59,6 +59,7 @@ class MoneroTransactionInfo extends TransactionInfo {
@override @override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() => String feeFormatted() =>
'${formatAmount(moneroAmountToString(amount: fee))} XMR'; '${formatAmount(moneroAmountToString(amount: fee))} XMR';
} }

View file

@ -1,10 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:cake_wallet/monero/monero_amount_format.dart'; import 'package:cake_wallet/monero/monero_amount_format.dart';
import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart'; import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart';
import 'package:cake_wallet/monero/monero_transaction_info.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cw_monero/transaction_history.dart'
as monero_transaction_history;
import 'package:cw_monero/wallet.dart'; import 'package:cw_monero/wallet.dart';
import 'package:cw_monero/wallet.dart' as monero_wallet; import 'package:cw_monero/wallet.dart' as monero_wallet;
import 'package:cw_monero/transaction_history.dart' as transaction_history; import 'package:cw_monero/transaction_history.dart' as transaction_history;
@ -30,19 +32,21 @@ const moneroBlockSize = 1000;
class MoneroWallet = MoneroWalletBase with _$MoneroWallet; class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store { abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
MoneroWalletBase({String filename, WalletInfo walletInfo}) MoneroTransactionHistory, MoneroTransactionInfo> with Store {
: transactionHistory = MoneroTransactionHistory(), MoneroWalletBase({WalletInfo walletInfo})
accountList = MoneroAccountList(), : accountList = MoneroAccountList(),
subaddressList = MoneroSubaddressList(), subaddressList = MoneroSubaddressList(),
super(walletInfo) { super(walletInfo) {
_filename = filename; transactionHistory = MoneroTransactionHistory();
balance = MoneroBalance( balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: 0), fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0)); unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
_lastAutosaveTimestamp = 0; _lastAutosaveTimestamp = 0;
_lastSaveTimestamp = 0;
_isSavingAfterSync = false; _isSavingAfterSync = false;
_isSavingAfterNewTransaction = false; _isSavingAfterNewTransaction = false;
_isTransactionUpdating = false;
_onAccountChangeReaction = reaction((_) => account, (Account account) { _onAccountChangeReaction = reaction((_) => account, (Account account) {
balance = MoneroBalance( balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id), fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
@ -56,9 +60,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
static const int _autoAfterSyncSaveInterval = 60000; static const int _autoAfterSyncSaveInterval = 60000;
@override
final MoneroTransactionHistory transactionHistory;
@observable @observable
Account account; Account account;
@ -91,19 +92,18 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
final MoneroAccountList accountList; final MoneroAccountList accountList;
String _filename;
SyncListener _listener; SyncListener _listener;
ReactionDisposer _onAccountChangeReaction; ReactionDisposer _onAccountChangeReaction;
int _lastAutosaveTimestamp; int _lastAutosaveTimestamp;
bool _isSavingAfterSync; bool _isSavingAfterSync;
bool _isSavingAfterNewTransaction; bool _isSavingAfterNewTransaction;
bool _isTransactionUpdating;
int _lastSaveTimestamp;
Future<void> init() async { Future<void> init() async {
accountList.update(); accountList.update();
accountList.accounts.observe((_) async => await save());
account = accountList.accounts.first; account = accountList.accounts.first;
subaddressList.update(accountIndex: account.id ?? 0); subaddressList.update(accountIndex: account.id ?? 0);
subaddressList.subaddresses.observe((_) async => await save());
subaddress = subaddressList.getAll().first; subaddress = subaddressList.getAll().first;
balance = MoneroBalance( balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id), fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
@ -111,7 +111,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
monero_wallet.getUnlockedBalance(accountIndex: account.id)); monero_wallet.getUnlockedBalance(accountIndex: account.id));
address = subaddress.address; address = subaddress.address;
_setListeners(); _setListeners();
await transactionHistory.update(); await updateTransactions();
if (walletInfo.isRecovery) { if (walletInfo.isRecovery) {
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery); monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
@ -152,7 +152,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
try { try {
syncStatus = ConnectingSyncStatus(); syncStatus = ConnectingSyncStatus();
await monero_wallet.setupNode( await monero_wallet.setupNode(
address: node.uri, address: node.uri.toString(),
login: node.login, login: node.login,
password: node.password, password: node.password,
useSSL: node.isSSL, useSSL: node.isSSL,
@ -238,6 +238,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
@override @override
Future<void> save() async { Future<void> save() async {
final now = DateTime.now().millisecondsSinceEpoch;
if (now - _lastSaveTimestamp < Duration(seconds: 10).inMilliseconds) {
return;
}
_lastSaveTimestamp = now;
await monero_wallet.store(); await monero_wallet.store();
} }
@ -264,6 +271,40 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
await walletInfo.save(); await walletInfo.save();
} }
@override
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
monero_transaction_history.refreshTransactions();
return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
<String, MoneroTransactionInfo>{},
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
acc[tx.id] = tx;
return acc;
});
}
Future<void> updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (e) {
print(e);
_isTransactionUpdating = false;
}
}
List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
monero_transaction_history
.getAllTransations()
.map((row) => MoneroTransactionInfo.fromRow(row))
.toList();
void _setListeners() { void _setListeners() {
_listener?.stop(); _listener?.stop();
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction); _listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
@ -315,7 +356,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
} }
Future<void> _askForUpdateTransactionHistory() async => Future<void> _askForUpdateTransactionHistory() async =>
await transactionHistory.update(); await updateTransactions();
int _getFullBalance() => int _getFullBalance() =>
monero_wallet.getFullBalance(accountIndex: account.id); monero_wallet.getFullBalance(accountIndex: account.id);
@ -389,11 +430,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
} }
} }
void _onNewTransaction() { void _onNewTransaction() async {
try { try {
_askForUpdateTransactionHistory(); await _askForUpdateTransactionHistory();
_askForUpdateBalance(); _askForUpdateBalance();
Timer(Duration(seconds: 1), () => _afterNewTransactionSave()); await Future<void>.delayed(Duration(seconds: 1));
await _afterNewTransactionSave();
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }

View file

@ -68,18 +68,18 @@ class MoneroWalletService extends WalletService<
static bool walletFilesExist(String path) => static bool walletFilesExist(String path) =>
!File(path).existsSync() && !File('$path.keys').existsSync(); !File(path).existsSync() && !File('$path.keys').existsSync();
@override
WalletType getType() => WalletType.monero;
@override @override
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async { Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
try { try {
final path = final path = await pathForWallet(name: credentials.name, type: getType());
await pathForWallet(name: credentials.name, type: WalletType.monero);
await monero_wallet_manager.createWallet( await monero_wallet_manager.createWallet(
path: path, path: path,
password: credentials.password, password: credentials.password,
language: credentials.language); language: credentials.language);
final wallet = MoneroWallet( final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
filename: monero_wallet.getFilename(),
walletInfo: credentials.walletInfo);
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -93,7 +93,7 @@ class MoneroWalletService extends WalletService<
@override @override
Future<bool> isWalletExit(String name) async { Future<bool> isWalletExit(String name) async {
try { try {
final path = await pathForWallet(name: name, type: WalletType.monero); final path = await pathForWallet(name: name, type: getType());
return monero_wallet_manager.isWalletExist(path: path); return monero_wallet_manager.isWalletExist(path: path);
} catch (e) { } catch (e) {
// TODO: Implement Exception for wallet list service. // TODO: Implement Exception for wallet list service.
@ -105,7 +105,7 @@ class MoneroWalletService extends WalletService<
@override @override
Future<MoneroWallet> openWallet(String name, String password) async { Future<MoneroWallet> openWallet(String name, String password) async {
try { try {
final path = await pathForWallet(name: name, type: WalletType.monero); final path = await pathForWallet(name: name, type: getType());
if (walletFilesExist(path)) { if (walletFilesExist(path)) {
await repairOldAndroidWallet(name); await repairOldAndroidWallet(name);
@ -114,10 +114,9 @@ class MoneroWalletService extends WalletService<
await monero_wallet_manager await monero_wallet_manager
.openWalletAsync({'path': path, 'password': password}); .openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values.firstWhere( final walletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(name, WalletType.monero), (info) => info.id == WalletBase.idFor(name, getType()),
orElse: () => null); orElse: () => null);
final wallet = MoneroWallet( final wallet = MoneroWallet(walletInfo: walletInfo);
filename: monero_wallet.getFilename(), walletInfo: walletInfo);
final isValid = wallet.validate(); final isValid = wallet.validate();
if (!isValid) { if (!isValid) {
@ -146,7 +145,7 @@ class MoneroWalletService extends WalletService<
@override @override
Future<void> remove(String wallet) async { Future<void> remove(String wallet) async {
final path = await pathForWalletDir(name: wallet, type: WalletType.monero); final path = await pathForWalletDir(name: wallet, type: getType());
final file = Directory(path); final file = Directory(path);
final isExist = file.existsSync(); final isExist = file.existsSync();
@ -159,8 +158,7 @@ class MoneroWalletService extends WalletService<
Future<MoneroWallet> restoreFromKeys( Future<MoneroWallet> restoreFromKeys(
MoneroRestoreWalletFromKeysCredentials credentials) async { MoneroRestoreWalletFromKeysCredentials credentials) async {
try { try {
final path = final path = await pathForWallet(name: credentials.name, type: getType());
await pathForWallet(name: credentials.name, type: WalletType.monero);
await monero_wallet_manager.restoreFromKeys( await monero_wallet_manager.restoreFromKeys(
path: path, path: path,
password: credentials.password, password: credentials.password,
@ -169,9 +167,7 @@ class MoneroWalletService extends WalletService<
address: credentials.address, address: credentials.address,
viewKey: credentials.viewKey, viewKey: credentials.viewKey,
spendKey: credentials.spendKey); spendKey: credentials.spendKey);
final wallet = MoneroWallet( final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
filename: monero_wallet.getFilename(),
walletInfo: credentials.walletInfo);
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -186,16 +182,13 @@ class MoneroWalletService extends WalletService<
Future<MoneroWallet> restoreFromSeed( Future<MoneroWallet> restoreFromSeed(
MoneroRestoreWalletFromSeedCredentials credentials) async { MoneroRestoreWalletFromSeedCredentials credentials) async {
try { try {
final path = final path = await pathForWallet(name: credentials.name, type: getType());
await pathForWallet(name: credentials.name, type: WalletType.monero);
await monero_wallet_manager.restoreFromSeed( await monero_wallet_manager.restoreFromSeed(
path: path, path: path,
password: credentials.password, password: credentials.password,
seed: credentials.mnemonic, seed: credentials.mnemonic,
restoreHeight: credentials.height); restoreHeight: credentials.height);
final wallet = MoneroWallet( final wallet = MoneroWallet(walletInfo: credentials.walletInfo);
filename: monero_wallet.getFilename(),
walletInfo: credentials.walletInfo);
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -221,14 +214,14 @@ class MoneroWalletService extends WalletService<
} }
final newWalletDirPath = final newWalletDirPath =
await pathForWalletDir(name: name, type: WalletType.monero); await pathForWalletDir(name: name, type: getType());
dir.listSync().forEach((f) { dir.listSync().forEach((f) {
final file = File(f.path); final file = File(f.path);
final name = f.path.split('/').last; final name = f.path.split('/').last;
final newPath = newWalletDirPath + '/$name'; final newPath = newWalletDirPath + '/$name';
final newFile = File(newPath); final newFile = File(newPath);
print(file.path);
if (!newFile.existsSync()) { if (!newFile.existsSync()) {
newFile.createSync(); newFile.createSync();
} }

View file

@ -7,19 +7,22 @@ import 'package:connectivity/connectivity.dart';
Timer _checkConnectionTimer; Timer _checkConnectionTimer;
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore, {int timeInterval = 5}) { void startCheckConnectionReaction(
WalletBase wallet, SettingsStore settingsStore,
{int timeInterval = 5}) {
_checkConnectionTimer?.cancel(); _checkConnectionTimer?.cancel();
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async { _checkConnectionTimer =
final connectivityResult = await (Connectivity().checkConnectivity()); Timer.periodic(Duration(seconds: timeInterval), (_) async {
try {
final connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) { if (connectivityResult == ConnectivityResult.none) {
wallet.syncStatus = FailedSyncStatus(); wallet.syncStatus = FailedSyncStatus();
return; return;
} }
if (wallet.syncStatus is LostConnectionSyncStatus || if (wallet.syncStatus is LostConnectionSyncStatus ||
wallet.syncStatus is FailedSyncStatus) { wallet.syncStatus is FailedSyncStatus) {
try {
final alive = final alive =
await settingsStore.getCurrentNode(wallet.type).requestNode(); await settingsStore.getCurrentNode(wallet.type).requestNode();
@ -27,9 +30,9 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
await wallet.connectToNode( await wallet.connectToNode(
node: settingsStore.getCurrentNode(wallet.type)); node: settingsStore.getCurrentNode(wallet.type));
} }
} catch (_) {
// FIXME: empty catch clojure
} }
} catch (e) {
print(e.toString());
} }
}); });
} }

View file

@ -1,4 +1,6 @@
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
@ -18,9 +20,11 @@ ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction;
void startCurrentWalletChangeReaction(AppStore appStore, void startCurrentWalletChangeReaction(AppStore appStore,
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) { SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
_onCurrentWalletChangeReaction?.reaction?.dispose(); _onCurrentWalletChangeReaction?.reaction?.dispose();
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction?.dispose();
_onCurrentWalletChangeReaction = _onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async { Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
wallet) async {
try { try {
final node = settingsStore.getCurrentNode(wallet.type); final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet); startWalletSyncStatusChangeReaction(wallet);
@ -45,7 +49,9 @@ void startCurrentWalletChangeReaction(AppStore appStore,
}); });
_onCurrentWalletChangeFiatRateUpdateReaction = _onCurrentWalletChangeFiatRateUpdateReaction =
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async { reaction((_) => appStore.wallet, (WalletBase<Balance,
TransactionHistoryBase<TransactionInfo>, TransactionInfo>
wallet) async {
try { try {
fiatConversionStore.prices[wallet.currency] = 0; fiatConversionStore.prices[wallet.currency] = 0;
fiatConversionStore.prices[wallet.currency] = fiatConversionStore.prices[wallet.currency] =

View file

@ -1,16 +1,21 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/entities/sync_status.dart'; import 'package:cake_wallet/entities/sync_status.dart';
ReactionDisposer _onWalletSyncStatusChangeReaction; ReactionDisposer _onWalletSyncStatusChangeReaction;
void startWalletSyncStatusChangeReaction(WalletBase<Balance> wallet) { void startWalletSyncStatusChangeReaction(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
TransactionInfo>
wallet) {
_onWalletSyncStatusChangeReaction?.reaction?.dispose(); _onWalletSyncStatusChangeReaction?.reaction?.dispose();
_onWalletSyncStatusChangeReaction = _onWalletSyncStatusChangeReaction =
reaction((_) => wallet.syncStatus, (SyncStatus status) async { reaction((_) => wallet.syncStatus, (SyncStatus status) async {
if (status is ConnectedSyncStatus) { if (status is ConnectedSyncStatus) {
await wallet.startSync(); await wallet.startSync();
} }
}); });
} }

View file

@ -239,9 +239,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.unlock: case Routes.unlock:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => getIt.get<AuthPage>( builder: (_) => WillPopScope(
param1: settings.arguments as OnAuthenticationFinished, child: getIt.get<AuthPage>(
param2: false)); param1: settings.arguments as OnAuthenticationFinished,
param2: false),
onWillPop: () async => false));
case Routes.nodeList: case Routes.nodeList:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(

View file

@ -1,7 +1,9 @@
import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@ -14,6 +16,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart';
@ -24,8 +27,8 @@ class DashboardPage extends BasePage {
}); });
@override @override
Color get backgroundLightColor => currentTheme.type == ThemeType.bright Color get backgroundLightColor =>
? Colors.transparent : Colors.white; currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@override @override
Color get backgroundDarkColor => Colors.transparent; Color get backgroundDarkColor => Colors.transparent;
@ -42,7 +45,7 @@ class DashboardPage extends BasePage {
child: scaffold); child: scaffold);
@override @override
bool get resizeToAvoidBottomPadding => false; bool get resizeToAvoidBottomInset => false;
@override @override
Widget get endDrawer => MenuWidget(walletViewModel); Widget get endDrawer => MenuWidget(walletViewModel);
@ -54,9 +57,8 @@ class DashboardPage extends BasePage {
@override @override
Widget trailing(BuildContext context) { Widget trailing(BuildContext context) {
final menuButton = final menuButton = Image.asset('assets/images/menu.png',
Image.asset('assets/images/menu.png', color: Theme.of(context).accentTextTheme.display3.backgroundColor);
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
return Container( return Container(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
@ -79,15 +81,18 @@ class DashboardPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
final sendImage = Image.asset('assets/images/upload.png', final sendImage = Image.asset('assets/images/upload.png',
height: 22.24, width: 24, height: 22.24,
width: 24,
color: Theme.of(context).accentTextTheme.display3.backgroundColor); color: Theme.of(context).accentTextTheme.display3.backgroundColor);
final exchangeImage = Image.asset('assets/images/transfer.png', final exchangeImage = Image.asset('assets/images/transfer.png',
height: 24.27, width: 22.25, height: 24.27,
width: 22.25,
color: Theme.of(context).accentTextTheme.display3.backgroundColor); color: Theme.of(context).accentTextTheme.display3.backgroundColor);
final buyImage = Image.asset('assets/images/coins.png', final buyImage = Image.asset('assets/images/coins.png',
height: 22.24, width: 24, height: 22.24,
width: 24,
color: Theme.of(context).accentTextTheme.display3.backgroundColor); color: Theme.of(context).accentTextTheme.display3.backgroundColor);
_setEffects(); _setEffects(context);
return SafeArea( return SafeArea(
child: Column( child: Column(
@ -109,12 +114,14 @@ class DashboardPage extends BasePage {
dotWidth: 6.0, dotWidth: 6.0,
dotHeight: 6.0, dotHeight: 6.0,
dotColor: Theme.of(context).indicatorColor, dotColor: Theme.of(context).indicatorColor,
activeDotColor: Theme.of(context).accentTextTheme.display1 activeDotColor: Theme.of(context)
.accentTextTheme
.display1
.backgroundColor), .backgroundColor),
)), )),
Container( Container(
padding: EdgeInsets.only(left: 45, right: 45, bottom: 24), padding: EdgeInsets.only(left: 45, right: 45, bottom: 24),
child: Observer(builder: (_) => Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
ActionButton( ActionButton(
@ -125,21 +132,20 @@ class DashboardPage extends BasePage {
image: exchangeImage, image: exchangeImage,
title: S.of(context).exchange, title: S.of(context).exchange,
route: Routes.exchange), route: Routes.exchange),
if (walletViewModel.type == WalletType.bitcoin) ActionButton( ActionButton(
image: buyImage, image: buyImage,
title: S.of(context).buy, title: S.of(context).buy,
onClick: () { onClick: () async =>
Navigator.of(context).pushNamed(Routes.preOrder); await _onClickBuyButton(context),
},
), ),
], ],
)), ),
) )
], ],
)); ));
} }
void _setEffects() { void _setEffects(BuildContext context) {
if (_isEffectsInstalled) { if (_isEffectsInstalled) {
return; return;
} }
@ -148,6 +154,45 @@ class DashboardPage extends BasePage {
pages.add(BalancePage(dashboardViewModel: walletViewModel)); pages.add(BalancePage(dashboardViewModel: walletViewModel));
pages.add(TransactionsPage(dashboardViewModel: walletViewModel)); pages.add(TransactionsPage(dashboardViewModel: walletViewModel));
autorun((_) async {
if (!walletViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent:
S.of(context).outdated_electrum_wallet_desceription,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop());
});
});
_isEffectsInstalled = true; _isEffectsInstalled = true;
} }
Future<void> _onClickBuyButton(BuildContext context) async {
final walletType = walletViewModel.type;
switch (walletType) {
case WalletType.bitcoin:
Navigator.of(context).pushNamed(Routes.preOrder);
break;
default:
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: S.of(context).buy_alert_content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
break;
}
}
} }

View file

@ -21,6 +21,7 @@ class MenuWidget extends StatefulWidget {
class MenuWidgetState extends State<MenuWidget> { class MenuWidgetState extends State<MenuWidget> {
Image moneroIcon; Image moneroIcon;
Image bitcoinIcon; Image bitcoinIcon;
Image litecoinIcon;
final largeScreen = 731; final largeScreen = 731;
double menuWidth; double menuWidth;
@ -76,6 +77,7 @@ class MenuWidgetState extends State<MenuWidget> {
color: Theme.of(context).accentTextTheme.overline.decorationColor); color: Theme.of(context).accentTextTheme.overline.decorationColor);
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png', bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
color: Theme.of(context).accentTextTheme.overline.decorationColor); color: Theme.of(context).accentTextTheme.overline.decorationColor);
litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
return Row( return Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
@ -238,6 +240,8 @@ class MenuWidgetState extends State<MenuWidget> {
return moneroIcon; return moneroIcon;
case WalletType.bitcoin: case WalletType.bitcoin:
return bitcoinIcon; return bitcoinIcon;
case WalletType.litecoin:
return litecoinIcon;
default: default:
return null; return null;
} }

View file

@ -248,7 +248,8 @@ class ExchangePage extends BasePage {
exchangeViewModel exchangeViewModel
.isReceiveAddressEnabled, .isReceiveAddressEnabled,
isAmountEstimated: true, isAmountEstimated: true,
currencies: CryptoCurrency.all, currencies:
exchangeViewModel.receiveCurrencies,
onCurrencySelected: (currency) => onCurrencySelected: (currency) =>
exchangeViewModel exchangeViewModel
.changeReceiveCurrency( .changeReceiveCurrency(

View file

@ -59,6 +59,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
Image.asset('assets/images/monero_logo.png', height: 24, width: 24); Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
final bitcoinIcon = final bitcoinIcon =
Image.asset('assets/images/bitcoin.png', height: 24, width: 24); Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final litecoinIcon =
Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final walletTypeImage = Image.asset('assets/images/wallet_type.png'); final walletTypeImage = Image.asset('assets/images/wallet_type.png');
final walletTypeLightImage = final walletTypeLightImage =
Image.asset('assets/images/wallet_type_light.png'); Image.asset('assets/images/wallet_type_light.png');
@ -69,7 +71,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
@override @override
void initState() { void initState() {
types = [WalletType.bitcoin, WalletType.monero]; types = [WalletType.bitcoin, WalletType.monero, WalletType.litecoin];
super.initState(); super.initState();
} }
@ -84,8 +86,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
padding: EdgeInsets.only(left: 12, right: 12), padding: EdgeInsets.only(left: 12, right: 12),
child: AspectRatio( child: AspectRatio(
aspectRatio: aspectRatioImage, aspectRatio: aspectRatioImage,
child: child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
), ),
Padding( Padding(
padding: EdgeInsets.only(top: 48), padding: EdgeInsets.only(top: 48),
@ -99,13 +100,13 @@ class WalletTypeFormState extends State<WalletTypeForm> {
), ),
), ),
...types.map((type) => Padding( ...types.map((type) => Padding(
padding: EdgeInsets.only(top: 24), padding: EdgeInsets.only(top: 24),
child: SelectButton( child: SelectButton(
image: _iconFor(type), image: _iconFor(type),
text: walletTypeToDisplayName(type), text: walletTypeToDisplayName(type),
isSelected: selected == type, isSelected: selected == type,
onTap: () => setState(() => selected = type)), onTap: () => setState(() => selected = type)),
)) ))
], ],
), ),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
@ -125,6 +126,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
return moneroIcon; return moneroIcon;
case WalletType.bitcoin: case WalletType.bitcoin:
return bitcoinIcon; return bitcoinIcon;
case WalletType.litecoin:
return litecoinIcon;
default: default:
return null; return null;
} }

View file

@ -87,7 +87,7 @@ class NodeListPage extends BasePage {
final isSelected = final isSelected =
node.keyIndex == nodeListViewModel.currentNode?.keyIndex; node.keyIndex == nodeListViewModel.currentNode?.keyIndex;
final nodeListRow = NodeListRow( final nodeListRow = NodeListRow(
title: node.uri, title: node.uriRaw,
isSelected: isSelected, isSelected: isSelected,
isAlive: node.requestNode(), isAlive: node.requestNode(),
onTap: (_) async { onTap: (_) async {
@ -101,8 +101,9 @@ class NodeListPage extends BasePage {
return AlertWithTwoActions( return AlertWithTwoActions(
alertTitle: alertTitle:
S.of(context).change_current_node_title, S.of(context).change_current_node_title,
alertContent: alertContent: S
S.of(context).change_current_node(node.uri), .of(context)
.change_current_node(node.uriRaw),
leftButtonText: S.of(context).cancel, leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change, rightButtonText: S.of(context).change,
actionLeftButton: () => actionLeftButton: () =>

Some files were not shown because too many files have changed in this diff Show more