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/**
ios/Flutter/.last_build_id
/lib/generated/**
#**#
/**/#**#

View file

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

View file

@ -1,15 +1,49 @@
package com.cakewallet.cake_wallet;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterFragmentActivity;
import io.flutter.embedding.engine.FlutterEngine;
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 {
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine 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;
uint64_t m_last_known_wallet_height;
uint64_t m_cached_syncing_blockchain_height = 0;
std::mutex store_mutex;
void change_current_wallet(Monero::Wallet *wallet)
{
@ -451,9 +449,7 @@ extern "C"
void store(char *path)
{
store_mutex.lock();
get_current_wallet()->store(std::string(path));
store_mutex.unlock();
}
bool transaction_create(char *address, char *payment_id, char *amount,

View file

@ -5,20 +5,18 @@
Pod::Spec.new do |s|
s.name = 'cw_monero'
s.version = '0.0.2'
s.summary = 'A new flutter plugin project.'
s.description = <<-DESC
A new flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.summary = 'CW Monero'
s.description = 'Cake Wallet wrapper over Monero project.'
s.homepage = 'http://cakewallet.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.author = { 'CakeWallet' => 'support@cakewallet.com' }
s.source = { :path => '.' }
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.dependency 'Flutter'
s.platform = :ios, '9.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.subspec 'OpenSSL' do |openssl|
@ -53,4 +51,4 @@ A new flutter plugin project.
lmdb.vendored_libraries = 'External/ios/libs/lmdb/liblmdb.a'
lmdb.libraries = 'lmdb'
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/structs/account_row.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_monero/wallet.dart';
final accountSizeNative = moneroApi
.lookup<NativeFunction<account_size>>('account_size')
@ -70,8 +71,13 @@ void _setLabelForAccount(Map<String, dynamic> args) {
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 =>
compute(
_setLabelForAccount, {'accountIndex': accountIndex, 'label': label});
Future<void> setLabelForAccount({int accountIndex, String label}) async {
await compute(
_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/monero_api.dart';
import 'package:cw_monero/structs/subaddress_row.dart';
import 'package:cw_monero/wallet.dart';
final subaddressSizeNative = moneroApi
.lookup<NativeFunction<subaddrress_size>>('subaddrress_size')
@ -79,14 +80,18 @@ void _setLabelForSubaddress(Map<String, dynamic> args) {
accountIndex: accountIndex, addressIndex: addressIndex, label: label);
}
Future addSubaddress({int accountIndex, String label}) async =>
compute<Map<String, Object>, void>(
Future addSubaddress({int accountIndex, String label}) async {
await compute<Map<String, Object>, void>(
_addSubaddress, {'accountIndex': accountIndex, 'label': label});
await store();
}
Future setLabelForSubaddress(
{int accountIndex, int addressIndex, String label}) =>
compute<Map<String, Object>, void>(_setLabelForSubaddress, {
'accountIndex': accountIndex,
'addressIndex': addressIndex,
'label': label
});
{int accountIndex, int addressIndex, String label}) async {
await compute<Map<String, Object>, void>(_setLabelForSubaddress, {
'accountIndex': accountIndex,
'addressIndex': addressIndex,
'label': label
});
await store();
}

View file

@ -1,12 +1,12 @@
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:flutter/foundation.dart';
import 'package:cw_monero/convert_utf8_to_string.dart';
import 'package:cw_monero/signatures.dart';
import 'package:cw_monero/types.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_restore_from_keys_exception.dart';
import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart';

View file

@ -7,49 +7,49 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.2"
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.13"
version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
ffi:
dependency: "direct main"
description:
@ -73,21 +73,21 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.8"
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.0"
path_provider:
dependency: "direct main"
description:
@ -113,56 +113,56 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.5"
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.17"
version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
version: "2.1.0"
sdks:
dart: ">=2.9.0-14.0.dev <3.0.0"
flutter: ">=0.1.4 <2.0.0"
dart: ">=2.12.0-0.0 <3.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
Android SDK 28
Android NDK 17c
Flutter 1.22.6
Flutter 2 or above
```
## Building CakeWallet on Android
@ -51,9 +51,9 @@ You may download and install the latest version of Android Studio [here](https:/
### 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
@ -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.
```
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 Studio (version 4.0)
```
@ -87,7 +87,7 @@ $ cd /opt/android
..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:

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

View file

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
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 */; };
20ED0868E1BD7E12278C0CB3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */; };
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>"; };
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; };
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>"; };
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>"; };
@ -65,6 +67,7 @@
isa = PBXGroup;
children = (
0C44A7192518EF8000B570ED /* decrypt.swift */,
0C9D68C8264854B60011B691 /* secRandom.swift */,
);
path = CakeWallet;
sourceTree = "<group>";
@ -274,6 +277,7 @@
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
0C9D68C9264854B60011B691 /* secRandom.swift in Sources */,
0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -331,6 +335,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
@ -357,7 +362,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 31;
CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -374,7 +379,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.1.3;
MARKETING_VERSION = 4.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -415,6 +420,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
@ -470,6 +476,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
@ -498,7 +505,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 31;
CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -515,7 +522,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.1.3;
MARKETING_VERSION = 4.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -533,7 +540,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 31;
CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -550,7 +557,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.1.3;
MARKETING_VERSION = 4.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View file

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

View file

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

View file

@ -8,9 +8,10 @@ import Flutter
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "com.cakewallet.cakewallet/legacy_wallet_migration",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler({
let legacyMigrationChannel = FlutterMethodChannel(
name: "com.cakewallet.cakewallet/legacy_wallet_migration",
binaryMessenger: controller.binaryMessenger)
legacyMigrationChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
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)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

View file

@ -2,100 +2,100 @@
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
"scale" : "3x",
"size" : "40x40"
},
{
"size" : "60x60",
"filename" : "app_icon_120.png",
"idiom" : "iphone",
"filename" : "cake_xmr_120.png",
"scale" : "2x"
"scale" : "2x",
"size" : "60x60"
},
{
"size" : "60x60",
"filename" : "app_icon_180.png",
"idiom" : "iphone",
"filename" : "cake_xmr_180.png",
"scale" : "3x"
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"size" : "1024x1024",
"filename" : "app_icon_1024.png",
"idiom" : "ios-marketing",
"filename" : "cake_xmr_1024.png",
"scale" : "1x"
"scale" : "1x",
"size" : "1024x1024"
}
],
"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>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>Used for scan QR code</string>
<key>NSDocumentsFolderUsageDescription</key>

View file

@ -1,25 +1,29 @@
import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bs58check/bs58check.dart' as bs58check;
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
import 'package:bitcoin_flutter/src/address.dart';
Uint8List p2shAddressToOutputScript(String address) {
final decodeBase58 = bs58check.decode(address);
final hash = decodeBase58.sublist(1);
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
}
Uint8List addressToOutputScript(String address) {
Uint8List addressToOutputScript(
String address, bitcoin.NetworkType networkType) {
try {
// 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 Address.addressToOutputScript(address);
} catch (_) {
return Address.addressToOutputScript(address, networkType);
} catch (err) {
print(err);
return Uint8List(0);
}
}
}

View file

@ -1,14 +1,14 @@
import 'dart:convert';
import 'package:quiver/core.dart';
class BitcoinAddressRecord {
BitcoinAddressRecord(this.address, {this.index});
BitcoinAddressRecord(this.address, {this.index, bool isHidden})
: _isHidden = isHidden;
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(decoded['address'] as String,
index: decoded['index'] as int);
index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool);
}
@override
@ -16,10 +16,13 @@ class BitcoinAddressRecord {
o is BitcoinAddressRecord && address == o.address;
final String address;
bool get isHidden => _isHidden ?? false;
int index;
final bool _isHidden;
@override
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:unorm_dart/unorm_dart.dart' as unorm;
import 'package:cryptography/cryptography.dart' as cryptography;
import 'package:cake_wallet/core/sec_random_native.dart';
const segwit = '100';
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);
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();
}
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 wordCount = strength / wordBitlen;
final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil();
var result = '';
do {
final bytes = randomBytes(byteCount);
final bytes = await secRandom(byteCount);
maskBytes(bytes, strength);
result = encode(bytes);
} while (!prefixMatches(result, [prefix]).first);
@ -134,7 +123,7 @@ bool matchesAnyPrefix(String mnemonic) =>
bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
try {
return matchesAnyPrefix(mnemonic);
} catch(e) {
} catch (e) {
return false;
}
}

View file

@ -1,5 +1,5 @@
class BitcoinMnemonicIsIncorrectException implements Exception {
@override
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
String toString() {
var label = '';
switch (this) {
case BitcoinTransactionPriority.slow:
label = S.current.transaction_priority_slow;
label = '${S.current.transaction_priority_slow} ~24hrs';
break;
case BitcoinTransactionPriority.medium:
label = S.current.transaction_priority_medium;
@ -46,4 +48,56 @@ class BitcoinTransactionPriority extends TransactionPriority {
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 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:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
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/bitcoin_amount_format.dart';
import 'package:cake_wallet/entities/sync_status.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet.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/file.dart';
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
part 'bitcoin_wallet.g.dart';
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
BitcoinWalletBase._internal(
{@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(
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinWalletBase(
{@required String mnemonic,
@required String password,
@required String name,
@required String dirPath,
@required WalletInfo walletInfo,
List<BitcoinAddressRecord> initialAddresses,
BitcoinBalance initialBalance,
int accountIndex = 0}) {
final walletPath = '$dirPath/$name';
final eclient = ElectrumClient();
final history = BitcoinTransactionHistory(
eclient: eclient, dirPath: dirPath, password: password);
ElectrumBalance initialBalance,
int accountIndex = 0})
: super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
accountIndex: accountIndex);
return BitcoinWallet._internal(
eclient: eclient,
path: walletPath,
mnemonic: mnemonic,
static Future<BitcoinWallet> open({
@required String name,
@required WalletInfo walletInfo,
@required String password,
}) async {
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
await snp.load();
return BitcoinWallet(
mnemonic: snp.mnemonic,
password: password,
accountIndex: accountIndex,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
transactionHistory: history,
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();
}
walletInfo: walletInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
accountIndex: snp.accountIndex);
}
@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: 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);
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
}

View file

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

View file

@ -2,21 +2,12 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
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/script_hash.dart';
import 'package:flutter/foundation.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) {
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
return '[$_params]';
@ -53,17 +44,8 @@ class ElectrumClient {
Timer _aliveTimer;
String unterminatedString;
Future<void> connectToUri(String uri) async {
final splittedUri = uri.split(':');
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> connectToUri(Uri uri) async =>
await connect(host: uri.host, port: uri.port);
Future<void> connect({@required String host, @required int port}) async {
try {
@ -173,10 +155,11 @@ class ElectrumClient {
});
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address) =>
String address, NetworkType networkType) =>
call(
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address)]).then((dynamic result) {
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address, networkType: networkType)])
.then((dynamic result) {
if (result is List) {
return result.map((dynamic val) {
if (val is Map<String, Object>) {
@ -259,7 +242,7 @@ class ElectrumClient {
if (result is String) {
return result;
}
print(result);
return '';
});
@ -303,14 +286,24 @@ class ElectrumClient {
});
Future<List<int>> feeRates() async {
final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 20);
final bottomDoubleString = await estimatefee(p: 150);
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
try {
final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 20);
final bottomDoubleString = await estimatefee(p: 100);
final top =
(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) {

View file

@ -1,21 +1,20 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/entities/balance.dart';
class BitcoinBalance extends Balance {
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed})
class ElectrumBalance extends Balance {
const ElectrumBalance({@required this.confirmed, @required this.unconfirmed})
: super(confirmed, unconfirmed);
factory BitcoinBalance.fromJSON(String jsonSource) {
factory ElectrumBalance.fromJSON(String jsonSource) {
if (jsonSource == null) {
return null;
}
final decoded = json.decode(jsonSource) as Map;
return BitcoinBalance(
return ElectrumBalance(
confirmed: decoded['confirmed'] as int ?? 0,
unconfirmed: decoded['unconfirmed'] as int ?? 0);
}
@ -24,7 +23,8 @@ class BitcoinBalance extends Balance {
final int unconfirmed;
@override
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed);
String get formattedAvailableBalance =>
bitcoinAmountToString(amount: confirmed);
@override
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_info.dart';
import 'package:cake_wallet/entities/format_amount.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
class BitcoinTransactionInfo extends TransactionInfo {
BitcoinTransactionInfo(
class ElectrumTransactionInfo extends TransactionInfo {
ElectrumTransactionInfo(this.type,
{@required String id,
@required int height,
@required int amount,
@ -27,7 +28,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
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}) {
final addressesSet = addresses.map((addr) => addr.address).toSet();
final id = obj['txid'] as String;
@ -47,7 +49,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
final out = vin['tx']['vout'][vout] as Map;
final outAddresses =
(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) {
direction = TransactionDirection.outgoing;
@ -58,7 +61,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? [];
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;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
@ -69,7 +73,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final fee = inputsAmount - totalOutAmount;
return BitcoinTransactionInfo(
return ElectrumTransactionInfo(type,
id: id,
height: height,
isPending: false,
@ -80,7 +84,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
confirmations: confirmations);
}
factory BitcoinTransactionInfo.fromHexAndHeader(String hex,
factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
{List<String> addresses, int height, int timestamp, int confirmations}) {
final tx = bitcoin.Transaction.fromHex(hex);
var exist = false;
@ -104,7 +108,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
: DateTime.now();
return BitcoinTransactionInfo(
return ElectrumTransactionInfo(type,
id: tx.getId(),
height: height,
isPending: false,
@ -115,8 +119,9 @@ class BitcoinTransactionInfo extends TransactionInfo {
confirmations: confirmations);
}
factory BitcoinTransactionInfo.fromJson(Map<String, dynamic> data) {
return BitcoinTransactionInfo(
factory ElectrumTransactionInfo.fromJson(
Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type,
id: data['id'] as String,
height: data['height'] as int,
amount: data['amount'] as int,
@ -127,15 +132,17 @@ class BitcoinTransactionInfo extends TransactionInfo {
confirmations: data['confirmations'] as int);
}
final WalletType type;
String _fiatAmount;
@override
String amountFormatted() =>
'${formatAmount(bitcoinAmountToString(amount: amount))} BTC';
'${formatAmount(bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}';
@override
String feeFormatted() => fee != null
? '${formatAmount(bitcoinAmountToString(amount: fee))} BTC'
? '${formatAmount(bitcoinAmountToString(amount: fee))} ${walletTypeToCryptoCurrency(type).title}'
: '';
@override
@ -144,8 +151,8 @@ class BitcoinTransactionInfo extends TransactionInfo {
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
BitcoinTransactionInfo updated(BitcoinTransactionInfo info) {
return BitcoinTransactionInfo(
ElectrumTransactionInfo updated(ElectrumTransactionInfo info) {
return ElectrumTransactionInfo(info.type,
id: id,
height: info.height,
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:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cake_wallet/core/pending_transaction.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 {
PendingBitcoinTransaction(this._tx,
{@required this.eclient, @required this.amount, @required this.fee})
: _listeners = <void Function(BitcoinTransactionInfo transaction)>[];
PendingBitcoinTransaction(this._tx, this.type,
{@required this.electrumClient,
@required this.amount,
@required this.fee})
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
final bitcoin.Transaction _tx;
final ElectrumClient eclient;
final ElectrumClient electrumClient;
final int amount;
final int fee;
@ -25,24 +29,25 @@ class PendingBitcoinTransaction with PendingTransaction {
@override
String get feeFormatted => bitcoinAmountToString(amount: fee);
final List<void Function(BitcoinTransactionInfo transaction)> _listeners;
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
@override
Future<void> commit() async {
await eclient.broadcastTransaction(transactionRaw: _tx.toHex());
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
_listeners?.forEach((listener) => listener(transactionInfo()));
}
void addListener(
void Function(BitcoinTransactionInfo transaction) listener) =>
void Function(ElectrumTransactionInfo transaction) listener) =>
_listeners.add(listener);
BitcoinTransactionInfo transactionInfo() => BitcoinTransactionInfo(
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
id: id,
height: 0,
amount: amount,
direction: TransactionDirection.outgoing,
date: DateTime.now(),
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:crypto/crypto.dart';
String scriptHash(String address) {
final outputScript = bitcoin.Address.addressToOutputScript(address);
final splitted = sha256.convert(outputScript).toString().split('');
String scriptHash(String address, {@required bitcoin.NetworkType networkType}) {
final outputScript =
bitcoin.Address.addressToOutputScript(address, networkType);
final parts = sha256.convert(outputScript).toString().split('');
var res = '';
for (var i = splitted.length - 1; i >= 0; i--) {
final char = splitted[i];
for (var i = parts.length - 1; i >= 0; i--) {
final char = parts[i];
i--;
final nextChar = splitted[i];
final nextChar = parts[i];
res += nextChar;
res += char;
}
return res;
}
}

View file

@ -13,14 +13,43 @@ bitcoin.ECPair generateKeyPair(
{@required bitcoin.HDWallet hd,
@required int index,
bitcoin.NetworkType network}) =>
bitcoin.ECPair.fromWIF(hd.derive(index).wif,
network: network ?? bitcoin.bitcoin);
bitcoin.ECPair.fromWIF(hd.derive(index).wif, network: network);
String generateAddress({@required bitcoin.HDWallet hd, @required int index}) =>
String generateP2WPKHAddress(
{@required bitcoin.HDWallet hd,
@required int index,
bitcoin.NetworkType networkType}) =>
bitcoin
.P2WPKH(
data: PaymentData(
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
.address;

View file

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

View file

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

View file

@ -4,9 +4,9 @@ import 'package:cake_wallet/entities/wallet_type.dart';
String generateWalletPassword(WalletType type) {
switch (type) {
case WalletType.bitcoin:
return generateKey();
default:
case WalletType.monero:
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/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) {
case WalletType.bitcoin:
return getBitcoinWordList(language);
case WalletType.litecoin:
return getBitcoinWordList(language);
case WalletType.monero:
return getMoneroWordList(language);
default:

View file

@ -3,43 +3,50 @@ import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
TransactionHistoryBase() : _isUpdating = false;
TransactionHistoryBase();
// : _isUpdating = false;
@observable
ObservableMap<String, TransactionType> transactions;
bool _isUpdating;
Future<void> save();
@action
Future<void> update() async {
if (_isUpdating) {
return;
}
void addOne(TransactionType transaction);
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 addMany(Map<String, TransactionType> transactions);
void updateAsync({void Function() onFinished}) {
fetchTransactionsAsync(
(transaction) => transactions[transaction.id] = transaction,
onFinished: onFinished);
}
// bool _isUpdating;
void fetchTransactionsAsync(
void Function(TransactionType transaction) onTransactionLoaded,
{void Function() onFinished});
// @action
// Future<void> update() async {
// 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/transaction_info.dart';
import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:flutter/foundation.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/wallet_type.dart';
abstract class WalletBase<BalanceType extends Balance> {
abstract class WalletBase<
BalanceType extends Balance,
HistoryType extends TransactionHistoryBase,
TransactionType extends TransactionInfo> {
WalletBase(this.walletInfo);
static String idFor(String name, WalletType type) =>
@ -41,7 +45,7 @@ abstract class WalletBase<BalanceType extends Balance> {
Object get keys;
TransactionHistoryBase transactionHistory;
HistoryType transactionHistory;
Future<void> connectToNode({@required Node node});
@ -51,6 +55,12 @@ abstract class WalletBase<BalanceType extends Balance> {
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> rescan({int height});

View file

@ -1,8 +1,11 @@
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_credentials.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
abstract class WalletService<N extends WalletCredentials,
RFS extends WalletCredentials, RFK extends WalletCredentials> {
WalletType getType();
Future<WalletBase> create(N 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/litecoin_wallet_service.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/entities/biometric_auth.dart';
@ -441,6 +442,8 @@ Future setup(
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
getIt.registerFactoryParam<WalletService, WalletType, void>(
(WalletType param1, __) {
switch (param1) {
@ -448,6 +451,8 @@ Future setup(
return getIt.get<MoneroWalletService>();
case WalletType.bitcoin:
return getIt.get<BitcoinWalletService>();
case WalletType.litecoin:
return getIt.get<LitecoinWalletService>();
default:
return null;
}

View file

@ -4,11 +4,12 @@ String calculateFiatAmount({double price, String 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) {
return '0.00';
}
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.eth,
CryptoCurrency.ltc,
CryptoCurrency.nano,
CryptoCurrency.trx,
CryptoCurrency.usdt,
CryptoCurrency.usdterc20,
@ -29,7 +28,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
static const xmr = CryptoCurrency(title: 'XMR', raw: 0);
static const ada = CryptoCurrency(title: 'ADA', raw: 1);
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 dai = CryptoCurrency(title: 'DAI', raw: 5);
static const dash = CryptoCurrency(title: 'DASH', raw: 6);
@ -90,7 +89,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
return CryptoCurrency.ada;
case 'bch':
return CryptoCurrency.bch;
case 'bnb':
case 'bnbmainnet':
return CryptoCurrency.bnb;
case 'btc':
return CryptoCurrency.btc;

View file

@ -7,7 +7,9 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.btc;
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.litecoin:
return CryptoCurrency.ltc;
default:
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 '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/secret_store_key.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
@ -24,7 +20,8 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
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(
{@required int version,
@ -68,6 +65,8 @@ Future defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeBitcoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 2:
@ -97,6 +96,7 @@ Future defaultSettingsMigration(
case 9:
await generateBackupPassword(secureStorage);
break;
case 10:
await changeTransactionPriorityAndFeeRateKeys(sharedPreferences);
break;
@ -110,7 +110,14 @@ Future defaultSettingsMigration(
break;
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;
default:
@ -142,7 +149,7 @@ Future<void> replaceNodesMigration({@required Box<Node> nodes}) async {
final nodeToReplace = replaceNodes[node.uri];
if (nodeToReplace != null) {
node.uri = nodeToReplace.uri;
node.uriRaw = nodeToReplace.uriRaw;
node.login = nodeToReplace.login;
node.password = nodeToReplace.password;
await node.save();
@ -160,12 +167,21 @@ Future<void> changeMoneroCurrentNodeToDefault(
}
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
return nodes.values
.firstWhere((Node node) => node.uri == cakeWalletElectrumUri, orElse: () => null) ??
return nodes.values.firstWhere(
(Node node) => node.uri == cakeWalletBitcoinElectrumUri,
orElse: () => null) ??
nodes.values.firstWhere((node) => node.type == WalletType.bitcoin,
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}) {
final timeZone = DateTime.now().timeZoneOffset.inHours;
var nodeUri = '';
@ -192,6 +208,15 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
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(
{@required SharedPreferences sharedPreferences,
@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 {
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);
}
@ -284,57 +314,97 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
Future<void> changeDefaultMoneroNode(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern);
final currentMoneroNodeId =
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode =
currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
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();
}
});
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
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 {
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentElectrumSeverId = await sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId, orElse: () => null);
final currentElectrumServer = nodeSource.values.firstWhere((node) => node.key == currentElectrumSeverId, orElse: () => null);
Future<void> checkCurrentNodes(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId =
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
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) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
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) {
final cakeWalletElectrum = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin);
if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
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> resetElectrumServer(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final oldElectrumServer = nodeSource.values.firstWhere((node) => node.uri.contains('electrumx.cakewallet.com'), orElse: () => null);
var cakeWalletNode = nodeSource.values.firstWhere((node) => node.uri == cakeWalletElectrumUri, orElse: () => null);
Future<void> resetBitcoinElectrumServer(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
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) {
cakeWalletNode = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin);
cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletNode);
}
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();

View file

@ -1,7 +1,10 @@
import 'dart:io';
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/di.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/crypto_currency.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/exchange/exchange_provider_description.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;
const reservedNames = ["flutter_assets", "wallets", "db"];
@ -407,7 +405,7 @@ Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
}
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 _item = item as Map<String, dynamic>;
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 prefs.setBool('ios_migration_address_book_completed', true);
} catch(e) {
} catch (e) {
print(e.toString());
}
}

View file

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

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:flutter/foundation.dart';
import 'dart:convert';
@ -8,19 +10,23 @@ import 'package:cake_wallet/entities/digest_request.dart';
part 'node.g.dart';
Uri createUriFromElectrumAddress(String address) =>
Uri.tryParse('tcp://$address');
@HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable {
Node(
{@required this.uri,
{@required String uri,
@required WalletType type,
this.login,
this.password,
this.useSSL}) {
uriRaw = uri;
this.type = type;
}
Node.fromMap(Map map)
: uri = map['uri'] as String ?? '',
: uriRaw = map['uri'] as String ?? '',
login = map['login'] as String,
password = map['password'] as String,
typeRaw = map['typeRaw'] as int,
@ -30,7 +36,7 @@ class Node extends HiveObject with Keyable {
static const boxName = 'Nodes';
@HiveField(0)
String uri;
String uriRaw;
@HiveField(1)
String login;
@ -46,6 +52,19 @@ class Node extends HiveObject with Keyable {
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
dynamic get keyIndex {
_keyIndex ??= key;
@ -64,7 +83,9 @@ class Node extends HiveObject with Keyable {
case WalletType.monero:
return requestMoneroNode();
case WalletType.bitcoin:
return requestBitcoinElectrumServer();
return requestElectrumServer();
case WalletType.litecoin:
return requestElectrumServer();
default:
return false;
}
@ -80,15 +101,15 @@ class Node extends HiveObject with Keyable {
if (login != null && password != null) {
final digestRequest = DigestRequest();
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>;
} else {
final url = Uri.http(uri, '/json_rpc');
final rpcUri = Uri.http(uri.authority, '/json_rpc');
final headers = {'Content-type': 'application/json'};
final body =
json.encode({'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'});
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>;
}
@ -98,8 +119,13 @@ class Node extends HiveObject with Keyable {
}
}
Future<bool> requestBitcoinElectrumServer() async {
// FIXME: IMPLEMENT ME
return true;
Future<bool> requestElectrumServer() async {
try {
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();
}
Future<List<Node>> loadElectrumServerList() async {
Future<List<Node>> loadBitcoinElectrumServerList() async {
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;
return serverList.map((dynamic raw) {
@ -37,10 +37,29 @@ Future<List<Node>> loadElectrumServerList() async {
}).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 {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadElectrumServerList();
final nodes = moneroNodes + bitcoinElectrumServerList;
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final nodes =
moneroNodes + bitcoinElectrumServerList + litecoinElectrumServerList;
await nodeSource.clear();
await nodeSource.addAll(nodes);

View file

@ -1,8 +1,9 @@
class PreferencesKey {
static const currentWalletType ='current_wallet_type';
static const currentWalletName ='current_wallet_name';
static const currentWalletType = 'current_wallet_type';
static const currentWalletName = 'current_wallet_name';
static const currentNodeIdKey = 'current_node_id';
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentFiatCurrencyKey = 'current_fiat_currency';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
@ -14,7 +15,8 @@ class PreferencesKey {
static const displayActionListModeKey = 'display_list_mode';
static const currentPinLength = 'current_pin_length';
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 bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
}
}

View file

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

View file

@ -48,7 +48,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
@override
Future<Limits> fetchLimits({CryptoCurrency from, CryptoCurrency to,
bool isFixedRateMode}) async {
final symbol = from.toString() + '_' + to.toString();
final fromTitle = defineCurrencyTitle(from);
final toTitle = defineCurrencyTitle(to);
final symbol = fromTitle + '_' + toTitle;
final url = isFixedRateMode
? apiUri + _marketInfoUriSufix + _fixedRateUriSufix + apiKey
: apiUri + _minAmountUriSufix + symbol;
@ -61,8 +63,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final elemFrom = elem["from"] as String;
final elemTo = elem["to"] as String;
if ((elemFrom == from.toString().toLowerCase()) &&
(elemTo == to.toString().toLowerCase())) {
if ((elemFrom == fromTitle) && (elemTo == toTitle)) {
final min = elem["min"] as double;
final max = elem["max"] as double;
@ -84,9 +85,11 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
? apiUri + _transactionsUriSufix + _fixedRateUriSufix + apiKey
: apiUri + _transactionsUriSufix + apiKey;
final _request = request as ChangeNowRequest;
final fromTitle = defineCurrencyTitle(_request.from);
final toTitle = defineCurrencyTitle(_request.to);
final body = {
'from': _request.from.toString(),
'to': _request.to.toString(),
'from': fromTitle,
'to': toTitle,
'address': _request.address,
'amount': _request.amount,
'refundAddress': _request.refundAddress
@ -182,6 +185,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final url = apiUri + _marketInfoUriSufix + _fixedRateUriSufix + apiKey;
final response = await get(url);
final responseJSON = json.decode(response.body) as List<dynamic>;
final fromTitle = defineCurrencyTitle(from);
final toTitle = defineCurrencyTitle(to);
var rate = 0.0;
var fee = 0.0;
@ -189,8 +194,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final elemFrom = elem["from"] as String;
final elemTo = elem["to"] as String;
if ((elemFrom == to.toString().toLowerCase()) &&
(elemTo == from.toString().toLowerCase())) {
if ((elemFrom == toTitle) && (elemTo == fromTitle)) {
rate = elem["rate"] as double;
fee = elem["minerFee"] as double;
break;
@ -216,22 +220,32 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
CryptoCurrency to,
double amount,
bool isFixedRateMode) {
final fromTitle = defineCurrencyTitle(from);
final toTitle = defineCurrencyTitle(to);
return isFixedRateMode
? apiUri +
_exchangeAmountUriSufix +
_fixedRateUriSufix +
amount.toString() +
'/' +
from.toString() +
fromTitle +
'_' +
to.toString() +
toTitle +
'?api_key=' + apiKey
: apiUri +
_exchangeAmountUriSufix +
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,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
initialMigrationVersion: 13);
initialMigrationVersion: 15);
runApp(App());
} catch (e) {
runApp(MaterialApp(
@ -135,7 +135,7 @@ Future<void> initialSetup(
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
FlutterSecureStorage secureStorage,
int initialMigrationVersion = 13}) async {
int initialMigrationVersion = 15}) async {
LanguageService.loadLocaleList();
await defaultSettingsMigration(
secureStorage: secureStorage,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,19 +7,22 @@ import 'package:connectivity/connectivity.dart';
Timer _checkConnectionTimer;
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore, {int timeInterval = 5}) {
void startCheckConnectionReaction(
WalletBase wallet, SettingsStore settingsStore,
{int timeInterval = 5}) {
_checkConnectionTimer?.cancel();
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
final connectivityResult = await (Connectivity().checkConnectivity());
_checkConnectionTimer =
Timer.periodic(Duration(seconds: timeInterval), (_) async {
try {
final connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) {
wallet.syncStatus = FailedSyncStatus();
return;
}
if (connectivityResult == ConnectivityResult.none) {
wallet.syncStatus = FailedSyncStatus();
return;
}
if (wallet.syncStatus is LostConnectionSyncStatus ||
wallet.syncStatus is FailedSyncStatus) {
try {
if (wallet.syncStatus is LostConnectionSyncStatus ||
wallet.syncStatus is FailedSyncStatus) {
final alive =
await settingsStore.getCurrentNode(wallet.type).requestNode();
@ -27,9 +30,9 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
await wallet.connectToNode(
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/transaction_info.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/di.dart';
@ -18,9 +20,11 @@ ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction;
void startCurrentWalletChangeReaction(AppStore appStore,
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
_onCurrentWalletChangeReaction?.reaction?.dispose();
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction?.dispose();
_onCurrentWalletChangeReaction =
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
wallet) async {
try {
final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet);
@ -45,7 +49,9 @@ void startCurrentWalletChangeReaction(AppStore appStore,
});
_onCurrentWalletChangeFiatRateUpdateReaction =
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
reaction((_) => appStore.wallet, (WalletBase<Balance,
TransactionHistoryBase<TransactionInfo>, TransactionInfo>
wallet) async {
try {
fiatConversionStore.prices[wallet.currency] = 0;
fiatConversionStore.prices[wallet.currency] =

View file

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

View file

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

View file

@ -1,7 +1,9 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.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/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.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/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
@ -24,8 +27,8 @@ class DashboardPage extends BasePage {
});
@override
Color get backgroundLightColor => currentTheme.type == ThemeType.bright
? Colors.transparent : Colors.white;
Color get backgroundLightColor =>
currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@override
Color get backgroundDarkColor => Colors.transparent;
@ -42,7 +45,7 @@ class DashboardPage extends BasePage {
child: scaffold);
@override
bool get resizeToAvoidBottomPadding => false;
bool get resizeToAvoidBottomInset => false;
@override
Widget get endDrawer => MenuWidget(walletViewModel);
@ -54,9 +57,8 @@ class DashboardPage extends BasePage {
@override
Widget trailing(BuildContext context) {
final menuButton =
Image.asset('assets/images/menu.png',
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
final menuButton = Image.asset('assets/images/menu.png',
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
return Container(
alignment: Alignment.centerRight,
@ -79,15 +81,18 @@ class DashboardPage extends BasePage {
@override
Widget body(BuildContext context) {
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);
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);
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);
_setEffects();
_setEffects(context);
return SafeArea(
child: Column(
@ -109,12 +114,14 @@ class DashboardPage extends BasePage {
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).indicatorColor,
activeDotColor: Theme.of(context).accentTextTheme.display1
activeDotColor: Theme.of(context)
.accentTextTheme
.display1
.backgroundColor),
)),
Container(
padding: EdgeInsets.only(left: 45, right: 45, bottom: 24),
child: Observer(builder: (_) => Row(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ActionButton(
@ -125,21 +132,20 @@ class DashboardPage extends BasePage {
image: exchangeImage,
title: S.of(context).exchange,
route: Routes.exchange),
if (walletViewModel.type == WalletType.bitcoin) ActionButton(
image: buyImage,
title: S.of(context).buy,
onClick: () {
Navigator.of(context).pushNamed(Routes.preOrder);
},
ActionButton(
image: buyImage,
title: S.of(context).buy,
onClick: () async =>
await _onClickBuyButton(context),
),
],
)),
),
)
],
));
}
void _setEffects() {
void _setEffects(BuildContext context) {
if (_isEffectsInstalled) {
return;
}
@ -148,6 +154,45 @@ class DashboardPage extends BasePage {
pages.add(BalancePage(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;
}
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> {
Image moneroIcon;
Image bitcoinIcon;
Image litecoinIcon;
final largeScreen = 731;
double menuWidth;
@ -76,6 +77,7 @@ class MenuWidgetState extends State<MenuWidget> {
color: Theme.of(context).accentTextTheme.overline.decorationColor);
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
color: Theme.of(context).accentTextTheme.overline.decorationColor);
litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
return Row(
mainAxisSize: MainAxisSize.max,
@ -238,6 +240,8 @@ class MenuWidgetState extends State<MenuWidget> {
return moneroIcon;
case WalletType.bitcoin:
return bitcoinIcon;
case WalletType.litecoin:
return litecoinIcon;
default:
return null;
}

View file

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

View file

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

View file

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

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