Merge branch 'main' into zano-pr

This commit is contained in:
cyan 2024-11-12 13:22:20 +01:00 committed by GitHub
commit 71e817fa8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 2981 additions and 234 deletions

38
build-guide-win.md Normal file
View file

@ -0,0 +1,38 @@
# Building CakeWallet for Windows
## Requirements and Setup
The following are the system requirements to build CakeWallet for your Windows PC.
```
Windows 10 or later (64-bit), x86-64 based
Flutter 3 or above
```
## Building CakeWallet on Windows
These steps will help you configure and execute a build of CakeWallet from its source code.
### 1. Installing Package Dependencies
For build CakeWallet windows application from sources you will be needed to have:
> [Install Flutter]Follow installation guide (https://docs.flutter.dev/get-started/install/windows) and install do not miss to dev tools (install https://docs.flutter.dev/get-started/install/windows/desktop#development-tools) which are required for windows desktop development (need to install Git for Windows and Visual Studio 2022). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windwos`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`.
> [Install WSL] for building monero dependencies need to install Windows WSL (https://learn.microsoft.com/en-us/windows/wsl/install) and required packages for WSL (Ubuntu):
`$ sudo apt update `
`$ sudo apt build-essential cmake gcc-mingw-w64 g++-mingw-w64 autoconf libtool pkg-config`
### 2. Pull CakeWallet source code
You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command:
`$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart`
OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip)
### 3. Build Monero, Monero_c and their dependencies
For use monero in the application need to build Monero wrapper - Monero_C which will be used by monero.dart package. For that need to run shell (bash - typically same named utility should be available after WSL is enabled in your system) with previously installed WSL, then change current directory to the application project directory with your used shell and then change current directory to `scripts/windows`: `$ cd scripts/windows`. Run build script: `$ ./build_all.sh`.
### 4. Configure and build CakeWallet application
To configure the application open directory where you have downloaded or unarchived CakeWallet sources and run `cakewallet.bat`.
Or if you used WSL and have active shell session you can run `$ ./cakewallet.sh` script in `scripts/windows` which will run `cakewallet.bat` in WSL.
After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contains `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application.

View file

@ -394,10 +394,10 @@ packages:
dependency: transitive
description:
name: flutter_web_bluetooth
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
sha256: fcd03e2e5f82edcedcbc940f1b6a0635a50757374183254f447640886c53208e
url: "https://pub.dev"
source: hosted
version: "0.2.3"
version: "0.2.4"
flutter_web_plugins:
dependency: transitive
description: flutter
@ -568,7 +568,7 @@ packages:
description:
path: "packages/ledger-bitcoin"
ref: HEAD
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.3"
@ -585,7 +585,7 @@ packages:
description:
path: "packages/ledger-litecoin"
ref: HEAD
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.2"

View file

@ -7,6 +7,7 @@ enum DeviceConnectionType {
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
[bool isIOS = false]) {
switch (walletType) {
case WalletType.monero:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:

View file

@ -61,4 +61,8 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
return '';
}
}
/// Check if the Wallet requires a hardware wallet to be connected during
/// the opening flow. (Currently only the case for Monero)
bool requireHardwareWalletConnection(String name) => false;
}

View file

@ -119,7 +119,7 @@ Future<bool> setupNodeSync(
daemonUsername: login ?? '',
daemonPassword: password ?? '');
});
// monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true);
// monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '');
final status = monero.Wallet_status(wptr!);

View file

@ -7,8 +7,9 @@ import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/api/transaction_history.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/ledger.dart';
import 'package:monero/monero.dart' as monero;
class MoneroCException implements Exception {
@ -17,9 +18,7 @@ class MoneroCException implements Exception {
MoneroCException(this.message);
@override
String toString() {
return message;
}
String toString() => message;
}
void checkIfMoneroCIsFine() {
@ -43,7 +42,6 @@ void checkIfMoneroCIsFine() {
throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'");
}
}
monero.WalletManager? _wmPtr;
final monero.WalletManager wmPtr = Pointer.fromAddress((() {
try {
@ -60,6 +58,13 @@ final monero.WalletManager wmPtr = Pointer.fromAddress((() {
return _wmPtr!.address;
})());
void createWalletPointer() {
final newWptr = monero.WalletManager_createWallet(wmPtr,
path: "", password: "", language: "", networkType: 0);
wptr = newWptr;
}
void createWalletSync(
{required String path,
required String password,
@ -124,13 +129,13 @@ void restoreWalletFromKeysSync(
int restoreHeight = 0}) {
txhistory = null;
var newWptr = (spendKey != "")
? monero.WalletManager_createDeterministicWalletFromSpendKey(
wmPtr,
? monero.WalletManager_createDeterministicWalletFromSpendKey(wmPtr,
path: path,
password: password,
language: language,
spendKeyString: spendKey,
newWallet: true, // TODO(mrcyjanek): safe to remove
newWallet: true,
// TODO(mrcyjanek): safe to remove
restoreHeight: restoreHeight)
: monero.WalletManager_createWalletFromKeys(
wmPtr,
@ -230,41 +235,39 @@ void restoreWalletFromSpendKeySync(
String _lastOpenedWallet = "";
// void restoreMoneroWalletFromDevice(
// {required String path,
// required String password,
// required String deviceName,
// int nettype = 0,
// int restoreHeight = 0}) {
//
// final pathPointer = path.toNativeUtf8();
// final passwordPointer = password.toNativeUtf8();
// final deviceNamePointer = deviceName.toNativeUtf8();
// final errorMessagePointer = ''.toNativeUtf8();
//
// final isWalletRestored = restoreWalletFromDeviceNative(
// pathPointer,
// passwordPointer,
// deviceNamePointer,
// nettype,
// restoreHeight,
// errorMessagePointer) != 0;
//
// calloc.free(pathPointer);
// calloc.free(passwordPointer);
//
// storeSync();
//
// if (!isWalletRestored) {
// throw WalletRestoreFromKeysException(
// message: convertUTF8ToString(pointer: errorMessagePointer));
// }
// }
Future<void> restoreWalletFromHardwareWallet(
{required String path,
required String password,
required String deviceName,
int nettype = 0,
int restoreHeight = 0}) async {
txhistory = null;
final newWptrAddr = await Isolate.run(() {
return monero.WalletManager_createWalletFromDevice(wmPtr,
path: path,
password: password,
restoreHeight: restoreHeight,
deviceName: deviceName)
.address;
});
final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
final status = monero.Wallet_status(newWptr);
if (status != 0) {
final error = monero.Wallet_errorString(newWptr);
throw WalletRestoreFromSeedException(message: error);
}
wptr = newWptr;
openedWalletsByPath[path] = wptr!;
}
Map<String, monero.wallet> openedWalletsByPath = {};
void loadWallet(
{required String path, required String password, int nettype = 0}) {
Future<void> loadWallet(
{required String path, required String password, int nettype = 0}) async {
if (openedWalletsByPath[path] != null) {
txhistory = null;
wptr = openedWalletsByPath[path]!;
@ -278,8 +281,29 @@ void loadWallet(
});
}
txhistory = null;
final newWptr = monero.WalletManager_openWallet(wmPtr,
path: path, password: password);
/// Get the device type
/// 0: Software Wallet
/// 1: Ledger
/// 2: Trezor
final deviceType = monero.WalletManager_queryWalletDevice(wmPtr,
keysFileName: "$path.keys", password: password, kdfRounds: 1);
if (deviceType == 1) {
final dummyWPtr = wptr ??
monero.WalletManager_openWallet(wmPtr, path: '', password: '');
enableLedgerExchange(dummyWPtr, gLedger!);
}
final addr = wmPtr.address;
final newWptrAddr = await Isolate.run(() {
return monero.WalletManager_openWallet(Pointer.fromAddress(addr),
path: path, password: password)
.address;
});
final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
_lastOpenedWallet = path;
final status = monero.Wallet_status(newWptr);
if (status != 0) {
@ -287,6 +311,7 @@ void loadWallet(
print(err);
throw WalletOpeningException(message: err);
}
wptr = newWptr;
openedWalletsByPath[path] = wptr!;
}
@ -351,7 +376,7 @@ Future<void> _openWallet(Map<String, String> args) async => loadWallet(
bool _isWalletExist(String path) => isWalletExistSync(path: path);
void openWallet(
Future<void> openWallet(
{required String path,
required String password,
int nettype = 0}) async =>

88
cw_monero/lib/ledger.dart Normal file
View file

@ -0,0 +1,88 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
import 'package:monero/monero.dart' as monero;
// import 'package:polyseed/polyseed.dart';
LedgerConnection? gLedger;
Timer? _ledgerExchangeTimer;
Timer? _ledgerKeepAlive;
void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
_ledgerExchangeTimer?.cancel();
_ledgerExchangeTimer = Timer.periodic(Duration(milliseconds: 1), (_) async {
final ledgerRequestLength = monero.Wallet_getSendToDeviceLength(ptr);
final ledgerRequest = monero.Wallet_getSendToDevice(ptr)
.cast<Uint8>()
.asTypedList(ledgerRequestLength);
if (ledgerRequestLength > 0) {
_ledgerKeepAlive?.cancel();
final Pointer<Uint8> emptyPointer = malloc<Uint8>(0);
monero.Wallet_setDeviceSendData(
ptr, emptyPointer.cast<UnsignedChar>(), 0);
malloc.free(emptyPointer);
// print("> ${ledgerRequest.toHexString()}");
final response = await exchange(connection, ledgerRequest);
// print("< ${response.toHexString()}");
final Pointer<Uint8> result = malloc<Uint8>(response.length);
for (var i = 0; i < response.length; i++) {
result.asTypedList(response.length)[i] = response[i];
}
monero.Wallet_setDeviceReceivedData(
ptr, result.cast<UnsignedChar>(), response.length);
malloc.free(result);
keepAlive(connection);
}
});
}
void keepAlive(LedgerConnection connection) {
if (connection.connectionType == ConnectionType.ble) {
UniversalBle.onConnectionChange = (String deviceId, bool isConnected) {
print("[Monero] Ledger Disconnected");
_ledgerKeepAlive?.cancel();
};
_ledgerKeepAlive = Timer.periodic(Duration(seconds: 10), (_) async {
try {
UniversalBle.setNotifiable(
connection.device.id,
connection.device.deviceInfo.serviceId,
connection.device.deviceInfo.notifyCharacteristicKey,
BleInputProperty.notification,
);
} catch (_){}
});
}
}
void disableLedgerExchange() {
_ledgerExchangeTimer?.cancel();
_ledgerKeepAlive?.cancel();
gLedger?.disconnect();
gLedger = null;
}
Future<Uint8List> exchange(LedgerConnection connection, Uint8List data) async =>
connection.sendOperation<Uint8List>(ExchangeOperation(data));
class ExchangeOperation extends LedgerRawOperation<Uint8List> {
final Uint8List inputData;
ExchangeOperation(this.inputData);
@override
Future<Uint8List> read(ByteDataReader reader) async =>
reader.read(reader.remainingLength);
@override
Future<List<Uint8List>> write(ByteDataWriter writer) async => [inputData];
}

View file

@ -28,6 +28,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet;
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart';
import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_transaction_creation_credentials.dart';
import 'package:cw_monero/monero_transaction_history.dart';
import 'package:cw_monero/monero_transaction_info.dart';
@ -36,6 +37,7 @@ import 'package:cw_monero/monero_wallet_addresses.dart';
import 'package:cw_monero/pending_monero_transaction.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:mobx/mobx.dart';
import 'package:monero/monero.dart' as monero;
@ -828,4 +830,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
return monero_wallet.verifyMessage(message, address, signature);
}
void setLedgerConnection(LedgerConnection connection) {
final dummyWPtr = wptr ??
monero.WalletManager_openWallet(wmPtr, path: '', password: '');
enableLedgerExchange(dummyWPtr, connection);
}
}

View file

@ -9,10 +9,13 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_wallet.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:polyseed/polyseed.dart';
import 'package:monero/monero.dart' as monero;
@ -25,6 +28,15 @@ class MoneroNewWalletCredentials extends WalletCredentials {
final bool isPolyseed;
}
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
MoneroRestoreWalletFromHardwareCredentials({required String name,
required this.ledgerConnection,
int height = 0,
String? password})
: super(name: name, password: password, height: height);
LedgerConnection ledgerConnection;
}
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
MoneroRestoreWalletFromSeedCredentials(
{required String name, required this.mnemonic, int height = 0, String? password})
@ -39,8 +51,7 @@ class MoneroWalletLoadingException implements Exception {
}
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
MoneroRestoreWalletFromKeysCredentials(
{required String name,
MoneroRestoreWalletFromKeysCredentials({required String name,
required String password,
required this.language,
required this.address,
@ -59,7 +70,7 @@ class MoneroWalletService extends WalletService<
MoneroNewWalletCredentials,
MoneroRestoreWalletFromSeedCredentials,
MoneroRestoreWalletFromKeysCredentials,
MoneroNewWalletCredentials> {
MoneroRestoreWalletFromHardwareCredentials> {
MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -185,8 +196,7 @@ class MoneroWalletService extends WalletService<
}
@override
Future<void> rename(
String currentName, String password, String newName) async {
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(currentName, getType()));
final currentWallet = MoneroWallet(
@ -232,9 +242,34 @@ class MoneroWalletService extends WalletService<
}
@override
Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) {
throw UnimplementedError(
"Restoring a Monero wallet from a hardware wallet is not yet supported!");
Future<MoneroWallet> restoreFromHardwareWallet(
MoneroRestoreWalletFromHardwareCredentials credentials) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
final password = credentials.password;
final height = credentials.height;
if (wptr == null ) monero_wallet_manager.createWalletPointer();
enableLedgerExchange(wptr!, credentials.ledgerConnection);
await monero_wallet_manager.restoreWalletFromHardwareWallet(
path: path,
password: password!,
restoreHeight: height!,
deviceName: 'Ledger');
final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
password: credentials.password!);
await wallet.init();
return wallet;
} catch (e) {
// TODO: Implement Exception for wallet list service.
print('MoneroWalletsManager Error: $e');
rethrow;
}
}
@override
@ -283,8 +318,8 @@ class MoneroWalletService extends WalletService<
}
}
Future<MoneroWallet> _restoreFromPolyseed(
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
final height = overrideHeight ??
getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
@ -329,7 +364,9 @@ class MoneroWalletService extends WalletService<
dir.listSync().forEach((f) {
final file = File(f.path);
final name = f.path.split('/').last;
final name = f.path
.split('/')
.last;
final newPath = newWalletDirPath + '/$name';
final newFile = File(newPath);
@ -366,4 +403,11 @@ class MoneroWalletService extends WalletService<
return '';
}
}
@override
bool requireHardwareWalletConnection(String name) {
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
return walletInfo.isHardwareWallet;
}
}

View file

@ -29,10 +29,10 @@ packages:
dependency: transitive
description:
name: asn1lib
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70"
url: "https://pub.dev"
source: hosted
version: "1.5.3"
version: "1.5.5"
async:
dependency: transitive
description:
@ -41,6 +41,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bluez:
dependency: transitive
description:
name: bluez
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
url: "https://pub.dev"
source: hosted
version: "0.8.2"
boolean_selector:
dependency: transitive
description:
@ -217,6 +225,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.3"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
encrypt:
dependency: "direct main"
description:
@ -237,10 +253,10 @@ packages:
dependency: "direct main"
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
file:
dependency: transitive
description:
@ -275,6 +291,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_bluetooth:
dependency: transitive
description:
name: flutter_web_bluetooth
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
frontend_server_client:
dependency: transitive
description:
@ -303,18 +327,18 @@ packages:
dependency: transitive
description:
name: hashlib
sha256: d41795742c10947930630118c6836608deeb9047cd05aee32d2baeb697afd66a
sha256: f572f2abce09fc7aee53f15927052b9732ea1053e540af8cae211111ee0b99b1
url: "https://pub.dev"
source: hosted
version: "1.19.2"
version: "1.21.0"
hashlib_codecs:
dependency: transitive
description:
name: hashlib_codecs
sha256: "2b570061f5a4b378425be28a576c1e11783450355ad4345a19f606ff3d96db0f"
sha256: "8cea9ccafcfeaa7324d2ae52c61c69f7ff71f4237507a018caab31b9e416e3b1"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.6.0"
hive:
dependency: transitive
description:
@ -335,10 +359,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
http_multi_server:
dependency: transitive
description:
@ -411,6 +435,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
ledger_flutter_plus:
dependency: "direct main"
description:
name: ledger_flutter_plus
sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
ledger_usb_plus:
dependency: transitive
description:
name: ledger_usb_plus
sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
logging:
dependency: transitive
description:
@ -447,10 +487,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
mobx:
dependency: "direct main"
description:
@ -512,10 +552,10 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
@ -552,10 +592,18 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
@ -628,6 +676,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.3"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shelf:
dependency: transitive
description:
@ -753,6 +809,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
universal_ble:
dependency: transitive
description:
name: universal_ble
sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
url: "https://pub.dev"
source: hosted
version: "0.12.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
unorm_dart:
dependency: transitive
description:
@ -801,22 +873,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.5"
win32:
dependency: transitive
description:
name: win32
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev"
source: hosted
version: "5.5.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
@ -827,4 +899,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.6"
flutter: ">=3.19.0"

View file

@ -25,9 +25,10 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: 5e93594f8d94d5723be25a7de71317e798e7a027 # monero_c hash
ref: 292bd2181ab048b2505126e52f32de5f7812e707 # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0
ledger_flutter_plus: ^1.4.1
dev_dependencies:
flutter_test:

View file

@ -0,0 +1,5 @@
class ConnectionToNodeException implements Exception {
ConnectionToNodeException({required this.message});
final String message;
}

View file

@ -0,0 +1,12 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class AccountRow extends Struct {
@Int64()
external int id;
external Pointer<Utf8> label;
String getLabel() => label.toDartString();
int getId() => id;
}

View file

@ -0,0 +1,73 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class CoinsInfoRow extends Struct {
@Int64()
external int blockHeight;
external Pointer<Utf8> hash;
@Uint64()
external int internalOutputIndex;
@Uint64()
external int globalOutputIndex;
@Int8()
external int spent;
@Int8()
external int frozen;
@Uint64()
external int spentHeight;
@Uint64()
external int amount;
@Int8()
external int rct;
@Int8()
external int keyImageKnown;
@Uint64()
external int pkIndex;
@Uint32()
external int subaddrIndex;
@Uint32()
external int subaddrAccount;
external Pointer<Utf8> address;
external Pointer<Utf8> addressLabel;
external Pointer<Utf8> keyImage;
@Uint64()
external int unlockTime;
@Int8()
external int unlocked;
external Pointer<Utf8> pubKey;
@Int8()
external int coinbase;
external Pointer<Utf8> description;
String getHash() => hash.toDartString();
String getAddress() => address.toDartString();
String getAddressLabel() => addressLabel.toDartString();
String getKeyImage() => keyImage.toDartString();
String getPubKey() => pubKey.toDartString();
String getDescription() => description.toDartString();
}

View file

@ -0,0 +1,15 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class SubaddressRow extends Struct {
@Int64()
external int id;
external Pointer<Utf8> address;
external Pointer<Utf8> label;
String getLabel() => label.toDartString();
String getAddress() => address.toDartString();
int getId() => id;
}

View file

@ -0,0 +1,41 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class TransactionInfoRow extends Struct {
@Uint64()
external int amount;
@Uint64()
external int fee;
@Uint64()
external int blockHeight;
@Uint64()
external int confirmations;
@Uint32()
external int subaddrAccount;
@Int8()
external int direction;
@Int8()
external int isPending;
@Uint32()
external int subaddrIndex;
external Pointer<Utf8> hash;
external Pointer<Utf8> paymentId;
@Int64()
external int datetime;
int getDatetime() => datetime;
int getAmount() => amount >= 0 ? amount : amount * -1;
bool getIsPending() => isPending != 0;
String getHash() => hash.toDartString();
String getPaymentId() => paymentId.toDartString();
}

View file

@ -0,0 +1,8 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class Utf8Box extends Struct {
external Pointer<Utf8> value;
String getValue() => value.toDartString();
}

View file

@ -0,0 +1,8 @@
import 'cw_wownero_platform_interface.dart';
class CwWownero {
Future<String?> getPlatformVersion() {
return CwWowneroPlatform.instance.getPlatformVersion();
}
}

View file

@ -0,0 +1,17 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'cw_wownero_platform_interface.dart';
/// An implementation of [CwWowneroPlatform] that uses method channels.
class MethodChannelCwWownero extends CwWowneroPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('cw_wownero');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}

View file

@ -0,0 +1,29 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'cw_wownero_method_channel.dart';
abstract class CwWowneroPlatform extends PlatformInterface {
/// Constructs a CwWowneroPlatform.
CwWowneroPlatform() : super(token: _token);
static final Object _token = Object();
static CwWowneroPlatform _instance = MethodChannelCwWownero();
/// The default instance of [CwWowneroPlatform] to use.
///
/// Defaults to [MethodChannelCwWownero].
static CwWowneroPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [CwWowneroPlatform] when
/// they register themselves.
static set instance(CwWowneroPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}

File diff suppressed because it is too large Load diff

View file

@ -41,6 +41,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bluez:
dependency: transitive
description:
name: bluez
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
url: "https://pub.dev"
source: hosted
version: "0.8.2"
boolean_selector:
dependency: transitive
description:
@ -217,6 +225,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.3"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
encrypt:
dependency: "direct main"
description:
@ -275,6 +291,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_bluetooth:
dependency: transitive
description:
name: flutter_web_bluetooth
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
frontend_server_client:
dependency: transitive
description:
@ -411,6 +435,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
ledger_flutter_plus:
dependency: transitive
description:
name: ledger_flutter_plus
sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
url: "https://pub.dev"
source: hosted
version: "1.2.5"
ledger_usb_plus:
dependency: transitive
description:
name: ledger_usb_plus
sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
logging:
dependency: transitive
description:
@ -548,6 +588,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
@ -560,10 +608,10 @@ packages:
dependency: transitive
description:
name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.8"
pointycastle:
dependency: transitive
description:
@ -612,6 +660,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.3"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shelf:
dependency: transitive
description:
@ -737,6 +793,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
universal_ble:
dependency: transitive
description:
name: universal_ble
sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
url: "https://pub.dev"
source: hosted
version: "0.12.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
unorm_dart:
dependency: transitive
description:
@ -793,6 +865,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
@ -802,5 +882,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: 5e93594f8d94d5723be25a7de71317e798e7a027 # monero_c hash
ref: 292bd2181ab048b2505126e52f32de5f7812e707 # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0

View file

@ -14,7 +14,11 @@ import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WalletLoadingService {
WalletLoadingService(this.sharedPreferences, this.keyService, this.walletServiceFactory);
WalletLoadingService(
this.sharedPreferences,
this.keyService,
this.walletServiceFactory,
);
final SharedPreferences sharedPreferences;
final KeyService keyService;
@ -77,7 +81,8 @@ class WalletLoadingService {
await updateMoneroWalletPassword(wallet);
}
await sharedPreferences.setString(PreferencesKey.currentWalletName, wallet.name);
await sharedPreferences.setString(
PreferencesKey.currentWalletName, wallet.name);
await sharedPreferences.setInt(
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
@ -129,4 +134,9 @@ class WalletLoadingService {
return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}";
}
bool requireHardwareWalletConnection(WalletType type, String name) {
final walletService = walletServiceFactory.call(type);
return walletService.requireHardwareWalletConnection(name);
}
}

View file

@ -32,12 +32,14 @@ import 'package:cake_wallet/entities/biometric_auth.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_manager.dart';
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
@ -184,7 +186,6 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_
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/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@ -587,7 +588,7 @@ Future<void> setup({
);
} else {
// wallet is already loaded:
if (appStore.wallet != null) {
if (appStore.wallet != null || requireHardwareWalletConnection()) {
// goes to the dashboard:
authStore.allowed();
// trigger any deep links:
@ -781,9 +782,11 @@ Future<void> setup({
);
}
getIt.registerFactory(() => WalletListPage(
getIt.registerFactoryParam<WalletListPage, Function(BuildContext)?, void>(
(Function(BuildContext)? onWalletLoaded, _) => WalletListPage(
walletListViewModel: getIt.get<WalletListViewModel>(),
authService: getIt.get<AuthService>(),
onWalletLoaded: onWalletLoaded,
));
getIt.registerFactoryParam<WalletEditViewModel, WalletListViewModel, void>(

View file

@ -0,0 +1,25 @@
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:shared_preferences/shared_preferences.dart';
bool requireHardwareWalletConnection() {
final name = getIt
.get<SharedPreferences>()
.getString(PreferencesKey.currentWalletName);
final typeRaw =
getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
if (typeRaw == null) {
return false;
}
if (name == null) {
throw Exception('Incorrect current wallet name: $name');
}
final type = deserializeFromInt(typeRaw);
final walletLoadingService = getIt.get<WalletLoadingService>();
return walletLoadingService.requireHardwareWalletConnection(type, name);
}

View file

@ -225,6 +225,19 @@ class CWMonero extends Monero {
language: language,
height: height);
@override
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({
required String name,
required String password,
required int height,
required ledger.LedgerConnection ledgerConnection,
}) =>
MoneroRestoreWalletFromHardwareCredentials(
name: name,
password: password,
height: height,
ledgerConnection: ledgerConnection);
@override
WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
{required String name,
@ -383,6 +396,18 @@ class CWMonero extends Monero {
checkIfMoneroCIsFine();
}
@override
void setLedgerConnection(Object wallet, ledger.LedgerConnection connection) {
final moneroWallet = wallet as MoneroWallet;
moneroWallet.setLedgerConnection(connection);
}
@override
void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
gLedger = connection;
keepAlive(connection);
}
bool isViewOnly() {
return isViewOnlyBySpendKey();
}

View file

@ -1,18 +1,28 @@
import 'dart:async';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
import 'package:cake_wallet/entities/load_current_wallet.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/load_current_wallet.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:rxdart/subjects.dart';
ReactionDisposer? _onAuthenticationStateChange;
dynamic loginError;
StreamController<dynamic> authenticatedErrorStreamController = BehaviorSubject<dynamic>();
StreamController<dynamic> authenticatedErrorStreamController =
BehaviorSubject<dynamic>();
void startAuthenticationStateChange(
AuthenticationStore authenticationStore,
@ -27,18 +37,49 @@ void startAuthenticationStateChange(
_onAuthenticationStateChange ??= autorun((_) async {
final state = authenticationStore.state;
if (state == AuthenticationState.installed && !SettingsStoreBase.walletPasswordDirectInput) {
if (state == AuthenticationState.installed &&
!SettingsStoreBase.walletPasswordDirectInput) {
try {
await loadCurrentWallet();
if (!requireHardwareWalletConnection()) await loadCurrentWallet();
} catch (error, stack) {
loginError = error;
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
ExceptionHandler.onError(
FlutterErrorDetails(exception: error, stack: stack));
}
return;
}
if (state == AuthenticationState.allowed) {
await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
if (requireHardwareWalletConnection()) {
await navigatorKey.currentState!.pushNamedAndRemoveUntil(
Routes.connectDevices,
(route) => false,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
onConnectDevice: (context, ledgerVM) async {
monero!.setGlobalLedgerConnection(ledgerVM.connection);
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
buttonAction: () => Navigator.of(context).pop()),
);
await loadCurrentWallet();
getIt.get<BottomSheetService>().resetCurrentSheet();
await navigatorKey.currentState!
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
},
allowChangeWallet: true,
),
);
// await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.connectDevices, (route) => false, arguments: ConnectDevicePageParams(walletType: walletType, onConnectDevice: onConnectDevice));
} else {
await navigatorKey.currentState!
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
}
if (!(await authenticatedErrorStreamController.stream.isEmpty)) {
ExceptionHandler.showError(
(await authenticatedErrorStreamController.stream.first).toString());

View file

@ -24,6 +24,7 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@ -212,6 +213,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
final type = arguments[0] as WalletType;
final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
if (type == WalletType.monero)
return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
case Routes.setupPin:
@ -403,8 +407,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
case Routes.walletList:
final onWalletLoaded = settings.arguments as Function(BuildContext)?;
return MaterialPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<WalletListPage>());
fullscreenDialog: true,
builder: (_) => getIt.get<WalletListPage>(param1: onWalletLoaded),
);
case Routes.walletEdit:
return MaterialPageRoute<void>(

View file

@ -2,9 +2,12 @@ import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/wallet_type.dart';
@ -17,35 +20,46 @@ typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
class ConnectDevicePageParams {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
ConnectDevicePageParams(
{required this.walletType, required this.onConnectDevice});
ConnectDevicePageParams({
required this.walletType,
required this.onConnectDevice,
this.allowChangeWallet = false,
});
}
class ConnectDevicePage extends BasePage {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final LedgerViewModel ledgerVM;
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
: walletType = params.walletType,
onConnectDevice = params.onConnectDevice;
onConnectDevice = params.onConnectDevice,
allowChangeWallet = params.allowChangeWallet;
@override
String get title => S.current.restore_title_from_hardware_wallet;
@override
Widget body(BuildContext context) =>
ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
Widget body(BuildContext context) => ConnectDevicePageBody(
walletType, onConnectDevice, allowChangeWallet, ledgerVM);
}
class ConnectDevicePageBody extends StatefulWidget {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final LedgerViewModel ledgerVM;
const ConnectDevicePageBody(
this.walletType, this.onConnectDevice, this.ledgerVM);
this.walletType,
this.onConnectDevice,
this.allowChangeWallet,
this.ledgerVM,
);
@override
ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
@ -102,6 +116,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Future<void> _refreshBleDevices() async {
try {
if (widget.ledgerVM.bleIsEnabled) {
_bleRefresh = widget.ledgerVM
.scanForBleDevices()
.listen((device) => setState(() => bleDevices.add(device)))
@ -110,6 +125,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
});
_bleRefreshTimer?.cancel();
_bleRefreshTimer = null;
}
} catch (e) {
print(e);
}
@ -227,9 +243,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
),
@ -247,11 +261,27 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
),
)
.toList(),
]
],
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
color: Theme.of(context).extension<WalletListTheme>()!.createNewWalletButtonBackgroundColor,
textColor: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor,
onPressed: _onChangeWallet,
)
],
],
),
),
),
);
}
void _onChangeWallet() {
Navigator.of(context).pushNamed(
Routes.walletList,
arguments: (BuildContext context) => Navigator.of(context)
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
);
}
}

View file

@ -0,0 +1,230 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class MoneroHardwareWalletOptionsPage extends BasePage {
MoneroHardwareWalletOptionsPage(this._walletHardwareRestoreVM);
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
@override
String get title => S.current.restore_title_from_hardware_wallet;
@override
Widget body(BuildContext context) =>
_MoneroHardwareWalletOptionsForm(_walletHardwareRestoreVM);
}
class _MoneroHardwareWalletOptionsForm extends StatefulWidget {
const _MoneroHardwareWalletOptionsForm(this._walletHardwareRestoreVM);
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
@override
_MoneroHardwareWalletOptionsFormState createState() =>
_MoneroHardwareWalletOptionsFormState(_walletHardwareRestoreVM);
}
class _MoneroHardwareWalletOptionsFormState
extends State<_MoneroHardwareWalletOptionsForm> {
_MoneroHardwareWalletOptionsFormState(this._walletHardwareRestoreVM)
: _formKey = GlobalKey<FormState>(),
_blockchainHeightKey = GlobalKey<BlockchainHeightState>(),
_blockHeightFocusNode = FocusNode(),
_controller = TextEditingController();
final GlobalKey<FormState> _formKey;
final GlobalKey<BlockchainHeightState> _blockchainHeightKey;
final FocusNode _blockHeightFocusNode;
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
final TextEditingController _controller;
@override
void initState() {
super.initState();
_setEffects(context);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
content: Center(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 0),
child: Form(
key: _formKey,
child: Stack(
alignment: Alignment.centerRight,
children: [
TextFormField(
onChanged: (value) =>
_walletHardwareRestoreVM.name = value,
controller: _controller,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
),
decoration: InputDecoration(
hintStyle: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<NewWalletTheme>()!
.hintTextColor,
),
hintText: S.of(context).wallet_name,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.extension<NewWalletTheme>()!
.underlineColor,
width: 1.0,
),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context)
.extension<NewWalletTheme>()!
.underlineColor,
width: 1.0,
),
),
suffixIcon: Semantics(
label: S.of(context).generate_name,
child: IconButton(
onPressed: _onGenerateName,
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Theme.of(context).hintColor,
),
width: 34,
height: 34,
child: Image.asset(
'assets/images/refresh_icon.png',
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor,
),
),
),
),
),
validator: WalletNameValidator(),
),
],
),
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: BlockchainHeightWidget(
focusNode: _blockHeightFocusNode,
key: _blockchainHeightKey,
hasDatePicker: true,
walletType: WalletType.monero,
),
),
],
),
),
),
bottomSectionPadding: EdgeInsets.all(24),
bottomSection: Observer(
builder: (context) => LoadingPrimaryButton(
onPressed: _confirmForm,
text: S.of(context).seed_language_next,
color: Colors.green,
textColor: Colors.white,
isDisabled: _walletHardwareRestoreVM.name.isEmpty,
),
),
),
);
}
Future<void> _onGenerateName() async {
final rName = await generateName();
FocusManager.instance.primaryFocus?.unfocus();
setState(() {
_controller.text = rName;
_walletHardwareRestoreVM.name = rName;
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length));
});
}
Future<void> _confirmForm() async {
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
buttonAction: () => Navigator.of(context).pop(),
),
);
final options = {'height': _blockchainHeightKey.currentState?.height ?? -1};
await _walletHardwareRestoreVM.create(options: options);
}
bool _effectsInstalled = false;
void _setEffects(BuildContext context) {
if (_effectsInstalled) return;
reaction((_) => _walletHardwareRestoreVM.error, (String? error) {
if (error != null) {
if (error == S.current.ledger_connection_error)
Navigator.of(context).pop();
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: error,
buttonText: S.of(context).ok,
buttonAction: () {
_walletHardwareRestoreVM.error = null;
Navigator.of(context).pop();
},
),
);
});
}
});
_effectsInstalled = true;
}
}

View file

@ -11,6 +11,7 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
@ -160,25 +161,60 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
Widget _buildWalletContacts(BuildContext context) {
final walletContacts = widget.contactListViewModel.walletContactsToShow;
final groupedContacts = <String, List<ContactBase>>{};
for (var contact in walletContacts) {
final baseName = _extractBaseName(contact.name);
groupedContacts.putIfAbsent(baseName, () => []).add(contact);
}
return ListView.builder(
shrinkWrap: true,
itemCount: walletContacts.length * 2,
itemCount: groupedContacts.length * 2,
itemBuilder: (context, index) {
if (index.isOdd) {
return StandardListSeparator();
} else {
final walletInfo = walletContacts[index ~/ 2];
return generateRaw(context, walletInfo);
final groupIndex = index ~/ 2;
final groupName = groupedContacts.keys.elementAt(groupIndex);
final groupContacts = groupedContacts[groupName]!;
if (groupContacts.length == 1) {
final contact = groupContacts[0];
return generateRaw(context, contact);
} else {
final activeContact = groupContacts.firstWhere(
(contact) => contact.name.contains('Active'),
orElse: () => groupContacts[0],
);
return ExpansionTile(
title: Text(
groupName,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
leading: _buildCurrencyIcon(activeContact),
tilePadding: EdgeInsets.zero,
childrenPadding: const EdgeInsets.only(left: 16),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
expandedAlignment: Alignment.topLeft,
children: groupContacts.map((contact) => generateRaw(context, contact)).toList(),
);
}
}
},
);
}
String _extractBaseName(String name) {
final bracketIndex = name.indexOf('(');
return (bracketIndex != -1) ? name.substring(0, bracketIndex).trim() : name;
}
Widget generateRaw(BuildContext context, ContactBase contact) {
final image = contact.type.iconPath;
final currencyIcon = image != null
? Image.asset(image, height: 24, width: 24)
: const SizedBox(height: 24, width: 24);
final currencyIcon = _buildCurrencyIcon(contact);
return GestureDetector(
onTap: () async {
@ -219,6 +255,13 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
);
}
Widget _buildCurrencyIcon(ContactBase contact) {
final image = contact.type.iconPath;
return image != null
? Image.asset(image, height: 24, width: 24)
: const SizedBox(height: 24, width: 24);
}
Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
return await showPopUp<bool>(
context: context,
@ -263,12 +306,13 @@ class _ContactListBodyState extends State<ContactListBody> {
@override
void dispose() {
widget.tabController.removeListener(_handleTabChange);
widget.contactListViewModel.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final contacts = widget.contactListViewModel.contacts;
final contacts = widget.contactListViewModel.contactsToShow;
return Scaffold(
body: Container(
child: FilteredList(

View file

@ -1,44 +1,56 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart';
import 'package:cake_wallet/src/screens/wallet_list/edit_wallet_button_widget.dart';
import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
class WalletListPage extends BasePage {
WalletListPage({required this.walletListViewModel, required this.authService});
WalletListPage({
required this.walletListViewModel,
required this.authService,
this.onWalletLoaded,
});
final WalletListViewModel walletListViewModel;
final AuthService authService;
final Function(BuildContext)? onWalletLoaded;
@override
String get title => S.current.wallets;
@override
Widget body(BuildContext context) =>
WalletListBody(walletListViewModel: walletListViewModel, authService: authService);
Widget body(BuildContext context) => WalletListBody(
walletListViewModel: walletListViewModel,
authService: authService,
onWalletLoaded:
onWalletLoaded ?? (context) => Navigator.of(context).pop(),
);
@override
Widget trailing(BuildContext context) {
@ -89,10 +101,15 @@ class WalletListPage extends BasePage {
}
class WalletListBody extends StatefulWidget {
WalletListBody({required this.walletListViewModel, required this.authService});
WalletListBody({
required this.walletListViewModel,
required this.authService,
required this.onWalletLoaded,
});
final WalletListViewModel walletListViewModel;
final AuthService authService;
final Function(BuildContext) onWalletLoaded;
@override
WalletListBodyState createState() => WalletListBodyState();
@ -119,8 +136,8 @@ class WalletListBodyState extends State<WalletListBody> {
@override
Widget build(BuildContext context) {
final newWalletImage =
Image.asset('assets/images/new_wallet.png', height: 12, width: 12, color: Colors.white);
final newWalletImage = Image.asset('assets/images/new_wallet.png',
height: 12, width: 12, color: Colors.white);
final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
height: 12,
width: 12,
@ -181,8 +198,7 @@ class WalletListBodyState extends State<WalletListBody> {
trailingWidget: EditWalletButtonWidget(
width: 74,
isGroup: true,
isExpanded:
widget.walletListViewModel.expansionTileStateTrack[index]!,
isExpanded: widget.walletListViewModel.expansionTileStateTrack[index]!,
onTap: () {
final wallet = widget.walletListViewModel
.convertWalletInfoToWalletListItem(group.wallets.first);
@ -199,8 +215,7 @@ class WalletListBodyState extends State<WalletListBody> {
},
),
childWallets: group.wallets.map((walletInfo) {
return widget.walletListViewModel
.convertWalletInfoToWalletListItem(walletInfo);
return widget.walletListViewModel.convertWalletInfoToWalletListItem(walletInfo);
}).toList(),
isSelected: false,
onChildItemTapped: (wallet) =>
@ -330,8 +345,7 @@ class WalletListBodyState extends State<WalletListBody> {
arguments: NewWalletArguments(
type: widget.walletListViewModel.currentWalletType,
),
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(
@ -346,8 +360,7 @@ class WalletListBodyState extends State<WalletListBody> {
widget.authService.authenticateAction(
context,
route: Routes.newWalletType,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(Routes.newWalletType);
@ -368,8 +381,7 @@ class WalletListBodyState extends State<WalletListBody> {
context,
route: Routes.restoreOptions,
arguments: false,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
@ -441,12 +453,36 @@ class WalletListBodyState extends State<WalletListBody> {
await widget.authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
return;
}
if (!isAuthenticatedSuccessfully) return;
try {
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
if (widget.walletListViewModel
.requireHardwareWalletConnection(wallet)) {
await Navigator.of(context).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
onConnectDevice: (context, ledgerVM) async {
monero!.setGlobalLedgerConnection(ledgerVM.connection);
Navigator.of(context).pop();
},
),
);
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
buttonAction: () => Navigator.of(context).pop()),
);
}
changeProcessText(
S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet);
await hideProgressText();
// only pop the wallets route in mobile as it will go back to dashboard page
@ -454,13 +490,15 @@ class WalletListBodyState extends State<WalletListBody> {
if (responsiveLayoutUtil.shouldRenderMobileUI) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (this.mounted) {
Navigator.of(context).pop();
widget.onWalletLoaded.call(context);
}
});
}
} catch (e) {
if (this.mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
changeProcessText(S
.of(context)
.wallet_list_failed_to_load(wallet.name, e.toString()));
}
}
},

View file

@ -28,7 +28,8 @@ abstract class ContactListViewModelBase with Store {
isAutoGenerateEnabled =
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled {
walletInfoSource.values.forEach((info) {
if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type) && info.addressInfos != null) {
if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type) &&
info.addressInfos != null) {
for (var key in info.addressInfos!.keys) {
final value = info.addressInfos![key];
final address = value?.first;
@ -60,15 +61,19 @@ abstract class ContactListViewModelBase with Store {
address,
name,
walletTypeToCryptoCurrency(info.type,
isTestnet:
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
isTestnet: info.network == null
? false
: info.network!.toLowerCase().contains("testnet")),
));
});
}
} else {
walletContacts.add(WalletContact(
info.address,
_createName(info.name, "", key: [WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type) ? 0 : null),
_createName(info.name, "",
key: [WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)
? 0
: null),
walletTypeToCryptoCurrency(info.type),
));
}
@ -82,8 +87,11 @@ abstract class ContactListViewModelBase with Store {
}
String _createName(String walletName, String label, {int? key = null}) {
final actualLabel = label.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active).replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments);
return '$walletName${key == null ? "" : " [#${key}]"} ${actualLabel.isNotEmpty ? "($actualLabel)" : ""}'.trim();
final actualLabel = label
.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active)
.replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments);
return '$walletName${key == null ? "" : " [#${key}]"} ${actualLabel.isNotEmpty ? "($actualLabel)" : ""}'
.trim();
}
final bool isAutoGenerateEnabled;
@ -108,18 +116,19 @@ abstract class ContactListViewModelBase with Store {
Future<void> delete(ContactRecord contact) async => contact.original.delete();
ObservableList<ContactRecord> get contactsToShow =>
ObservableList.of(contacts.where((element) => _isValidForCurrency(element)));
ObservableList.of(contacts.where((element) => _isValidForCurrency(element, false)));
@computed
List<WalletContact> get walletContactsToShow =>
walletContacts.where((element) => _isValidForCurrency(element)).toList();
walletContacts.where((element) => _isValidForCurrency(element, true)).toList();
bool _isValidForCurrency(ContactBase element) {
if (element.name.contains('Silent Payments')) return false;
if (element.name.contains('MWEB')) return false;
bool _isValidForCurrency(ContactBase element, bool isWalletContact) {
if (_currency == null) return true;
if (!element.name.contains('Active') &&
isWalletContact &&
(element.type == CryptoCurrency.btc || element.type == CryptoCurrency.ltc)) return false;
return _currency == null ||
element.type == _currency ||
return element.type == _currency ||
(element.type.tag != null &&
_currency?.tag != null &&
element.type.tag == _currency?.tag) ||

View file

@ -428,7 +428,7 @@ abstract class DashboardViewModelBase with Store {
// to not cause work duplication, this will do the job as well, it will be slightly less precise
// about what happened - but still enough.
// if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0",
if (keys['privateViewKey'] == List.generate(64, (index) => "0").join(""))
if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") && !wallet.isHardwareWallet)
"private view key is 0",
// if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0",
if (keys['publicViewKey'] == List.generate(64, (index) => "0").join(""))

View file

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
@ -114,6 +115,8 @@ abstract class LedgerViewModelBase with Store {
void setLedger(WalletBase wallet) {
switch (wallet.type) {
case WalletType.monero:
return monero!.setLedgerConnection(wallet, connection);
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.setLedgerConnection(wallet, connection);

View file

@ -1,7 +1,9 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
@ -104,6 +106,15 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
case WalletType.polygon:
credentials = polygon!.createPolygonHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
break;
case WalletType.monero:
final password = walletPassword ?? generateWalletPassword();
credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
name: name,
ledgerConnection: ledgerViewModel.connection,
password: password,
height: _options['height'] as int? ?? 0,
);
default:
throw Exception('Unexpected type: ${type.toString()}');
}

View file

@ -68,6 +68,10 @@ abstract class WalletListViewModelBase with Store {
WalletType get currentWalletType => _appStore.wallet!.type;
bool requireHardwareWalletConnection(WalletListItem walletItem) =>
_walletLoadingService.requireHardwareWalletConnection(
walletItem.type, walletItem.name);
@action
Future<void> loadWallet(WalletListItem walletItem) async {
// bool switchingToSameWalletType = walletItem.type == _appStore.wallet?.type;
@ -87,7 +91,8 @@ abstract class WalletListViewModelBase with Store {
singleWalletsList.clear();
wallets.addAll(
_walletInfoSource.values.map((info) => convertWalletInfoToWalletListItem(info)),
_walletInfoSource.values
.map((info) => convertWalletInfoToWalletListItem(info)),
);
//========== Split into shared seed groups and single wallets list
@ -95,7 +100,8 @@ abstract class WalletListViewModelBase with Store {
for (var group in _walletManager.walletGroups) {
if (group.wallets.length == 1) {
singleWalletsList.add(convertWalletInfoToWalletListItem(group.wallets.first));
singleWalletsList
.add(convertWalletInfoToWalletListItem(group.wallets.first));
} else {
multiWalletGroups.add(group);
}
@ -148,9 +154,11 @@ abstract class WalletListViewModelBase with Store {
List<WalletInfo> walletInfoSourceCopy = _walletInfoSource.values.toList();
await _walletInfoSource.clear();
if (ascending) {
walletInfoSourceCopy.sort((a, b) => a.type.toString().compareTo(b.type.toString()));
walletInfoSourceCopy
.sort((a, b) => a.type.toString().compareTo(b.type.toString()));
} else {
walletInfoSourceCopy.sort((a, b) => b.type.toString().compareTo(a.type.toString()));
walletInfoSourceCopy
.sort((a, b) => b.type.toString().compareTo(a.type.toString()));
}
await _walletInfoSource.addAll(walletInfoSourceCopy);
updateList();
@ -213,7 +221,8 @@ abstract class WalletListViewModelBase with Store {
name: info.name,
type: info.type,
key: info.key,
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
isCurrent: info.name == _appStore.wallet?.name &&
info.type == _appStore.wallet?.type,
isEnabled: availableWalletTypes.contains(info.type),
isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
);

View file

@ -6,9 +6,9 @@ cd "$(dirname "$0")"
if [[ ! -d "monero_c" ]];
then
git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip
git clone https://github.com/mrcyjanek/monero_c --branch master
cd monero_c
git checkout e0891d041216099699043da6ac9d08320274b3c6
git checkout 292bd2181ab048b2505126e52f32de5f7812e707
git reset --hard
git submodule update --init --force --recursive
./apply_patches.sh monero

View file

@ -275,20 +275,22 @@ import 'package:cw_core/output_info.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:polyseed/polyseed.dart';""";
const moneroCWHeaders = """
import 'package:cw_core/account.dart' as monero_account;
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/monero_amount_format.dart';
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/api/wallet.dart' as monero_wallet_api;
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_unspent.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/monero_wallet_service.dart';
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/monero_wallet.dart';
import 'package:cw_monero/monero_transaction_info.dart';
import 'package:cw_monero/monero_transaction_creation_credentials.dart';
import 'package:cw_core/account.dart' as monero_account;
import 'package:cw_monero/api/wallet.dart' as monero_wallet_api;
import 'package:cw_monero/mnemonics/english.dart';
import 'package:cw_monero/mnemonics/chinese_simplified.dart';
import 'package:cw_monero/mnemonics/dutch.dart';
@ -400,6 +402,7 @@ abstract class Monero {
required String language,
required int height});
WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic});
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password});
Map<String, String> getKeys(Object wallet);
int? getRestoreHeight(Object wallet);
@ -416,6 +419,8 @@ abstract class Monero {
int getTransactionInfoAccountId(TransactionInfo tx);
WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
Map<String, String> pendingTransactionInfo(Object transaction);
void setLedgerConnection(Object wallet, ledger.LedgerConnection connection);
void setGlobalLedgerConnection(ledger.LedgerConnection connection);
}
abstract class MoneroSubaddressList {