parent
719842964b
commit
81cee186db
@ -0,0 +1,6 @@
|
||||
[submodule "inject.dart"]
|
||||
path = inject.dart
|
||||
url = https://github.com/google/inject.dart
|
||||
[submodule ".vendor/inject.dart"]
|
||||
path = .vendor/inject.dart
|
||||
url = https://github.com/google/inject.dart
|
@ -0,0 +1,17 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class BitcoinAddressRecord {
|
||||
BitcoinAddressRecord(this.address, {this.label});
|
||||
|
||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinAddressRecord(decoded['address'] as String,
|
||||
label: decoded['label'] as String);
|
||||
}
|
||||
|
||||
final String address;
|
||||
String label;
|
||||
|
||||
String toJSON() => json.encode({'label': label, 'address': address});
|
||||
}
|
@ -1,14 +1,35 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cake_wallet/src/domain/common/balance.dart';
|
||||
|
||||
class BitcoinBalance extends Balance {
|
||||
BitcoinBalance({@required this.confirmed, @required this.unconfirmed});
|
||||
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) : super();
|
||||
|
||||
factory BitcoinBalance.fromJSON(String jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinBalance(
|
||||
confirmed: decoded['confirmed'] as int ?? 0,
|
||||
unconfirmed: decoded['unconfirmed'] as int ?? 0);
|
||||
}
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
|
||||
int get total => confirmed + unconfirmed;
|
||||
|
||||
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
|
||||
|
||||
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
|
||||
|
||||
String get totalFormatted => bitcoinAmountToString(amount: total);
|
||||
|
||||
String toJSON() =>
|
||||
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed});
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_description.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallets_manager.dart';
|
||||
|
||||
class BitcoinWalletManager extends WalletsManager {
|
||||
@override
|
||||
Future<Wallet> create(String name, String password, String language) async {
|
||||
final wallet = await BitcoinWallet.build(
|
||||
mnemonic: bip39.generateMnemonic(), password: password, name: name);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
||||
.existsSync();
|
||||
|
||||
@override
|
||||
Future<Wallet> openWallet(String name, String password) async {
|
||||
return BitcoinWallet.load(
|
||||
name: name, password: password);
|
||||
}
|
||||
|
||||
@override
|
||||
Future remove(WalletDescription wallet) async {
|
||||
final path = await pathForWalletDir(name: wallet.name, type: wallet.type);
|
||||
final f = File(path);
|
||||
|
||||
if (!f.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
f.deleteSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Wallet> restoreFromKeys(String name, String password, String language,
|
||||
int restoreHeight, String address, String viewKey, String spendKey) {
|
||||
// TODO: implement restoreFromKeys
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Wallet> restoreFromSeed(
|
||||
String name, String password, String seed, int restoreHeight) async {
|
||||
final wallet = await BitcoinWallet.build(
|
||||
name: name, password: password, mnemonic: seed);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
|
||||
class BitcoinNewWalletCredentials extends WalletCredentials {
|
||||
BitcoinNewWalletCredentials({String name}) : super(name: name);
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromSeedCredentials(
|
||||
{String name, String password, this.mnemonic})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromWIFCredentials(
|
||||
{String name, String password, this.wif})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String wif;
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
class BitcoinWalletService extends WalletService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
@override
|
||||
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
||||
final dirPath = await pathForWalletDir(
|
||||
type: WalletType.bitcoin, name: credentials.name);
|
||||
final wallet = BitcoinWalletBase.build(
|
||||
dirPath: dirPath,
|
||||
mnemonic: bip39.generateMnemonic(),
|
||||
password: credentials.password,
|
||||
name: credentials.name);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
||||
.existsSync();
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> openWallet(String name, String password) async {
|
||||
final walletDirPath =
|
||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
||||
final walletPath = '$walletDirPath/$name';
|
||||
final walletJSONRaw = await read(path: walletPath, password: password);
|
||||
final wallet = BitcoinWalletBase.fromJSON(
|
||||
password: password,
|
||||
name: name,
|
||||
dirPath: walletDirPath,
|
||||
jsonSource: walletJSONRaw);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) {
|
||||
// TODO: implement remove
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromKeys(
|
||||
BitcoinRestoreWalletFromWIFCredentials credentials) async {
|
||||
// TODO: implement restoreFromKeys
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromSeed(
|
||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||
final dirPath = await pathForWalletDir(
|
||||
type: WalletType.bitcoin, name: credentials.name);
|
||||
final wallet = BitcoinWalletBase.build(
|
||||
dirPath: dirPath,
|
||||
name: credentials.name,
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
class AddressLabelValidator extends TextValidator {
|
||||
AddressLabelValidator({WalletType type})
|
||||
: super(
|
||||
errorMessage: S.current.error_text_subaddress_name,
|
||||
pattern: '''^[^`,'"]{1,20}\$''',
|
||||
minLength: 1,
|
||||
maxLength: 20);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
class AmountValidator extends TextValidator {
|
||||
AmountValidator({WalletType type})
|
||||
: super(
|
||||
errorMessage: S.current.error_text_amount,
|
||||
pattern: _pattern(type),
|
||||
minLength: 0,
|
||||
maxLength: 0);
|
||||
|
||||
static String _pattern(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12})\$';
|
||||
case WalletType.bitcoin:
|
||||
// FIXME: Incorrect pattern for bitcoin
|
||||
return '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12})\$';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
|
||||
part 'app_service.g.dart';
|
||||
|
||||
class AppService = AppServiceBase with _$AppService;
|
||||
|
||||
abstract class AppServiceBase with Store {
|
||||
AppServiceBase({this.walletCreationService, this.authService, this.wallet});
|
||||
|
||||
WalletCreationService walletCreationService;
|
||||
AuthService authService;
|
||||
WalletBase wallet;
|
||||
}
|
@ -1,21 +1,41 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/setup_pin_code_state.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/encrypt.dart';
|
||||
|
||||
part 'auth_service.g.dart';
|
||||
class AuthService with Store {
|
||||
AuthService({this.secureStorage, this.sharedPreferences});
|
||||
|
||||
class AuthService = AuthServiceBase with _$AuthService;
|
||||
final FlutterSecureStorage secureStorage;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
abstract class AuthServiceBase with Store {
|
||||
@observable
|
||||
SetupPinCodeState setupPinCodeState;
|
||||
Future setPassword(String password) async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final encodedPassword = encodedPinCode(pin: password);
|
||||
await secureStorage.write(key: key, value: encodedPassword);
|
||||
}
|
||||
|
||||
Future<bool> canAuthenticate() async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final walletName = sharedPreferences.getString('current_wallet_name') ?? '';
|
||||
var password = '';
|
||||
|
||||
Future<void> setupPinCode({@required String pin}) async {}
|
||||
try {
|
||||
password = await secureStorage.read(key: key);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
Future<bool> authenticate({@required String pin}) async {
|
||||
return false;
|
||||
return walletName.isNotEmpty && password.isNotEmpty;
|
||||
}
|
||||
|
||||
void resetSetupPinCodeState() =>
|
||||
setupPinCodeState = InitialSetupPinCodeState();
|
||||
Future<bool> authenticate(String pin) async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final encodedPin = await secureStorage.read(key: key);
|
||||
final decodedPin = decodedPinCode(pin: encodedPin);
|
||||
|
||||
return decodedPin == pin;
|
||||
}
|
||||
}
|
||||
|
@ -1,121 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/core/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
|
||||
part 'bitcoin_transaction_history.g.dart';
|
||||
|
||||
// TODO: Think about another transaction store for bitcoin transaction history..
|
||||
|
||||
const _transactionsHistoryFileName = 'transactions.json';
|
||||
|
||||
class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
|
||||
with _$BitcoinTransactionHistory;
|
||||
|
||||
abstract class BitcoinTransactionHistoryBase
|
||||
extends TranasctionHistoryBase<BitcoinTransactionInfo> with Store {
|
||||
BitcoinTransactionHistoryBase(
|
||||
{this.eclient, String dirPath, @required String password})
|
||||
: path = '$dirPath/$_transactionsHistoryFileName',
|
||||
_password = password,
|
||||
_height = 0;
|
||||
|
||||
BitcoinWallet wallet;
|
||||
final ElectrumClient eclient;
|
||||
final String path;
|
||||
final String _password;
|
||||
int _height;
|
||||
|
||||
Future<void> init() async {
|
||||
// TODO: throw exeption if wallet is null;
|
||||
final info = await _read();
|
||||
_height = (info['height'] as int) ?? _height;
|
||||
transactions = info['transactions'] as List<BitcoinTransactionInfo>;
|
||||
}
|
||||
|
||||
@override
|
||||
Future update() async {
|
||||
await super.update();
|
||||
_updateHeight();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
||||
final addresses = wallet.getAddresses();
|
||||
final histories =
|
||||
addresses.map((address) => eclient.getHistory(address: address));
|
||||
final _historiesWithDetails = await Future.wait(histories)
|
||||
.then((histories) => histories
|
||||
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
|
||||
.expand((i) => i)
|
||||
.toList())
|
||||
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
||||
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
||||
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
||||
|
||||
return historiesWithDetails
|
||||
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
||||
info['raw'] as String, info['header'] as Map<String, Object>,
|
||||
addresses: addresses))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Map<String, Object>> fetchTransactionInfo(
|
||||
{@required String hash, @required int height}) async {
|
||||
final rawFetching = eclient.getTransactionRaw(hash: hash);
|
||||
final headerFetching = eclient.getHeader(height: height);
|
||||
final result = await Future.wait([rawFetching, headerFetching]);
|
||||
final raw = result.first as String;
|
||||
final header = result[1] as Map<String, Object>;
|
||||
|
||||
return {'raw': raw, 'header': header};
|
||||
}
|
||||
|
||||
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
|
||||
this.transactions.addAll(transactions);
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
||||
transactions.add(tx);
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> save() async => writeData(
|
||||
path: path,
|
||||
password: _password,
|
||||
data: json.encode({'height': _height, 'transactions': transactions}));
|
||||
|
||||
Future<Map<String, Object>> _read() async {
|
||||
try {
|
||||
final content = await read(path: path, password: _password);
|
||||
final jsoned = json.decode(content) as Map<String, Object>;
|
||||
final height = jsoned['height'] as int;
|
||||
final transactions = (jsoned['transactions'] as List<dynamic>)
|
||||
.map((dynamic row) {
|
||||
if (row is Map<String, Object>) {
|
||||
return BitcoinTransactionInfo.fromJson(row);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.where((el) => el != null)
|
||||
.toList();
|
||||
|
||||
return {'transactions': transactions, 'height': height};
|
||||
} catch (_) {
|
||||
return {'transactions': <TransactionInfo>[], 'height': 0};
|
||||
}
|
||||
}
|
||||
|
||||
void _updateHeight() {
|
||||
final newHeight = transactions.fold(
|
||||
0, (int acc, val) => val.height > acc ? val.height : acc);
|
||||
_height = newHeight > _height ? newHeight : _height;
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:cake_wallet/core/bitcoin_transaction_history.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||
import 'wallet_base.dart';
|
||||
|
||||
part 'bitcoin_wallet.g.dart';
|
||||
|
||||
/* TODO: Save balance to a wallet file.
|
||||
Load balance from the wallet file in `init` method.
|
||||
*/
|
||||
|
||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||
|
||||
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||
static Future<BitcoinWalletBase> load(
|
||||
{@required String name, @required String password}) async {
|
||||
final walletDirPath =
|
||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
||||
final walletPath = '$walletDirPath/$name';
|
||||
final walletJSONRaw = await read(path: walletPath, password: password);
|
||||
final jsoned = json.decode(walletJSONRaw) as Map<String, Object>;
|
||||
final mnemonic = jsoned['mnemonic'] as String;
|
||||
final accountIndex =
|
||||
(jsoned['account_index'] == "null" || jsoned['account_index'] == null)
|
||||
? 0
|
||||
: int.parse(jsoned['account_index'] as String);
|
||||
|
||||
return BitcoinWalletBase.build(
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
name: name,
|
||||
accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
factory BitcoinWalletBase.build(
|
||||
{@required String mnemonic,
|
||||
@required String password,
|
||||
@required String name,
|
||||
@required String dirPath,
|
||||
int accountIndex = 0}) {
|
||||
final walletPath = '$dirPath/$name';
|
||||
final eclient = ElectrumClient();
|
||||
final history = BitcoinTransactionHistory(
|
||||
eclient: eclient, dirPath: dirPath, password: password);
|
||||
|
||||
return BitcoinWallet._internal(
|
||||
eclient: eclient,
|
||||
path: walletPath,
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
accountIndex: accountIndex,
|
||||
transactionHistory: history);
|
||||
}
|
||||
|
||||
BitcoinWalletBase._internal(
|
||||
{@required this.eclient,
|
||||
@required this.path,
|
||||
@required String password,
|
||||
int accountIndex = 0,
|
||||
this.transactionHistory,
|
||||
this.mnemonic}) {
|
||||
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
||||
network: bitcoin.bitcoin);
|
||||
_password = password;
|
||||
_accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
final BitcoinTransactionHistory transactionHistory;
|
||||
final String path;
|
||||
bitcoin.HDWallet hd;
|
||||
final ElectrumClient eclient;
|
||||
final String mnemonic;
|
||||
int _accountIndex;
|
||||
String _password;
|
||||
|
||||
@override
|
||||
String get name => path.split('/').last ?? '';
|
||||
|
||||
@override
|
||||
String get filename => hd.address;
|
||||
|
||||
String get xpub => hd.base58;
|
||||
|
||||
List<String> getAddresses() => _accountIndex == 0
|
||||
? [address]
|
||||
: List<String>.generate(
|
||||
_accountIndex, (i) => _getAddress(hd: hd, index: i));
|
||||
|
||||
Future<void> init() async {
|
||||
await transactionHistory.init();
|
||||
}
|
||||
|
||||
Future<String> newAddress() async {
|
||||
_accountIndex += 1;
|
||||
final address = _getAddress(hd: hd, index: _accountIndex);
|
||||
await save();
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startSync() async {}
|
||||
|
||||
@override
|
||||
Future<void> connectToNode({@required Node node}) async {}
|
||||
|
||||
@override
|
||||
Future<void> createTransaction(Object credentials) async {}
|
||||
|
||||
@override
|
||||
Future<void> save() async => await write(
|
||||
path: path,
|
||||
password: _password,
|
||||
obj: {'mnemonic': mnemonic, 'account_index': _accountIndex.toString()});
|
||||
|
||||
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
|
||||
.P2PKH(
|
||||
data: PaymentData(
|
||||
pubkey: Uint8List.fromList(hd.derive(index).pubKey.codeUnits)))
|
||||
.data
|
||||
.address;
|
||||
|
||||
Future<Map<String, int>> _fetchBalances() async {
|
||||
final balances = await Future.wait(
|
||||
getAddresses().map((address) => eclient.getBalance(address: address)));
|
||||
final balance = balances.fold(<String, int>{}, (Map<String, int> acc, val) {
|
||||
acc['confirmed'] =
|
||||
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
|
||||
acc['unconfirmed'] =
|
||||
(val['unconfirmed'] as int ?? 0) + (acc['unconfirmed'] ?? 0);
|
||||
|
||||
return acc;
|
||||
});
|
||||
|
||||
return balance;
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/core/wallet_list_service.dart';
|
||||
import 'package:cake_wallet/core/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_description.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallets_manager.dart';
|
||||
/*
|
||||
*
|
||||
* BitcoinRestoreWalletFromSeedCredentials
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinNewWalletCredentials extends WalletCredentials {}
|
||||
|
||||
/*
|
||||
*
|
||||
* BitcoinRestoreWalletFromSeedCredentials
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
const BitcoinRestoreWalletFromSeedCredentials(
|
||||
{String name, String password, this.mnemonic})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* BitcoinRestoreWalletFromWIFCredentials
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
||||
const BitcoinRestoreWalletFromWIFCredentials(
|
||||
{String name, String password, this.wif})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String wif;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* BitcoinWalletListService
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinWalletListService extends WalletListService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
@override
|
||||
Future<void> create(BitcoinNewWalletCredentials credentials) async {
|
||||
final wallet = await BitcoinWalletBase.build(
|
||||
mnemonic: bip39.generateMnemonic(),
|
||||
password: credentials.password,
|
||||
name: credentials.name);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
||||
.existsSync();
|
||||
|
||||
@override
|
||||
Future<void> openWallet(String name, String password) async {
|
||||
// TODO: implement openWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<void> remove(String wallet) {
|
||||
// TODO: implement remove
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> restoreFromKeys(
|
||||
BitcoinRestoreWalletFromWIFCredentials credentials) async {
|
||||
// TODO: implement restoreFromKeys
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> restoreFromSeed(
|
||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||
final wallet = await BitcoinWalletBase.build(
|
||||
name: credentials.name,
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:cake_wallet/bitcoin/key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
String generateWalletPassword(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return generateKey();
|
||||
default:
|
||||
return Uuid().v4();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
const bitcoinMnemonicLength = 12;
|
||||
const moneroMnemonicLength = 25;
|
||||
|
||||
int mnemonicLength(WalletType type) {
|
||||
// TODO: need to have only one place for get(set) mnemonic string lenth;
|
||||
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return moneroMnemonicLength;
|
||||
case WalletType.bitcoin:
|
||||
return bitcoinMnemonicLength;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import 'package:bip39/src/wordlists/english.dart' as bitcoin_english;
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/chinese_simplified.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/dutch.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/english.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/german.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/japanese.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/portuguese.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/russian.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/spanish.dart';
|
||||
|
||||
class SeedValidator extends Validator<MnemonicItem> {
|
||||
SeedValidator({this.type, this.language})
|
||||
: _words = getWordList(type: type, language: language);
|
||||
|
||||
final WalletType type;
|
||||
final String language;
|
||||
final List<String> _words;
|
||||
|
||||
static List<String> getWordList({WalletType type, String language}) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return getBitcoinWordList(language);
|
||||
case WalletType.monero:
|
||||
return getMoneroWordList(language);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static List<String> getMoneroWordList(String language) {
|
||||
// FIXME: Unnamed constants; Need to be sure that string are in same case;
|
||||
|
||||
switch (language) {
|
||||
case 'English':
|
||||
return EnglishMnemonics.words;
|
||||
break;
|
||||
case 'Chinese (simplified)':
|
||||
return ChineseSimplifiedMnemonics.words;
|
||||
break;
|
||||
case 'Dutch':
|
||||
return DutchMnemonics.words;
|
||||
break;
|
||||
case 'German':
|
||||
return GermanMnemonics.words;
|
||||
break;
|
||||
case 'Japanese':
|
||||
return JapaneseMnemonics.words;
|
||||
break;
|
||||
case 'Portuguese':
|
||||
return PortugueseMnemonics.words;
|
||||
break;
|
||||
case 'Russian':
|
||||
return RussianMnemonics.words;
|
||||
break;
|
||||
case 'Spanish':
|
||||
return SpanishMnemonics.words;
|
||||
break;
|
||||
default:
|
||||
return EnglishMnemonics.words;
|
||||
}
|
||||
}
|
||||
|
||||
static List<String> getBitcoinWordList(String language) {
|
||||
assert(language.toLowerCase() == 'english');
|
||||
return bitcoin_english.WORDLIST;
|
||||
}
|
||||
|
||||
@override
|
||||
bool isValid(MnemonicItem value) => _words.contains(value.text);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class SetupPinCodeState {}
|
||||
|
||||
class InitialSetupPinCodeState extends SetupPinCodeState {}
|
||||
|
||||
class SetupPinCodeInProgress extends SetupPinCodeState {}
|
||||
|
||||
class SetupPinCodeFinishedSuccessfully extends SetupPinCodeState {}
|
||||
|
||||
class SetupPinCodeFinishedFailure extends SetupPinCodeState {
|
||||
SetupPinCodeFinishedFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class Validator<T> {
|
||||
Validator({@required this.errorMessage});
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
bool isValid(T value);
|
||||
|
||||
String call(T value) => !isValid(value) ? errorMessage : null;
|
||||
}
|
||||
|
||||
class TextValidator extends Validator<String> {
|
||||
TextValidator(
|
||||
{this.minLength, this.maxLength, this.pattern, String errorMessage})
|
||||
: super(errorMessage: errorMessage);
|
||||
|
||||
final int minLength;
|
||||
final int maxLength;
|
||||
String pattern;
|
||||
|
||||
@override
|
||||
bool isValid(String value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return value.length > minLength &&
|
||||
(maxLength > 0 ? (value.length <= maxLength) : true) &&
|
||||
(pattern != null ? match(value) : true);
|
||||
}
|
||||
|
||||
bool match(String value) => RegExp(pattern).hasMatch(value);
|
||||
}
|
||||
|
||||
class WalletNameValidator extends TextValidator {
|
||||
WalletNameValidator()
|
||||
: super(minLength: 1, maxLength: 15, pattern: '^[a-zA-Z0-9_]\$');
|
||||
}
|
@ -1,20 +1,24 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
abstract class WalletBase<BalaceType> {
|
||||
String get name;
|
||||
WalletType type;
|
||||
|
||||
String get filename;
|
||||
String get name;
|
||||
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
BalaceType balance;
|
||||
|
||||
TransactionHistoryBase transactionHistory;
|
||||
|
||||
Future<void> connectToNode({@required Node node});
|
||||
|
||||
Future<void> startSync();
|
||||
|
||||
Future<void> createTransaction(Object credentials);
|
||||
|
||||
Future<void> save();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
abstract class WalletCredentials {
|
||||
const WalletCredentials({this.name, this.password});
|
||||
WalletCredentials({this.name, this.password});
|
||||
|
||||
final String name;
|
||||
final String password;
|
||||
String password;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
|
||||
abstract class WalletListService<N extends WalletCredentials,
|
||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
||||
Future<void> create(N credentials);
|
||||
|
||||
Future<void> restoreFromSeed(RFS credentials);
|
||||
|
||||
Future<void> restoreFromKeys(RFK credentials);
|
||||
|
||||
Future<void> openWallet(String name, String password);
|
||||
|
||||
Future<bool> isWalletExit(String name);
|
||||
|
||||
Future<void> remove(String wallet);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
|
||||
abstract class WalletService<N extends WalletCredentials,
|
||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
||||
Future<WalletBase> create(N credentials);
|
||||
|
||||
Future<WalletBase> restoreFromSeed(RFS credentials);
|
||||
|
||||
Future<WalletBase> restoreFromKeys(RFK credentials);
|
||||
|
||||
Future<WalletBase> openWallet(String name, String password);
|
||||
|
||||
Future<bool> isWalletExit(String name);
|
||||
|
||||
Future<void> remove(String wallet);
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
|
||||
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_view_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
ReactionDisposer _onCurrentWalletChangeReaction;
|
||||
|
||||
void setup() {
|
||||
getIt.registerSingleton(AuthenticationStore());
|
||||
getIt.registerSingleton<AppStore>(
|
||||
AppStore(authenticationStore: getIt.get<AuthenticationStore>()));
|
||||
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
|
||||
getIt.registerSingletonAsync<SharedPreferences>(
|
||||
() => SharedPreferences.getInstance());
|
||||
getIt.registerFactoryParam<WalletCreationService, WalletType, void>(
|
||||
(type, _) => WalletCreationService(
|
||||
initialType: type,
|
||||
appStore: getIt.get<AppStore>(),
|
||||
secureStorage: getIt.get<FlutterSecureStorage>(),
|
||||
sharedPreferences: getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
|
||||
WalletNewVM(getIt.get<WalletCreationService>(param1: type), type: type));
|
||||
|
||||
getIt
|
||||
.registerFactoryParam<WalletRestorationFromSeedVM, List, void>((args, _) {
|
||||
final type = args.first as WalletType;
|
||||
final language = args[1] as String;
|
||||
final mnemonic = args[2] as String;
|
||||
|
||||
return WalletRestorationFromSeedVM(
|
||||
getIt.get<WalletCreationService>(param1: type),
|
||||
type: type,
|
||||
language: language,
|
||||
seed: mnemonic);
|
||||
});
|
||||
|
||||
getIt.registerFactory<AddressListViewModel>(
|
||||
() => AddressListViewModel(wallet: getIt.get<AppStore>().wallet));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => DashboardViewModel(appStore: getIt.get<AppStore>()));
|
||||
|
||||
getIt.registerFactory<AuthService>(() => AuthService(
|
||||
secureStorage: getIt.get<FlutterSecureStorage>(),
|
||||
sharedPreferences: getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactory<AuthViewModel>(() => AuthViewModel(
|
||||
authService: getIt.get<AuthService>(),
|
||||
sharedPreferences: getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactory<AuthPage>(() => AuthPage(
|
||||
authViewModel: getIt.get<AuthViewModel>(),
|
||||
onAuthenticationFinished: (isAuthenticated, __) {
|
||||
if (isAuthenticated) {
|
||||
getIt.get<AuthenticationStore>().allowed();
|
||||
}
|
||||
},
|
||||
closable: false));
|
||||
|
||||
getIt.registerFactory<DashboardPage>(() => DashboardPage(
|
||||
walletViewModel: getIt.get<DashboardViewModel>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<ReceivePage>(() =>
|
||||
ReceivePage(addressListViewModel: getIt.get<AddressListViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<AddressEditOrCreateViewModel, dynamic, void>(
|
||||
(dynamic item, _) => AddressEditOrCreateViewModel(
|
||||
wallet: getIt.get<AppStore>().wallet, item: item));
|
||||
|
||||
getIt.registerFactoryParam<AddressEditOrCreatePage, dynamic, void>(
|
||||
(dynamic item, _) => AddressEditOrCreatePage(
|
||||
addressEditOrCreateViewModel:
|
||||
getIt.get<AddressEditOrCreateViewModel>(param1: item)));
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
||||
_onCurrentWalletChangeReaction ??=
|
||||
reaction((_) => appStore.wallet, (WalletBase wallet) async {
|
||||
print('Wallet name ${wallet.name}');
|
||||
await getIt
|
||||
.get<SharedPreferences>()
|
||||
.setString('current_wallet_name', wallet.name);
|
||||
});
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_monero/subaddress_list.dart' as subaddress_list;
|
||||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||
|
||||
part 'monero_subaddress_list.g.dart';
|
||||
|
||||
class MoneroSubaddressList = MoneroSubaddressListBase
|
||||
with _$MoneroSubaddressList;
|
||||
|
||||
abstract class MoneroSubaddressListBase with Store {
|
||||
MoneroSubaddressListBase() {
|
||||
_isRefreshing = false;
|
||||
_isUpdating = false;
|
||||
subaddresses = ObservableList<Subaddress>();
|
||||
}
|
||||
|
||||
@observable
|
||||
ObservableList<Subaddress> subaddresses;
|
||||
|
||||
bool _isRefreshing;
|
||||
bool _isUpdating;
|
||||
|
||||
void update({int accountIndex}) {
|
||||
if (_isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_isUpdating = true;
|
||||
refresh(accountIndex: accountIndex);
|
||||
subaddresses.clear();
|
||||
subaddresses.addAll(getAll());
|
||||
_isUpdating = false;
|
||||
} catch (e) {
|
||||
_isUpdating = false;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
List<Subaddress> getAll() {
|
||||
return subaddress_list
|
||||
.getAllSubaddresses()
|
||||
.map((subaddressRow) => Subaddress.fromRow(subaddressRow))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future addSubaddress({int accountIndex, String label}) async {
|
||||
await subaddress_list.addSubaddress(
|
||||
accountIndex: accountIndex, label: label);
|
||||
update(accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
Future setLabelSubaddress(
|
||||
{int accountIndex, int addressIndex, String label}) async {
|
||||
await subaddress_list.setLabelForSubaddress(
|
||||
accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
||||
update(accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
void refresh({int accountIndex}) {
|
||||
if (_isRefreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_isRefreshing = true;
|
||||
subaddress_list.refreshSubaddresses(accountIndex: accountIndex);
|
||||
_isRefreshing = false;
|
||||
} on PlatformException catch (e) {
|
||||
_isRefreshing = false;
|
||||
print(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/encrypt.dart';
|
||||
|
||||
// FIXME: move me
|
||||
Future<String> getWalletPassword({String walletName}) async {
|
||||
final secureStorage = getIt.get<FlutterSecureStorage>();
|
||||
final key = generateStoreKeyFor(
|
||||
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
|
||||
final encodedPassword = await secureStorage.read(key: key);
|
||||
|
||||
return decodeWalletPassword(password: encodedPassword);
|
||||
}
|
||||
|
||||
// FIXME: move me
|
||||
Future<void> loadCurrentWallet() async {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final name = getIt.get<SharedPreferences>().getString('current_wallet_name');
|
||||
final type = WalletType.monero; // FIXME
|
||||
final password = await getWalletPassword(walletName: name);
|
||||
|
||||
WalletService _service;
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
_service = MoneroWalletService();
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
_service = BitcoinWalletService();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final wallet = await _service.openWallet(name, password);
|
||||
appStore.wallet = wallet;
|
||||
}
|
||||
|
||||
ReactionDisposer _initialAuthReaction;
|
||||
|
||||
Future<void> bootstrap() async {
|
||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
|
||||
if (authenticationStore.state == AuthenticationState.uninitialized) {
|
||||
authenticationStore.state =
|
||||
getIt.get<SharedPreferences>().getString('current_wallet_name') == null
|
||||
? AuthenticationState.denied
|
||||
: AuthenticationState.installed;
|
||||
}
|
||||
|
||||
_initialAuthReaction ??= autorun((_) async {
|
||||
final state = authenticationStore.state;
|
||||
|
||||
if (state == AuthenticationState.installed) {
|
||||
await loadCurrentWallet();
|
||||
}
|
||||
});
|
||||
}
|
@ -1 +1,3 @@
|
||||
abstract class Balance {}
|
||||
abstract class Balance {
|
||||
const Balance();
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
class MnemonicItem {
|
||||
MnemonicItem({String text}) : _text = text;
|
||||
|
||||
String get text => _text;
|
||||
String _text;
|
||||
|
||||
void changeText(String text) => _text = text;
|
||||
|
||||
@override
|
||||
String toString() => text;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
class MnemoticItem {
|
||||
MnemoticItem({String text, this.dic}) : _text = text;
|
||||
|
||||
String get text => _text;
|
||||
final List<String> dic;
|
||||
|
||||
String _text;
|
||||
|
||||
bool isCorrect() => dic.contains(text);
|
||||
|
||||
void changeText(String text) {
|
||||
_text = text;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => text;
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
|
||||
|
||||
class AddressCell extends StatelessWidget {
|
||||
factory AddressCell.fromItem(AddressListItem item,
|
||||
{@required bool isCurrent,
|
||||
Function(String) onTap,
|
||||
Function() onEdit}) =>
|
||||
AddressCell(
|
||||
address: item.address,
|
||||
name: item.name,
|
||||
isCurrent: isCurrent,
|
||||
onTap: onTap,
|
||||
onEdit: onEdit);
|
||||
|
||||
AddressCell(
|
||||
{@required this.address,
|
||||
@required this.name,
|
||||
@required this.isCurrent,
|
||||
this.onTap,
|
||||
this.onEdit});
|
||||
|
||||
final String address;
|
||||
final String name;
|
||||
final bool isCurrent;
|
||||
final Function(String) onTap;
|
||||
final Function() onEdit;
|
||||
|
||||
String get label => name ?? address;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const currentTextColor = Colors.blue; // FIXME: Why it's defined here ?
|
||||
final currentColor =
|
||||
Theme.of(context).accentTextTheme.subtitle.decorationColor;
|
||||
final notCurrentColor = Theme.of(context).backgroundColor;
|
||||
final notCurrentTextColor =
|
||||
Theme.of(context).primaryTextTheme.caption.color;
|
||||
final Widget cell = InkWell(
|
||||
onTap: () => onTap(address),
|
||||
child: Container(
|
||||
color: isCurrent ? currentColor : notCurrentColor,
|
||||
padding: EdgeInsets.only(left: 24, right: 24, top: 28, bottom: 28),
|
||||
child: Text(
|
||||
name ?? address,
|
||||
style: TextStyle(
|
||||
fontSize: name?.isNotEmpty ?? false ? 18 : 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isCurrent ? currentTextColor : notCurrentTextColor,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
return isCurrent
|
||||
? cell
|
||||
: Slidable(
|
||||
key: Key(address),
|
||||
actionPane: SlidableDrawerActionPane(),
|
||||
child: cell,
|
||||
secondaryActions: <Widget>[
|
||||
IconSlideAction(
|
||||
caption: S.of(context).edit,
|
||||
color: Theme.of(context).primaryTextTheme.overline.color,
|
||||
icon: Icons.edit,
|
||||
onTap: () => onEdit?.call())
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/core/AddressLabelValidator.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
|
||||
class AddressEditOrCreatePage extends BasePage {
|
||||
AddressEditOrCreatePage({@required this.addressEditOrCreateViewModel})
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_labelController = TextEditingController(),
|
||||
super() {
|
||||
_labelController.addListener(
|
||||
() => addressEditOrCreateViewModel.label = _labelController.text);
|
||||
_labelController.text = addressEditOrCreateViewModel.label;
|
||||
print(_labelController.text);
|
||||
print(addressEditOrCreateViewModel.label);
|
||||
}
|
||||
|
||||
final AddressEditOrCreateViewModel addressEditOrCreateViewModel;
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final TextEditingController _labelController;
|
||||
|
||||
@override
|
||||
String get title => S.current.new_subaddress_title;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => addressEditOrCreateViewModel.state,
|
||||
(AddressEditOrCreateState state) {
|
||||
if (state is AddressSavedSuccessfully) {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => Navigator.of(context).pop());
|
||||
}
|
||||
});
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: BaseTextFormField(
|
||||
controller: _labelController,
|
||||
hintText: S.of(context).new_subaddress_label_name,
|
||||
validator: AddressLabelValidator()))),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState.validate()) {
|
||||
await addressEditOrCreateViewModel.save();
|
||||
}
|
||||
},
|
||||
text: addressEditOrCreateViewModel.isEdit
|
||||
? S.of(context).rename
|
||||
: S.of(context).new_subaddress_create,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isLoading:
|
||||
addressEditOrCreateViewModel.state is AddressIsSaving,
|
||||
isDisabled:
|
||||
addressEditOrCreateViewModel.label?.isEmpty ?? true,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_state.dart';
|
||||
import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_store.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
|
||||
class NewSubaddressPage extends BasePage {
|
||||
NewSubaddressPage({this.subaddress});
|
||||
|
||||
final Subaddress subaddress;
|
||||
|
||||
@override
|
||||
String get title => S.current.new_subaddress_title;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => NewSubaddressForm(subaddress);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subaddressCreationStore =
|
||||
Provider.of<SubadrressCreationStore>(context);
|
||||
|
||||
reaction((_) => subaddressCreationStore.state, (SubaddressCreationState state) {
|
||||
if (state is SubaddressCreatedSuccessfully) {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => Navigator.of(context).pop());
|
||||
}
|
||||
});
|
||||
|
||||
return super.build(context);
|
||||
}
|
||||
}
|
||||
|
||||
class NewSubaddressForm extends StatefulWidget {
|
||||
NewSubaddressForm(this.subaddress);
|
||||
|
||||
final Subaddress subaddress;
|
||||
|
||||
@override
|
||||
NewSubaddressFormState createState() => NewSubaddressFormState(subaddress);
|
||||
}
|
||||
|
||||
class NewSubaddressFormState extends State<NewSubaddressForm> {
|
||||
NewSubaddressFormState(this.subaddress);
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _labelController = TextEditingController();
|
||||
final Subaddress subaddress;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (subaddress != null) _labelController.text = subaddress.label;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_labelController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subaddressCreationStore =
|
||||
Provider.of<SubadrressCreationStore>(context);
|
||||
|
||||
_labelController.addListener(() {
|
||||
if (_labelController.text.isNotEmpty) {
|
||||
subaddressCreationStore.setDisabledStatus(false);
|
||||
} else {
|
||||
subaddressCreationStore.setDisabledStatus(true);
|
||||
}
|
||||
});
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: BaseTextFormField(
|
||||
controller: _labelController,
|
||||
hintText: S.of(context).new_subaddress_label_name,
|
||||
validator: (value) {
|
||||
subaddressCreationStore.validateSubaddressName(value);
|
||||
return subaddressCreationStore.errorMessage;
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState.validate()) {
|
||||
if (subaddress != null) {
|
||||
await subaddressCreationStore.setLabel(
|
||||
addressIndex: subaddress.id,
|
||||
label: _labelController.text
|
||||
);
|
||||
} else {
|
||||
await subaddressCreationStore.add(
|
||||
label: _labelController.text);
|
||||
}
|
||||
}
|
||||
},
|
||||
text: subaddress != null
|
||||
? S.of(context).rename
|
||||
: S.of(context).new_subaddress_create,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isLoading:
|
||||
subaddressCreationStore.state is SubaddressIsCreating,
|
||||
isDisabled: subaddressCreationStore.isDisabledStatus,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -1,99 +1,99 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/src/domain/services/user_service.dart';
|
||||
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||
import 'package:cake_wallet/src/stores/auth/auth_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
part 'auth_store.g.dart';
|
||||
|
||||
class AuthStore = AuthStoreBase with _$AuthStore;
|
||||
|
||||
abstract class AuthStoreBase with Store {
|
||||
AuthStoreBase(
|
||||
{@required this.userService,
|
||||
@required this.walletService,
|
||||
@required this.sharedPreferences}) {
|
||||
state = AuthenticationStateInitial();
|
||||
_failureCounter = 0;
|
||||
}
|
||||
|
||||
static const maxFailedLogins = 3;
|
||||
static const banTimeout = 180; // 3 mins
|
||||
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||
|
||||
final UserService userService;
|
||||
final WalletService walletService;
|
||||
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
AuthState state;
|
||||
|
||||
@observable
|
||||
int _failureCounter;
|
||||
|
||||
@action
|
||||
Future auth({String password}) async {
|
||||
state = AuthenticationStateInitial();
|
||||
final _banDuration = banDuration();
|
||||
|
||||
if (_banDuration != null) {
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthenticationInProgress();
|
||||
final isAuth = await userService.authenticate(password);
|
||||
|
||||
if (isAuth) {
|
||||
state = AuthenticatedSuccessfully();
|
||||
_failureCounter = 0;
|
||||
} else {
|
||||
_failureCounter += 1;
|
||||
|
||||
if (_failureCounter >= maxFailedLogins) {
|
||||
final banDuration = await ban();
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
||||
}
|
||||
}
|
||||
|
||||
Duration banDuration() {
|
||||
final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
||||
|
||||
if (unbanTimestamp == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
||||
final now = DateTime.now();
|
||||
|
||||
if (now.isAfter(unbanTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
Future<Duration> ban() async {
|
||||
final multiplier = _failureCounter - maxFailedLogins + 1;
|
||||
final timeout = (multiplier * banTimeout) * 1000;
|
||||
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
||||
await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
||||
|
||||
return Duration(milliseconds: timeout);
|
||||
}
|
||||
|
||||
@action
|
||||
void biometricAuth() {
|
||||
state = AuthenticatedSuccessfully();
|
||||
}
|
||||
}
|
||||
//import 'dart:async';
|
||||
//import 'package:flutter/foundation.dart';
|
||||
//import 'package:shared_preferences/shared_preferences.dart';
|
||||
//import 'package:mobx/mobx.dart';
|
||||
//import 'package:cake_wallet/src/domain/services/user_service.dart';
|
||||
//import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||
//import 'package:cake_wallet/view_model/auth_state.dart';
|
||||
//import 'package:cake_wallet/generated/i18n.dart';
|
||||
//
|
||||
//part 'auth_store.g.dart';
|
||||
//
|
||||
//class AuthStore = AuthStoreBase with _$AuthStore;
|
||||
//
|
||||
//abstract class AuthStoreBase with Store {
|
||||
// AuthStoreBase(
|
||||
// {@required this.userService,
|
||||
// @required this.walletService,
|
||||
// @required this.sharedPreferences}) {
|
||||
// state = AuthenticationStateInitial();
|
||||
// _failureCounter = 0;
|
||||
// }
|
||||
//
|
||||
// static const maxFailedLogins = 3;
|
||||
// static const banTimeout = 180; // 3 mins
|
||||
// final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||
//
|
||||
// final UserService userService;
|
||||
// final WalletService walletService;
|
||||
//
|
||||
// final SharedPreferences sharedPreferences;
|
||||
//
|
||||
// @observable
|
||||
// AuthState state;
|
||||
//
|
||||
// @observable
|
||||
// int _failureCounter;
|
||||
//
|
||||
// @action
|
||||
// Future auth({String password}) async {
|
||||
// state = AuthenticationStateInitial();
|
||||
// final _banDuration = banDuration();
|
||||
//
|
||||
// if (_banDuration != null) {
|
||||
// state = AuthenticationBanned(
|
||||
// error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// state = AuthenticationInProgress();
|
||||
// final isAuth = await userService.authenticate(password);
|
||||
//
|
||||
// if (isAuth) {
|
||||
// state = AuthenticatedSuccessfully();
|
||||
// _failureCounter = 0;
|
||||
// } else {
|
||||
// _failureCounter += 1;
|
||||
//
|
||||
// if (_failureCounter >= maxFailedLogins) {
|
||||
// final banDuration = await ban();
|
||||
// state = AuthenticationBanned(
|
||||
// error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Duration banDuration() {
|
||||
// final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
||||
//
|
||||
// if (unbanTimestamp == null) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
||||
// final now = DateTime.now();
|
||||
//
|
||||
// if (now.isAfter(unbanTime)) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
||||
// }
|
||||
//
|
||||
// Future<Duration> ban() async {
|
||||
// final multiplier = _failureCounter - maxFailedLogins + 1;
|
||||
// final timeout = (multiplier * banTimeout) * 1000;
|
||||
// final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
||||
// await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
||||
//
|
||||
// return Duration(milliseconds: timeout);
|
||||
// }
|
||||
//
|
||||
// @action
|
||||
// void biometricAuth() {
|
||||
// state = AuthenticatedSuccessfully();
|
||||
// }
|
||||
//}
|
||||
|
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
|
||||
|
||||
class SeedLanguageSelector extends StatefulWidget {
|
||||
SeedLanguageSelector({Key key, this.initialSelected}) : super(key: key);
|
||||
|
||||
final String initialSelected;
|
||||
|
||||
@override
|
||||
SeedLanguageSelectorState createState() =>
|
||||
SeedLanguageSelectorState(selected: initialSelected);
|
||||
}
|
||||
|
||||
class SeedLanguageSelectorState extends State<SeedLanguageSelector> {
|
||||
SeedLanguageSelectorState({this.selected});
|
||||
|
||||
final seedLocales = [
|
||||
S.current.seed_language_english,
|
||||
S.current.seed_language_chinese,
|
||||
S.current.seed_language_dutch,
|
||||
S.current.seed_language_german,
|
||||
S.current.seed_language_japanese,
|
||||
S.current.seed_language_portuguese,
|
||||
S.current.seed_language_russian,
|
||||
S.current.seed_language_spanish
|
||||
];
|
||||
String selected;
|
||||
final _pickerKey = GlobalKey<SeedLanguagePickerState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectButton(
|
||||
image: null,
|
||||
text: seedLocales[seedLanguages.indexOf(selected)],
|
||||
color: Theme.of(context).accentTextTheme.title.backgroundColor,
|
||||
textColor: Theme.of(context).primaryTextTheme.title.color,
|
||||
onTap: () async {
|
||||
final selected = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
SeedLanguagePicker(key: _pickerKey, selected: this.selected));
|
||||
setState(() => this.selected = selected);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
|
||||
part 'app_store.g.dart';
|
||||
|
||||
class AppStore = AppStoreBase with _$AppStore;
|
||||
|
||||
abstract class AppStoreBase with Store {
|
||||
AppStoreBase({this.authenticationStore});
|
||||
|
||||
AuthenticationStore authenticationStore;
|
||||
|
||||
@observable
|
||||
WalletBase wallet;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'authentication_store.g.dart';
|
||||
|
||||
class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore;
|
||||
|
||||
enum AuthenticationState { uninitialized, installed, allowed, denied }
|
||||
|
||||
abstract class AuthenticationStoreBase with Store {
|
||||
AuthenticationStoreBase() : state = AuthenticationState.uninitialized;
|
||||
|
||||
@observable
|
||||
AuthenticationState state;
|
||||
|
||||
@action
|
||||
void installed() => state = AuthenticationState.installed;
|
||||
|
||||
@action
|
||||
void allowed() => state = AuthenticationState.allowed;
|
||||
|
||||
@action
|
||||
void denied() => state = AuthenticationState.denied;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'wallet_list_store.g.dart';
|
||||
|
||||
class WalletListStore = WalletListStoreBase with _$WalletListStore;
|
||||
|
||||
abstract class WalletListStoreBase with Store {
|
||||
@observable
|
||||
Object state;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
abstract class ListItem {
|
||||
const ListItem();
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
class ListSection<Item> {
|
||||
const ListSection({this.items});
|
||||
|
||||
final List<Item> items;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class AccountListHeader extends ListItem {}
|
@ -0,0 +1,93 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||
|
||||
part 'address_edit_or_create_view_model.g.dart';
|
||||
|
||||
class AddressEditOrCreateViewModel = AddressEditOrCreateViewModelBase
|
||||
with _$AddressEditOrCreateViewModel;
|
||||
|
||||
abstract class AddressEditOrCreateState {}
|
||||
|
||||
class AddressEditOrCreateStateInitial extends AddressEditOrCreateState {}
|
||||
|
||||
class AddressIsSaving extends AddressEditOrCreateState {}
|
||||
|
||||
class AddressSavedSuccessfully extends AddressEditOrCreateState {}
|
||||
|
||||
class AddressEditOrCreateStateFailure extends AddressEditOrCreateState {
|
||||
AddressEditOrCreateStateFailure({this.error});
|
||||
|
||||
String error;
|
||||
}
|
||||
|
||||
abstract class AddressEditOrCreateViewModelBase with Store {
|
||||
AddressEditOrCreateViewModelBase({@required WalletBase wallet, dynamic item})
|
||||
: isEdit = item != null,
|
||||
state = AddressEditOrCreateStateInitial(),
|
||||
label = item?.name as String,
|
||||
_item = item,
|
||||
_wallet = wallet;
|
||||
|
||||
dynamic _item;
|
||||
|
||||
@observable
|
||||
AddressEditOrCreateState state;
|
||||
|
||||
@observable
|
||||
String label;
|
||||
|
||||
bool isEdit;
|
||||
|
||||
final WalletBase _wallet;
|
||||
|
||||
Future<void> save() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
try {
|
||||
state = AddressIsSaving();
|
||||
|
||||
if (isEdit) {
|
||||
await _update();
|
||||
} else {
|
||||
await _createNew();
|
||||
}
|
||||
|
||||
state = AddressSavedSuccessfully();
|
||||
} catch (e) {
|
||||
state = AddressEditOrCreateStateFailure(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createNew() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
await wallet.generateNewAddress(label: label);
|
||||
}
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList
|
||||
.addSubaddress(accountIndex: wallet.account.id, label: label);
|
||||
await wallet.save();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _update() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
await wallet.updateAddress(_item.address as String, label: label);
|
||||
}
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList.setLabelSubaddress(
|
||||
accountIndex: wallet.account.id,
|
||||
addressIndex: _item.id as int,
|
||||
label: label);
|
||||
await wallet.save();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class AddressListHeader extends ListItem {}
|
@ -0,0 +1,14 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class AddressListItem extends ListItem {
|
||||
const AddressListItem({@required this.address, this.name, this.id})
|
||||
: super();
|
||||
|
||||
final int id;
|
||||
final String address;
|
||||
final String name;
|
||||
|
||||
@override
|
||||
String toString() => name ?? address;
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
|
||||
|
||||
part 'address_list_view_model.g.dart';
|
||||
|
||||
class AddressListViewModel = AddressListViewModelBase
|
||||
with _$AddressListViewModel;
|
||||
|
||||
abstract class PaymentURI {
|
||||
PaymentURI({this.amount, this.address});
|
||||
|
||||
final String amount;
|
||||
final String address;
|
||||
}
|
||||
|
||||
class MoneroURI extends PaymentURI {
|
||||
MoneroURI({String amount, String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'monero:' + address;
|
||||
|
||||
if (amount?.isNotEmpty ?? false) {
|
||||
base += '?tx_amount=$amount';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class BitcoinURI extends PaymentURI {
|
||||
BitcoinURI({String amount, String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'bitcoin:' + address;
|
||||
|
||||
if (amount?.isNotEmpty ?? false) {
|
||||
base += '?amount=$amount';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AddressListViewModelBase with Store {
|
||||
AddressListViewModelBase({@required WalletBase wallet}) {
|
||||
hasAccounts = _wallet is MoneroWallet;
|
||||
_wallet = wallet;
|
||||
_init();
|
||||
}
|
||||
|
||||
@observable
|
||||
String amount;
|
||||
|
||||
@computed
|
||||
AddressListItem get address => AddressListItem(address: _wallet.address);
|
||||
|
||||
@computed
|
||||
PaymentURI get uri {
|
||||
if (_wallet is MoneroWallet) {
|
||||
return MoneroURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (_wallet is BitcoinWallet) {
|
||||
return BitcoinURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@computed
|
||||
ObservableList<ListItem> get items =>
|
||||
ObservableList<ListItem>()..addAll(_baseItems)..addAll(addressList);
|
||||
|
||||
@computed
|
||||
ObservableList<ListItem> get addressList {
|
||||
final wallet = _wallet;
|
||||
final addressList = ObservableList<ListItem>();
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
addressList.addAll(wallet.subaddressList.subaddresses.map((subaddress) =>
|
||||
AddressListItem(
|
||||
id: subaddress.id,
|
||||
name: subaddress.label,
|
||||
address: subaddress.address)));
|
||||
}
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
final bitcoinAddresses = wallet.addresses.map(
|
||||
(addr) => AddressListItem(name: addr.label, address: addr.address));
|
||||
addressList.addAll(bitcoinAddresses);
|
||||
}
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
set address(AddressListItem address) => _wallet.address = address.address;
|
||||
|
||||
bool hasAccounts;
|
||||
|
||||
WalletBase _wallet;
|
||||
|
||||
List<ListItem> _baseItems;
|
||||
|
||||
@computed
|
||||
String get accountLabel {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
return wallet.account.label;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void _init() {
|
||||
_baseItems = [];
|
||||
|
||||
if (_wallet is MoneroWallet) {
|
||||
_baseItems.add(AccountListHeader());
|
||||
}
|
||||
|
||||
_baseItems.add(AddressListHeader());
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
abstract class AuthState {}
|
||||
|
||||
class AuthenticationStateInitial extends AuthState {}
|
||||
|
||||
class AuthenticationInProgress extends AuthState {}
|
||||
|
||||
class AuthenticatedSuccessfully extends AuthState {}
|
||||
|
||||
class AuthenticationFailure extends AuthState {
|
||||
AuthenticationFailure({this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
class AuthenticationBanned extends AuthState {
|
||||
AuthenticationBanned({this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/view_model/auth_state.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
part 'auth_view_model.g.dart';
|
||||
|
||||
class AuthViewModel = AuthViewModelBase with _$AuthViewModel;
|
||||
|
||||
abstract class AuthViewModelBase with Store {
|
||||
AuthViewModelBase(
|
||||
{@required this.authService, @required this.sharedPreferences}) {
|
||||
state = AuthenticationStateInitial();
|
||||
_failureCounter = 0;
|
||||
}
|
||||
|
||||
static const maxFailedLogins = 3;
|
||||
static const banTimeout = 180; // 3 mins
|
||||
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||
|
||||
final AuthService authService;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
AuthState state;
|
||||
|
||||
@observable
|
||||
int _failureCounter;
|
||||
|
||||
@action
|
||||
Future<void> auth({String password}) async {
|
||||
state = AuthenticationStateInitial();
|
||||
final _banDuration = banDuration();
|
||||
|
||||
if (_banDuration != null) {
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for +
|
||||
'${_banDuration.inMinutes}' +
|
||||
S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthenticationInProgress();
|
||||
final isAuth = await authService.authenticate(password);
|
||||
|
||||
if (isAuth) {
|
||||
state = AuthenticatedSuccessfully();
|
||||
_failureCounter = 0;
|
||||
} else {
|
||||
_failureCounter += 1;
|
||||
|
||||
if (_failureCounter >= maxFailedLogins) {
|
||||
final banDuration = await ban();
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for +
|
||||
'${banDuration.inMinutes}' +
|
||||
S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state =
|
||||
AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
||||
}
|
||||
}
|
||||
|
||||
Duration banDuration() {
|
||||
final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
||||
|
||||
if (unbanTimestamp == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
||||
final now = DateTime.now();
|
||||
|
||||
if (now.isAfter(unbanTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
Future<Duration> ban() async {
|
||||
final multiplier = _failureCounter - maxFailedLogins + 1;
|
||||
final timeout = (multiplier * banTimeout) * 1000;
|
||||
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
||||
await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
||||
|
||||
return Duration(milliseconds: timeout);
|
||||
}
|
||||
|
||||
@action
|
||||
void biometricAuth() => state = AuthenticatedSuccessfully();
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
|
||||
part 'dashboard_view_model.g.dart';
|
||||
|
||||
class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel;
|
||||
|
||||
class WalletBalace {
|
||||
WalletBalace({this.unlockedBalance, this.totalBalance});
|
||||
|
||||
final String unlockedBalance;
|
||||
final String totalBalance;
|
||||
}
|
||||
|
||||
abstract class DashboardViewModelBase with Store {
|
||||
DashboardViewModelBase({this.appStore}) {
|
||||
name = appStore.wallet?.name;
|
||||
balance = WalletBalace(unlockedBalance: '0.001', totalBalance: '0.005');
|
||||
status = SyncedSyncStatus();
|
||||
type = WalletType.bitcoin;
|
||||
wallet ??= appStore.wallet;
|
||||
_reaction = reaction((_) => appStore.wallet, _onWalletChange);
|
||||
transactions = ObservableList.of(wallet.transactionHistory.transactions
|
||||
.map((transaction) => TransactionListItem(transaction: transaction)));
|
||||
}
|
||||
|
||||
@observable
|
||||
WalletType type;
|
||||
|
||||
@observable
|
||||
String name;
|
||||
|
||||
@computed
|
||||
String get address => wallet.address;
|
||||
|
||||
@observable
|
||||
WalletBalace balance;
|
||||
|
||||
@observable
|
||||
SyncStatus status;
|
||||
|
||||
@observable
|
||||
ObservableList<Object> transactions;
|
||||
|
||||
@observable
|
||||
String subname;
|
||||
|
||||
WalletBase wallet;
|
||||
|
||||
AppStore appStore;
|
||||
|
||||
ReactionDisposer _reaction;
|
||||
|
||||
void _onWalletChange(WalletBase wallet) {
|
||||
name = wallet.name;
|
||||
transactions.clear();
|
||||
transactions.addAll(wallet.transactionHistory.transactions
|
||||
.map((transaction) => TransactionListItem(transaction: transaction)));
|
||||
balance = WalletBalace(unlockedBalance: '0.001', totalBalance: '0.005');
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class WalletCreationState {}
|
||||
|
||||
class InitialWalletCreationState extends WalletCreationState {}
|
||||
|
||||
class WalletCreating extends WalletCreationState {}
|
||||
|
||||
class WalletCreatedSuccessfully extends WalletCreationState {}
|
||||
|
||||
class WalletCreationFailure extends WalletCreationState {
|
||||
WalletCreationFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_state.dart';
|
||||
|
||||
part 'wallet_creation_vm.g.dart';
|
||||
|
||||
class WalletCreationVM = WalletCreationVMBase with _$WalletCreationVM;
|
||||
|
||||
abstract class WalletCreationVMBase with Store {
|
||||
WalletCreationVMBase({@required this.type}) {
|
||||
state = InitialWalletCreationState();
|
||||
name = '';
|
||||
}
|
||||
|
||||
@observable
|
||||
String name;
|
||||
|
||||
@observable
|
||||
WalletCreationState state;
|
||||
|
||||
WalletType type;
|
||||
|
||||
Future<void> create({dynamic options}) async {
|
||||
try {
|
||||
state = WalletCreating();
|
||||
await process(getCredentials(options));
|
||||
state = WalletCreatedSuccessfully();
|
||||
} catch (e) {
|
||||
state = WalletCreationFailure(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
WalletCredentials getCredentials(dynamic options) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
Future<void> process(WalletCredentials credentials) =>
|
||||
throw UnimplementedError();
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
|
||||
part 'wallet_new_vm.g.dart';
|
||||
|
||||
class WalletNewVM = WalletNewVMBase with _$WalletNewVM;
|
||||
|
||||
abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||
WalletNewVMBase(this._walletCreationService, {@required WalletType type})
|
||||
: selectedMnemonicLanguage = '',
|
||||
super(type: type);
|
||||
|
||||
@observable
|
||||
String selectedMnemonicLanguage;
|
||||
|
||||
bool get hasLanguageSelector => type == WalletType.monero;
|
||||
|
||||
final WalletCreationService _walletCreationService;
|
||||
|
||||
@override
|
||||
WalletCredentials getCredentials(dynamic options) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return MoneroNewWalletCredentials(
|
||||
name: name, language: options as String);
|
||||
case WalletType.bitcoin:
|
||||
return BitcoinNewWalletCredentials(name: name);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> process(WalletCredentials credentials) async =>
|
||||
_walletCreationService.create(credentials);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
|
||||
part 'wallet_restoration_from_seed_vm.g.dart';
|
||||
|
||||
class WalletRestorationFromSeedVM = WalletRestorationFromSeedVMBase
|
||||
with _$WalletRestorationFromSeedVM;
|
||||
|
||||
abstract class WalletRestorationFromSeedVMBase extends WalletCreationVM
|
||||
with Store {
|
||||
WalletRestorationFromSeedVMBase(this._walletCreationService,
|
||||
{@required WalletType type, @required this.language, this.seed})
|
||||
: super(type: type);
|
||||
|
||||
@observable
|
||||
String seed;
|
||||
|
||||
@observable
|
||||
int height;
|
||||
|
||||
bool get hasRestorationHeight => type == WalletType.monero;
|
||||
|
||||
final String language;
|
||||
final WalletCreationService _walletCreationService;
|
||||
|
||||
@override
|
||||
WalletCredentials getCredentials(dynamic options) {
|
||||
final password = generateWalletPassword(type);
|
||||
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return MoneroRestoreWalletFromSeedCredentials(
|
||||
name: name, height: height, mnemonic: seed, password: password);
|
||||
case WalletType.bitcoin:
|
||||
return BitcoinRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: seed, password: password);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> process(WalletCredentials credentials) async =>
|
||||
_walletCreationService.restoreFromSeed(credentials);
|
||||
}
|
Loading…
Reference in new issue