mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branch 'release-4.2.4' into CAKE-345-batch-sending
# Conflicts: # lib/bitcoin/electrum_wallet.dart # lib/src/screens/send/send_page.dart # lib/src/screens/send/send_template_page.dart # lib/view_model/send/send_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_hr.arb # res/values/strings_it.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:
commit
2a047e1f51
74 changed files with 1750 additions and 338 deletions
|
@ -71,8 +71,9 @@ android {
|
|||
release {
|
||||
signingConfig signingConfigs.release
|
||||
|
||||
minifyEnabled true
|
||||
useProguard true
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
useProguard false
|
||||
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
@ -85,8 +86,9 @@ flutter {
|
|||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
implementation 'com.unstoppabledomains:resolution:1.13.0'
|
||||
implementation 'com.google.firebase:firebase-core:19.0.0'
|
||||
implementation 'com.google.firebase:firebase-messaging:19.0.0'
|
||||
}
|
||||
|
|
|
@ -10,14 +10,19 @@ import io.flutter.plugin.common.MethodCall;
|
|||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.unstoppabledomains.resolution.DomainResolution;
|
||||
import com.unstoppabledomains.resolution.Resolution;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class MainActivity extends FlutterFragmentActivity {
|
||||
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
||||
|
||||
final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24;
|
||||
|
||||
@Override
|
||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||
|
@ -33,17 +38,44 @@ public class MainActivity extends FlutterFragmentActivity {
|
|||
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());
|
||||
switch (call.method) {
|
||||
case "sec_random":
|
||||
int count = call.argument("count");
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte bytes[] = new byte[count];
|
||||
random.nextBytes(bytes);
|
||||
handler.post(() -> result.success(bytes));
|
||||
break;
|
||||
case "getUnstoppableDomainAddress":
|
||||
int version = Build.VERSION.SDK_INT;
|
||||
if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) {
|
||||
getUnstoppableDomainAddress(call, result);
|
||||
} else {
|
||||
handler.post(() -> result.success(""));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
handler.post(() -> result.notImplemented());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handler.post(() -> result.error("UNCAUGHT_ERROR", e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
DomainResolution resolution = new Resolution();
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
String domain = call.argument("domain");
|
||||
String ticker = call.argument("ticker");
|
||||
|
||||
AsyncTask.execute(() -> {
|
||||
try {
|
||||
String address = resolution.getAddress(domain, ticker);
|
||||
handler.post(() -> result.success(address));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Expected Address, but got " + e.getMessage());
|
||||
handler.post(() -> result.success(""));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.4'
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath 'com.google.gms:google-services:4.3.8'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
#Mon Apr 19 18:19:26 EEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '9.0'
|
||||
platform :ios, '11.0'
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
|
@ -36,6 +36,7 @@ target 'Runner' do
|
|||
|
||||
# Cake Wallet (Legacy)
|
||||
pod 'CryptoSwift'
|
||||
pod 'UnstoppableDomainsResolution', '~> 0.3.6'
|
||||
pod 'Firebase/Messaging', '6.33.0'
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ PODS:
|
|||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- SwiftProtobuf
|
||||
- BigInt (5.2.0)
|
||||
- connectivity (0.0.1):
|
||||
- Flutter
|
||||
- Reachability
|
||||
|
@ -59,6 +60,8 @@ PODS:
|
|||
- SwiftyGif
|
||||
- esys_flutter_share (0.0.1):
|
||||
- Flutter
|
||||
- EthereumAddress (1.3.0):
|
||||
- CryptoSwift (~> 1.0)
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
|
@ -150,6 +153,10 @@ PODS:
|
|||
- Flutter
|
||||
- SwiftProtobuf (1.12.0)
|
||||
- SwiftyGif (5.3.0)
|
||||
- UnstoppableDomainsResolution (0.3.6):
|
||||
- BigInt
|
||||
- CryptoSwift (~> 1.0)
|
||||
- EthereumAddress (~> 1.3)
|
||||
- url_launcher (0.0.1):
|
||||
- Flutter
|
||||
- webview_flutter (0.0.1):
|
||||
|
@ -174,14 +181,17 @@ DEPENDENCIES:
|
|||
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
|
||||
- share (from `.symlinks/plugins/share/ios`)
|
||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||
- UnstoppableDomainsResolution (~> 0.3.6)
|
||||
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||
- webview_flutter (from `.symlinks/plugins/webview_flutter/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/CocoaPods/Specs.git:
|
||||
- BigInt
|
||||
- CryptoSwift
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- EthereumAddress
|
||||
- Firebase
|
||||
- FirebaseCore
|
||||
- FirebaseCoreDiagnostics
|
||||
|
@ -198,6 +208,7 @@ SPEC REPOS:
|
|||
- SDWebImage
|
||||
- SwiftProtobuf
|
||||
- SwiftyGif
|
||||
- UnstoppableDomainsResolution
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
barcode_scan:
|
||||
|
@ -239,6 +250,7 @@ EXTERNAL SOURCES:
|
|||
|
||||
SPEC CHECKSUMS:
|
||||
barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479
|
||||
BigInt: f668a80089607f521586bbe29513d708491ef2f7
|
||||
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
|
||||
CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060
|
||||
cw_monero: 78f369253cc913efc23db9cf6be81a11eaf40fe1
|
||||
|
@ -246,6 +258,7 @@ SPEC CHECKSUMS:
|
|||
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4
|
||||
EthereumAddress: 39fe8e11cf04e4e9902b55ae653dbc4e0aee5f30
|
||||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
||||
Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5
|
||||
firebase_core: 5d6a02f3d85acd5f8321c2d6d62877626a670659
|
||||
|
@ -273,9 +286,9 @@ SPEC CHECKSUMS:
|
|||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||
SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
|
||||
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
|
||||
UnstoppableDomainsResolution: 63abb84858d3e91eb838a5bfa6f7e3c0e0593f24
|
||||
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||
webview_flutter: 9f491a9b5a66f2573946a389b2677987b0ff8c0b
|
||||
|
||||
PODFILE CHECKSUM: f316539722a6f9dbb0d0f1065a27fa7ea38c6f88
|
||||
|
||||
COCOAPODS: 1.9.3
|
||||
COCOAPODS: 1.10.1
|
||||
|
|
|
@ -366,7 +366,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
||||
ENABLE_BITCODE = NO;
|
||||
EXCLUDED_SOURCE_FILE_NAMES = "";
|
||||
|
@ -384,7 +384,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 4.2.3;
|
||||
MARKETING_VERSION = 4.2.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
@ -510,7 +510,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
||||
ENABLE_BITCODE = NO;
|
||||
EXCLUDED_SOURCE_FILE_NAMES = "";
|
||||
|
@ -528,7 +528,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 4.2.3;
|
||||
MARKETING_VERSION = 4.2.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
@ -546,7 +546,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = 32J6BB6VUS;
|
||||
ENABLE_BITCODE = NO;
|
||||
EXCLUDED_SOURCE_FILE_NAMES = "";
|
||||
|
@ -564,7 +564,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 4.2.3;
|
||||
MARKETING_VERSION = 4.2.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import UIKit
|
||||
import Flutter
|
||||
import UnstoppableDomainsResolution
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
lazy var resolution : Resolution? = {
|
||||
return try? Resolution()
|
||||
}()
|
||||
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
|
@ -60,7 +65,7 @@ import Flutter
|
|||
let utilsChannel = FlutterMethodChannel(
|
||||
name: "com.cake_wallet/native_utils",
|
||||
binaryMessenger: controller.binaryMessenger)
|
||||
utilsChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||
utilsChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||
switch call.method {
|
||||
case "sec_random":
|
||||
guard let args = call.arguments as? Dictionary<String, Any>,
|
||||
|
@ -70,6 +75,27 @@ import Flutter
|
|||
}
|
||||
|
||||
result(secRandom(count: count))
|
||||
case "getUnstoppableDomainAddress":
|
||||
guard let args = call.arguments as? Dictionary<String, String>,
|
||||
let domain = args["domain"],
|
||||
let ticker = args["ticker"],
|
||||
let resolution = self?.resolution else {
|
||||
result(nil)
|
||||
return
|
||||
}
|
||||
|
||||
resolution.addr(domain: domain, ticker: ticker) { addrResult in
|
||||
var address : String = ""
|
||||
|
||||
switch addrResult {
|
||||
case .success(let returnValue):
|
||||
address = returnValue
|
||||
case .failure(let error):
|
||||
print("Expected Address, but got \(error)")
|
||||
}
|
||||
|
||||
result(address)
|
||||
}
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
|
||||
class BitcoinTransactionWrongBalanceException implements Exception {
|
||||
BitcoinTransactionWrongBalanceException(this.currency);
|
||||
|
||||
final CryptoCurrency currency;
|
||||
|
||||
@override
|
||||
String toString() => 'Wrong balance. Not enough BTC on your balance.';
|
||||
String toString() => 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
|
||||
class BitcoinUnspent {
|
||||
BitcoinUnspent(this.address, this.hash, this.value, this.vout);
|
||||
BitcoinUnspent(this.address, this.hash, this.value, this.vout)
|
||||
: isSending = true,
|
||||
isFrozen = false,
|
||||
note = '';
|
||||
|
||||
factory BitcoinUnspent.fromJSON(
|
||||
BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
||||
|
@ -15,4 +18,7 @@ class BitcoinUnspent {
|
|||
|
||||
bool get isP2wpkh =>
|
||||
address.address.startsWith('bc') || address.address.startsWith('ltc');
|
||||
bool isSending;
|
||||
bool isFrozen;
|
||||
String note;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/bitcoin/utils.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_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_addresses.dart';
|
||||
|
||||
part 'bitcoin_wallet.g.dart';
|
||||
|
||||
|
@ -17,6 +19,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
{@required String mnemonic,
|
||||
@required String password,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
List<BitcoinAddressRecord> initialAddresses,
|
||||
ElectrumBalance initialBalance,
|
||||
int accountIndex = 0})
|
||||
|
@ -24,14 +27,22 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: bitcoin.bitcoin,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
accountIndex: accountIndex);
|
||||
initialBalance: initialBalance) {
|
||||
walletAddresses = BitcoinWalletAddresses(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd,
|
||||
networkType: networkType);
|
||||
}
|
||||
|
||||
static Future<BitcoinWallet> open({
|
||||
@required String name,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
@required String password,
|
||||
}) async {
|
||||
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
||||
|
@ -40,12 +51,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: snp.mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
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);
|
||||
}
|
||||
|
|
33
lib/bitcoin/bitcoin_wallet_addresses.dart
Normal file
33
lib/bitcoin/bitcoin_wallet_addresses.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'bitcoin_wallet_addresses.g.dart';
|
||||
|
||||
class BitcoinWalletAddresses = BitcoinWalletAddressesBase
|
||||
with _$BitcoinWalletAddresses;
|
||||
|
||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||
with Store {
|
||||
BitcoinWalletAddressesBase(
|
||||
WalletInfo walletInfo,
|
||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||
int accountIndex = 0,
|
||||
@required bitcoin.HDWallet hd,
|
||||
@required this.networkType})
|
||||
: super(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd);
|
||||
|
||||
bitcoin.NetworkType networkType;
|
||||
|
||||
@override
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||
}
|
|
@ -2,6 +2,7 @@ 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/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
|
@ -14,9 +15,10 @@ class BitcoinWalletService extends WalletService<
|
|||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
BitcoinWalletService(this.walletInfoSource);
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.bitcoin;
|
||||
|
@ -26,7 +28,8 @@ class BitcoinWalletService extends WalletService<
|
|||
final wallet = BitcoinWallet(
|
||||
mnemonic: await generateMnemonic(),
|
||||
password: credentials.password,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
@ -42,7 +45,8 @@ class BitcoinWalletService extends WalletService<
|
|||
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||
orElse: () => null);
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo);
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
@ -67,7 +71,8 @@ class BitcoinWalletService extends WalletService<
|
|||
final wallet = BitcoinWallet(
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:rxdart/subjects.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -38,30 +41,28 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
ElectrumWalletBase(
|
||||
{@required String password,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
@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;
|
||||
this.unspentCoinsInfo = unspentCoinsInfo;
|
||||
transactionHistory =
|
||||
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
||||
_unspent = [];
|
||||
unspentCoins = [];
|
||||
_scripthashesUpdateSubject = {};
|
||||
}
|
||||
|
||||
|
@ -72,10 +73,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
final String mnemonic;
|
||||
|
||||
ElectrumClient electrumClient;
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
ElectrumWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
@observable
|
||||
|
@ -85,9 +86,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
ObservableList<BitcoinAddressRecord> addresses;
|
||||
|
||||
List<String> get scriptHashes => addresses
|
||||
List<String> get scriptHashes => walletAddresses.addresses
|
||||
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
||||
.toList();
|
||||
|
||||
|
@ -103,85 +102,26 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
|
||||
|
||||
final String _password;
|
||||
List<BitcoinUnspent> _unspent;
|
||||
List<BitcoinUnspent> unspentCoins;
|
||||
List<int> _feeRates;
|
||||
int _accountIndex;
|
||||
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
|
||||
bool _isTransactionUpdating;
|
||||
|
||||
Future<void> init() async {
|
||||
await generateAddresses();
|
||||
address = addresses[_accountIndex].address;
|
||||
await walletAddresses.init();
|
||||
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();
|
||||
await updateTransactions();
|
||||
_subscribeForUpdates();
|
||||
await _updateBalance();
|
||||
await _updateUnspent();
|
||||
await updateUnspent();
|
||||
_feeRates = await electrumClient.feeRates();
|
||||
|
||||
Timer.periodic(const Duration(minutes: 1),
|
||||
|
@ -219,6 +159,23 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||
final inputs = <BitcoinUnspent>[];
|
||||
final sendItemList = transactionCredentials.sendItemList;
|
||||
var allInputsAmount = 0;
|
||||
|
||||
if (unspentCoins.isEmpty) {
|
||||
await updateUnspent();
|
||||
}
|
||||
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
allInputsAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.isEmpty) {
|
||||
throw BitcoinTransactionNoInputsException();
|
||||
}
|
||||
|
||||
final allAmountFee =
|
||||
calculateEstimatedFee(transactionCredentials.priority, null);
|
||||
final allAmount = balance.confirmed - allAmountFee;
|
||||
|
@ -280,22 +237,48 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
feeAmountForPriority(transactionCredentials.priority, inputs.length, 1);
|
||||
final allAmount = allInputsAmount - allAmountFee;
|
||||
|
||||
final credentialsAmount = transactionCredentials.amount != null
|
||||
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
|
||||
: 0;
|
||||
final amount = transactionCredentials.amount == null ||
|
||||
allAmount - credentialsAmount < minAmount
|
||||
? allAmount
|
||||
: credentialsAmount;
|
||||
final fee = transactionCredentials.amount == null || amount == allAmount
|
||||
? allAmountFee
|
||||
: calculateEstimatedFee(transactionCredentials.priority, amount);
|
||||
|
||||
if (fee == 0) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
final totalAmount = amount + fee;
|
||||
|
||||
if (totalAmount > balance.confirmed || totalAmount > allInputsAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
final txb = bitcoin.TransactionBuilder(network: networkType);
|
||||
final changeAddress = walletAddresses.address;
|
||||
var leftAmount = totalAmount;
|
||||
final changeAddress = address;
|
||||
var leftAmount = totalAmount;
|
||||
var totalInputAmount = 0;
|
||||
|
||||
if (_unspent.isEmpty) {
|
||||
await _updateUnspent();
|
||||
}
|
||||
inputs.clear();
|
||||
|
||||
for (final utx in _unspent) {
|
||||
leftAmount = leftAmount - utx.value;
|
||||
totalInputAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
leftAmount = leftAmount - utx.value;
|
||||
totalInputAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
|
||||
if (leftAmount <= 0) {
|
||||
break;
|
||||
if (leftAmount <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,6 +286,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
throw BitcoinTransactionNoInputsException();
|
||||
}
|
||||
|
||||
if (amount <= 0 || totalInputAmount < totalAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
if (amount <= 0 || totalInputAmount < amount) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
@ -361,8 +346,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': mnemonic,
|
||||
'account_index': _accountIndex.toString(),
|
||||
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
|
||||
'account_index': walletAddresses.accountIndex.toString(),
|
||||
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
|
||||
'balance': balance?.toJSON()
|
||||
});
|
||||
|
||||
|
@ -387,17 +372,26 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
if (amount != null) {
|
||||
int totalValue = 0;
|
||||
|
||||
for (final input in _unspent) {
|
||||
for (final input in unspentCoins) {
|
||||
if (totalValue >= amount) {
|
||||
break;
|
||||
}
|
||||
|
||||
totalValue += input.value;
|
||||
inputsCount += 1;
|
||||
if (input.isSending) {
|
||||
totalValue += input.value;
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalValue < amount) return 0;
|
||||
} else {
|
||||
inputsCount = _unspent.length;
|
||||
for (final input in unspentCoins) {
|
||||
if (input.isSending) {
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If send all, then we have no change value
|
||||
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
||||
|
||||
|
@ -428,24 +422,84 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
} 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
|
||||
Future<void> updateUnspent() async {
|
||||
final unspent = await Future.wait(walletAddresses
|
||||
.addresses.map((address) => electrumClient
|
||||
.getListUnspentWithAddress(address.address, networkType)
|
||||
.then((unspent) => unspent
|
||||
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
|
||||
_unspent = unspent.expand((e) => e).toList();
|
||||
unspentCoins = unspent.expand((e) => e).toList();
|
||||
|
||||
if (unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId.contains(id) && element.hash.contains(coin.hash));
|
||||
|
||||
if (coinInfoList.isNotEmpty) {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await _refreshUnspentCoinsInfo();
|
||||
}
|
||||
|
||||
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: id,
|
||||
hash: coin.hash,
|
||||
isFrozen: coin.isFrozen,
|
||||
isSending: coin.isSending,
|
||||
note: coin.note
|
||||
);
|
||||
|
||||
await unspentCoinsInfo.add(newInfo);
|
||||
}
|
||||
|
||||
Future<void> _refreshUnspentCoinsInfo() async {
|
||||
try {
|
||||
final List<dynamic> keys = <dynamic>[];
|
||||
final currentWalletUnspentCoins = unspentCoinsInfo.values
|
||||
.where((element) => element.walletId.contains(id));
|
||||
|
||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||
currentWalletUnspentCoins.forEach((element) {
|
||||
final existUnspentCoins = unspentCoins
|
||||
?.where((coin) => element.hash.contains(coin?.hash));
|
||||
|
||||
if (existUnspentCoins?.isEmpty ?? true) {
|
||||
keys.add(element.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
await unspentCoinsInfo.deleteAll(keys);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
height: height, addresses: walletAddresses.addresses);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -489,7 +543,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
_scripthashesUpdateSubject[sh].listen((event) async {
|
||||
try {
|
||||
await _updateBalance();
|
||||
await _updateUnspent();
|
||||
await updateUnspent();
|
||||
await updateTransactions();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
|
|
111
lib/bitcoin/electrum_wallet_addresses.dart
Normal file
111
lib/bitcoin/electrum_wallet_addresses.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'electrum_wallet_addresses.g.dart';
|
||||
|
||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase
|
||||
with _$ElectrumWalletAddresses;
|
||||
|
||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||
int accountIndex = 0,
|
||||
@required bitcoin.HDWallet hd})
|
||||
: super(walletInfo) {
|
||||
this.hd = hd;
|
||||
this.accountIndex = accountIndex;
|
||||
addresses = ObservableList<BitcoinAddressRecord>.of(
|
||||
(initialAddresses ?? []).toSet());
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
bitcoin.HDWallet hd;
|
||||
|
||||
ObservableList<BitcoinAddressRecord> addresses;
|
||||
|
||||
int accountIndex;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
await generateAddresses();
|
||||
address = addresses[accountIndex].address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> nextAddress() async {
|
||||
accountIndex += 1;
|
||||
|
||||
if (accountIndex >= addresses.length) {
|
||||
accountIndex = 0;
|
||||
}
|
||||
|
||||
address = addresses[accountIndex].address;
|
||||
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
return list;
|
||||
}
|
||||
|
||||
/*Future<void> updateAddress(String address) async {
|
||||
for (final addr in addresses) {
|
||||
if (addr.address == address) {
|
||||
await save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
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/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/litecoin_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
|
||||
|
@ -10,7 +11,6 @@ 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';
|
||||
|
||||
|
@ -21,6 +21,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
{@required String mnemonic,
|
||||
@required String password,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
List<BitcoinAddressRecord> initialAddresses,
|
||||
ElectrumBalance initialBalance,
|
||||
int accountIndex = 0})
|
||||
|
@ -28,14 +29,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: litecoinNetwork,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
accountIndex: accountIndex);
|
||||
initialBalance: initialBalance) {
|
||||
walletAddresses = LitecoinWalletAddresses(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd,
|
||||
networkType: networkType,
|
||||
mnemonic: mnemonic);
|
||||
}
|
||||
|
||||
static Future<LitecoinWallet> open({
|
||||
@required String name,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
@required String password,
|
||||
}) async {
|
||||
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
||||
|
@ -44,32 +54,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: snp.mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
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) {
|
||||
|
|
54
lib/bitcoin/litecoin_wallet_addresses.dart
Normal file
54
lib/bitcoin/litecoin_wallet_addresses.dart
Normal file
|
@ -0,0 +1,54 @@
|
|||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'litecoin_wallet_addresses.g.dart';
|
||||
|
||||
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
|
||||
with _$LitecoinWalletAddresses;
|
||||
|
||||
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||
with Store {
|
||||
LitecoinWalletAddressesBase(
|
||||
WalletInfo walletInfo,
|
||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||
int accountIndex = 0,
|
||||
@required bitcoin.HDWallet hd,
|
||||
@required this.networkType,
|
||||
@required this.mnemonic})
|
||||
: super(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd);
|
||||
|
||||
bitcoin.NetworkType networkType;
|
||||
|
||||
final String mnemonic;
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:io';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
||||
|
@ -14,9 +15,10 @@ class LitecoinWalletService extends WalletService<
|
|||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
LitecoinWalletService(this.walletInfoSource);
|
||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.litecoin;
|
||||
|
@ -26,7 +28,8 @@ class LitecoinWalletService extends WalletService<
|
|||
final wallet = LitecoinWallet(
|
||||
mnemonic: await generateMnemonic(),
|
||||
password: credentials.password,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
|
@ -43,7 +46,8 @@ class LitecoinWalletService extends WalletService<
|
|||
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||
orElse: () => null);
|
||||
final wallet = await LitecoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo);
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
@ -68,7 +72,8 @@ class LitecoinWalletService extends WalletService<
|
|||
final wallet = LitecoinWallet(
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
|
32
lib/bitcoin/unspent_coins_info.dart
Normal file
32
lib/bitcoin/unspent_coins_info.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'unspent_coins_info.g.dart';
|
||||
|
||||
@HiveType(typeId: UnspentCoinsInfo.typeId)
|
||||
class UnspentCoinsInfo extends HiveObject {
|
||||
UnspentCoinsInfo({
|
||||
this.walletId,
|
||||
this.hash,
|
||||
this.isFrozen,
|
||||
this.isSending,
|
||||
this.note});
|
||||
|
||||
static const typeId = 9;
|
||||
static const boxName = 'Unspent';
|
||||
static const boxKey = 'unspentBoxKey';
|
||||
|
||||
@HiveField(0)
|
||||
String walletId;
|
||||
|
||||
@HiveField(1)
|
||||
String hash;
|
||||
|
||||
@HiveField(2)
|
||||
bool isFrozen;
|
||||
|
||||
@HiveField(3)
|
||||
bool isSending;
|
||||
|
||||
@HiveField(4)
|
||||
String note;
|
||||
}
|
|
@ -15,7 +15,7 @@ abstract class BuyProvider {
|
|||
String get trackUrl;
|
||||
|
||||
WalletType get walletType => wallet.type;
|
||||
String get walletAddress => wallet.address;
|
||||
String get walletAddress => wallet.walletAddresses.address;
|
||||
String get walletId => wallet.id;
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'package:cake_wallet/entities/balance.dart';
|
||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/entities/currency_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/monero_transaction_priority.dart';
|
||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
import 'package:cake_wallet/entities/sync_status.dart';
|
||||
import 'package:cake_wallet/entities/node.dart';
|
||||
|
@ -31,9 +31,9 @@ abstract class WalletBase<
|
|||
|
||||
String get name => walletInfo.name;
|
||||
|
||||
String get address;
|
||||
//String get address;
|
||||
|
||||
set address(String address);
|
||||
//set address(String address);
|
||||
|
||||
BalanceType get balance;
|
||||
|
||||
|
@ -45,6 +45,8 @@ abstract class WalletBase<
|
|||
|
||||
Object get keys;
|
||||
|
||||
WalletAddresses get walletAddresses;
|
||||
|
||||
HistoryType transactionHistory;
|
||||
|
||||
Future<void> connectToNode({@required Node node});
|
||||
|
|
50
lib/di.dart
50
lib/di.dart
|
@ -1,5 +1,6 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.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';
|
||||
|
@ -39,6 +40,8 @@ import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
|||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
|
@ -77,6 +80,9 @@ import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
|||
import 'package:cake_wallet/view_model/support_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
|
@ -126,6 +132,7 @@ Box<Template> _templates;
|
|||
Box<ExchangeTemplate> _exchangeTemplates;
|
||||
Box<TransactionDescription> _transactionDescriptionBox;
|
||||
Box<Order> _ordersSource;
|
||||
Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
|
||||
|
||||
Future setup(
|
||||
{Box<WalletInfo> walletInfoSource,
|
||||
|
@ -135,7 +142,8 @@ Future setup(
|
|||
Box<Template> templates,
|
||||
Box<ExchangeTemplate> exchangeTemplates,
|
||||
Box<TransactionDescription> transactionDescriptionBox,
|
||||
Box<Order> ordersSource}) async {
|
||||
Box<Order> ordersSource,
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfoSource}) async {
|
||||
_walletInfoSource = walletInfoSource;
|
||||
_nodeSource = nodeSource;
|
||||
_contactSource = contactSource;
|
||||
|
@ -144,6 +152,7 @@ Future setup(
|
|||
_exchangeTemplates = exchangeTemplates;
|
||||
_transactionDescriptionBox = transactionDescriptionBox;
|
||||
_ordersSource = ordersSource;
|
||||
_unspentCoinsInfoSource = unspentCoinsInfoSource;
|
||||
|
||||
if (!_isSetupFinished) {
|
||||
getIt.registerSingletonAsync<SharedPreferences>(
|
||||
|
@ -370,7 +379,8 @@ Future setup(
|
|||
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
|
||||
AccountListItem, void>(
|
||||
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
|
||||
(getIt.get<AppStore>().wallet as MoneroWallet).accountList,
|
||||
(getIt.get<AppStore>().wallet as MoneroWallet).walletAddresses.accountList,
|
||||
wallet: getIt.get<AppStore>().wallet,
|
||||
accountListItem: account));
|
||||
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem,
|
||||
|
@ -454,9 +464,11 @@ Future setup(
|
|||
|
||||
getIt.registerFactory(() => MoneroWalletService(_walletInfoSource));
|
||||
|
||||
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
|
||||
getIt.registerFactory(() =>
|
||||
BitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
|
||||
|
||||
getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
|
||||
getIt.registerFactory(() =>
|
||||
LitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
|
||||
|
||||
getIt.registerFactoryParam<WalletService, WalletType, void>(
|
||||
(WalletType param1, __) {
|
||||
|
@ -592,5 +604,35 @@ Future setup(
|
|||
|
||||
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
return UnspentCoinsListViewModel(
|
||||
wallet: wallet,
|
||||
unspentCoinsInfo: _unspentCoinsInfoSource);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => UnspentCoinsListPage(
|
||||
unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel,
|
||||
UnspentCoinsItem, UnspentCoinsListViewModel>((item, model) =>
|
||||
UnspentCoinsDetailsViewModel(
|
||||
unspentCoinsItem: item,
|
||||
unspentCoinsListViewModel: model));
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>(
|
||||
(List args, _) {
|
||||
final item = args.first as UnspentCoinsItem;
|
||||
final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel;
|
||||
|
||||
return UnspentCoinsDetailsPage(
|
||||
unspentCoinsDetailsViewModel:
|
||||
getIt.get<UnspentCoinsDetailsViewModel>(
|
||||
param1: item,
|
||||
param2: unspentCoinsListViewModel));
|
||||
});
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
47
lib/entities/parse_address_from_domain.dart
Normal file
47
lib/entities/parse_address_from_domain.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
|
||||
|
||||
const topLevelDomain = 'crypto';
|
||||
|
||||
Future<ParsedAddress> parseAddressFromDomain(
|
||||
String domain, String ticker) async {
|
||||
try {
|
||||
final formattedName = OpenaliasRecord.formatDomainName(domain);
|
||||
final domainParts = formattedName.split('.');
|
||||
final name = domainParts.last;
|
||||
|
||||
if (domainParts.length <= 1 || domainParts.first.isEmpty || name.isEmpty) {
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
||||
|
||||
if (name.contains(topLevelDomain)) {
|
||||
final address =
|
||||
await fetchUnstoppableDomainAddress(domain, ticker);
|
||||
|
||||
if (address?.isEmpty ?? true) {
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
||||
|
||||
return ParsedAddress(
|
||||
address: address,
|
||||
name: domain,
|
||||
parseFrom: ParseFrom.unstoppableDomains);
|
||||
}
|
||||
|
||||
final record = await OpenaliasRecord.fetchAddressAndName(formattedName);
|
||||
|
||||
if (record == null || record.address.contains(formattedName)) {
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
||||
|
||||
return ParsedAddress(
|
||||
address: record.address,
|
||||
name: record.name,
|
||||
parseFrom: ParseFrom.openAlias);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
12
lib/entities/parsed_address.dart
Normal file
12
lib/entities/parsed_address.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
enum ParseFrom {unstoppableDomains, openAlias, notParsed}
|
||||
|
||||
class ParsedAddress {
|
||||
ParsedAddress({
|
||||
this.address = '',
|
||||
this.name = '',
|
||||
this.parseFrom = ParseFrom.notParsed});
|
||||
|
||||
final String address;
|
||||
final String name;
|
||||
final ParseFrom parseFrom;
|
||||
}
|
22
lib/entities/unstoppable_domain_address.dart
Normal file
22
lib/entities/unstoppable_domain_address.dart
Normal file
|
@ -0,0 +1,22 @@
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
const channel = MethodChannel('com.cake_wallet/native_utils');
|
||||
|
||||
Future<String> fetchUnstoppableDomainAddress(String domain, String ticker) async {
|
||||
var address = '';
|
||||
|
||||
try {
|
||||
address = await channel.invokeMethod(
|
||||
'getUnstoppableDomainAddress',
|
||||
<String, String> {
|
||||
'domain' : domain,
|
||||
'ticker' : ticker
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
print('Unstoppable domain error: ${e.toString()}');
|
||||
address = '';
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
36
lib/entities/wallet_addresses.dart
Normal file
36
lib/entities/wallet_addresses.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
|
||||
abstract class WalletAddresses {
|
||||
WalletAddresses(this.walletInfo) {
|
||||
addressesMap = {};
|
||||
}
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
|
||||
String get address;
|
||||
|
||||
set address(String address);
|
||||
|
||||
Map<String, String> addressesMap;
|
||||
|
||||
Future<void> init();
|
||||
|
||||
Future<void> updateAddressesInBox();
|
||||
|
||||
Future<void> saveAddressesInBox() async {
|
||||
try {
|
||||
if (walletInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
walletInfo.address = address;
|
||||
walletInfo.addresses = addressesMap;
|
||||
|
||||
if (walletInfo.isInBox) {
|
||||
await walletInfo.save();
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,5 +53,8 @@ class WalletInfo extends HiveObject {
|
|||
@HiveField(8)
|
||||
String address;
|
||||
|
||||
@HiveField(10)
|
||||
Map<String, String> addresses;
|
||||
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/entities/language_service.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -75,6 +76,10 @@ Future<void> main() async {
|
|||
Hive.registerAdapter(OrderAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
|
||||
Hive.registerAdapter(UnspentCoinsInfoAdapter());
|
||||
}
|
||||
|
||||
final secureStorage = FlutterSecureStorage();
|
||||
final transactionDescriptionsBoxKey = await getEncryptionKey(
|
||||
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
|
||||
|
@ -95,6 +100,8 @@ Future<void> main() async {
|
|||
final templates = await Hive.openBox<Template>(Template.boxName);
|
||||
final exchangeTemplates =
|
||||
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
|
||||
final unspentCoinsInfoSource =
|
||||
await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
||||
await initialSetup(
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
|
@ -102,6 +109,7 @@ Future<void> main() async {
|
|||
contactSource: contacts,
|
||||
tradesSource: trades,
|
||||
ordersSource: orders,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
||||
// fiatConvertationService: fiatConvertationService,
|
||||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
|
@ -134,6 +142,7 @@ Future<void> initialSetup(
|
|||
@required Box<Template> templates,
|
||||
@required Box<ExchangeTemplate> exchangeTemplates,
|
||||
@required Box<TransactionDescription> transactionDescriptions,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
|
||||
FlutterSecureStorage secureStorage,
|
||||
int initialMigrationVersion = 15}) async {
|
||||
LanguageService.loadLocaleList();
|
||||
|
@ -153,7 +162,8 @@ Future<void> initialSetup(
|
|||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
transactionDescriptionBox: transactionDescriptions,
|
||||
ordersSource: ordersSource);
|
||||
ordersSource: ordersSource,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource);
|
||||
await bootstrap(navigatorKey);
|
||||
monero_wallet.onStartup();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ 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:cake_wallet/monero/monero_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_monero/transaction_history.dart'
|
||||
|
@ -15,10 +17,7 @@ import 'package:cake_wallet/monero/pending_monero_transaction.dart';
|
|||
import 'package:cake_wallet/monero/monero_wallet_keys.dart';
|
||||
import 'package:cake_wallet/monero/monero_balance.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_history.dart';
|
||||
import 'package:cake_wallet/monero/monero_subaddress_list.dart';
|
||||
import 'package:cake_wallet/monero/monero_account_list.dart';
|
||||
import 'package:cake_wallet/monero/account.dart';
|
||||
import 'package:cake_wallet/monero/subaddress.dart';
|
||||
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/entities/sync_status.dart';
|
||||
|
@ -35,9 +34,7 @@ class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
|||
abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
|
||||
MoneroWalletBase({WalletInfo walletInfo})
|
||||
: accountList = MoneroAccountList(),
|
||||
subaddressList = MoneroSubaddressList(),
|
||||
super(walletInfo) {
|
||||
: super(walletInfo) {
|
||||
transactionHistory = MoneroTransactionHistory();
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
||||
|
@ -47,33 +44,26 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
_isSavingAfterSync = false;
|
||||
_isSavingAfterNewTransaction = false;
|
||||
_isTransactionUpdating = false;
|
||||
_onAccountChangeReaction = reaction((_) => account, (Account account) {
|
||||
walletAddresses = MoneroWalletAddresses(walletInfo);
|
||||
_onAccountChangeReaction = reaction((_) => walletAddresses.account,
|
||||
(Account account) {
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||
unlockedBalance:
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id));
|
||||
subaddressList.update(accountIndex: account.id);
|
||||
subaddress = subaddressList.subaddresses.first;
|
||||
address = subaddress.address;
|
||||
walletAddresses.updateSubaddressList(accountIndex: account.id);
|
||||
});
|
||||
}
|
||||
|
||||
static const int _autoAfterSyncSaveInterval = 60000;
|
||||
|
||||
@observable
|
||||
Account account;
|
||||
|
||||
@observable
|
||||
Subaddress subaddress;
|
||||
@override
|
||||
MoneroWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
@observable
|
||||
MoneroBalance balance;
|
||||
|
@ -88,10 +78,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
publicSpendKey: monero_wallet.getPublicSpendKey(),
|
||||
publicViewKey: monero_wallet.getPublicViewKey());
|
||||
|
||||
final MoneroSubaddressList subaddressList;
|
||||
|
||||
final MoneroAccountList accountList;
|
||||
|
||||
SyncListener _listener;
|
||||
ReactionDisposer _onAccountChangeReaction;
|
||||
int _lastAutosaveTimestamp;
|
||||
|
@ -101,15 +87,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
int _lastSaveTimestamp;
|
||||
|
||||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
subaddressList.update(accountIndex: account.id ?? 0);
|
||||
subaddress = subaddressList.getAll().first;
|
||||
await walletAddresses.init();
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account.id),
|
||||
unlockedBalance:
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id));
|
||||
address = subaddress.address;
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id));
|
||||
_setListeners();
|
||||
await updateTransactions();
|
||||
|
||||
|
@ -129,24 +111,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
_onAccountChangeReaction?.reaction?.dispose();
|
||||
}
|
||||
|
||||
bool validate() {
|
||||
accountList.update();
|
||||
final accountListLength = accountList.accounts?.length ?? 0;
|
||||
|
||||
if (accountListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
||||
final subaddressListLength = subaddressList.subaddresses?.length ?? 0;
|
||||
|
||||
if (subaddressListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> connectToNode({@required Node node}) async {
|
||||
try {
|
||||
|
@ -189,7 +153,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
? moneroParseAmount(amount: _credentials.amount)
|
||||
: null;
|
||||
final unlockedBalance =
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id);
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
|
||||
|
||||
if ((amount != null && unlockedBalance < amount) ||
|
||||
(amount == null && unlockedBalance <= 0)) {
|
||||
|
@ -209,7 +173,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
paymentId: _credentials.paymentId,
|
||||
amount: _credentials.amount,
|
||||
priorityRaw: _credentials.priority.serialize(),
|
||||
accountIndex: account.id);
|
||||
accountIndex: walletAddresses.account.id);
|
||||
|
||||
return PendingMoneroTransaction(pendingTransactionDescription);
|
||||
}
|
||||
|
@ -238,12 +202,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
if (now - _lastSaveTimestamp < Duration(seconds: 10).inMilliseconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
await backupWalletFiles(name);
|
||||
_lastSaveTimestamp = now;
|
||||
await monero_wallet.store();
|
||||
}
|
||||
|
@ -265,7 +232,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
monero_wallet.rescanBlockchainAsync();
|
||||
await startSync();
|
||||
_askForUpdateBalance();
|
||||
accountList.update();
|
||||
walletAddresses.accountList.update();
|
||||
await _askForUpdateTransactionHistory();
|
||||
await save();
|
||||
await walletInfo.save();
|
||||
|
@ -364,10 +331,10 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
await updateTransactions();
|
||||
|
||||
int _getFullBalance() =>
|
||||
monero_wallet.getFullBalance(accountIndex: account.id);
|
||||
monero_wallet.getFullBalance(accountIndex: walletAddresses.account.id);
|
||||
|
||||
int _getUnlockedBalance() =>
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id);
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
|
||||
|
||||
Future<void> _afterSyncSave() async {
|
||||
try {
|
||||
|
@ -414,13 +381,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
if (walletInfo.isRecovery) {
|
||||
await _askForUpdateTransactionHistory();
|
||||
_askForUpdateBalance();
|
||||
accountList.update();
|
||||
walletAddresses.accountList.update();
|
||||
}
|
||||
|
||||
if (blocksLeft < 100) {
|
||||
await _askForUpdateTransactionHistory();
|
||||
_askForUpdateBalance();
|
||||
accountList.update();
|
||||
walletAddresses.accountList.update();
|
||||
syncStatus = SyncedSyncStatus();
|
||||
await _afterSyncSave();
|
||||
|
||||
|
|
85
lib/monero/monero_wallet_addresses.dart
Normal file
85
lib/monero/monero_wallet_addresses.dart
Normal file
|
@ -0,0 +1,85 @@
|
|||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/monero/account.dart';
|
||||
import 'package:cake_wallet/monero/monero_account_list.dart';
|
||||
import 'package:cake_wallet/monero/monero_subaddress_list.dart';
|
||||
import 'package:cake_wallet/monero/subaddress.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'monero_wallet_addresses.g.dart';
|
||||
|
||||
class MoneroWalletAddresses = MoneroWalletAddressesBase
|
||||
with _$MoneroWalletAddresses;
|
||||
|
||||
abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||
MoneroWalletAddressesBase(WalletInfo walletInfo) : super(walletInfo) {
|
||||
accountList = MoneroAccountList();
|
||||
subaddressList = MoneroSubaddressList();
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
Account account;
|
||||
|
||||
@observable
|
||||
Subaddress subaddress;
|
||||
|
||||
MoneroSubaddressList subaddressList;
|
||||
|
||||
MoneroAccountList accountList;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
updateSubaddressList(accountIndex: account.id ?? 0);
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
final _subaddressList = MoneroSubaddressList();
|
||||
|
||||
addressesMap.clear();
|
||||
|
||||
accountList.accounts.forEach((account) {
|
||||
_subaddressList.update(accountIndex: account.id);
|
||||
_subaddressList.subaddresses.forEach((subaddress) {
|
||||
addressesMap[subaddress.address] = subaddress.label;
|
||||
});
|
||||
});
|
||||
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
bool validate() {
|
||||
accountList.update();
|
||||
final accountListLength = accountList.accounts?.length ?? 0;
|
||||
|
||||
if (accountListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
||||
final subaddressListLength = subaddressList.subaddresses?.length ?? 0;
|
||||
|
||||
if (subaddressListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateSubaddressList({int accountIndex}) {
|
||||
subaddressList.update(accountIndex: accountIndex);
|
||||
subaddress = subaddressList.subaddresses.first;
|
||||
address = subaddress.address;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_utils.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cw_monero/wallet_manager.dart' as monero_wallet_manager;
|
||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||
|
@ -55,16 +56,7 @@ class MoneroWalletService extends WalletService<
|
|||
MoneroWalletService(this.walletInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
||||
static Future<void> _removeCache(String name) async {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
final cacheFile = File(path);
|
||||
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool walletFilesExist(String path) =>
|
||||
!File(path).existsSync() && !File('$path.keys').existsSync();
|
||||
|
||||
|
@ -117,10 +109,10 @@ class MoneroWalletService extends WalletService<
|
|||
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||
orElse: () => null);
|
||||
final wallet = MoneroWallet(walletInfo: walletInfo);
|
||||
final isValid = wallet.validate();
|
||||
final isValid = wallet.walletAddresses.validate();
|
||||
|
||||
if (!isValid) {
|
||||
await _removeCache(name);
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
@ -135,7 +127,7 @@ class MoneroWalletService extends WalletService<
|
|||
(e is WalletOpeningException &&
|
||||
(e.message == 'std::bad_alloc' ||
|
||||
e.message.contains('bad_alloc')))) {
|
||||
await _removeCache(name);
|
||||
await restoreOrResetWalletFiles(name);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
|
88
lib/monero/monero_wallet_utils.dart
Normal file
88
lib/monero/monero_wallet_utils.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
import 'dart:io';
|
||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
|
||||
String backupFileName(String originalPath) {
|
||||
final pathParts = originalPath.split('/');
|
||||
final newName = '#_${pathParts.last}';
|
||||
pathParts.removeLast();
|
||||
pathParts.add(newName);
|
||||
return pathParts.join('/');
|
||||
}
|
||||
|
||||
Future<void> backupWalletFiles(String name) async {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
final cacheFile = File(path);
|
||||
final keysFile = File('$path.keys');
|
||||
final addressListFile = File('$path.address.txt');
|
||||
final newCacheFilePath = backupFileName(cacheFile.path);
|
||||
final newKeysFilePath = backupFileName(keysFile.path);
|
||||
final newAddressListFilePath = backupFileName(addressListFile.path);
|
||||
|
||||
if (cacheFile.existsSync()) {
|
||||
await cacheFile.copy(newCacheFilePath);
|
||||
}
|
||||
|
||||
if (keysFile.existsSync()) {
|
||||
await keysFile.copy(newKeysFilePath);
|
||||
}
|
||||
|
||||
if (addressListFile.existsSync()) {
|
||||
await addressListFile.copy(newAddressListFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreWalletFiles(String name) async {
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final cacheFilePath = '$walletDirPath/$name';
|
||||
final keysFilePath = '$walletDirPath/$name.keys';
|
||||
final addressListFilePath = '$walletDirPath/$name.address.txt';
|
||||
final backupCacheFile = File(backupFileName(cacheFilePath));
|
||||
final backupKeysFile = File(backupFileName(keysFilePath));
|
||||
final backupAddressListFile = File(backupFileName(addressListFilePath));
|
||||
|
||||
if (backupCacheFile.existsSync()) {
|
||||
await backupCacheFile.copy(cacheFilePath);
|
||||
}
|
||||
|
||||
if (backupKeysFile.existsSync()) {
|
||||
await backupKeysFile.copy(keysFilePath);
|
||||
}
|
||||
|
||||
if (backupAddressListFile.existsSync()) {
|
||||
await backupAddressListFile.copy(addressListFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> backupWalletFilesExists(String name) async {
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final cacheFilePath = '$walletDirPath/$name';
|
||||
final keysFilePath = '$walletDirPath/$name.keys';
|
||||
final addressListFilePath = '$walletDirPath/$name.address.txt';
|
||||
final backupCacheFile = File(backupFileName(cacheFilePath));
|
||||
final backupKeysFile = File(backupFileName(keysFilePath));
|
||||
final backupAddressListFile = File(backupFileName(addressListFilePath));
|
||||
|
||||
return backupCacheFile.existsSync()
|
||||
&& backupKeysFile.existsSync()
|
||||
&& backupAddressListFile.existsSync();
|
||||
}
|
||||
|
||||
Future<void> removeCache(String name) async {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
final cacheFile = File(path);
|
||||
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreOrResetWalletFiles(String name) async {
|
||||
final backupsExists = await backupWalletFilesExists(name);
|
||||
|
||||
if (backupsExists) {
|
||||
await restoreWalletFiles(name);
|
||||
}
|
||||
|
||||
removeCache(name);
|
||||
}
|
|
@ -45,6 +45,7 @@ class Palette {
|
|||
static const Color dullGray = Color.fromRGBO(98, 98, 98, 1.0);
|
||||
static const Color protectiveBlue = Color.fromRGBO(33, 148, 255, 1.0);
|
||||
static const Color darkBlue = Color.fromRGBO(109, 128, 178, 1.0);
|
||||
static const Color paleCornflowerBlue = Color.fromRGBO(185, 196, 237, 1.0);
|
||||
}
|
||||
|
||||
class PaletteDark {
|
||||
|
|
|
@ -37,7 +37,7 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
|||
await wallet.connectToNode(node: node);
|
||||
|
||||
if (wallet.walletInfo.address?.isEmpty ?? true) {
|
||||
wallet.walletInfo.address = wallet.address;
|
||||
wallet.walletInfo.address = wallet.walletAddresses.address;
|
||||
|
||||
if (wallet.walletInfo.isInBox) {
|
||||
await wallet.walletInfo.save();
|
||||
|
|
|
@ -11,9 +11,12 @@ import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
|
|||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
@ -364,6 +367,18 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<SupportPage>());
|
||||
|
||||
case Routes.unspentCoinsList:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<UnspentCoinsListPage>());
|
||||
|
||||
case Routes.unspentCoinsDetails:
|
||||
final args = settings.arguments as List;
|
||||
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
getIt.get<UnspentCoinsDetailsPage>(
|
||||
param1: args));
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
|
|
@ -55,4 +55,6 @@ class Routes {
|
|||
static const orderDetails = '/order_details';
|
||||
static const preOrder = '/pre_order';
|
||||
static const buyWebView = '/buy_web_view';
|
||||
static const unspentCoinsList = '/unspent_coins_list';
|
||||
static const unspentCoinsDetails = '/unspent_coins_details';
|
||||
}
|
|
@ -151,17 +151,19 @@ class ContactListPage extends BasePage {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Padding(
|
||||
padding: image != null
|
||||
? EdgeInsets.only(left: 12)
|
||||
: EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
contact.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: image != null
|
||||
? EdgeInsets.only(left: 12)
|
||||
: EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
contact.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:ui';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/sync_status.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
@ -9,8 +11,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/limits_state.dart';
|
||||
|
@ -23,7 +23,6 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
|
@ -42,7 +41,9 @@ class ExchangePage extends BasePage {
|
|||
final checkBoxKey = GlobalKey<StandardCheckboxState>();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _depositAmountFocus = FocusNode();
|
||||
final _depositAddressFocus = FocusNode();
|
||||
final _receiveAmountFocus = FocusNode();
|
||||
final _receiveAddressFocus = FocusNode();
|
||||
var _isReactionsSet = false;
|
||||
|
||||
@override
|
||||
|
@ -171,6 +172,7 @@ class ExchangePage extends BasePage {
|
|||
.calculateDepositAllAmount()
|
||||
: null,
|
||||
amountFocusNode: _depositAmountFocus,
|
||||
addressFocusNode: _depositAddressFocus,
|
||||
key: depositKey,
|
||||
title: S.of(context).you_will_send,
|
||||
initialCurrency:
|
||||
|
@ -179,7 +181,7 @@ class ExchangePage extends BasePage {
|
|||
initialAddress:
|
||||
exchangeViewModel.depositCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.depositAddress,
|
||||
initialIsAmountEditable: true,
|
||||
initialIsAddressEditable:
|
||||
|
@ -223,6 +225,15 @@ class ExchangePage extends BasePage {
|
|||
type: exchangeViewModel.wallet.type),
|
||||
addressTextFieldValidator: AddressValidator(
|
||||
type: exchangeViewModel.depositCurrency),
|
||||
onPushPasteButton: (context) async {
|
||||
final domain =
|
||||
exchangeViewModel.depositAddress;
|
||||
final ticker = exchangeViewModel
|
||||
.depositCurrency.title.toLowerCase();
|
||||
exchangeViewModel.depositAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(
|
||||
context, domain, ticker);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -232,6 +243,7 @@ class ExchangePage extends BasePage {
|
|||
child: Observer(
|
||||
builder: (_) => ExchangeCard(
|
||||
amountFocusNode: _receiveAmountFocus,
|
||||
addressFocusNode: _receiveAddressFocus,
|
||||
key: receiveKey,
|
||||
title: S.of(context).you_will_get,
|
||||
initialCurrency:
|
||||
|
@ -240,7 +252,7 @@ class ExchangePage extends BasePage {
|
|||
initialAddress: exchangeViewModel
|
||||
.receiveCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.receiveAddress,
|
||||
initialIsAmountEditable: exchangeViewModel
|
||||
.isReceiveAmountEditable,
|
||||
|
@ -268,6 +280,15 @@ class ExchangePage extends BasePage {
|
|||
AddressValidator(
|
||||
type: exchangeViewModel
|
||||
.receiveCurrency),
|
||||
onPushPasteButton: (context) async {
|
||||
final domain =
|
||||
exchangeViewModel.receiveAddress;
|
||||
final ticker = exchangeViewModel
|
||||
.receiveCurrency.title.toLowerCase();
|
||||
exchangeViewModel.receiveAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(
|
||||
context, domain, ticker);
|
||||
},
|
||||
)),
|
||||
)
|
||||
],
|
||||
|
@ -371,7 +392,7 @@ class ExchangePage extends BasePage {
|
|||
from: template.depositCurrency,
|
||||
to: template.receiveCurrency,
|
||||
onTap: () {
|
||||
applyTemplate(
|
||||
applyTemplate(context,
|
||||
exchangeViewModel, template);
|
||||
},
|
||||
onRemove: () {
|
||||
|
@ -472,8 +493,8 @@ class ExchangePage extends BasePage {
|
|||
));
|
||||
}
|
||||
|
||||
void applyTemplate(
|
||||
ExchangeViewModel exchangeViewModel, ExchangeTemplate template) {
|
||||
void applyTemplate(BuildContext context,
|
||||
ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
|
||||
exchangeViewModel.changeDepositCurrency(
|
||||
currency: CryptoCurrency.fromString(template.depositCurrency));
|
||||
exchangeViewModel.changeReceiveCurrency(
|
||||
|
@ -491,6 +512,16 @@ class ExchangePage extends BasePage {
|
|||
exchangeViewModel.receiveAddress = template.receiveAddress;
|
||||
exchangeViewModel.isReceiveAmountEntered = false;
|
||||
exchangeViewModel.isFixedRateMode = false;
|
||||
|
||||
var domain = template.depositAddress;
|
||||
var ticker = template.depositCurrency.toLowerCase();
|
||||
exchangeViewModel.depositAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
|
||||
domain = template.receiveAddress;
|
||||
ticker = template.receiveCurrency.toLowerCase();
|
||||
exchangeViewModel.receiveAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
}
|
||||
|
||||
void _setReactions(
|
||||
|
@ -646,7 +677,8 @@ class ExchangePage extends BasePage {
|
|||
}
|
||||
});
|
||||
|
||||
reaction((_) => exchangeViewModel.wallet.address, (String address) {
|
||||
reaction((_) => exchangeViewModel.wallet.walletAddresses.address,
|
||||
(String address) {
|
||||
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
|
||||
depositKey.currentState.changeAddress(address: address);
|
||||
}
|
||||
|
@ -656,6 +688,26 @@ class ExchangePage extends BasePage {
|
|||
}
|
||||
});
|
||||
|
||||
_depositAddressFocus.addListener(() async {
|
||||
if (!_depositAddressFocus.hasFocus &&
|
||||
depositAddressController.text.isNotEmpty) {
|
||||
final domain = depositAddressController.text;
|
||||
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
||||
exchangeViewModel.depositAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
}
|
||||
});
|
||||
|
||||
_receiveAddressFocus.addListener(() async {
|
||||
if (!_receiveAddressFocus.hasFocus &&
|
||||
receiveAddressController.text.isNotEmpty) {
|
||||
final domain = receiveAddressController.text;
|
||||
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
||||
exchangeViewModel.receiveAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
}
|
||||
});
|
||||
|
||||
_receiveAmountFocus.addListener(() {
|
||||
if (_receiveAmountFocus.hasFocus && !exchangeViewModel.isFixedRateMode) {
|
||||
showPopUp<void>(
|
||||
|
@ -707,7 +759,8 @@ class ExchangePage extends BasePage {
|
|||
isCurrentTypeWallet ? exchangeViewModel.wallet.name : null);
|
||||
|
||||
key.currentState.changeAddress(
|
||||
address: isCurrentTypeWallet ? exchangeViewModel.wallet.address : '');
|
||||
address: isCurrentTypeWallet
|
||||
? exchangeViewModel.wallet.walletAddresses.address : '');
|
||||
|
||||
key.currentState.changeAmount(amount: '');
|
||||
}
|
||||
|
@ -719,11 +772,35 @@ class ExchangePage extends BasePage {
|
|||
if (isCurrentTypeWallet) {
|
||||
key.currentState.changeWalletName(exchangeViewModel.wallet.name);
|
||||
key.currentState.addressController.text =
|
||||
exchangeViewModel.wallet.address;
|
||||
exchangeViewModel.wallet.walletAddresses.address;
|
||||
} else if (key.currentState.addressController.text ==
|
||||
exchangeViewModel.wallet.address) {
|
||||
exchangeViewModel.wallet.walletAddresses.address) {
|
||||
key.currentState.changeWalletName(null);
|
||||
key.currentState.addressController.text = null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> applyOpenaliasOrUnstoppableDomains(
|
||||
BuildContext context, String domain, String ticker) async {
|
||||
final parsedAddress = await parseAddressFromDomain(domain, ticker);
|
||||
|
||||
switch (parsedAddress.parseFrom) {
|
||||
case ParseFrom.unstoppableDomains:
|
||||
showAddressAlert(
|
||||
context,
|
||||
S.of(context).address_detected,
|
||||
S.of(context).address_from_domain(parsedAddress.name));
|
||||
break;
|
||||
case ParseFrom.openAlias:
|
||||
showAddressAlert(
|
||||
context,
|
||||
S.of(context).openalias_alert_title,
|
||||
S.of(context).openalias_alert_content(parsedAddress.name));
|
||||
break;
|
||||
case ParseFrom.notParsed:
|
||||
break;
|
||||
}
|
||||
|
||||
return parsedAddress.address;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ class ExchangeTemplatePage extends BasePage {
|
|||
initialAddress: exchangeViewModel
|
||||
.depositCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.depositAddress,
|
||||
initialIsAmountEditable: true,
|
||||
initialIsAddressEditable: exchangeViewModel
|
||||
|
@ -150,9 +150,8 @@ class ExchangeTemplatePage extends BasePage {
|
|||
.color,
|
||||
currencyValueValidator: AmountValidator(
|
||||
type: exchangeViewModel.wallet.type),
|
||||
addressTextFieldValidator: AddressValidator(
|
||||
type:
|
||||
exchangeViewModel.depositCurrency),
|
||||
//addressTextFieldValidator: AddressValidator(
|
||||
// type: exchangeViewModel.depositCurrency),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -168,7 +167,7 @@ class ExchangeTemplatePage extends BasePage {
|
|||
initialAddress:
|
||||
exchangeViewModel.receiveCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.receiveAddress,
|
||||
initialIsAmountEditable:
|
||||
exchangeViewModel.provider is
|
||||
|
@ -190,8 +189,8 @@ class ExchangeTemplatePage extends BasePage {
|
|||
.decorationColor,
|
||||
currencyValueValidator: AmountValidator(
|
||||
type: exchangeViewModel.wallet.type),
|
||||
addressTextFieldValidator: AddressValidator(
|
||||
type: exchangeViewModel.receiveCurrency),
|
||||
//addressTextFieldValidator: AddressValidator(
|
||||
// type: exchangeViewModel.receiveCurrency),
|
||||
)),
|
||||
)
|
||||
],
|
||||
|
@ -383,7 +382,8 @@ class ExchangeTemplatePage extends BasePage {
|
|||
}
|
||||
});
|
||||
|
||||
reaction((_) => exchangeViewModel.wallet.address, (String address) {
|
||||
reaction((_) => exchangeViewModel.wallet.walletAddresses.address,
|
||||
(String address) {
|
||||
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
|
||||
depositKey.currentState.changeAddress(address: address);
|
||||
}
|
||||
|
@ -405,7 +405,8 @@ class ExchangeTemplatePage extends BasePage {
|
|||
isCurrentTypeWallet ? exchangeViewModel.wallet.name : null);
|
||||
|
||||
key.currentState.changeAddress(
|
||||
address: isCurrentTypeWallet ? exchangeViewModel.wallet.address : '');
|
||||
address: isCurrentTypeWallet
|
||||
? exchangeViewModel.wallet.walletAddresses.address : '');
|
||||
|
||||
key.currentState.changeAmount(amount: '');
|
||||
}
|
||||
|
@ -417,9 +418,9 @@ class ExchangeTemplatePage extends BasePage {
|
|||
if (isCurrentTypeWallet) {
|
||||
key.currentState.changeWalletName(exchangeViewModel.wallet.name);
|
||||
key.currentState.addressController.text =
|
||||
exchangeViewModel.wallet.address;
|
||||
exchangeViewModel.wallet.walletAddresses.address;
|
||||
} else if (key.currentState.addressController.text ==
|
||||
exchangeViewModel.wallet.address) {
|
||||
exchangeViewModel.wallet.walletAddresses.address) {
|
||||
key.currentState.changeWalletName(null);
|
||||
key.currentState.addressController.text = null;
|
||||
}
|
||||
|
|
|
@ -28,8 +28,10 @@ class ExchangeCard extends StatefulWidget {
|
|||
this.currencyValueValidator,
|
||||
this.addressTextFieldValidator,
|
||||
this.amountFocusNode,
|
||||
this.addressFocusNode,
|
||||
this.hasAllAmount = false,
|
||||
this.allAmount})
|
||||
this.allAmount,
|
||||
this.onPushPasteButton})
|
||||
: super(key: key);
|
||||
|
||||
final List<CryptoCurrency> currencies;
|
||||
|
@ -49,8 +51,10 @@ class ExchangeCard extends StatefulWidget {
|
|||
final FormFieldValidator<String> currencyValueValidator;
|
||||
final FormFieldValidator<String> addressTextFieldValidator;
|
||||
final FocusNode amountFocusNode;
|
||||
final FocusNode addressFocusNode;
|
||||
final bool hasAllAmount;
|
||||
Function allAmount;
|
||||
final Function allAmount;
|
||||
final Function(BuildContext context) onPushPasteButton;
|
||||
|
||||
@override
|
||||
ExchangeCardState createState() => ExchangeCardState();
|
||||
|
@ -288,6 +292,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
|||
? Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: AddressTextField(
|
||||
focusNode: widget.addressFocusNode,
|
||||
controller: addressController,
|
||||
placeholder: widget.hasRefundAddress
|
||||
? S.of(context).refund_address
|
||||
|
@ -311,6 +316,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
|||
.decorationColor),
|
||||
buttonColor: widget.addressButtonsColor,
|
||||
validator: widget.addressTextFieldValidator,
|
||||
onPushPasteButton: widget.onPushPasteButton,
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import 'dart:ui';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class UnspentCoinsDetailsPage extends BasePage {
|
||||
UnspentCoinsDetailsPage({this.unspentCoinsDetailsViewModel});
|
||||
|
||||
@override
|
||||
String get title => S.current.unspent_coins_details_title;
|
||||
|
||||
final UnspentCoinsDetailsViewModel unspentCoinsDetailsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => unspentCoinsDetailsViewModel.items.length,
|
||||
itemBuilder: (_, __, index) {
|
||||
final item = unspentCoinsDetailsViewModel.items[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return StandartListRow(
|
||||
title: '${item.title}:',
|
||||
value: item.value);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is UnspentCoinsSwitchItem) {
|
||||
return Observer(builder: (_) => UnspentCoinsSwitchRow(
|
||||
title: item.title,
|
||||
switchValue: item.switchValue(),
|
||||
onSwitchValueChange: item.onSwitchValueChange
|
||||
));
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
116
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
Normal file
116
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
Normal file
|
@ -0,0 +1,116 @@
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class UnspentCoinsListPage extends BasePage {
|
||||
UnspentCoinsListPage({this.unspentCoinsListViewModel});
|
||||
|
||||
@override
|
||||
String get title => S.current.unspent_coins_title;
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
final questionImage = Image.asset('assets/images/question_mark.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color);
|
||||
|
||||
return SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => showUnspentCoinsAlert(context),
|
||||
child: questionImage),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
UnspentCoinsListForm(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListForm extends StatefulWidget {
|
||||
UnspentCoinsListForm(this.unspentCoinsListViewModel);
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
UnspentCoinsListFormState createState() =>
|
||||
UnspentCoinsListFormState(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
UnspentCoinsListFormState(this.unspentCoinsListViewModel);
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
|
||||
}
|
||||
|
||||
void afterLayout(dynamic _) {
|
||||
showUnspentCoinsAlert(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.fromLTRB(24, 12, 24, 24),
|
||||
child: Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
itemCount: unspentCoinsListViewModel.items.length,
|
||||
separatorBuilder: (_, __) =>
|
||||
SizedBox(height: 15),
|
||||
itemBuilder: (_, int index) {
|
||||
return Observer(builder: (_) {
|
||||
final item = unspentCoinsListViewModel.items[index];
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.unspentCoinsDetails,
|
||||
arguments: [item, unspentCoinsListViewModel]),
|
||||
child: UnspentCoinsListItem(
|
||||
address: item.address,
|
||||
amount: item.amount,
|
||||
isSending: item.isSending,
|
||||
onCheckBoxTap: item.isFrozen
|
||||
? null
|
||||
: () async {
|
||||
item.isSending = !item.isSending;
|
||||
await unspentCoinsListViewModel
|
||||
.saveUnspentCoinInfo(item);}));
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showUnspentCoinsAlert(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: '',
|
||||
alertContent: 'Information about unspent coins',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class UnspentCoinsListItem extends StatelessWidget {
|
||||
UnspentCoinsListItem({
|
||||
@required this.address,
|
||||
@required this.amount,
|
||||
@required this.isSending,
|
||||
@required this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
static const amountColor = Palette.darkBlueCraiola;
|
||||
static const addressColor = Palette.darkGray;
|
||||
static const selectedItemColor = Palette.paleCornflowerBlue;
|
||||
static const unselectedItemColor = Palette.moderateLavender;
|
||||
|
||||
final String address;
|
||||
final String amount;
|
||||
final bool isSending;
|
||||
final Function() onCheckBoxTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final itemColor = isSending? selectedItemColor : unselectedItemColor;
|
||||
|
||||
return Container(
|
||||
height: 62,
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
color: itemColor),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: GestureDetector(
|
||||
onTap: () => onCheckBoxTap?.call(),
|
||||
child: Container(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color,
|
||||
width: 1.0),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8.0)),
|
||||
color: Theme.of(context).backgroundColor),
|
||||
child: isSending
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.blue,
|
||||
size: 20.0,
|
||||
)
|
||||
: Offstage(),
|
||||
)
|
||||
)
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AutoSizeText(
|
||||
amount,
|
||||
style: TextStyle(
|
||||
color: amountColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600
|
||||
),
|
||||
maxLines: 1,
|
||||
),
|
||||
AutoSizeText(
|
||||
address,
|
||||
style: TextStyle(
|
||||
color: addressColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 1,
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import 'package:cake_wallet/src/widgets/standart_switch.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UnspentCoinsSwitchRow extends StatelessWidget {
|
||||
UnspentCoinsSwitchRow(
|
||||
{this.title,
|
||||
this.titleFontSize = 14,
|
||||
this.switchValue,
|
||||
this.onSwitchValueChange});
|
||||
|
||||
final String title;
|
||||
final double titleFontSize;
|
||||
final bool switchValue;
|
||||
final void Function(bool value) onSwitchValueChange;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme.overline.color),
|
||||
textAlign: TextAlign.left),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: StandartSwitch(
|
||||
value: switchValue,
|
||||
onTaped: () => onSwitchValueChange(!switchValue))
|
||||
)
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,8 @@ class AddressTextField extends StatelessWidget {
|
|||
this.iconColor,
|
||||
this.textStyle,
|
||||
this.hintStyle,
|
||||
this.validator});
|
||||
this.validator,
|
||||
this.onPushPasteButton});
|
||||
|
||||
static const prefixIconWidth = 34.0;
|
||||
static const prefixIconHeight = 34.0;
|
||||
|
@ -43,6 +44,7 @@ class AddressTextField extends StatelessWidget {
|
|||
final TextStyle textStyle;
|
||||
final TextStyle hintStyle;
|
||||
final FocusNode focusNode;
|
||||
final Function(BuildContext context) onPushPasteButton;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -225,5 +227,7 @@ class AddressTextField extends StatelessWidget {
|
|||
if (address?.isNotEmpty ?? false) {
|
||||
controller.text = address;
|
||||
}
|
||||
|
||||
onPushPasteButton?.call(context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,9 +44,6 @@ class StandardCheckboxState extends State<StandardCheckbox> {
|
|||
Container(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
margin: EdgeInsets.only(
|
||||
right: 10.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
|
@ -65,14 +62,17 @@ class StandardCheckboxState extends State<StandardCheckbox> {
|
|||
)
|
||||
: Offstage(),
|
||||
),
|
||||
Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
if (caption.isNotEmpty) Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
|
@ -16,11 +16,22 @@ class ContactListViewModel = ContactListViewModelBase
|
|||
abstract class ContactListViewModelBase with Store {
|
||||
ContactListViewModelBase(this.contactSource, this.walletInfoSource)
|
||||
: contacts = ObservableList<ContactRecord>(),
|
||||
walletContacts = walletInfoSource.values
|
||||
.where((info) => info.address?.isNotEmpty ?? false)
|
||||
.map((info) => WalletContact(
|
||||
info.address, info.name, walletTypeToCryptoCurrency(info.type)))
|
||||
.toList() {
|
||||
walletContacts = [] {
|
||||
walletInfoSource.values.forEach((info) {
|
||||
if (info.addresses?.isNotEmpty ?? false) {
|
||||
info.addresses?.forEach((address, label) {
|
||||
final name = label.isNotEmpty
|
||||
? info.name + ' ($label)'
|
||||
: info.name;
|
||||
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type)));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_subscription = contactSource.bindToListWithTransform(
|
||||
contacts, (Contact contact) => ContactRecord(contactSource, contact),
|
||||
initialFire: true);
|
||||
|
|
|
@ -92,17 +92,17 @@ abstract class DashboardViewModelBase with Store {
|
|||
final _wallet = wallet;
|
||||
|
||||
if (_wallet is MoneroWallet) {
|
||||
subname = _wallet.account?.label;
|
||||
subname = _wallet.walletAddresses.account?.label;
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction((_) => _wallet.account,
|
||||
(Account account) => _onMoneroAccountChange(_wallet));
|
||||
_onMoneroAccountChangeReaction = reaction((_) => _wallet.walletAddresses
|
||||
.account, (Account account) => _onMoneroAccountChange(_wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction((_) => _wallet.balance,
|
||||
(MoneroBalance balance) => _onMoneroTransactionsUpdate(_wallet));
|
||||
|
||||
final _accountTransactions = _wallet
|
||||
.transactionHistory.transactions.values
|
||||
.where((tx) => tx.accountIndex == _wallet.account.id)
|
||||
.where((tx) => tx.accountIndex == _wallet.walletAddresses.account.id)
|
||||
.toList();
|
||||
|
||||
transactions = ObservableList.of(_accountTransactions.map((transaction) =>
|
||||
|
@ -131,7 +131,7 @@ abstract class DashboardViewModelBase with Store {
|
|||
filter: (TransactionInfo tx) {
|
||||
final wallet = _wallet;
|
||||
if (tx is MoneroTransactionInfo && wallet is MoneroWallet) {
|
||||
return tx.accountIndex == wallet.account.id;
|
||||
return tx.accountIndex == wallet.walletAddresses.account.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -153,7 +153,7 @@ abstract class DashboardViewModelBase with Store {
|
|||
String subname;
|
||||
|
||||
@computed
|
||||
String get address => wallet.address;
|
||||
String get address => wallet.walletAddresses.address;
|
||||
|
||||
@computed
|
||||
SyncStatus get status => wallet.syncStatus;
|
||||
|
@ -251,13 +251,13 @@ abstract class DashboardViewModelBase with Store {
|
|||
wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24;
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
subname = wallet.account?.label;
|
||||
subname = wallet.walletAddresses.account?.label;
|
||||
|
||||
_onMoneroAccountChangeReaction?.reaction?.dispose();
|
||||
_onMoneroBalanceChangeReaction?.reaction?.dispose();
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction((_) => wallet.account,
|
||||
(Account account) => _onMoneroAccountChange(wallet));
|
||||
_onMoneroAccountChangeReaction = reaction((_) => wallet.walletAddresses
|
||||
.account, (Account account) => _onMoneroAccountChange(wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction((_) => wallet.balance,
|
||||
(MoneroBalance balance) => _onMoneroTransactionsUpdate(wallet));
|
||||
|
@ -284,7 +284,7 @@ abstract class DashboardViewModelBase with Store {
|
|||
settingsStore: appStore.settingsStore),
|
||||
filter: (TransactionInfo tx) {
|
||||
if (tx is MoneroTransactionInfo && wallet is MoneroWallet) {
|
||||
return tx.accountIndex == wallet.account.id;
|
||||
return tx.accountIndex == wallet.walletAddresses.account.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -293,7 +293,7 @@ abstract class DashboardViewModelBase with Store {
|
|||
|
||||
@action
|
||||
void _onMoneroAccountChange(MoneroWallet wallet) {
|
||||
subname = wallet.account?.label;
|
||||
subname = wallet.walletAddresses.account?.label;
|
||||
_onMoneroTransactionsUpdate(wallet);
|
||||
}
|
||||
|
||||
|
@ -302,7 +302,7 @@ abstract class DashboardViewModelBase with Store {
|
|||
transactions.clear();
|
||||
|
||||
final _accountTransactions = wallet.transactionHistory.transactions.values
|
||||
.where((tx) => tx.accountIndex == wallet.account.id)
|
||||
.where((tx) => tx.accountIndex == wallet.walletAddresses.account.id)
|
||||
.toList();
|
||||
|
||||
transactions.addAll(_accountTransactions.map((transaction) =>
|
||||
|
|
|
@ -41,7 +41,8 @@ abstract class ExchangeViewModelBase with Store {
|
|||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
receiveAddress = '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
|
||||
depositAddress = depositCurrency == wallet.currency
|
||||
? wallet.walletAddresses.address : '';
|
||||
limitsState = LimitsInitialState();
|
||||
tradeState = ExchangeTradeStateInitial();
|
||||
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
|
||||
|
@ -308,8 +309,10 @@ abstract class ExchangeViewModelBase with Store {
|
|||
isReceiveAmountEntered = false;
|
||||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
|
||||
receiveAddress = receiveCurrency == wallet.currency ? wallet.address : '';
|
||||
depositAddress = depositCurrency == wallet.currency
|
||||
? wallet.walletAddresses.address : '';
|
||||
receiveAddress = receiveCurrency == wallet.currency
|
||||
? wallet.walletAddresses.address : '';
|
||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
||||
isFixedRateMode = false;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/monero/monero_account_list.dart';
|
||||
|
@ -10,11 +12,12 @@ class MoneroAccountEditOrCreateViewModel = MoneroAccountEditOrCreateViewModelBas
|
|||
|
||||
abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
||||
MoneroAccountEditOrCreateViewModelBase(this._moneroAccountList,
|
||||
{AccountListItem accountListItem})
|
||||
{@required WalletBase wallet, AccountListItem accountListItem})
|
||||
: state = InitialExecutionState(),
|
||||
isEdit = accountListItem != null,
|
||||
label = accountListItem?.label??'',
|
||||
_accountListItem = accountListItem;
|
||||
_accountListItem = accountListItem,
|
||||
_wallet = wallet;
|
||||
|
||||
final bool isEdit;
|
||||
|
||||
|
@ -26,6 +29,7 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
|||
|
||||
final MoneroAccountList _moneroAccountList;
|
||||
final AccountListItem _accountListItem;
|
||||
final WalletBase _wallet;
|
||||
|
||||
Future<void> save() async {
|
||||
try {
|
||||
|
@ -38,6 +42,7 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
|||
await _moneroAccountList.addAccount(label: label);
|
||||
}
|
||||
|
||||
await _wallet.save();
|
||||
state = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
|
|
|
@ -20,15 +20,16 @@ abstract class MoneroAccountListViewModelBase with Store {
|
|||
}
|
||||
|
||||
@computed
|
||||
List<AccountListItem> get accounts => _moneroWallet.accountList.accounts
|
||||
.map((acc) => AccountListItem(
|
||||
List<AccountListItem> get accounts => _moneroWallet.walletAddresses
|
||||
.accountList.accounts.map((acc) => AccountListItem(
|
||||
label: acc.label,
|
||||
id: acc.id,
|
||||
isSelected: acc.id == _moneroWallet.account.id))
|
||||
isSelected: acc.id == _moneroWallet.walletAddresses.account.id))
|
||||
.toList();
|
||||
|
||||
final MoneroWallet _moneroWallet;
|
||||
|
||||
void select(AccountListItem item) =>
|
||||
_moneroWallet.account = Account(id: item.id, label: item.label);
|
||||
_moneroWallet.walletAddresses.account =
|
||||
Account(id: item.id, label: item.label);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'unspent_coins_details_view_model.g.dart';
|
||||
|
||||
class UnspentCoinsDetailsViewModel = UnspentCoinsDetailsViewModelBase
|
||||
with _$UnspentCoinsDetailsViewModel;
|
||||
|
||||
abstract class UnspentCoinsDetailsViewModelBase with Store {
|
||||
UnspentCoinsDetailsViewModelBase({
|
||||
this.unspentCoinsItem, this.unspentCoinsListViewModel}) {
|
||||
|
||||
final amount = unspentCoinsItem.amount ?? '';
|
||||
final address = unspentCoinsItem.address ?? '';
|
||||
isFrozen = unspentCoinsItem.isFrozen ?? false;
|
||||
note = unspentCoinsItem.note ?? '';
|
||||
|
||||
items = [
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: amount
|
||||
),
|
||||
StandartListItem(
|
||||
title: S.current.widgets_address,
|
||||
value: address
|
||||
),
|
||||
TextFieldListItem(
|
||||
title: S.current.note_tap_to_change,
|
||||
value: note,
|
||||
onSubmitted: (value) {
|
||||
unspentCoinsItem.note = value;
|
||||
unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
|
||||
}),
|
||||
UnspentCoinsSwitchItem(
|
||||
title: S.current.freeze,
|
||||
value: '',
|
||||
switchValue: () => isFrozen,
|
||||
onSwitchValueChange: (value) async {
|
||||
isFrozen = value;
|
||||
unspentCoinsItem.isFrozen = value;
|
||||
if (value) {
|
||||
unspentCoinsItem.isSending = !value;
|
||||
}
|
||||
await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@observable
|
||||
bool isFrozen;
|
||||
|
||||
@observable
|
||||
String note;
|
||||
|
||||
final UnspentCoinsItem unspentCoinsItem;
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
List<TransactionDetailsListItem> items;
|
||||
}
|
33
lib/view_model/unspent_coins/unspent_coins_item.dart
Normal file
33
lib/view_model/unspent_coins/unspent_coins_item.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'unspent_coins_item.g.dart';
|
||||
|
||||
class UnspentCoinsItem = UnspentCoinsItemBase with _$UnspentCoinsItem;
|
||||
|
||||
abstract class UnspentCoinsItemBase with Store {
|
||||
UnspentCoinsItemBase({
|
||||
this.address,
|
||||
this.amount,
|
||||
this.hash,
|
||||
this.isFrozen,
|
||||
this.note,
|
||||
this.isSending});
|
||||
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
String amount;
|
||||
|
||||
@observable
|
||||
String hash;
|
||||
|
||||
@observable
|
||||
bool isFrozen;
|
||||
|
||||
@observable
|
||||
String note;
|
||||
|
||||
@observable
|
||||
bool isSending;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'unspent_coins_list_view_model.g.dart';
|
||||
|
||||
class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel;
|
||||
|
||||
abstract class UnspentCoinsListViewModelBase with Store {
|
||||
UnspentCoinsListViewModelBase({
|
||||
@required WalletBase wallet,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo}) {
|
||||
_unspentCoinsInfo = unspentCoinsInfo;
|
||||
_wallet = wallet as ElectrumWallet;
|
||||
_wallet.updateUnspent();
|
||||
}
|
||||
|
||||
ElectrumWallet _wallet;
|
||||
Box<UnspentCoinsInfo> _unspentCoinsInfo;
|
||||
|
||||
@computed
|
||||
ObservableList<UnspentCoinsItem> get items =>
|
||||
ObservableList.of(_wallet.unspentCoins.map((elem) {
|
||||
final amount = bitcoinAmountToString(amount: elem.value) +
|
||||
' ${_wallet.currency.title}';
|
||||
|
||||
return UnspentCoinsItem(
|
||||
address: elem.address.address,
|
||||
amount: amount,
|
||||
hash: elem.hash,
|
||||
isFrozen: elem.isFrozen,
|
||||
note: elem.note,
|
||||
isSending: elem.isSending
|
||||
);
|
||||
}));
|
||||
|
||||
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
|
||||
try {
|
||||
final info = _unspentCoinsInfo.values
|
||||
.firstWhere((element) => element.walletId.contains(_wallet.id) &&
|
||||
element.hash.contains(item.hash));
|
||||
|
||||
info.isFrozen = item.isFrozen;
|
||||
info.isSending = item.isSending;
|
||||
info.note = item.note;
|
||||
|
||||
await info.save();
|
||||
await _wallet.updateUnspent();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
12
lib/view_model/unspent_coins/unspent_coins_switch_item.dart
Normal file
12
lib/view_model/unspent_coins/unspent_coins_switch_item.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
|
||||
class UnspentCoinsSwitchItem extends TransactionDetailsListItem {
|
||||
UnspentCoinsSwitchItem({
|
||||
String title,
|
||||
String value,
|
||||
this.switchValue,
|
||||
this.onSwitchValueChange}) : super(title: title, value: value);
|
||||
|
||||
final bool Function() switchValue;
|
||||
final void Function(bool value) onSwitchValueChange;
|
||||
}
|
|
@ -64,12 +64,15 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
final wallet = _wallet;
|
||||
|
||||
if (wallet is ElectrumWallet) {
|
||||
await wallet.generateNewAddress();
|
||||
await wallet.walletAddresses.generateNewAddress();
|
||||
await wallet.save();
|
||||
}
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList
|
||||
.addSubaddress(accountIndex: wallet.account.id, label: label);
|
||||
await wallet.walletAddresses.subaddressList
|
||||
.addSubaddress(
|
||||
accountIndex: wallet.walletAddresses.account.id,
|
||||
label: label);
|
||||
await wallet.save();
|
||||
}
|
||||
}
|
||||
|
@ -77,13 +80,14 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
Future<void> _update() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
await wallet.updateAddress(_item.address as String);
|
||||
}
|
||||
/*if (wallet is BitcoinWallet) {
|
||||
await wallet.walletAddresses.updateAddress(_item.address as String);
|
||||
await wallet.save();
|
||||
}*/
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList.setLabelSubaddress(
|
||||
accountIndex: wallet.account.id,
|
||||
await wallet.walletAddresses.subaddressList.setLabelSubaddress(
|
||||
accountIndex: wallet.walletAddresses.account.id,
|
||||
addressIndex: _item.id as int,
|
||||
label: label);
|
||||
await wallet.save();
|
||||
|
|
|
@ -80,7 +80,7 @@ abstract class WalletAddressListViewModelBase with Store {
|
|||
|
||||
@computed
|
||||
WalletAddressListItem get address =>
|
||||
WalletAddressListItem(address: _wallet.address);
|
||||
WalletAddressListItem(address: _wallet.walletAddresses.address);
|
||||
|
||||
@computed
|
||||
PaymentURI get uri {
|
||||
|
@ -105,8 +105,10 @@ abstract class WalletAddressListViewModelBase with Store {
|
|||
final addressList = ObservableList<ListItem>();
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
final primaryAddress = wallet.subaddressList.subaddresses.first;
|
||||
addressList.addAll(wallet.subaddressList.subaddresses.map((subaddress) {
|
||||
final primaryAddress =
|
||||
wallet.walletAddresses.subaddressList.subaddresses.first;
|
||||
addressList.addAll(wallet.walletAddresses.subaddressList.subaddresses
|
||||
.map((subaddress) {
|
||||
final isPrimary = subaddress == primaryAddress;
|
||||
|
||||
return WalletAddressListItem(
|
||||
|
@ -118,8 +120,8 @@ abstract class WalletAddressListViewModelBase with Store {
|
|||
}
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
final primaryAddress = wallet.addresses.first;
|
||||
final bitcoinAddresses = wallet.addresses.map((addr) {
|
||||
final primaryAddress = wallet.walletAddresses.addresses.first;
|
||||
final bitcoinAddresses = wallet.walletAddresses.addresses.map((addr) {
|
||||
final isPrimary = addr == primaryAddress;
|
||||
|
||||
return WalletAddressListItem(
|
||||
|
@ -139,7 +141,7 @@ abstract class WalletAddressListViewModelBase with Store {
|
|||
final wallet = _wallet;
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
return wallet.account.label;
|
||||
return wallet.walletAddresses.account.label;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -160,7 +162,7 @@ abstract class WalletAddressListViewModelBase with Store {
|
|||
|
||||
@action
|
||||
void setAddress(WalletAddressListItem address) =>
|
||||
_wallet.address = address.address;
|
||||
_wallet.walletAddresses.address = address.address;
|
||||
|
||||
void _init() {
|
||||
_baseItems = [];
|
||||
|
@ -177,7 +179,8 @@ abstract class WalletAddressListViewModelBase with Store {
|
|||
final wallet = _wallet;
|
||||
|
||||
if (wallet is ElectrumWallet) {
|
||||
wallet.nextAddress();
|
||||
wallet.walletAddresses.nextAddress();
|
||||
wallet.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ abstract class WalletCreationVMBase with Store {
|
|||
dirPath: dirPath);
|
||||
credentials.walletInfo = walletInfo;
|
||||
final wallet = await process(credentials);
|
||||
walletInfo.address = wallet.address;
|
||||
walletInfo.address = wallet.walletAddresses.address;
|
||||
await _walletInfoSource.add(walletInfo);
|
||||
_appStore.changeCurrentWallet(wallet);
|
||||
_appStore.authenticationStore.allowed();
|
||||
|
|
|
@ -481,9 +481,17 @@
|
|||
"buy_bitcoin" : "Bitcoin kaufen",
|
||||
"buy_with" : "Kaufen mit",
|
||||
"moonpay_alert_text" : "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein",
|
||||
|
||||
|
||||
"outdated_electrum_wallet_receive_warning": "Wenn diese Brieftasche einen 12-Wort-Seed hat und in Cake erstellt wurde, zahlen Sie KEINE Bitcoins in diese Brieftasche ein. Alle auf diese Wallet übertragenen BTC können verloren gehen. Erstellen Sie eine neue 24-Wort-Wallet (tippen Sie auf das Menü oben rechts, wählen Sie Wallets, wählen Sie Neue Wallet erstellen und dann Bitcoin) und verschieben Sie Ihre BTC SOFORT dorthin. Neue (24-Wort-)BTC-Wallets von Cake sind sicher",
|
||||
"do_not_show_me": "Zeig mir das nicht noch einmal",
|
||||
|
||||
"unspent_coins_title" : "Nicht ausgegebene Münzen",
|
||||
"unspent_coins_details_title" : "Details zu nicht ausgegebenen Münzen",
|
||||
"freeze" : "Einfrieren",
|
||||
"coin_control" : "Münzkontrolle (optional)",
|
||||
|
||||
"address_detected" : "Adresse erkannt",
|
||||
"address_from_domain" : "Sie haben die Adresse von der unaufhaltsamen Domain ${domain} erhalten",
|
||||
|
||||
"add_receiver" : "Empfänger hinzufügen"
|
||||
}
|
||||
|
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "If this wallet has a 12-word seed and was created in Cake, DO NOT deposit Bitcoin into this wallet. Any BTC transferred to this wallet may be lost. Create a new 24-word wallet (tap the menu at the top right, select Wallets, choose Create New Wallet, then select Bitcoin) and IMMEDIATELY move your BTC there. New (24-word) BTC wallets from Cake are secure",
|
||||
"do_not_show_me": "Do not show me this again",
|
||||
|
||||
"unspent_coins_title" : "Unspent coins",
|
||||
"unspent_coins_details_title" : "Unspent coins details",
|
||||
"freeze" : "Freeze",
|
||||
"coin_control" : "Coin control (optional)",
|
||||
|
||||
"address_detected" : "Address detected",
|
||||
"address_from_domain" : "You got address from unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Add receiver"
|
||||
}
|
|
@ -472,7 +472,7 @@
|
|||
"submit_request" : "presentar una solicitud",
|
||||
|
||||
"buy_alert_content" : "Actualmente solo apoyamos la compra de Bitcoin y Litecoin. Para comprar Bitcoin o Litecoin, cree o cambie a su billetera Bitcoin o Litecoin.",
|
||||
|
||||
|
||||
"outdated_electrum_wallet_description" : "Las nuevas carteras de Bitcoin creadas en Cake ahora tienen una semilla de 24 palabras. Es obligatorio que cree una nueva billetera de Bitcoin y transfiera todos sus fondos a la nueva billetera de 24 palabras, y deje de usar billeteras con una semilla de 12 palabras. Haga esto de inmediato para asegurar sus fondos.",
|
||||
"understand" : "Entiendo",
|
||||
|
||||
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Si esta billetera tiene una semilla de 12 palabras y se creó en Cake, NO deposite Bitcoin en esta billetera. Cualquier BTC transferido a esta billetera se puede perder. Cree una nueva billetera de 24 palabras (toque el menú en la parte superior derecha, seleccione Monederos, elija Crear nueva billetera, luego seleccione Bitcoin) e INMEDIATAMENTE mueva su BTC allí. Las nuevas carteras BTC (24 palabras) de Cake son seguras",
|
||||
"do_not_show_me": "no me muestres esto otra vez",
|
||||
|
||||
"unspent_coins_title" : "Monedas no gastadas",
|
||||
"unspent_coins_details_title" : "Detalles de monedas no gastadas",
|
||||
"freeze" : "Congelar",
|
||||
"coin_control" : "Control de monedas (opcional)",
|
||||
|
||||
"address_detected" : "Dirección detectada",
|
||||
"address_from_domain" : "Tienes la dirección de unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Agregar receptor"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "अगर इस वॉलेट में 12 शब्दों का बीज है और इसे केक में बनाया गया है, तो इस वॉलेट में बिटकॉइन जमा न करें। इस वॉलेट में स्थानांतरित किया गया कोई भी बीटीसी खो सकता है। एक नया 24-शब्द वॉलेट बनाएं (ऊपर दाईं ओर स्थित मेनू पर टैप करें, वॉलेट चुनें, नया वॉलेट बनाएं चुनें, फिर बिटकॉइन चुनें) और तुरंत अपना बीटीसी वहां ले जाएं। केक से नए (24-शब्द) बीटीसी वॉलेट सुरक्षित हैं",
|
||||
"do_not_show_me": "मुझे यह फिर न दिखाएं",
|
||||
|
||||
"unspent_coins_title" : "खर्च न किए गए सिक्के",
|
||||
"unspent_coins_details_title" : "अव्ययित सिक्कों का विवरण",
|
||||
"freeze" : "फ्रीज",
|
||||
"coin_control" : "सिक्का नियंत्रण (वैकल्पिक)",
|
||||
|
||||
"address_detected" : "पता लग गया",
|
||||
"address_from_domain" : "आपको अजेय डोमेन ${domain} से पता मिला है",
|
||||
|
||||
"add_receiver" : "रिसीवर जोड़ें"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Ako ovaj novčanik sadrži sjeme od 12 riječi i stvoren je u Torti, NEMOJTE polagati Bitcoin u ovaj novčanik. Bilo koji BTC prebačen u ovaj novčanik može se izgubiti. Stvorite novi novčanik od 24 riječi (taknite izbornik u gornjem desnom dijelu, odaberite Novčanici, odaberite Stvori novi novčanik, a zatim odaberite Bitcoin) i ODMAH premjestite svoj BTC tamo. Novi BTC novčanici (s 24 riječi) tvrtke Cake sigurni su",
|
||||
"do_not_show_me": "Ne pokazuj mi ovo više",
|
||||
|
||||
"unspent_coins_title" : "Nepotrošeni novčići",
|
||||
"unspent_coins_details_title" : "Nepotrošeni detalji o novčićima",
|
||||
"freeze" : "Zamrznuti",
|
||||
"coin_control" : "Kontrola novca (nije obavezno)",
|
||||
|
||||
"address_detected" : "Adresa je otkrivena",
|
||||
"address_from_domain" : "Dobili ste adresu od unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Dodajte prijamnik"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Se questo portafoglio ha un seme di 12 parole ed è stato creato in Cake, NON depositare Bitcoin in questo portafoglio. Qualsiasi BTC trasferito su questo portafoglio potrebbe andare perso. Crea un nuovo portafoglio di 24 parole (tocca il menu in alto a destra, seleziona Portafogli, scegli Crea nuovo portafoglio, quindi seleziona Bitcoin) e sposta IMMEDIATAMENTE lì il tuo BTC. I nuovi portafogli BTC (24 parole) di Cake sono sicuri",
|
||||
"do_not_show_me": "Non mostrarmelo di nuovo",
|
||||
|
||||
"unspent_coins_title" : "Monete non spese",
|
||||
"unspent_coins_details_title" : "Dettagli sulle monete non spese",
|
||||
"freeze" : "Congelare",
|
||||
"coin_control" : "Controllo monete (opzionale)",
|
||||
|
||||
"address_detected" : "Indirizzo rilevato",
|
||||
"address_from_domain" : "Hai l'indirizzo da unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Aggiungi ricevitore"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "このウォレットに 12 ワードのシードがあり、Cake で作成された場合、このウォレットにビットコインを入金しないでください。 このウォレットに転送された BTC は失われる可能性があります。 新しい 24 ワードのウォレットを作成し (右上のメニューをタップし、[ウォレット]、[新しいウォレットの作成]、[ビットコイン] の順に選択)、すぐに BTC をそこに移動します。 Cake の新しい (24 ワード) BTC ウォレットは安全です",
|
||||
"do_not_show_me": "また僕にこれを見せないでください",
|
||||
|
||||
"unspent_coins_title" : "未使用のコイン",
|
||||
"unspent_coins_details_title" : "未使用のコインの詳細",
|
||||
"freeze" : "氷結",
|
||||
"coin_control" : "コインコントロール(オプション)",
|
||||
|
||||
"address_detected" : "アドレスが検出されました",
|
||||
"address_from_domain" : "あなたはからアドレスを得ました unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "レシーバーを追加"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "이 지갑에 12 단어 시드가 있고 Cake에서 생성 된 경우이 지갑에 비트 코인을 입금하지 마십시오. 이 지갑으로 전송 된 모든 BTC는 손실 될 수 있습니다. 새로운 24 단어 지갑을 생성하고 (오른쪽 상단의 메뉴를 탭하고 지갑을 선택한 다음 새 지갑 생성을 선택한 다음 비트 코인을 선택하십시오) 즉시 BTC를 그곳으로 이동하십시오. Cake의 새로운 (24 단어) BTC 지갑은 안전합니다",
|
||||
"do_not_show_me": "나를 다시 표시하지 않음",
|
||||
|
||||
"unspent_coins_title" : "사용하지 않은 동전",
|
||||
"unspent_coins_details_title" : "사용하지 않은 동전 세부 정보",
|
||||
"freeze" : "얼다",
|
||||
"coin_control" : "코인 제어 (옵션)",
|
||||
|
||||
"address_detected" : "주소 감지",
|
||||
"address_from_domain" : "주소는 unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "수신기 추가"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Als deze portemonnee een seed van 12 woorden heeft en is gemaakt in Cake, stort dan GEEN Bitcoin in deze portemonnee. Elke BTC die naar deze portemonnee is overgebracht, kan verloren gaan. Maak een nieuwe portemonnee van 24 woorden (tik op het menu rechtsboven, selecteer Portefeuilles, kies Nieuwe portemonnee maken en selecteer vervolgens Bitcoin) en verplaats je BTC ONMIDDELLIJK daar. Nieuwe (24-woorden) BTC-portefeuilles van Cake zijn veilig",
|
||||
"do_not_show_me": "laat me dit niet opnieuw zien",
|
||||
|
||||
"unspent_coins_title" : "Ongebruikte munten",
|
||||
"unspent_coins_details_title" : "Details van niet-uitgegeven munten",
|
||||
"freeze" : "Bevriezen",
|
||||
"coin_control" : "Muntcontrole (optioneel)",
|
||||
|
||||
"address_detected" : "Adres gedetecteerd",
|
||||
"address_from_domain" : "Je adres is van unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Ontvanger toevoegen"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Jeśli ten portfel ma 12-wyrazowy seed i został utworzony w Cake, NIE Wpłacaj Bitcoina do tego portfela. Wszelkie BTC przeniesione do tego portfela mogą zostać utracone. Utwórz nowy portfel z 24 słowami (dotknij menu w prawym górnym rogu, wybierz Portfele, wybierz Utwórz nowy portfel, a następnie Bitcoin) i NATYCHMIAST przenieś tam swoje BTC. Nowe (24 słowa) portfele BTC firmy Cake są bezpieczne",
|
||||
"do_not_show_me": "Nie pokazuj mi tego ponownie",
|
||||
|
||||
"unspent_coins_title" : "Niewydane monety",
|
||||
"unspent_coins_details_title" : "Szczegóły niewydanych monet",
|
||||
"freeze" : "Zamrażać",
|
||||
"coin_control" : "Kontrola monet (opcjonalnie)",
|
||||
|
||||
"address_detected" : "Wykryto adres",
|
||||
"address_from_domain" : "Dostałeś adres od unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Dodaj odbiorcę"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Se esta carteira tiver uma semente de 12 palavras e foi criada no Cake, NÃO deposite Bitcoin nesta carteira. Qualquer BTC transferido para esta carteira pode ser perdido. Crie uma nova carteira de 24 palavras (toque no menu no canto superior direito, selecione Carteiras, escolha Criar Nova Carteira e selecione Bitcoin) e mova IMEDIATAMENTE seu BTC para lá. As novas carteiras BTC (24 palavras) da Cake são seguras",
|
||||
"do_not_show_me": "não me mostre isso novamente",
|
||||
|
||||
"unspent_coins_title" : "Moedas não gastas",
|
||||
"unspent_coins_details_title" : "Detalhes de moedas não gastas",
|
||||
"freeze" : "Congelar",
|
||||
"coin_control" : "Controle de moedas (opcional)",
|
||||
|
||||
"address_detected" : "Endereço detectado",
|
||||
"address_from_domain" : "Você obteve o endereço de unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Adicionar receptor"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Если этот кошелек имеет мнемоническую фразу из 12 слов и был создан в Cake, НЕ переводите биткойны на этот кошелек. Любые BTC, переведенные на этот кошелек, могут быть потеряны. Создайте новый кошелек с мнемоническои фразы из 24 слов (коснитесь меню в правом верхнем углу, выберите «Кошельки», выберите «Создать новый кошелек», затем выберите «Bitcoin») и НЕМЕДЛЕННО переведите туда свои BTC. Новые (24 слова) кошельки BTC от Cake безопасны",
|
||||
"do_not_show_me": "Не показывай мне это больше",
|
||||
|
||||
"unspent_coins_title" : "Неизрасходованные монеты",
|
||||
"unspent_coins_details_title" : "Сведения о неизрасходованных монетах",
|
||||
"freeze" : "Заморозить",
|
||||
"coin_control" : "Контроль монет (необязательно)",
|
||||
|
||||
"address_detected" : "Обнаружен адрес",
|
||||
"address_from_domain" : "Вы получили адрес из unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Добавить получателя"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Якщо цей гаманець має мнемонічну фразу з 12 слів і був створений у Cake, НЕ переводьте біткойни на цей гаманець. Будь-які BTC, переведений на цей гаманець, можуть бути втраченими. Створіть новий гаманець з мнемонічною фразою з 24 слів (торкніться меню у верхньому правому куті, виберіть Гаманці, виберіть Створити новий гаманець, потім виберіть Bitcoin) і НЕГАЙНО переведіть туди свії BTC. Нові (з мнемонічною фразою з 24 слів) гаманці BTC від Cake надійно захищені",
|
||||
"do_not_show_me": "Не показуй мені це знову",
|
||||
|
||||
"unspent_coins_title" : "Невитрачені монети",
|
||||
"unspent_coins_details_title" : "Відомості про невитрачені монети",
|
||||
"freeze" : "Заморозити",
|
||||
"coin_control" : "Контроль монет (необов’язково)",
|
||||
|
||||
"address_detected" : "Виявлено адресу",
|
||||
"address_from_domain" : "Ви отримали адресу від unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "Додати одержувача"
|
||||
}
|
|
@ -485,5 +485,13 @@
|
|||
"outdated_electrum_wallet_receive_warning": "如果这个钱包有一个 12 字的种子并且是在 Cake 中创建的,不要将比特币存入这个钱包。 任何转移到此钱包的 BTC 都可能丢失。 创建一个新的 24 字钱包(点击右上角的菜单,选择钱包,选择创建新钱包,然后选择比特币)并立即将您的 BTC 移到那里。 Cake 的新(24 字)BTC 钱包是安全的",
|
||||
"do_not_show_me": "不再提示",
|
||||
|
||||
"unspent_coins_title" : "未使用的硬幣",
|
||||
"unspent_coins_details_title" : "未使用代幣詳情",
|
||||
"freeze" : "凍結",
|
||||
"coin_control" : "硬幣控制(可選)",
|
||||
|
||||
"address_detected" : "檢測到地址",
|
||||
"address_from_domain" : "您有以下地址 unstoppable domain ${domain}",
|
||||
|
||||
"add_receiver" : "添加接收器"
|
||||
}
|
Loading…
Reference in a new issue