Cw 660 add ledger monero (#1747)

* Fix conflicts with main * fix for multiple wallets * Add tron to windows application configuration. * Add macOS option for description message in configure_cake_wallet.sh * Include missed monero dll for windows. * fix conflicts with main * Disable haven configuration for iOS as default. Add ability to configure cakewallet for iOS with for configuration script. Remove cw_shared configuration for cw_monero. * fix: scan fixes, add date, allow sending while scanning * add missing nano secrets file [skip ci] * ios library * don't pull prebuilds android * Add auto generation of manifest file for android project even for iOS, macOS, Windows. * feat: sync fixes, sp settings * feat: fix resyncing * store crash fix * make init async so it won't lag disable print starts * fix monero_c build issues * libstdc++ * Fix MacOS saving wallet file issue Fix Secure Storage issue (somehow) * update pubspec.lock * fix build script * Use dylib as iOS framework. Use custom path for loading of iOS framework for monero.dart. Add script for generate iOS framework for monero wallet. * fix: date from height logic, status disconnected & chain tip get * fix: params * feat: electrum migration if using cake electrum * fix nodes update versions * re-enable tron * update sp_scanner to work on iOS [skip ci] * bump monero_c hash * bump monero_c commit * bump moneroc version * bump monero_c commit * Add ability to build monero wallet lib as universal lib. Update macOS build guide. Change default arch for macOS project to . * fix: wrong socket for old electrum nodes * Fix unchecked wallet type call * get App Dir correctly in default_settings_migration.dart * handle previous issue with fetching linux documents directory [skip ci] * backup fix * fix NTFS issues * Close the wallet when the wallet gets changed * fix: double balance * feat: node domain * fix: menu name * bump monero_c commit * fix: update tip on set scanning * fix: connection switching back and forth * feat: check if node is electrs, and supports sp * chore: fix build * minor enhancements * fixes and enhancements * solve conflicts with main * Only stop wallet on rename and delete * fix: status toggle * minor enhancement * Monero.com fixes * bump monero_c commit * update sp_scanner to include windows and linux * Update macOS build guide. Change brew dependencies for build unbound locally. * fix conflicts and update macos build guide * remove build cache when on gh actions * update secure storage * free up even more storage * free up more storage * Add initial wownero * fix conflicts * fix workflow issue * build wownero * ios and windows changes * macos * complete wownero flow (app side) * add keychain group entitlement and update script for RunnerBase on macos * update secure_storage version to 8.1.0 in configure.dart * add wownero framework * update ios builds * proper path for wownero and monero * finalizing wownero * finalizing wownero * free up even more storage * revert commenting of build gradle configs * revert commenting of secrets [skip ci] * free more storage * minor fixes * link android wownero libraries * bump monero_c commit * wownero fixes * rename target * build_single.sh using clean env * bump monero_c commit * minor fix * Add wownero polyseed * fix conflicts with main * fix: wallet seed display fix: wownero not refreshing * fix: wallet seed display fix: wownero not refreshing * bump monero_c commit * minor fixes * fix: incorrectly displaying XMR instead of WOW * fix: incorrect restore height in wownero * bump monero_c commit * Add Inno Setup Script for windows exe installer * drop libc++_shared.so * fixes from comments * Fix CMake for windows * Merge latest monero dart changes [skip ci] * bump monero_c commit * add wownero to build scripts for macos [skip ci] * add 14 word seed support to wownero * UI fixes for wownero seed restore * minor fixes * reformat code to pass lints * Add debug ledger code * Add Litecoin Hardware Wallet Creation * Add Litecoin Hardware Wallet Creation * Fix Bitcoin not sending on Ledger * Fixes to sending LTC using Ledger * CW-679 Fix merge conflicts * CW-679 Fix merge conflicts * CW-679 Minor fixes * CW-679 Add derivation Path of change address * Add create Monero Wallet from Ledger * bug fix to create Monero Wallet from Ledger * ledger flutter plus refactoring * ledger flutter plus refactoring * ledger flutter plus refactoring * Ups :| * Ups :| I forgot USB * Handle BT Off * Fix Issue with A14 and USB * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Small Ledger Quality of life improvements * Pls work * Pls work * Pls work * Pls work * Fix overpopulation * Fix ble device detection and support for Stax and Flex * clean up pubspec * clean up * MWeb merge fix * MWeb merge fix * Migrate to Ledger Flutter Plus * Add connect device page before loading the wallet (Only monero) * Add connect device page before loading the wallet (Only monero) * Fix merge error * Fix merge error * Fix merge error && Allow for wallet switching * Please compile now * Move monero/ledger.dart from monero_c to cw_monero * Upgrade ledger_flutter_plus * Add more popups if action on the device is needed. * Update monero_c dependency hash * Yay ledger monero is even more efficient and avoids memory leaks 🥳 * [skip-ci] more code * Fix Minor Bug * Fix Minor Bug * Apply requested changes * [skip ci] Apply requested changes * Minor Cleanup * Welp I'm dumb :/ * Implement requested changes * Increase ledger refresh speed * Add Monero Ledger keep connection alive * Add Monero Ledger keep connection alive --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: m <m@cakewallet.com> Co-authored-by: Rafael Saes <git@rafael.saes.dev> Co-authored-by: Matthew Fosse <matt@fosse.co>

Konstantin Ullrich committed Nov 12, 2024 at 04:26 UTC 2c37e427e9eb7e2c882fdd7577f7ab8cfa28c558
38 files changed +2910 -247
build-guide-win.md new
+38
@@ -0,0 +1,38 @@
1 +# Building CakeWallet for Windows
2 +
3 +## Requirements and Setup
4 +
5 +The following are the system requirements to build CakeWallet for your Windows PC.
6 +
7 +```
8 +Windows 10 or later (64-bit), x86-64 based
9 +Flutter 3 or above
10 +```
11 +
12 +## Building CakeWallet on Windows
13 +
14 +These steps will help you configure and execute a build of CakeWallet from its source code.
15 +
16 +### 1. Installing Package Dependencies
17 +
18 +For build CakeWallet windows application from sources you will be needed to have:
19 +> [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`.
20 +> [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):
21 +`$ sudo apt update `
22 +`$ sudo apt build-essential cmake gcc-mingw-w64 g++-mingw-w64 autoconf libtool pkg-config`
23 +
24 +### 2. Pull CakeWallet source code
25 +
26 +You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command:
27 +`$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart`
28 +OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip)
29 +
30 +### 3. Build Monero, Monero_c and their dependencies
31 +
32 +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`.
33 +
34 +### 4. Configure and build CakeWallet application
35 +
36 +To configure the application open directory where you have downloaded or unarchived CakeWallet sources and run `cakewallet.bat`.
37 +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.
38 +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.
cw_bitcoin/pubspec.lock
+4 -4
@@ -386,10 +386,10 @@ packages:
386 dependency: transitive
387 description:
388 name: flutter_web_bluetooth
389 - sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
389 + sha256: fcd03e2e5f82edcedcbc940f1b6a0635a50757374183254f447640886c53208e
390 url: "https://pub.dev"
391 source: hosted
392 - version: "0.2.3"
392 + version: "0.2.4"
393 flutter_web_plugins:
394 dependency: transitive
395 description: flutter
@@ -560,7 +560,7 @@ packages:
560 description:
561 path: "packages/ledger-bitcoin"
562 ref: HEAD
563 - resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
563 + resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
564 url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
565 source: git
566 version: "0.0.3"
@@ -577,7 +577,7 @@ packages:
577 description:
578 path: "packages/ledger-litecoin"
579 ref: HEAD
580 - resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
580 + resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
581 url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
582 source: git
583 version: "0.0.2"
cw_core/lib/hardware/device_connection_type.dart
+1
@@ -7,6 +7,7 @@ enum DeviceConnectionType {
7 static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
8 [bool isIOS = false]) {
9 switch (walletType) {
10 + case WalletType.monero:
11 case WalletType.bitcoin:
12 case WalletType.litecoin:
13 case WalletType.ethereum:
cw_core/lib/wallet_service.dart
+4
@@ -61,4 +61,8 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
61 return '';
62 }
63 }
64 +
65 + /// Check if the Wallet requires a hardware wallet to be connected during
66 + /// the opening flow. (Currently only the case for Monero)
67 + bool requireHardwareWalletConnection(String name) => false;
68 }
cw_monero/lib/api/wallet.dart
+2 -2
@@ -119,7 +119,7 @@ Future<bool> setupNodeSync(
119 daemonUsername: login ?? '',
120 daemonPassword: password ?? '');
121 });
122 - // monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true);
122 + // monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '');
123
124 final status = monero.Wallet_status(wptr!);
125
@@ -330,4 +330,4 @@ String signMessage(String message, {String address = ""}) {
330
331 bool verifyMessage(String message, String address, String signature) {
332 return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
333 -}
\ No newline at end of file
333 +}
cw_monero/lib/api/wallet_manager.dart
+86 -61
@@ -7,19 +7,18 @@ import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
7 import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
8 import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
9 import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
10 -import 'package:cw_monero/api/wallet.dart';
10 import 'package:cw_monero/api/transaction_history.dart';
11 +import 'package:cw_monero/api/wallet.dart';
12 +import 'package:cw_monero/ledger.dart';
13 import 'package:monero/monero.dart' as monero;
14
15 class MoneroCException implements Exception {
16 final String message;
17
18 MoneroCException(this.message);
18 -
19 +
20 @override
20 - String toString() {
21 - return message;
22 - }
21 + String toString() => message;
22 }
23
24 void checkIfMoneroCIsFine() {
@@ -43,7 +42,6 @@ void checkIfMoneroCIsFine() {
42 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'");
43 }
44 }
46 -
45 monero.WalletManager? _wmPtr;
46 final monero.WalletManager wmPtr = Pointer.fromAddress((() {
47 try {
@@ -60,6 +58,13 @@ final monero.WalletManager wmPtr = Pointer.fromAddress((() {
58 return _wmPtr!.address;
59 })());
60
61 +void createWalletPointer() {
62 + final newWptr = monero.WalletManager_createWallet(wmPtr,
63 + path: "", password: "", language: "", networkType: 0);
64 +
65 + wptr = newWptr;
66 +}
67 +
68 void createWalletSync(
69 {required String path,
70 required String password,
@@ -124,24 +129,24 @@ void restoreWalletFromKeysSync(
129 int restoreHeight = 0}) {
130 txhistory = null;
131 var newWptr = (spendKey != "")
127 - ? monero.WalletManager_createDeterministicWalletFromSpendKey(
128 - wmPtr,
129 - path: path,
130 - password: password,
131 - language: language,
132 - spendKeyString: spendKey,
133 - newWallet: true, // TODO(mrcyjanek): safe to remove
134 - restoreHeight: restoreHeight)
135 - : monero.WalletManager_createWalletFromKeys(
136 - wmPtr,
137 - path: path,
138 - password: password,
139 - restoreHeight: restoreHeight,
140 - addressString: address,
141 - viewKeyString: viewKey,
142 - spendKeyString: spendKey,
143 - nettype: 0,
144 - );
132 + ? monero.WalletManager_createDeterministicWalletFromSpendKey(wmPtr,
133 + path: path,
134 + password: password,
135 + language: language,
136 + spendKeyString: spendKey,
137 + newWallet: true,
138 + // TODO(mrcyjanek): safe to remove
139 + restoreHeight: restoreHeight)
140 + : monero.WalletManager_createWalletFromKeys(
141 + wmPtr,
142 + path: path,
143 + password: password,
144 + restoreHeight: restoreHeight,
145 + addressString: address,
146 + viewKeyString: viewKey,
147 + spendKeyString: spendKey,
148 + nettype: 0,
149 + );
150
151 final status = monero.Wallet_status(newWptr);
152 if (status != 0) {
@@ -156,7 +161,7 @@ void restoreWalletFromKeysSync(
161 if (viewKey != viewKeyRestored && viewKey != "") {
162 monero.WalletManager_closeWallet(wmPtr, newWptr, false);
163 File(path).deleteSync();
159 - File(path+".keys").deleteSync();
164 + File(path + ".keys").deleteSync();
165 newWptr = monero.WalletManager_createWalletFromKeys(
166 wmPtr,
167 path: path,
@@ -199,7 +204,7 @@ void restoreWalletFromSpendKeySync(
204 // viewKeyString: '',
205 // nettype: 0,
206 // );
202 -
207 +
208 txhistory = null;
209 final newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey(
210 wmPtr,
@@ -230,41 +235,39 @@ void restoreWalletFromSpendKeySync(
235
236 String _lastOpenedWallet = "";
237
233 -// void restoreMoneroWalletFromDevice(
234 -// {required String path,
235 -// required String password,
236 -// required String deviceName,
237 -// int nettype = 0,
238 -// int restoreHeight = 0}) {
239 -//
240 -// final pathPointer = path.toNativeUtf8();
241 -// final passwordPointer = password.toNativeUtf8();
242 -// final deviceNamePointer = deviceName.toNativeUtf8();
243 -// final errorMessagePointer = ''.toNativeUtf8();
244 -//
245 -// final isWalletRestored = restoreWalletFromDeviceNative(
246 -// pathPointer,
247 -// passwordPointer,
248 -// deviceNamePointer,
249 -// nettype,
250 -// restoreHeight,
251 -// errorMessagePointer) != 0;
252 -//
253 -// calloc.free(pathPointer);
254 -// calloc.free(passwordPointer);
255 -//
256 -// storeSync();
257 -//
258 -// if (!isWalletRestored) {
259 -// throw WalletRestoreFromKeysException(
260 -// message: convertUTF8ToString(pointer: errorMessagePointer));
261 -// }
262 -// }
238 +Future<void> restoreWalletFromHardwareWallet(
239 + {required String path,
240 + required String password,
241 + required String deviceName,
242 + int nettype = 0,
243 + int restoreHeight = 0}) async {
244 + txhistory = null;
245 +
246 + final newWptrAddr = await Isolate.run(() {
247 + return monero.WalletManager_createWalletFromDevice(wmPtr,
248 + path: path,
249 + password: password,
250 + restoreHeight: restoreHeight,
251 + deviceName: deviceName)
252 + .address;
253 + });
254 + final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
255 +
256 + final status = monero.Wallet_status(newWptr);
257 +
258 + if (status != 0) {
259 + final error = monero.Wallet_errorString(newWptr);
260 + throw WalletRestoreFromSeedException(message: error);
261 + }
262 + wptr = newWptr;
263 +
264 + openedWalletsByPath[path] = wptr!;
265 +}
266
267 Map<String, monero.wallet> openedWalletsByPath = {};
268
266 -void loadWallet(
267 - {required String path, required String password, int nettype = 0}) {
269 +Future<void> loadWallet(
270 + {required String path, required String password, int nettype = 0}) async {
271 if (openedWalletsByPath[path] != null) {
272 txhistory = null;
273 wptr = openedWalletsByPath[path]!;
@@ -278,8 +281,29 @@ void loadWallet(
281 });
282 }
283 txhistory = null;
281 - final newWptr = monero.WalletManager_openWallet(wmPtr,
282 - path: path, password: password);
284 +
285 + /// Get the device type
286 + /// 0: Software Wallet
287 + /// 1: Ledger
288 + /// 2: Trezor
289 + final deviceType = monero.WalletManager_queryWalletDevice(wmPtr,
290 + keysFileName: "$path.keys", password: password, kdfRounds: 1);
291 +
292 + if (deviceType == 1) {
293 + final dummyWPtr = wptr ??
294 + monero.WalletManager_openWallet(wmPtr, path: '', password: '');
295 + enableLedgerExchange(dummyWPtr, gLedger!);
296 + }
297 +
298 + final addr = wmPtr.address;
299 + final newWptrAddr = await Isolate.run(() {
300 + return monero.WalletManager_openWallet(Pointer.fromAddress(addr),
301 + path: path, password: password)
302 + .address;
303 + });
304 +
305 + final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
306 +
307 _lastOpenedWallet = path;
308 final status = monero.Wallet_status(newWptr);
309 if (status != 0) {
@@ -287,6 +311,7 @@ void loadWallet(
311 print(err);
312 throw WalletOpeningException(message: err);
313 }
314 +
315 wptr = newWptr;
316 openedWalletsByPath[path] = wptr!;
317 }
@@ -351,7 +376,7 @@ Future<void> _openWallet(Map<String, String> args) async => loadWallet(
376
377 bool _isWalletExist(String path) => isWalletExistSync(path: path);
378
354 -void openWallet(
379 +Future<void> openWallet(
380 {required String path,
381 required String password,
382 int nettype = 0}) async =>
cw_monero/lib/ledger.dart new
+88
@@ -0,0 +1,88 @@
1 +import 'dart:async';
2 +import 'dart:ffi';
3 +import 'dart:typed_data';
4 +
5 +import 'package:ffi/ffi.dart';
6 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
7 +import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
8 +import 'package:monero/monero.dart' as monero;
9 +// import 'package:polyseed/polyseed.dart';
10 +
11 +LedgerConnection? gLedger;
12 +
13 +Timer? _ledgerExchangeTimer;
14 +Timer? _ledgerKeepAlive;
15 +
16 +void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
17 + _ledgerExchangeTimer?.cancel();
18 + _ledgerExchangeTimer = Timer.periodic(Duration(milliseconds: 1), (_) async {
19 + final ledgerRequestLength = monero.Wallet_getSendToDeviceLength(ptr);
20 + final ledgerRequest = monero.Wallet_getSendToDevice(ptr)
21 + .cast<Uint8>()
22 + .asTypedList(ledgerRequestLength);
23 + if (ledgerRequestLength > 0) {
24 + _ledgerKeepAlive?.cancel();
25 +
26 + final Pointer<Uint8> emptyPointer = malloc<Uint8>(0);
27 + monero.Wallet_setDeviceSendData(
28 + ptr, emptyPointer.cast<UnsignedChar>(), 0);
29 + malloc.free(emptyPointer);
30 +
31 + // print("> ${ledgerRequest.toHexString()}");
32 + final response = await exchange(connection, ledgerRequest);
33 + // print("< ${response.toHexString()}");
34 +
35 + final Pointer<Uint8> result = malloc<Uint8>(response.length);
36 + for (var i = 0; i < response.length; i++) {
37 + result.asTypedList(response.length)[i] = response[i];
38 + }
39 +
40 + monero.Wallet_setDeviceReceivedData(
41 + ptr, result.cast<UnsignedChar>(), response.length);
42 + malloc.free(result);
43 + keepAlive(connection);
44 + }
45 + });
46 +}
47 +
48 +void keepAlive(LedgerConnection connection) {
49 + if (connection.connectionType == ConnectionType.ble) {
50 + UniversalBle.onConnectionChange = (String deviceId, bool isConnected) {
51 + print("[Monero] Ledger Disconnected");
52 + _ledgerKeepAlive?.cancel();
53 + };
54 + _ledgerKeepAlive = Timer.periodic(Duration(seconds: 10), (_) async {
55 + try {
56 + UniversalBle.setNotifiable(
57 + connection.device.id,
58 + connection.device.deviceInfo.serviceId,
59 + connection.device.deviceInfo.notifyCharacteristicKey,
60 + BleInputProperty.notification,
61 + );
62 + } catch (_){}
63 + });
64 + }
65 +}
66 +
67 +void disableLedgerExchange() {
68 + _ledgerExchangeTimer?.cancel();
69 + _ledgerKeepAlive?.cancel();
70 + gLedger?.disconnect();
71 + gLedger = null;
72 +}
73 +
74 +Future<Uint8List> exchange(LedgerConnection connection, Uint8List data) async =>
75 + connection.sendOperation<Uint8List>(ExchangeOperation(data));
76 +
77 +class ExchangeOperation extends LedgerRawOperation<Uint8List> {
78 + final Uint8List inputData;
79 +
80 + ExchangeOperation(this.inputData);
81 +
82 + @override
83 + Future<Uint8List> read(ByteDataReader reader) async =>
84 + reader.read(reader.remainingLength);
85 +
86 + @override
87 + Future<List<Uint8List>> write(ByteDataWriter writer) async => [inputData];
88 +}
cw_monero/lib/monero_wallet.dart
+7
@@ -28,6 +28,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet;
28 import 'package:cw_monero/api/wallet_manager.dart';
29 import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart';
30 import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart';
31 +import 'package:cw_monero/ledger.dart';
32 import 'package:cw_monero/monero_transaction_creation_credentials.dart';
33 import 'package:cw_monero/monero_transaction_history.dart';
34 import 'package:cw_monero/monero_transaction_info.dart';
@@ -36,6 +37,7 @@ import 'package:cw_monero/monero_wallet_addresses.dart';
37 import 'package:cw_monero/pending_monero_transaction.dart';
38 import 'package:flutter/foundation.dart';
39 import 'package:hive/hive.dart';
40 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
41 import 'package:mobx/mobx.dart';
42 import 'package:monero/monero.dart' as monero;
43
@@ -828,4 +830,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
830 return monero_wallet.verifyMessage(message, address, signature);
831 }
832
833 + void setLedgerConnection(LedgerConnection connection) {
834 + final dummyWPtr = wptr ??
835 + monero.WalletManager_openWallet(wmPtr, path: '', password: '');
836 + enableLedgerExchange(dummyWPtr, connection);
837 + }
838 }
cw_monero/lib/monero_wallet_service.dart
+76 -32
@@ -9,10 +9,13 @@ import 'package:cw_core/wallet_info.dart';
9 import 'package:cw_core/wallet_service.dart';
10 import 'package:cw_core/wallet_type.dart';
11 import 'package:cw_core/get_height_by_date.dart';
12 +import 'package:cw_monero/api/account_list.dart';
13 import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
14 import 'package:cw_monero/api/wallet_manager.dart';
15 +import 'package:cw_monero/ledger.dart';
16 import 'package:cw_monero/monero_wallet.dart';
17 import 'package:hive/hive.dart';
18 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
19 import 'package:polyseed/polyseed.dart';
20 import 'package:monero/monero.dart' as monero;
21
@@ -25,6 +28,15 @@ class MoneroNewWalletCredentials extends WalletCredentials {
28 final bool isPolyseed;
29 }
30
31 +class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
32 + MoneroRestoreWalletFromHardwareCredentials({required String name,
33 + required this.ledgerConnection,
34 + int height = 0,
35 + String? password})
36 + : super(name: name, password: password, height: height);
37 + LedgerConnection ledgerConnection;
38 +}
39 +
40 class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
41 MoneroRestoreWalletFromSeedCredentials(
42 {required String name, required this.mnemonic, int height = 0, String? password})
@@ -39,14 +51,13 @@ class MoneroWalletLoadingException implements Exception {
51 }
52
53 class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
42 - MoneroRestoreWalletFromKeysCredentials(
43 - {required String name,
44 - required String password,
45 - required this.language,
46 - required this.address,
47 - required this.viewKey,
48 - required this.spendKey,
49 - int height = 0})
54 + MoneroRestoreWalletFromKeysCredentials({required String name,
55 + required String password,
56 + required this.language,
57 + required this.address,
58 + required this.viewKey,
59 + required this.spendKey,
60 + int height = 0})
61 : super(name: name, password: password, height: height);
62
63 final String language;
@@ -59,7 +70,7 @@ class MoneroWalletService extends WalletService<
70 MoneroNewWalletCredentials,
71 MoneroRestoreWalletFromSeedCredentials,
72 MoneroRestoreWalletFromKeysCredentials,
62 - MoneroNewWalletCredentials> {
73 + MoneroRestoreWalletFromHardwareCredentials> {
74 MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
75
76 final Box<WalletInfo> walletInfoSource;
@@ -81,7 +92,7 @@ class MoneroWalletService extends WalletService<
92 final lang = PolyseedLang.getByEnglishName(credentials.language);
93
94 final heightOverride =
84 - getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
95 + getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
96
97 return _restoreFromPolyseed(
98 path, credentials.password!, polyseed, credentials.walletInfo!, lang,
@@ -91,9 +102,9 @@ class MoneroWalletService extends WalletService<
102 await monero_wallet_manager.createWallet(
103 path: path, password: credentials.password!, language: credentials.language);
104 final wallet = MoneroWallet(
94 - walletInfo: credentials.walletInfo!,
95 - unspentCoinsInfo: unspentCoinsInfoSource,
96 - password: credentials.password!);
105 + walletInfo: credentials.walletInfo!,
106 + unspentCoinsInfo: unspentCoinsInfoSource,
107 + password: credentials.password!);
108 await wallet.init();
109
110 return wallet;
@@ -128,11 +139,11 @@ class MoneroWalletService extends WalletService<
139 await monero_wallet_manager
140 .openWalletAsync({'path': path, 'password': password});
141 final walletInfo = walletInfoSource.values.firstWhere(
131 - (info) => info.id == WalletBase.idFor(name, getType()));
142 + (info) => info.id == WalletBase.idFor(name, getType()));
143 final wallet = MoneroWallet(
133 - walletInfo: walletInfo,
134 - unspentCoinsInfo: unspentCoinsInfoSource,
135 - password: password);
144 + walletInfo: walletInfo,
145 + unspentCoinsInfo: unspentCoinsInfoSource,
146 + password: password);
147 final isValid = wallet.walletAddresses.validate();
148
149 if (!isValid) {
@@ -185,10 +196,9 @@ class MoneroWalletService extends WalletService<
196 }
197
198 @override
188 - Future<void> rename(
189 - String currentName, String password, String newName) async {
199 + Future<void> rename(String currentName, String password, String newName) async {
200 final currentWalletInfo = walletInfoSource.values.firstWhere(
191 - (info) => info.id == WalletBase.idFor(currentName, getType()));
201 + (info) => info.id == WalletBase.idFor(currentName, getType()));
202 final currentWallet = MoneroWallet(
203 walletInfo: currentWalletInfo,
204 unspentCoinsInfo: unspentCoinsInfoSource,
@@ -218,9 +228,9 @@ class MoneroWalletService extends WalletService<
228 viewKey: credentials.viewKey,
229 spendKey: credentials.spendKey);
230 final wallet = MoneroWallet(
221 - walletInfo: credentials.walletInfo!,
222 - unspentCoinsInfo: unspentCoinsInfoSource,
223 - password: credentials.password!);
231 + walletInfo: credentials.walletInfo!,
232 + unspentCoinsInfo: unspentCoinsInfoSource,
233 + password: credentials.password!);
234 await wallet.init();
235
236 return wallet;
@@ -232,9 +242,34 @@ class MoneroWalletService extends WalletService<
242 }
243
244 @override
235 - Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) {
236 - throw UnimplementedError(
237 - "Restoring a Monero wallet from a hardware wallet is not yet supported!");
245 + Future<MoneroWallet> restoreFromHardwareWallet(
246 + MoneroRestoreWalletFromHardwareCredentials credentials) async {
247 + try {
248 + final path = await pathForWallet(name: credentials.name, type: getType());
249 + final password = credentials.password;
250 + final height = credentials.height;
251 +
252 + if (wptr == null ) monero_wallet_manager.createWalletPointer();
253 +
254 + enableLedgerExchange(wptr!, credentials.ledgerConnection);
255 + await monero_wallet_manager.restoreWalletFromHardwareWallet(
256 + path: path,
257 + password: password!,
258 + restoreHeight: height!,
259 + deviceName: 'Ledger');
260 +
261 + final wallet = MoneroWallet(
262 + walletInfo: credentials.walletInfo!,
263 + unspentCoinsInfo: unspentCoinsInfoSource,
264 + password: credentials.password!);
265 + await wallet.init();
266 +
267 + return wallet;
268 + } catch (e) {
269 + // TODO: Implement Exception for wallet list service.
270 + print('MoneroWalletsManager Error: $e');
271 + rethrow;
272 + }
273 }
274
275 @override
@@ -253,9 +288,9 @@ class MoneroWalletService extends WalletService<
288 seed: credentials.mnemonic,
289 restoreHeight: credentials.height!);
290 final wallet = MoneroWallet(
256 - walletInfo: credentials.walletInfo!,
257 - unspentCoinsInfo: unspentCoinsInfoSource,
258 - password: credentials.password!);
291 + walletInfo: credentials.walletInfo!,
292 + unspentCoinsInfo: unspentCoinsInfoSource,
293 + password: credentials.password!);
294 await wallet.init();
295
296 return wallet;
@@ -283,8 +318,8 @@ class MoneroWalletService extends WalletService<
318 }
319 }
320
286 - Future<MoneroWallet> _restoreFromPolyseed(
287 - String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
321 + Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
322 + WalletInfo walletInfo, PolyseedLang lang,
323 {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
324 final height = overrideHeight ??
325 getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
@@ -329,7 +364,9 @@ class MoneroWalletService extends WalletService<
364
365 dir.listSync().forEach((f) {
366 final file = File(f.path);
332 - final name = f.path.split('/').last;
367 + final name = f.path
368 + .split('/')
369 + .last;
370 final newPath = newWalletDirPath + '/$name';
371 final newFile = File(newPath);
372
@@ -366,4 +403,11 @@ class MoneroWalletService extends WalletService<
403 return '';
404 }
405 }
406 +
407 + @override
408 + bool requireHardwareWalletConnection(String name) {
409 + final walletInfo = walletInfoSource.values
410 + .firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
411 + return walletInfo.isHardwareWallet;
412 + }
413 }
cw_monero/pubspec.lock
+99 -27
@@ -29,10 +29,10 @@ packages:
29 dependency: transitive
30 description:
31 name: asn1lib
32 - sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
32 + sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70"
33 url: "https://pub.dev"
34 source: hosted
35 - version: "1.5.3"
35 + version: "1.5.5"
36 async:
37 dependency: transitive
38 description:
@@ -41,6 +41,14 @@ packages:
41 url: "https://pub.dev"
42 source: hosted
43 version: "2.11.0"
44 + bluez:
45 + dependency: transitive
46 + description:
47 + name: bluez
48 + sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
49 + url: "https://pub.dev"
50 + source: hosted
51 + version: "0.8.2"
52 boolean_selector:
53 dependency: transitive
54 description:
@@ -209,6 +217,14 @@ packages:
217 url: "https://pub.dev"
218 source: hosted
219 version: "2.2.4"
220 + dbus:
221 + dependency: transitive
222 + description:
223 + name: dbus
224 + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
225 + url: "https://pub.dev"
226 + source: hosted
227 + version: "0.7.10"
228 encrypt:
229 dependency: "direct main"
230 description:
@@ -229,10 +245,10 @@ packages:
245 dependency: "direct main"
246 description:
247 name: ffi
232 - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
248 + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
249 url: "https://pub.dev"
250 source: hosted
235 - version: "2.1.2"
251 + version: "2.1.3"
252 file:
253 dependency: transitive
254 description:
@@ -267,6 +283,14 @@ packages:
283 description: flutter
284 source: sdk
285 version: "0.0.0"
286 + flutter_web_bluetooth:
287 + dependency: transitive
288 + description:
289 + name: flutter_web_bluetooth
290 + sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
291 + url: "https://pub.dev"
292 + source: hosted
293 + version: "0.2.3"
294 frontend_server_client:
295 dependency: transitive
296 description:
@@ -295,18 +319,18 @@ packages:
319 dependency: transitive
320 description:
321 name: hashlib
298 - sha256: d41795742c10947930630118c6836608deeb9047cd05aee32d2baeb697afd66a
322 + sha256: f572f2abce09fc7aee53f15927052b9732ea1053e540af8cae211111ee0b99b1
323 url: "https://pub.dev"
324 source: hosted
301 - version: "1.19.2"
325 + version: "1.21.0"
326 hashlib_codecs:
327 dependency: transitive
328 description:
329 name: hashlib_codecs
306 - sha256: "2b570061f5a4b378425be28a576c1e11783450355ad4345a19f606ff3d96db0f"
330 + sha256: "8cea9ccafcfeaa7324d2ae52c61c69f7ff71f4237507a018caab31b9e416e3b1"
331 url: "https://pub.dev"
332 source: hosted
309 - version: "2.5.0"
333 + version: "2.6.0"
334 hive:
335 dependency: transitive
336 description:
@@ -327,10 +351,10 @@ packages:
351 dependency: "direct main"
352 description:
353 name: http
330 - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
354 + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
355 url: "https://pub.dev"
356 source: hosted
333 - version: "1.2.1"
357 + version: "1.2.2"
358 http_multi_server:
359 dependency: transitive
360 description:
@@ -403,6 +427,22 @@ packages:
427 url: "https://pub.dev"
428 source: hosted
429 version: "2.0.1"
430 + ledger_flutter_plus:
431 + dependency: "direct main"
432 + description:
433 + name: ledger_flutter_plus
434 + sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
435 + url: "https://pub.dev"
436 + source: hosted
437 + version: "1.4.1"
438 + ledger_usb_plus:
439 + dependency: transitive
440 + description:
441 + name: ledger_usb_plus
442 + sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
443 + url: "https://pub.dev"
444 + source: hosted
445 + version: "1.0.4"
446 logging:
447 dependency: transitive
448 description:
@@ -439,10 +479,10 @@ packages:
479 dependency: transitive
480 description:
481 name: mime
442 - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
482 + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
483 url: "https://pub.dev"
484 source: hosted
445 - version: "1.0.5"
485 + version: "1.0.6"
486 mobx:
487 dependency: "direct main"
488 description:
@@ -463,8 +503,8 @@ packages:
503 dependency: "direct main"
504 description:
505 path: "impls/monero.dart"
466 - ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
467 - resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
506 + ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
507 + resolved-ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
508 url: "https://github.com/mrcyjanek/monero_c"
509 source: git
510 version: "0.0.0"
@@ -504,10 +544,10 @@ packages:
544 dependency: "direct main"
545 description:
546 name: path_provider
507 - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
547 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
548 url: "https://pub.dev"
549 source: hosted
510 - version: "2.1.3"
550 + version: "2.1.4"
551 path_provider_android:
552 dependency: transitive
553 description:
@@ -544,10 +584,18 @@ packages:
584 dependency: transitive
585 description:
586 name: path_provider_windows
547 - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
587 + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
588 url: "https://pub.dev"
589 source: hosted
550 - version: "2.2.1"
590 + version: "2.3.0"
591 + petitparser:
592 + dependency: transitive
593 + description:
594 + name: petitparser
595 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
596 + url: "https://pub.dev"
597 + source: hosted
598 + version: "6.0.2"
599 platform:
600 dependency: transitive
601 description:
@@ -612,6 +660,14 @@ packages:
660 url: "https://pub.dev"
661 source: hosted
662 version: "1.3.0"
663 + rxdart:
664 + dependency: transitive
665 + description:
666 + name: rxdart
667 + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
668 + url: "https://pub.dev"
669 + source: hosted
670 + version: "0.28.0"
671 shelf:
672 dependency: transitive
673 description:
@@ -737,6 +793,22 @@ packages:
793 url: "https://pub.dev"
794 source: hosted
795 version: "1.3.2"
796 + universal_ble:
797 + dependency: transitive
798 + description:
799 + name: universal_ble
800 + sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
801 + url: "https://pub.dev"
802 + source: hosted
803 + version: "0.12.0"
804 + universal_platform:
805 + dependency: transitive
806 + description:
807 + name: universal_platform
808 + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
809 + url: "https://pub.dev"
810 + source: hosted
811 + version: "1.1.0"
812 unorm_dart:
813 dependency: transitive
814 description:
@@ -785,22 +857,22 @@ packages:
857 url: "https://pub.dev"
858 source: hosted
859 version: "2.4.5"
788 - win32:
860 + xdg_directories:
861 dependency: transitive
862 description:
791 - name: win32
792 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
863 + name: xdg_directories
864 + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
865 url: "https://pub.dev"
866 source: hosted
795 - version: "5.5.0"
796 - xdg_directories:
867 + version: "1.1.0"
868 + xml:
869 dependency: transitive
870 description:
799 - name: xdg_directories
800 - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
871 + name: xml
872 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
873 url: "https://pub.dev"
874 source: hosted
803 - version: "1.0.4"
875 + version: "6.5.0"
876 yaml:
877 dependency: transitive
878 description:
@@ -811,4 +883,4 @@ packages:
883 version: "3.1.2"
884 sdks:
885 dart: ">=3.3.0 <4.0.0"
814 - flutter: ">=3.16.6"
886 + flutter: ">=3.19.0"
cw_monero/pubspec.yaml
+3 -1
@@ -25,9 +25,11 @@ dependencies:
25 monero:
26 git:
27 url: https://github.com/mrcyjanek/monero_c
28 - ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
28 + ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
29 +# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
30 path: impls/monero.dart
31 mutex: ^3.1.0
32 + ledger_flutter_plus: ^1.4.1
33
34 dev_dependencies:
35 flutter_test:
cw_wownero/lib/api/exceptions/connection_to_node_exception.dart new
+5
@@ -0,0 +1,5 @@
1 +class ConnectionToNodeException implements Exception {
2 + ConnectionToNodeException({required this.message});
3 +
4 + final String message;
5 +}
\ No newline at end of file
cw_wownero/lib/api/structs/account_row.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'dart:ffi';
2 +import 'package:ffi/ffi.dart';
3 +
4 +class AccountRow extends Struct {
5 + @Int64()
6 + external int id;
7 +
8 + external Pointer<Utf8> label;
9 +
10 + String getLabel() => label.toDartString();
11 + int getId() => id;
12 +}
cw_wownero/lib/api/structs/coins_info_row.dart new
+73
@@ -0,0 +1,73 @@
1 +import 'dart:ffi';
2 +import 'package:ffi/ffi.dart';
3 +
4 +class CoinsInfoRow extends Struct {
5 + @Int64()
6 + external int blockHeight;
7 +
8 + external Pointer<Utf8> hash;
9 +
10 + @Uint64()
11 + external int internalOutputIndex;
12 +
13 + @Uint64()
14 + external int globalOutputIndex;
15 +
16 + @Int8()
17 + external int spent;
18 +
19 + @Int8()
20 + external int frozen;
21 +
22 + @Uint64()
23 + external int spentHeight;
24 +
25 + @Uint64()
26 + external int amount;
27 +
28 + @Int8()
29 + external int rct;
30 +
31 + @Int8()
32 + external int keyImageKnown;
33 +
34 + @Uint64()
35 + external int pkIndex;
36 +
37 + @Uint32()
38 + external int subaddrIndex;
39 +
40 + @Uint32()
41 + external int subaddrAccount;
42 +
43 + external Pointer<Utf8> address;
44 +
45 + external Pointer<Utf8> addressLabel;
46 +
47 + external Pointer<Utf8> keyImage;
48 +
49 + @Uint64()
50 + external int unlockTime;
51 +
52 + @Int8()
53 + external int unlocked;
54 +
55 + external Pointer<Utf8> pubKey;
56 +
57 + @Int8()
58 + external int coinbase;
59 +
60 + external Pointer<Utf8> description;
61 +
62 + String getHash() => hash.toDartString();
63 +
64 + String getAddress() => address.toDartString();
65 +
66 + String getAddressLabel() => addressLabel.toDartString();
67 +
68 + String getKeyImage() => keyImage.toDartString();
69 +
70 + String getPubKey() => pubKey.toDartString();
71 +
72 + String getDescription() => description.toDartString();
73 +}
cw_wownero/lib/api/structs/subaddress_row.dart new
+15
@@ -0,0 +1,15 @@
1 +import 'dart:ffi';
2 +import 'package:ffi/ffi.dart';
3 +
4 +class SubaddressRow extends Struct {
5 + @Int64()
6 + external int id;
7 +
8 + external Pointer<Utf8> address;
9 +
10 + external Pointer<Utf8> label;
11 +
12 + String getLabel() => label.toDartString();
13 + String getAddress() => address.toDartString();
14 + int getId() => id;
15 +}
\ No newline at end of file
cw_wownero/lib/api/structs/transaction_info_row.dart new
+41
@@ -0,0 +1,41 @@
1 +import 'dart:ffi';
2 +import 'package:ffi/ffi.dart';
3 +
4 +class TransactionInfoRow extends Struct {
5 + @Uint64()
6 + external int amount;
7 +
8 + @Uint64()
9 + external int fee;
10 +
11 + @Uint64()
12 + external int blockHeight;
13 +
14 + @Uint64()
15 + external int confirmations;
16 +
17 + @Uint32()
18 + external int subaddrAccount;
19 +
20 + @Int8()
21 + external int direction;
22 +
23 + @Int8()
24 + external int isPending;
25 +
26 + @Uint32()
27 + external int subaddrIndex;
28 +
29 + external Pointer<Utf8> hash;
30 +
31 + external Pointer<Utf8> paymentId;
32 +
33 + @Int64()
34 + external int datetime;
35 +
36 + int getDatetime() => datetime;
37 + int getAmount() => amount >= 0 ? amount : amount * -1;
38 + bool getIsPending() => isPending != 0;
39 + String getHash() => hash.toDartString();
40 + String getPaymentId() => paymentId.toDartString();
41 +}
cw_wownero/lib/api/structs/ut8_box.dart new
+8
@@ -0,0 +1,8 @@
1 +import 'dart:ffi';
2 +import 'package:ffi/ffi.dart';
3 +
4 +class Utf8Box extends Struct {
5 + external Pointer<Utf8> value;
6 +
7 + String getValue() => value.toDartString();
8 +}
cw_wownero/lib/cw_wownero.dart new
+8
@@ -0,0 +1,8 @@
1 +
2 +import 'cw_wownero_platform_interface.dart';
3 +
4 +class CwWownero {
5 + Future<String?> getPlatformVersion() {
6 + return CwWowneroPlatform.instance.getPlatformVersion();
7 + }
8 +}
cw_wownero/lib/cw_wownero_method_channel.dart new
+17
@@ -0,0 +1,17 @@
1 +import 'package:flutter/foundation.dart';
2 +import 'package:flutter/services.dart';
3 +
4 +import 'cw_wownero_platform_interface.dart';
5 +
6 +/// An implementation of [CwWowneroPlatform] that uses method channels.
7 +class MethodChannelCwWownero extends CwWowneroPlatform {
8 + /// The method channel used to interact with the native platform.
9 + @visibleForTesting
10 + final methodChannel = const MethodChannel('cw_wownero');
11 +
12 + @override
13 + Future<String?> getPlatformVersion() async {
14 + final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
15 + return version;
16 + }
17 +}
cw_wownero/lib/cw_wownero_platform_interface.dart new
+29
@@ -0,0 +1,29 @@
1 +import 'package:plugin_platform_interface/plugin_platform_interface.dart';
2 +
3 +import 'cw_wownero_method_channel.dart';
4 +
5 +abstract class CwWowneroPlatform extends PlatformInterface {
6 + /// Constructs a CwWowneroPlatform.
7 + CwWowneroPlatform() : super(token: _token);
8 +
9 + static final Object _token = Object();
10 +
11 + static CwWowneroPlatform _instance = MethodChannelCwWownero();
12 +
13 + /// The default instance of [CwWowneroPlatform] to use.
14 + ///
15 + /// Defaults to [MethodChannelCwWownero].
16 + static CwWowneroPlatform get instance => _instance;
17 +
18 + /// Platform-specific implementations should set this with their own
19 + /// platform-specific class that extends [CwWowneroPlatform] when
20 + /// they register themselves.
21 + static set instance(CwWowneroPlatform instance) {
22 + PlatformInterface.verifyToken(instance, _token);
23 + _instance = instance;
24 + }
25 +
26 + Future<String?> getPlatformVersion() {
27 + throw UnimplementedError('platformVersion() has not been implemented.');
28 + }
29 +}
cw_wownero/lib/mywownero.dart new
+1689
@@ -0,0 +1,1689 @@
1 +const prefixLength = 3;
2 +
3 +String swapEndianBytes(String original) {
4 + if (original.length != 8) {
5 + return '';
6 + }
7 +
8 + return original[6] +
9 + original[7] +
10 + original[4] +
11 + original[5] +
12 + original[2] +
13 + original[3] +
14 + original[0] +
15 + original[1];
16 +}
17 +
18 +List<String> tructWords(List<String> wordSet) {
19 + final start = 0;
20 + final end = prefixLength;
21 +
22 + return wordSet.map((word) => word.substring(start, end)).toList();
23 +}
24 +
25 +String mnemonicDecode(String seed) {
26 + final n = englistWordSet.length;
27 + var out = '';
28 + var wlist = seed.split(' ');
29 + wlist.removeLast();
30 +
31 + for (var i = 0; i < wlist.length; i += 3) {
32 + final w1 =
33 + tructWords(englistWordSet).indexOf(wlist[i].substring(0, prefixLength));
34 + final w2 = tructWords(englistWordSet)
35 + .indexOf(wlist[i + 1].substring(0, prefixLength));
36 + final w3 = tructWords(englistWordSet)
37 + .indexOf(wlist[i + 2].substring(0, prefixLength));
38 +
39 + if (w1 == -1 || w2 == -1 || w3 == -1) {
40 + print("invalid word in mnemonic");
41 + return '';
42 + }
43 +
44 + final x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n);
45 +
46 + if (x % n != w1) {
47 + print("Something went wrong when decoding your private key, please try again");
48 + return '';
49 + }
50 +
51 + final _res = '0000000' + x.toRadixString(16);
52 + final start = _res.length - 8;
53 + final end = _res.length;
54 + final res = _res.substring(start, end);
55 +
56 + out += swapEndianBytes(res);
57 + }
58 +
59 + return out;
60 +}
61 +
62 +final englistWordSet = [
63 + "abbey",
64 + "abducts",
65 + "ability",
66 + "ablaze",
67 + "abnormal",
68 + "abort",
69 + "abrasive",
70 + "absorb",
71 + "abyss",
72 + "academy",
73 + "aces",
74 + "aching",
75 + "acidic",
76 + "acoustic",
77 + "acquire",
78 + "across",
79 + "actress",
80 + "acumen",
81 + "adapt",
82 + "addicted",
83 + "adept",
84 + "adhesive",
85 + "adjust",
86 + "adopt",
87 + "adrenalin",
88 + "adult",
89 + "adventure",
90 + "aerial",
91 + "afar",
92 + "affair",
93 + "afield",
94 + "afloat",
95 + "afoot",
96 + "afraid",
97 + "after",
98 + "against",
99 + "agenda",
100 + "aggravate",
101 + "agile",
102 + "aglow",
103 + "agnostic",
104 + "agony",
105 + "agreed",
106 + "ahead",
107 + "aided",
108 + "ailments",
109 + "aimless",
110 + "airport",
111 + "aisle",
112 + "ajar",
113 + "akin",
114 + "alarms",
115 + "album",
116 + "alchemy",
117 + "alerts",
118 + "algebra",
119 + "alkaline",
120 + "alley",
121 + "almost",
122 + "aloof",
123 + "alpine",
124 + "already",
125 + "also",
126 + "altitude",
127 + "alumni",
128 + "always",
129 + "amaze",
130 + "ambush",
131 + "amended",
132 + "amidst",
133 + "ammo",
134 + "amnesty",
135 + "among",
136 + "amply",
137 + "amused",
138 + "anchor",
139 + "android",
140 + "anecdote",
141 + "angled",
142 + "ankle",
143 + "annoyed",
144 + "answers",
145 + "antics",
146 + "anvil",
147 + "anxiety",
148 + "anybody",
149 + "apart",
150 + "apex",
151 + "aphid",
152 + "aplomb",
153 + "apology",
154 + "apply",
155 + "apricot",
156 + "aptitude",
157 + "aquarium",
158 + "arbitrary",
159 + "archer",
160 + "ardent",
161 + "arena",
162 + "argue",
163 + "arises",
164 + "army",
165 + "around",
166 + "arrow",
167 + "arsenic",
168 + "artistic",
169 + "ascend",
170 + "ashtray",
171 + "aside",
172 + "asked",
173 + "asleep",
174 + "aspire",
175 + "assorted",
176 + "asylum",
177 + "athlete",
178 + "atlas",
179 + "atom",
180 + "atrium",
181 + "attire",
182 + "auburn",
183 + "auctions",
184 + "audio",
185 + "august",
186 + "aunt",
187 + "austere",
188 + "autumn",
189 + "avatar",
190 + "avidly",
191 + "avoid",
192 + "awakened",
193 + "awesome",
194 + "awful",
195 + "awkward",
196 + "awning",
197 + "awoken",
198 + "axes",
199 + "axis",
200 + "axle",
201 + "aztec",
202 + "azure",
203 + "baby",
204 + "bacon",
205 + "badge",
206 + "baffles",
207 + "bagpipe",
208 + "bailed",
209 + "bakery",
210 + "balding",
211 + "bamboo",
212 + "banjo",
213 + "baptism",
214 + "basin",
215 + "batch",
216 + "bawled",
217 + "bays",
218 + "because",
219 + "beer",
220 + "befit",
221 + "begun",
222 + "behind",
223 + "being",
224 + "below",
225 + "bemused",
226 + "benches",
227 + "berries",
228 + "bested",
229 + "betting",
230 + "bevel",
231 + "beware",
232 + "beyond",
233 + "bias",
234 + "bicycle",
235 + "bids",
236 + "bifocals",
237 + "biggest",
238 + "bikini",
239 + "bimonthly",
240 + "binocular",
241 + "biology",
242 + "biplane",
243 + "birth",
244 + "biscuit",
245 + "bite",
246 + "biweekly",
247 + "blender",
248 + "blip",
249 + "bluntly",
250 + "boat",
251 + "bobsled",
252 + "bodies",
253 + "bogeys",
254 + "boil",
255 + "boldly",
256 + "bomb",
257 + "border",
258 + "boss",
259 + "both",
260 + "bounced",
261 + "bovine",
262 + "bowling",
263 + "boxes",
264 + "boyfriend",
265 + "broken",
266 + "brunt",
267 + "bubble",
268 + "buckets",
269 + "budget",
270 + "buffet",
271 + "bugs",
272 + "building",
273 + "bulb",
274 + "bumper",
275 + "bunch",
276 + "business",
277 + "butter",
278 + "buying",
279 + "buzzer",
280 + "bygones",
281 + "byline",
282 + "bypass",
283 + "cabin",
284 + "cactus",
285 + "cadets",
286 + "cafe",
287 + "cage",
288 + "cajun",
289 + "cake",
290 + "calamity",
291 + "camp",
292 + "candy",
293 + "casket",
294 + "catch",
295 + "cause",
296 + "cavernous",
297 + "cease",
298 + "cedar",
299 + "ceiling",
300 + "cell",
301 + "cement",
302 + "cent",
303 + "certain",
304 + "chlorine",
305 + "chrome",
306 + "cider",
307 + "cigar",
308 + "cinema",
309 + "circle",
310 + "cistern",
311 + "citadel",
312 + "civilian",
313 + "claim",
314 + "click",
315 + "clue",
316 + "coal",
317 + "cobra",
318 + "cocoa",
319 + "code",
320 + "coexist",
321 + "coffee",
322 + "cogs",
323 + "cohesive",
324 + "coils",
325 + "colony",
326 + "comb",
327 + "cool",
328 + "copy",
329 + "corrode",
330 + "costume",
331 + "cottage",
332 + "cousin",
333 + "cowl",
334 + "criminal",
335 + "cube",
336 + "cucumber",
337 + "cuddled",
338 + "cuffs",
339 + "cuisine",
340 + "cunning",
341 + "cupcake",
342 + "custom",
343 + "cycling",
344 + "cylinder",
345 + "cynical",
346 + "dabbing",
347 + "dads",
348 + "daft",
349 + "dagger",
350 + "daily",
351 + "damp",
352 + "dangerous",
353 + "dapper",
354 + "darted",
355 + "dash",
356 + "dating",
357 + "dauntless",
358 + "dawn",
359 + "daytime",
360 + "dazed",
361 + "debut",
362 + "decay",
363 + "dedicated",
364 + "deepest",
365 + "deftly",
366 + "degrees",
367 + "dehydrate",
368 + "deity",
369 + "dejected",
370 + "delayed",
371 + "demonstrate",
372 + "dented",
373 + "deodorant",
374 + "depth",
375 + "desk",
376 + "devoid",
377 + "dewdrop",
378 + "dexterity",
379 + "dialect",
380 + "dice",
381 + "diet",
382 + "different",
383 + "digit",
384 + "dilute",
385 + "dime",
386 + "dinner",
387 + "diode",
388 + "diplomat",
389 + "directed",
390 + "distance",
391 + "ditch",
392 + "divers",
393 + "dizzy",
394 + "doctor",
395 + "dodge",
396 + "does",
397 + "dogs",
398 + "doing",
399 + "dolphin",
400 + "domestic",
401 + "donuts",
402 + "doorway",
403 + "dormant",
404 + "dosage",
405 + "dotted",
406 + "double",
407 + "dove",
408 + "down",
409 + "dozen",
410 + "dreams",
411 + "drinks",
412 + "drowning",
413 + "drunk",
414 + "drying",
415 + "dual",
416 + "dubbed",
417 + "duckling",
418 + "dude",
419 + "duets",
420 + "duke",
421 + "dullness",
422 + "dummy",
423 + "dunes",
424 + "duplex",
425 + "duration",
426 + "dusted",
427 + "duties",
428 + "dwarf",
429 + "dwelt",
430 + "dwindling",
431 + "dying",
432 + "dynamite",
433 + "dyslexic",
434 + "each",
435 + "eagle",
436 + "earth",
437 + "easy",
438 + "eating",
439 + "eavesdrop",
440 + "eccentric",
441 + "echo",
442 + "eclipse",
443 + "economics",
444 + "ecstatic",
445 + "eden",
446 + "edgy",
447 + "edited",
448 + "educated",
449 + "eels",
450 + "efficient",
451 + "eggs",
452 + "egotistic",
453 + "eight",
454 + "either",
455 + "eject",
456 + "elapse",
457 + "elbow",
458 + "eldest",
459 + "eleven",
460 + "elite",
461 + "elope",
462 + "else",
463 + "eluded",
464 + "emails",
465 + "ember",
466 + "emerge",
467 + "emit",
468 + "emotion",
469 + "empty",
470 + "emulate",
471 + "energy",
472 + "enforce",
473 + "enhanced",
474 + "enigma",
475 + "enjoy",
476 + "enlist",
477 + "enmity",
478 + "enough",
479 + "enraged",
480 + "ensign",
481 + "entrance",
482 + "envy",
483 + "epoxy",
484 + "equip",
485 + "erase",
486 + "erected",
487 + "erosion",
488 + "error",
489 + "eskimos",
490 + "espionage",
491 + "essential",
492 + "estate",
493 + "etched",
494 + "eternal",
495 + "ethics",
496 + "etiquette",
497 + "evaluate",
498 + "evenings",
499 + "evicted",
500 + "evolved",
501 + "examine",
502 + "excess",
503 + "exhale",
504 + "exit",
505 + "exotic",
506 + "exquisite",
507 + "extra",
508 + "exult",
509 + "fabrics",
510 + "factual",
511 + "fading",
512 + "fainted",
513 + "faked",
514 + "fall",
515 + "family",
516 + "fancy",
517 + "farming",
518 + "fatal",
519 + "faulty",
520 + "fawns",
521 + "faxed",
522 + "fazed",
523 + "feast",
524 + "february",
525 + "federal",
526 + "feel",
527 + "feline",
528 + "females",
529 + "fences",
530 + "ferry",
531 + "festival",
532 + "fetches",
533 + "fever",
534 + "fewest",
535 + "fiat",
536 + "fibula",
537 + "fictional",
538 + "fidget",
539 + "fierce",
540 + "fifteen",
541 + "fight",
542 + "films",
543 + "firm",
544 + "fishing",
545 + "fitting",
546 + "five",
547 + "fixate",
548 + "fizzle",
549 + "fleet",
550 + "flippant",
551 + "flying",
552 + "foamy",
553 + "focus",
554 + "foes",
555 + "foggy",
556 + "foiled",
557 + "folding",
558 + "fonts",
559 + "foolish",
560 + "fossil",
561 + "fountain",
562 + "fowls",
563 + "foxes",
564 + "foyer",
565 + "framed",
566 + "friendly",
567 + "frown",
568 + "fruit",
569 + "frying",
570 + "fudge",
571 + "fuel",
572 + "fugitive",
573 + "fully",
574 + "fuming",
575 + "fungal",
576 + "furnished",
577 + "fuselage",
578 + "future",
579 + "fuzzy",
580 + "gables",
581 + "gadget",
582 + "gags",
583 + "gained",
584 + "galaxy",
585 + "gambit",
586 + "gang",
587 + "gasp",
588 + "gather",
589 + "gauze",
590 + "gave",
591 + "gawk",
592 + "gaze",
593 + "gearbox",
594 + "gecko",
595 + "geek",
596 + "gels",
597 + "gemstone",
598 + "general",
599 + "geometry",
600 + "germs",
601 + "gesture",
602 + "getting",
603 + "geyser",
604 + "ghetto",
605 + "ghost",
606 + "giant",
607 + "giddy",
608 + "gifts",
609 + "gigantic",
610 + "gills",
611 + "gimmick",
612 + "ginger",
613 + "girth",
614 + "giving",
615 + "glass",
616 + "gleeful",
617 + "glide",
618 + "gnaw",
619 + "gnome",
620 + "goat",
621 + "goblet",
622 + "godfather",
623 + "goes",
624 + "goggles",
625 + "going",
626 + "goldfish",
627 + "gone",
628 + "goodbye",
629 + "gopher",
630 + "gorilla",
631 + "gossip",
632 + "gotten",
633 + "gourmet",
634 + "governing",
635 + "gown",
636 + "greater",
637 + "grunt",
638 + "guarded",
639 + "guest",
640 + "guide",
641 + "gulp",
642 + "gumball",
643 + "guru",
644 + "gusts",
645 + "gutter",
646 + "guys",
647 + "gymnast",
648 + "gypsy",
649 + "gyrate",
650 + "habitat",
651 + "hacksaw",
652 + "haggled",
653 + "hairy",
654 + "hamburger",
655 + "happens",
656 + "hashing",
657 + "hatchet",
658 + "haunted",
659 + "having",
660 + "hawk",
661 + "haystack",
662 + "hazard",
663 + "hectare",
664 + "hedgehog",
665 + "heels",
666 + "hefty",
667 + "height",
668 + "hemlock",
669 + "hence",
670 + "heron",
671 + "hesitate",
672 + "hexagon",
673 + "hickory",
674 + "hiding",
675 + "highway",
676 + "hijack",
677 + "hiker",
678 + "hills",
679 + "himself",
680 + "hinder",
681 + "hippo",
682 + "hire",
683 + "history",
684 + "hitched",
685 + "hive",
686 + "hoax",
687 + "hobby",
688 + "hockey",
689 + "hoisting",
690 + "hold",
691 + "honked",
692 + "hookup",
693 + "hope",
694 + "hornet",
695 + "hospital",
696 + "hotel",
697 + "hounded",
698 + "hover",
699 + "howls",
700 + "hubcaps",
701 + "huddle",
702 + "huge",
703 + "hull",
704 + "humid",
705 + "hunter",
706 + "hurried",
707 + "husband",
708 + "huts",
709 + "hybrid",
710 + "hydrogen",
711 + "hyper",
712 + "iceberg",
713 + "icing",
714 + "icon",
715 + "identity",
716 + "idiom",
717 + "idled",
718 + "idols",
719 + "igloo",
720 + "ignore",
721 + "iguana",
722 + "illness",
723 + "imagine",
724 + "imbalance",
725 + "imitate",
726 + "impel",
727 + "inactive",
728 + "inbound",
729 + "incur",
730 + "industrial",
731 + "inexact",
732 + "inflamed",
733 + "ingested",
734 + "initiate",
735 + "injury",
736 + "inkling",
737 + "inline",
738 + "inmate",
739 + "innocent",
740 + "inorganic",
741 + "input",
742 + "inquest",
743 + "inroads",
744 + "insult",
745 + "intended",
746 + "inundate",
747 + "invoke",
748 + "inwardly",
749 + "ionic",
750 + "irate",
751 + "iris",
752 + "irony",
753 + "irritate",
754 + "island",
755 + "isolated",
756 + "issued",
757 + "italics",
758 + "itches",
759 + "items",
760 + "itinerary",
761 + "itself",
762 + "ivory",
763 + "jabbed",
764 + "jackets",
765 + "jaded",
766 + "jagged",
767 + "jailed",
768 + "jamming",
769 + "january",
770 + "jargon",
771 + "jaunt",
772 + "javelin",
773 + "jaws",
774 + "jazz",
775 + "jeans",
776 + "jeers",
777 + "jellyfish",
778 + "jeopardy",
779 + "jerseys",
780 + "jester",
781 + "jetting",
782 + "jewels",
783 + "jigsaw",
784 + "jingle",
785 + "jittery",
786 + "jive",
787 + "jobs",
788 + "jockey",
789 + "jogger",
790 + "joining",
791 + "joking",
792 + "jolted",
793 + "jostle",
794 + "journal",
795 + "joyous",
796 + "jubilee",
797 + "judge",
798 + "juggled",
799 + "juicy",
800 + "jukebox",
801 + "july",
802 + "jump",
803 + "junk",
804 + "jury",
805 + "justice",
806 + "juvenile",
807 + "kangaroo",
808 + "karate",
809 + "keep",
810 + "kennel",
811 + "kept",
812 + "kernels",
813 + "kettle",
814 + "keyboard",
815 + "kickoff",
816 + "kidneys",
817 + "king",
818 + "kiosk",
819 + "kisses",
820 + "kitchens",
821 + "kiwi",
822 + "knapsack",
823 + "knee",
824 + "knife",
825 + "knowledge",
826 + "knuckle",
827 + "koala",
828 + "laboratory",
829 + "ladder",
830 + "lagoon",
831 + "lair",
832 + "lakes",
833 + "lamb",
834 + "language",
835 + "laptop",
836 + "large",
837 + "last",
838 + "later",
839 + "launching",
840 + "lava",
841 + "lawsuit",
842 + "layout",
843 + "lazy",
844 + "lectures",
845 + "ledge",
846 + "leech",
847 + "left",
848 + "legion",
849 + "leisure",
850 + "lemon",
851 + "lending",
852 + "leopard",
853 + "lesson",
854 + "lettuce",
855 + "lexicon",
856 + "liar",
857 + "library",
858 + "licks",
859 + "lids",
860 + "lied",
861 + "lifestyle",
862 + "light",
863 + "likewise",
864 + "lilac",
865 + "limits",
866 + "linen",
867 + "lion",
868 + "lipstick",
869 + "liquid",
870 + "listen",
871 + "lively",
872 + "loaded",
873 + "lobster",
874 + "locker",
875 + "lodge",
876 + "lofty",
877 + "logic",
878 + "loincloth",
879 + "long",
880 + "looking",
881 + "lopped",
882 + "lordship",
883 + "losing",
884 + "lottery",
885 + "loudly",
886 + "love",
887 + "lower",
888 + "loyal",
889 + "lucky",
890 + "luggage",
891 + "lukewarm",
892 + "lullaby",
893 + "lumber",
894 + "lunar",
895 + "lurk",
896 + "lush",
897 + "luxury",
898 + "lymph",
899 + "lynx",
900 + "lyrics",
901 + "macro",
902 + "madness",
903 + "magically",
904 + "mailed",
905 + "major",
906 + "makeup",
907 + "malady",
908 + "mammal",
909 + "maps",
910 + "masterful",
911 + "match",
912 + "maul",
913 + "maverick",
914 + "maximum",
915 + "mayor",
916 + "maze",
917 + "meant",
918 + "mechanic",
919 + "medicate",
920 + "meeting",
921 + "megabyte",
922 + "melting",
923 + "memoir",
924 + "menu",
925 + "merger",
926 + "mesh",
927 + "metro",
928 + "mews",
929 + "mice",
930 + "midst",
931 + "mighty",
932 + "mime",
933 + "mirror",
934 + "misery",
935 + "mittens",
936 + "mixture",
937 + "moat",
938 + "mobile",
939 + "mocked",
940 + "mohawk",
941 + "moisture",
942 + "molten",
943 + "moment",
944 + "money",
945 + "moon",
946 + "mops",
947 + "morsel",
948 + "mostly",
949 + "motherly",
950 + "mouth",
951 + "movement",
952 + "mowing",
953 + "much",
954 + "muddy",
955 + "muffin",
956 + "mugged",
957 + "mullet",
958 + "mumble",
959 + "mundane",
960 + "muppet",
961 + "mural",
962 + "musical",
963 + "muzzle",
964 + "myriad",
965 + "mystery",
966 + "myth",
967 + "nabbing",
968 + "nagged",
969 + "nail",
970 + "names",
971 + "nanny",
972 + "napkin",
973 + "narrate",
974 + "nasty",
975 + "natural",
976 + "nautical",
977 + "navy",
978 + "nearby",
979 + "necklace",
980 + "needed",
981 + "negative",
982 + "neither",
983 + "neon",
984 + "nephew",
985 + "nerves",
986 + "nestle",
987 + "network",
988 + "neutral",
989 + "never",
990 + "newt",
991 + "nexus",
992 + "nibs",
993 + "niche",
994 + "niece",
995 + "nifty",
996 + "nightly",
997 + "nimbly",
998 + "nineteen",
999 + "nirvana",
1000 + "nitrogen",
1001 + "nobody",
1002 + "nocturnal",
1003 + "nodes",
1004 + "noises",
1005 + "nomad",
1006 + "noodles",
1007 + "northern",
1008 + "nostril",
1009 + "noted",
1010 + "nouns",
1011 + "novelty",
1012 + "nowhere",
1013 + "nozzle",
1014 + "nuance",
1015 + "nucleus",
1016 + "nudged",
1017 + "nugget",
1018 + "nuisance",
1019 + "null",
1020 + "number",
1021 + "nuns",
1022 + "nurse",
1023 + "nutshell",
1024 + "nylon",
1025 + "oaks",
1026 + "oars",
1027 + "oasis",
1028 + "oatmeal",
1029 + "obedient",
1030 + "object",
1031 + "obliged",
1032 + "obnoxious",
1033 + "observant",
1034 + "obtains",
1035 + "obvious",
1036 + "occur",
1037 + "ocean",
1038 + "october",
1039 + "odds",
1040 + "odometer",
1041 + "offend",
1042 + "often",
1043 + "oilfield",
1044 + "ointment",
1045 + "okay",
1046 + "older",
1047 + "olive",
1048 + "olympics",
1049 + "omega",
1050 + "omission",
1051 + "omnibus",
1052 + "onboard",
1053 + "oncoming",
1054 + "oneself",
1055 + "ongoing",
1056 + "onion",
1057 + "online",
1058 + "onslaught",
1059 + "onto",
1060 + "onward",
1061 + "oozed",
1062 + "opacity",
1063 + "opened",
1064 + "opposite",
1065 + "optical",
1066 + "opus",
1067 + "orange",
1068 + "orbit",
1069 + "orchid",
1070 + "orders",
1071 + "organs",
1072 + "origin",
1073 + "ornament",
1074 + "orphans",
1075 + "oscar",
1076 + "ostrich",
1077 + "otherwise",
1078 + "otter",
1079 + "ouch",
1080 + "ought",
1081 + "ounce",
1082 + "ourselves",
1083 + "oust",
1084 + "outbreak",
1085 + "oval",
1086 + "oven",
1087 + "owed",
1088 + "owls",
1089 + "owner",
1090 + "oxidant",
1091 + "oxygen",
1092 + "oyster",
1093 + "ozone",
1094 + "pact",
1095 + "paddles",
1096 + "pager",
1097 + "pairing",
1098 + "palace",
1099 + "pamphlet",
1100 + "pancakes",
1101 + "paper",
1102 + "paradise",
1103 + "pastry",
1104 + "patio",
1105 + "pause",
1106 + "pavements",
1107 + "pawnshop",
1108 + "payment",
1109 + "peaches",
1110 + "pebbles",
1111 + "peculiar",
1112 + "pedantic",
1113 + "peeled",
1114 + "pegs",
1115 + "pelican",
1116 + "pencil",
1117 + "people",
1118 + "pepper",
1119 + "perfect",
1120 + "pests",
1121 + "petals",
1122 + "phase",
1123 + "pheasants",
1124 + "phone",
1125 + "phrases",
1126 + "physics",
1127 + "piano",
1128 + "picked",
1129 + "pierce",
1130 + "pigment",
1131 + "piloted",
1132 + "pimple",
1133 + "pinched",
1134 + "pioneer",
1135 + "pipeline",
1136 + "pirate",
1137 + "pistons",
1138 + "pitched",
1139 + "pivot",
1140 + "pixels",
1141 + "pizza",
1142 + "playful",
1143 + "pledge",
1144 + "pliers",
1145 + "plotting",
1146 + "plus",
1147 + "plywood",
1148 + "poaching",
1149 + "pockets",
1150 + "podcast",
1151 + "poetry",
1152 + "point",
1153 + "poker",
1154 + "polar",
1155 + "ponies",
1156 + "pool",
1157 + "popular",
1158 + "portents",
1159 + "possible",
1160 + "potato",
1161 + "pouch",
1162 + "poverty",
1163 + "powder",
1164 + "pram",
1165 + "present",
1166 + "pride",
1167 + "problems",
1168 + "pruned",
1169 + "prying",
1170 + "psychic",
1171 + "public",
1172 + "puck",
1173 + "puddle",
1174 + "puffin",
1175 + "pulp",
1176 + "pumpkins",
1177 + "punch",
1178 + "puppy",
1179 + "purged",
1180 + "push",
1181 + "putty",
1182 + "puzzled",
1183 + "pylons",
1184 + "pyramid",
1185 + "python",
1186 + "queen",
1187 + "quick",
1188 + "quote",
1189 + "rabbits",
1190 + "racetrack",
1191 + "radar",
1192 + "rafts",
1193 + "rage",
1194 + "railway",
1195 + "raking",
1196 + "rally",
1197 + "ramped",
1198 + "randomly",
1199 + "rapid",
1200 + "rarest",
1201 + "rash",
1202 + "rated",
1203 + "ravine",
1204 + "rays",
1205 + "razor",
1206 + "react",
1207 + "rebel",
1208 + "recipe",
1209 + "reduce",
1210 + "reef",
1211 + "refer",
1212 + "regular",
1213 + "reheat",
1214 + "reinvest",
1215 + "rejoices",
1216 + "rekindle",
1217 + "relic",
1218 + "remedy",
1219 + "renting",
1220 + "reorder",
1221 + "repent",
1222 + "request",
1223 + "reruns",
1224 + "rest",
1225 + "return",
1226 + "reunion",
1227 + "revamp",
1228 + "rewind",
1229 + "rhino",
1230 + "rhythm",
1231 + "ribbon",
1232 + "richly",
1233 + "ridges",
1234 + "rift",
1235 + "rigid",
1236 + "rims",
1237 + "ringing",
1238 + "riots",
1239 + "ripped",
1240 + "rising",
1241 + "ritual",
1242 + "river",
1243 + "roared",
1244 + "robot",
1245 + "rockets",
1246 + "rodent",
1247 + "rogue",
1248 + "roles",
1249 + "romance",
1250 + "roomy",
1251 + "roped",
1252 + "roster",
1253 + "rotate",
1254 + "rounded",
1255 + "rover",
1256 + "rowboat",
1257 + "royal",
1258 + "ruby",
1259 + "rudely",
1260 + "ruffled",
1261 + "rugged",
1262 + "ruined",
1263 + "ruling",
1264 + "rumble",
1265 + "runway",
1266 + "rural",
1267 + "rustled",
1268 + "ruthless",
1269 + "sabotage",
1270 + "sack",
1271 + "sadness",
1272 + "safety",
1273 + "saga",
1274 + "sailor",
1275 + "sake",
1276 + "salads",
1277 + "sample",
1278 + "sanity",
1279 + "sapling",
1280 + "sarcasm",
1281 + "sash",
1282 + "satin",
1283 + "saucepan",
1284 + "saved",
1285 + "sawmill",
1286 + "saxophone",
1287 + "sayings",
1288 + "scamper",
1289 + "scenic",
1290 + "school",
1291 + "science",
1292 + "scoop",
1293 + "scrub",
1294 + "scuba",
1295 + "seasons",
1296 + "second",
1297 + "sedan",
1298 + "seeded",
1299 + "segments",
1300 + "seismic",
1301 + "selfish",
1302 + "semifinal",
1303 + "sensible",
1304 + "september",
1305 + "sequence",
1306 + "serving",
1307 + "session",
1308 + "setup",
1309 + "seventh",
1310 + "sewage",
1311 + "shackles",
1312 + "shelter",
1313 + "shipped",
1314 + "shocking",
1315 + "shrugged",
1316 + "shuffled",
1317 + "shyness",
1318 + "siblings",
1319 + "sickness",
1320 + "sidekick",
1321 + "sieve",
1322 + "sifting",
1323 + "sighting",
1324 + "silk",
1325 + "simplest",
1326 + "sincerely",
1327 + "sipped",
1328 + "siren",
1329 + "situated",
1330 + "sixteen",
1331 + "sizes",
1332 + "skater",
1333 + "skew",
1334 + "skirting",
1335 + "skulls",
1336 + "skydive",
1337 + "slackens",
1338 + "sleepless",
1339 + "slid",
1340 + "slower",
1341 + "slug",
1342 + "smash",
1343 + "smelting",
1344 + "smidgen",
1345 + "smog",
1346 + "smuggled",
1347 + "snake",
1348 + "sneeze",
1349 + "sniff",
1350 + "snout",
1351 + "snug",
1352 + "soapy",
1353 + "sober",
1354 + "soccer",
1355 + "soda",
1356 + "software",
1357 + "soggy",
1358 + "soil",
1359 + "solved",
1360 + "somewhere",
1361 + "sonic",
1362 + "soothe",
1363 + "soprano",
1364 + "sorry",
1365 + "southern",
1366 + "sovereign",
1367 + "sowed",
1368 + "soya",
1369 + "space",
1370 + "speedy",
1371 + "sphere",
1372 + "spiders",
1373 + "splendid",
1374 + "spout",
1375 + "sprig",
1376 + "spud",
1377 + "spying",
1378 + "square",
1379 + "stacking",
1380 + "stellar",
1381 + "stick",
1382 + "stockpile",
1383 + "strained",
1384 + "stunning",
1385 + "stylishly",
1386 + "subtly",
1387 + "succeed",
1388 + "suddenly",
1389 + "suede",
1390 + "suffice",
1391 + "sugar",
1392 + "suitcase",
1393 + "sulking",
1394 + "summon",
1395 + "sunken",
1396 + "superior",
1397 + "surfer",
1398 + "sushi",
1399 + "suture",
1400 + "swagger",
1401 + "swept",
1402 + "swiftly",
1403 + "sword",
1404 + "swung",
1405 + "syllabus",
1406 + "symptoms",
1407 + "syndrome",
1408 + "syringe",
1409 + "system",
1410 + "taboo",
1411 + "tacit",
1412 + "tadpoles",
1413 + "tagged",
1414 + "tail",
1415 + "taken",
1416 + "talent",
1417 + "tamper",
1418 + "tanks",
1419 + "tapestry",
1420 + "tarnished",
1421 + "tasked",
1422 + "tattoo",
1423 + "taunts",
1424 + "tavern",
1425 + "tawny",
1426 + "taxi",
1427 + "teardrop",
1428 + "technical",
1429 + "tedious",
1430 + "teeming",
1431 + "tell",
1432 + "template",
1433 + "tender",
1434 + "tepid",
1435 + "tequila",
1436 + "terminal",
1437 + "testing",
1438 + "tether",
1439 + "textbook",
1440 + "thaw",
1441 + "theatrics",
1442 + "thirsty",
1443 + "thorn",
1444 + "threaten",
1445 + "thumbs",
1446 + "thwart",
1447 + "ticket",
1448 + "tidy",
1449 + "tiers",
1450 + "tiger",
1451 + "tilt",
1452 + "timber",
1453 + "tinted",
1454 + "tipsy",
1455 + "tirade",
1456 + "tissue",
1457 + "titans",
1458 + "toaster",
1459 + "tobacco",
1460 + "today",
1461 + "toenail",
1462 + "toffee",
1463 + "together",
1464 + "toilet",
1465 + "token",
1466 + "tolerant",
1467 + "tomorrow",
1468 + "tonic",
1469 + "toolbox",
1470 + "topic",
1471 + "torch",
1472 + "tossed",
1473 + "total",
1474 + "touchy",
1475 + "towel",
1476 + "toxic",
1477 + "toyed",
1478 + "trash",
1479 + "trendy",
1480 + "tribal",
1481 + "trolling",
1482 + "truth",
1483 + "trying",
1484 + "tsunami",
1485 + "tubes",
1486 + "tucks",
1487 + "tudor",
1488 + "tuesday",
1489 + "tufts",
1490 + "tugs",
1491 + "tuition",
1492 + "tulips",
1493 + "tumbling",
1494 + "tunnel",
1495 + "turnip",
1496 + "tusks",
1497 + "tutor",
1498 + "tuxedo",
1499 + "twang",
1500 + "tweezers",
1501 + "twice",
1502 + "twofold",
1503 + "tycoon",
1504 + "typist",
1505 + "tyrant",
1506 + "ugly",
1507 + "ulcers",
1508 + "ultimate",
1509 + "umbrella",
1510 + "umpire",
1511 + "unafraid",
1512 + "unbending",
1513 + "uncle",
1514 + "under",
1515 + "uneven",
1516 + "unfit",
1517 + "ungainly",
1518 + "unhappy",
1519 + "union",
1520 + "unjustly",
1521 + "unknown",
1522 + "unlikely",
1523 + "unmask",
1524 + "unnoticed",
1525 + "unopened",
1526 + "unplugs",
1527 + "unquoted",
1528 + "unrest",
1529 + "unsafe",
1530 + "until",
1531 + "unusual",
1532 + "unveil",
1533 + "unwind",
1534 + "unzip",
1535 + "upbeat",
1536 + "upcoming",
1537 + "update",
1538 + "upgrade",
1539 + "uphill",
1540 + "upkeep",
1541 + "upload",
1542 + "upon",
1543 + "upper",
1544 + "upright",
1545 + "upstairs",
1546 + "uptight",
1547 + "upwards",
1548 + "urban",
1549 + "urchins",
1550 + "urgent",
1551 + "usage",
1552 + "useful",
1553 + "usher",
1554 + "using",
1555 + "usual",
1556 + "utensils",
1557 + "utility",
1558 + "utmost",
1559 + "utopia",
1560 + "uttered",
1561 + "vacation",
1562 + "vague",
1563 + "vain",
1564 + "value",
1565 + "vampire",
1566 + "vane",
1567 + "vapidly",
1568 + "vary",
1569 + "vastness",
1570 + "vats",
1571 + "vaults",
1572 + "vector",
1573 + "veered",
1574 + "vegan",
1575 + "vehicle",
1576 + "vein",
1577 + "velvet",
1578 + "venomous",
1579 + "verification",
1580 + "vessel",
1581 + "veteran",
1582 + "vexed",
1583 + "vials",
1584 + "vibrate",
1585 + "victim",
1586 + "video",
1587 + "viewpoint",
1588 + "vigilant",
1589 + "viking",
1590 + "village",
1591 + "vinegar",
1592 + "violin",
1593 + "vipers",
1594 + "virtual",
1595 + "visited",
1596 + "vitals",
1597 + "vivid",
1598 + "vixen",
1599 + "vocal",
1600 + "vogue",
1601 + "voice",
1602 + "volcano",
1603 + "vortex",
1604 + "voted",
1605 + "voucher",
1606 + "vowels",
1607 + "voyage",
1608 + "vulture",
1609 + "wade",
1610 + "waffle",
1611 + "wagtail",
1612 + "waist",
1613 + "waking",
1614 + "wallets",
1615 + "wanted",
1616 + "warped",
1617 + "washing",
1618 + "water",
1619 + "waveform",
1620 + "waxing",
1621 + "wayside",
1622 + "weavers",
1623 + "website",
1624 + "wedge",
1625 + "weekday",
1626 + "weird",
1627 + "welders",
1628 + "went",
1629 + "wept",
1630 + "were",
1631 + "western",
1632 + "wetsuit",
1633 + "whale",
1634 + "when",
1635 + "whipped",
1636 + "whole",
1637 + "wickets",
1638 + "width",
1639 + "wield",
1640 + "wife",
1641 + "wiggle",
1642 + "wildly",
1643 + "winter",
1644 + "wipeout",
1645 + "wiring",
1646 + "wise",
1647 + "withdrawn",
1648 + "wives",
1649 + "wizard",
1650 + "wobbly",
1651 + "woes",
1652 + "woken",
1653 + "wolf",
1654 + "womanly",
1655 + "wonders",
1656 + "woozy",
1657 + "worry",
1658 + "wounded",
1659 + "woven",
1660 + "wrap",
1661 + "wrist",
1662 + "wrong",
1663 + "yacht",
1664 + "yahoo",
1665 + "yanks",
1666 + "yard",
1667 + "yawning",
1668 + "yearbook",
1669 + "yellow",
1670 + "yesterday",
1671 + "yeti",
1672 + "yields",
1673 + "yodel",
1674 + "yoga",
1675 + "younger",
1676 + "yoyo",
1677 + "zapped",
1678 + "zeal",
1679 + "zebra",
1680 + "zero",
1681 + "zesty",
1682 + "zigzags",
1683 + "zinger",
1684 + "zippers",
1685 + "zodiac",
1686 + "zombie",
1687 + "zones",
1688 + "zoom"
1689 +];
cw_wownero/pubspec.lock
+86 -6
@@ -41,6 +41,14 @@ packages:
41 url: "https://pub.dev"
42 source: hosted
43 version: "2.11.0"
44 + bluez:
45 + dependency: transitive
46 + description:
47 + name: bluez
48 + sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
49 + url: "https://pub.dev"
50 + source: hosted
51 + version: "0.8.2"
52 boolean_selector:
53 dependency: transitive
54 description:
@@ -209,6 +217,14 @@ packages:
217 url: "https://pub.dev"
218 source: hosted
219 version: "2.2.4"
220 + dbus:
221 + dependency: transitive
222 + description:
223 + name: dbus
224 + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
225 + url: "https://pub.dev"
226 + source: hosted
227 + version: "0.7.10"
228 encrypt:
229 dependency: "direct main"
230 description:
@@ -267,6 +283,14 @@ packages:
283 description: flutter
284 source: sdk
285 version: "0.0.0"
286 + flutter_web_bluetooth:
287 + dependency: transitive
288 + description:
289 + name: flutter_web_bluetooth
290 + sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
291 + url: "https://pub.dev"
292 + source: hosted
293 + version: "0.2.3"
294 frontend_server_client:
295 dependency: transitive
296 description:
@@ -403,6 +427,22 @@ packages:
427 url: "https://pub.dev"
428 source: hosted
429 version: "2.0.1"
430 + ledger_flutter_plus:
431 + dependency: transitive
432 + description:
433 + name: ledger_flutter_plus
434 + sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
435 + url: "https://pub.dev"
436 + source: hosted
437 + version: "1.2.5"
438 + ledger_usb_plus:
439 + dependency: transitive
440 + description:
441 + name: ledger_usb_plus
442 + sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
443 + url: "https://pub.dev"
444 + source: hosted
445 + version: "1.0.4"
446 logging:
447 dependency: transitive
448 description:
@@ -463,8 +503,8 @@ packages:
503 dependency: "direct main"
504 description:
505 path: "impls/monero.dart"
466 - ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
467 - resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
506 + ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
507 + resolved-ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
508 url: "https://github.com/mrcyjanek/monero_c"
509 source: git
510 version: "0.0.0"
@@ -540,6 +580,14 @@ packages:
580 url: "https://pub.dev"
581 source: hosted
582 version: "2.2.1"
583 + petitparser:
584 + dependency: transitive
585 + description:
586 + name: petitparser
587 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
588 + url: "https://pub.dev"
589 + source: hosted
590 + version: "6.0.2"
591 platform:
592 dependency: transitive
593 description:
@@ -552,10 +600,10 @@ packages:
600 dependency: transitive
601 description:
602 name: plugin_platform_interface
555 - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
603 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
604 url: "https://pub.dev"
605 source: hosted
558 - version: "2.1.3"
606 + version: "2.1.8"
607 pointycastle:
608 dependency: transitive
609 description:
@@ -596,6 +644,14 @@ packages:
644 url: "https://pub.dev"
645 source: hosted
646 version: "1.2.1"
647 + rxdart:
648 + dependency: transitive
649 + description:
650 + name: rxdart
651 + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
652 + url: "https://pub.dev"
653 + source: hosted
654 + version: "0.28.0"
655 shelf:
656 dependency: transitive
657 description:
@@ -721,6 +777,22 @@ packages:
777 url: "https://pub.dev"
778 source: hosted
779 version: "1.3.1"
780 + universal_ble:
781 + dependency: transitive
782 + description:
783 + name: universal_ble
784 + sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
785 + url: "https://pub.dev"
786 + source: hosted
787 + version: "0.12.0"
788 + universal_platform:
789 + dependency: transitive
790 + description:
791 + name: universal_platform
792 + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
793 + url: "https://pub.dev"
794 + source: hosted
795 + version: "1.1.0"
796 unorm_dart:
797 dependency: transitive
798 description:
@@ -777,6 +849,14 @@ packages:
849 url: "https://pub.dev"
850 source: hosted
851 version: "1.0.4"
852 + xml:
853 + dependency: transitive
854 + description:
855 + name: xml
856 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
857 + url: "https://pub.dev"
858 + source: hosted
859 + version: "6.5.0"
860 yaml:
861 dependency: transitive
862 description:
@@ -786,5 +866,5 @@ packages:
866 source: hosted
867 version: "3.1.1"
868 sdks:
789 - dart: ">=3.2.0-0 <4.0.0"
790 - flutter: ">=3.7.0"
869 + dart: ">=3.3.0 <4.0.0"
870 + flutter: ">=3.19.0"
cw_wownero/pubspec.yaml
+2 -1
@@ -25,7 +25,8 @@ dependencies:
25 monero:
26 git:
27 url: https://github.com/mrcyjanek/monero_c
28 - ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
28 + ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
29 +# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
30 path: impls/monero.dart
31 mutex: ^3.1.0
32
lib/core/wallet_loading_service.dart
+12 -2
@@ -14,7 +14,11 @@ import 'package:flutter/material.dart';
14 import 'package:shared_preferences/shared_preferences.dart';
15
16 class WalletLoadingService {
17 - WalletLoadingService(this.sharedPreferences, this.keyService, this.walletServiceFactory);
17 + WalletLoadingService(
18 + this.sharedPreferences,
19 + this.keyService,
20 + this.walletServiceFactory,
21 + );
22
23 final SharedPreferences sharedPreferences;
24 final KeyService keyService;
@@ -77,7 +81,8 @@ class WalletLoadingService {
81 await updateMoneroWalletPassword(wallet);
82 }
83
80 - await sharedPreferences.setString(PreferencesKey.currentWalletName, wallet.name);
84 + await sharedPreferences.setString(
85 + PreferencesKey.currentWalletName, wallet.name);
86 await sharedPreferences.setInt(
87 PreferencesKey.currentWalletType, serializeToInt(wallet.type));
88
@@ -129,4 +134,9 @@ class WalletLoadingService {
134
135 return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}";
136 }
137 +
138 + bool requireHardwareWalletConnection(WalletType type, String name) {
139 + final walletService = walletServiceFactory.call(type);
140 + return walletService.requireHardwareWalletConnection(name);
141 + }
142 }
lib/di.dart
+9 -6
@@ -32,12 +32,14 @@ import 'package:cake_wallet/entities/biometric_auth.dart';
32 import 'package:cake_wallet/entities/contact.dart';
33 import 'package:cake_wallet/entities/contact_record.dart';
34 import 'package:cake_wallet/entities/exchange_api_mode.dart';
35 +import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
36 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
37 import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
38 import 'package:cake_wallet/entities/wallet_manager.dart';
39 import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
40 import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
41 import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
42 +import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
43 import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
44 import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
45 import 'package:cake_wallet/view_model/link_view_model.dart';
@@ -184,7 +186,6 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_
186 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
187 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
188 import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
187 -import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
189 import 'package:cake_wallet/store/app_store.dart';
190 import 'package:cake_wallet/store/authentication_store.dart';
191 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -586,7 +587,7 @@ Future<void> setup({
587 );
588 } else {
589 // wallet is already loaded:
589 - if (appStore.wallet != null) {
590 + if (appStore.wallet != null || requireHardwareWalletConnection()) {
591 // goes to the dashboard:
592 authStore.allowed();
593 // trigger any deep links:
@@ -780,10 +781,12 @@ Future<void> setup({
781 );
782 }
783
783 - getIt.registerFactory(() => WalletListPage(
784 - walletListViewModel: getIt.get<WalletListViewModel>(),
785 - authService: getIt.get<AuthService>(),
786 - ));
784 + getIt.registerFactoryParam<WalletListPage, Function(BuildContext)?, void>(
785 + (Function(BuildContext)? onWalletLoaded, _) => WalletListPage(
786 + walletListViewModel: getIt.get<WalletListViewModel>(),
787 + authService: getIt.get<AuthService>(),
788 + onWalletLoaded: onWalletLoaded,
789 + ));
790
791 getIt.registerFactoryParam<WalletEditViewModel, WalletListViewModel, void>(
792 (WalletListViewModel walletListViewModel, _) => WalletEditViewModel(
lib/entities/hardware_wallet/require_hardware_wallet_connection.dart new
+25
@@ -0,0 +1,25 @@
1 +import 'package:cake_wallet/core/wallet_loading_service.dart';
2 +import 'package:cake_wallet/di.dart';
3 +import 'package:cake_wallet/entities/preferences_key.dart';
4 +import 'package:cw_core/wallet_type.dart';
5 +import 'package:shared_preferences/shared_preferences.dart';
6 +
7 +bool requireHardwareWalletConnection() {
8 + final name = getIt
9 + .get<SharedPreferences>()
10 + .getString(PreferencesKey.currentWalletName);
11 + final typeRaw =
12 + getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
13 +
14 + if (typeRaw == null) {
15 + return false;
16 + }
17 +
18 + if (name == null) {
19 + throw Exception('Incorrect current wallet name: $name');
20 + }
21 +
22 + final type = deserializeFromInt(typeRaw);
23 + final walletLoadingService = getIt.get<WalletLoadingService>();
24 + return walletLoadingService.requireHardwareWalletConnection(type, name);
25 +}
lib/monero/cw_monero.dart
+25
@@ -225,6 +225,19 @@ class CWMonero extends Monero {
225 language: language,
226 height: height);
227
228 + @override
229 + WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({
230 + required String name,
231 + required String password,
232 + required int height,
233 + required ledger.LedgerConnection ledgerConnection,
234 + }) =>
235 + MoneroRestoreWalletFromHardwareCredentials(
236 + name: name,
237 + password: password,
238 + height: height,
239 + ledgerConnection: ledgerConnection);
240 +
241 @override
242 WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
243 {required String name,
@@ -383,6 +396,18 @@ class CWMonero extends Monero {
396 checkIfMoneroCIsFine();
397 }
398
399 + @override
400 + void setLedgerConnection(Object wallet, ledger.LedgerConnection connection) {
401 + final moneroWallet = wallet as MoneroWallet;
402 + moneroWallet.setLedgerConnection(connection);
403 + }
404 +
405 + @override
406 + void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
407 + gLedger = connection;
408 + keepAlive(connection);
409 + }
410 +
411 bool isViewOnly() {
412 return isViewOnlyBySpendKey();
413 }
lib/reactions/on_authentication_state_change.dart
+49 -8
@@ -1,18 +1,28 @@
1 import 'dart:async';
2
3 +import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
4 +import 'package:cake_wallet/di.dart';
5 +import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
6 +import 'package:cake_wallet/entities/load_current_wallet.dart';
7 +import 'package:cake_wallet/generated/i18n.dart';
8 +import 'package:cake_wallet/monero/monero.dart';
9 import 'package:cake_wallet/routes.dart';
4 -import 'package:cake_wallet/utils/exception_handler.dart';
10 +import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
11 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
12 +import 'package:cake_wallet/store/authentication_store.dart';
13 import 'package:cake_wallet/store/settings_store.dart';
14 +import 'package:cake_wallet/utils/exception_handler.dart';
15 +import 'package:cake_wallet/utils/show_pop_up.dart';
16 +import 'package:cw_core/wallet_type.dart';
17 import 'package:flutter/widgets.dart';
18 import 'package:mobx/mobx.dart';
8 -import 'package:cake_wallet/entities/load_current_wallet.dart';
9 -import 'package:cake_wallet/store/authentication_store.dart';
19 import 'package:rxdart/subjects.dart';
20
21 ReactionDisposer? _onAuthenticationStateChange;
22
23 dynamic loginError;
15 -StreamController<dynamic> authenticatedErrorStreamController = BehaviorSubject<dynamic>();
24 +StreamController<dynamic> authenticatedErrorStreamController =
25 + BehaviorSubject<dynamic>();
26
27 void startAuthenticationStateChange(
28 AuthenticationStore authenticationStore,
@@ -27,18 +37,49 @@ void startAuthenticationStateChange(
37 _onAuthenticationStateChange ??= autorun((_) async {
38 final state = authenticationStore.state;
39
30 - if (state == AuthenticationState.installed && !SettingsStoreBase.walletPasswordDirectInput) {
40 + if (state == AuthenticationState.installed &&
41 + !SettingsStoreBase.walletPasswordDirectInput) {
42 try {
32 - await loadCurrentWallet();
43 + if (!requireHardwareWalletConnection()) await loadCurrentWallet();
44 } catch (error, stack) {
45 loginError = error;
35 - ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
46 + ExceptionHandler.onError(
47 + FlutterErrorDetails(exception: error, stack: stack));
48 }
49 return;
50 }
51
52 if (state == AuthenticationState.allowed) {
41 - await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
53 + if (requireHardwareWalletConnection()) {
54 + await navigatorKey.currentState!.pushNamedAndRemoveUntil(
55 + Routes.connectDevices,
56 + (route) => false,
57 + arguments: ConnectDevicePageParams(
58 + walletType: WalletType.monero,
59 + onConnectDevice: (context, ledgerVM) async {
60 + monero!.setGlobalLedgerConnection(ledgerVM.connection);
61 + showPopUp<void>(
62 + context: context,
63 + builder: (BuildContext context) => AlertWithOneAction(
64 + alertTitle: S.of(context).proceed_on_device,
65 + alertContent: S.of(context).proceed_on_device_description,
66 + buttonText: S.of(context).cancel,
67 + buttonAction: () => Navigator.of(context).pop()),
68 + );
69 + await loadCurrentWallet();
70 + getIt.get<BottomSheetService>().resetCurrentSheet();
71 + await navigatorKey.currentState!
72 + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
73 + },
74 + allowChangeWallet: true,
75 + ),
76 + );
77 +
78 + // await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.connectDevices, (route) => false, arguments: ConnectDevicePageParams(walletType: walletType, onConnectDevice: onConnectDevice));
79 + } else {
80 + await navigatorKey.currentState!
81 + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
82 + }
83 if (!(await authenticatedErrorStreamController.stream.isEmpty)) {
84 ExceptionHandler.showError(
85 (await authenticatedErrorStreamController.stream.first).toString());
lib/router.dart
+8 -1
@@ -24,6 +24,7 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
24 import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart';
25 import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
26 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
27 +import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
28 import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
29 import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
30 import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@@ -212,6 +213,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
213 final type = arguments[0] as WalletType;
214 final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
215
216 + if (type == WalletType.monero)
217 + return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
218 +
219 return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
220
221 case Routes.setupPin:
@@ -403,8 +407,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
407 return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
408
409 case Routes.walletList:
410 + final onWalletLoaded = settings.arguments as Function(BuildContext)?;
411 return MaterialPageRoute<void>(
407 - fullscreenDialog: true, builder: (_) => getIt.get<WalletListPage>());
412 + fullscreenDialog: true,
413 + builder: (_) => getIt.get<WalletListPage>(param1: onWalletLoaded),
414 + );
415
416 case Routes.walletEdit:
417 return MaterialPageRoute<void>(
lib/src/screens/connect_device/connect_device_page.dart
+48 -18
@@ -2,9 +2,12 @@ import 'dart:async';
2 import 'dart:io';
3
4 import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/routes.dart';
6 import 'package:cake_wallet/src/screens/base_page.dart';
7 import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
8 +import 'package:cake_wallet/src/widgets/primary_button.dart';
9 import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
10 +import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
11 import 'package:cake_wallet/utils/responsive_layout_util.dart';
12 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
13 import 'package:cw_core/wallet_type.dart';
@@ -17,35 +20,46 @@ typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
20 class ConnectDevicePageParams {
21 final WalletType walletType;
22 final OnConnectDevice onConnectDevice;
23 + final bool allowChangeWallet;
24
21 - ConnectDevicePageParams(
22 - {required this.walletType, required this.onConnectDevice});
25 + ConnectDevicePageParams({
26 + required this.walletType,
27 + required this.onConnectDevice,
28 + this.allowChangeWallet = false,
29 + });
30 }
31
32 class ConnectDevicePage extends BasePage {
33 final WalletType walletType;
34 final OnConnectDevice onConnectDevice;
35 + final bool allowChangeWallet;
36 final LedgerViewModel ledgerVM;
37
38 ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
39 : walletType = params.walletType,
32 - onConnectDevice = params.onConnectDevice;
40 + onConnectDevice = params.onConnectDevice,
41 + allowChangeWallet = params.allowChangeWallet;
42
43 @override
44 String get title => S.current.restore_title_from_hardware_wallet;
45
46 @override
38 - Widget body(BuildContext context) =>
39 - ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
47 + Widget body(BuildContext context) => ConnectDevicePageBody(
48 + walletType, onConnectDevice, allowChangeWallet, ledgerVM);
49 }
50
51 class ConnectDevicePageBody extends StatefulWidget {
52 final WalletType walletType;
53 final OnConnectDevice onConnectDevice;
54 + final bool allowChangeWallet;
55 final LedgerViewModel ledgerVM;
56
57 const ConnectDevicePageBody(
48 - this.walletType, this.onConnectDevice, this.ledgerVM);
58 + this.walletType,
59 + this.onConnectDevice,
60 + this.allowChangeWallet,
61 + this.ledgerVM,
62 + );
63
64 @override
65 ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
@@ -102,14 +116,16 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
116
117 Future<void> _refreshBleDevices() async {
118 try {
105 - _bleRefresh = widget.ledgerVM
106 - .scanForBleDevices()
107 - .listen((device) => setState(() => bleDevices.add(device)))
108 - ..onError((e) {
109 - throw e.toString();
110 - });
111 - _bleRefreshTimer?.cancel();
112 - _bleRefreshTimer = null;
119 + if (widget.ledgerVM.bleIsEnabled) {
120 + _bleRefresh = widget.ledgerVM
121 + .scanForBleDevices()
122 + .listen((device) => setState(() => bleDevices.add(device)))
123 + ..onError((e) {
124 + throw e.toString();
125 + });
126 + _bleRefreshTimer?.cancel();
127 + _bleRefreshTimer = null;
128 + }
129 } catch (e) {
130 print(e);
131 }
@@ -227,9 +243,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
243 style: TextStyle(
244 fontSize: 14,
245 fontWeight: FontWeight.w400,
230 - color: Theme.of(context)
231 - .extension<CakeTextTheme>()!
232 - .titleColor,
246 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
247 ),
248 ),
249 ),
@@ -247,11 +261,27 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
261 ),
262 )
263 .toList(),
250 - ]
264 + ],
265 + if (widget.allowChangeWallet) ...[
266 + PrimaryButton(
267 + text: S.of(context).wallets,
268 + color: Theme.of(context).extension<WalletListTheme>()!.createNewWalletButtonBackgroundColor,
269 + textColor: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor,
270 + onPressed: _onChangeWallet,
271 + )
272 + ],
273 ],
274 ),
275 ),
276 ),
277 );
278 }
279 +
280 + void _onChangeWallet() {
281 + Navigator.of(context).pushNamed(
282 + Routes.walletList,
283 + arguments: (BuildContext context) => Navigator.of(context)
284 + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
285 + );
286 + }
287 }
lib/src/screens/connect_device/monero_hardware_wallet_options_page.dart new
+230
@@ -0,0 +1,230 @@
1 +import 'package:cake_wallet/core/wallet_name_validator.dart';
2 +import 'package:cake_wallet/entities/generate_name.dart';
3 +import 'package:cake_wallet/generated/i18n.dart';
4 +import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6 +import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
7 +import 'package:cake_wallet/src/widgets/primary_button.dart';
8 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9 +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
10 +import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
11 +import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
12 +import 'package:cake_wallet/utils/responsive_layout_util.dart';
13 +import 'package:cake_wallet/utils/show_pop_up.dart';
14 +import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
15 +import 'package:cw_core/wallet_type.dart';
16 +import 'package:flutter/material.dart';
17 +import 'package:flutter_mobx/flutter_mobx.dart';
18 +import 'package:mobx/mobx.dart';
19 +
20 +class MoneroHardwareWalletOptionsPage extends BasePage {
21 + MoneroHardwareWalletOptionsPage(this._walletHardwareRestoreVM);
22 +
23 + final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
24 +
25 + @override
26 + String get title => S.current.restore_title_from_hardware_wallet;
27 +
28 + @override
29 + Widget body(BuildContext context) =>
30 + _MoneroHardwareWalletOptionsForm(_walletHardwareRestoreVM);
31 +}
32 +
33 +class _MoneroHardwareWalletOptionsForm extends StatefulWidget {
34 + const _MoneroHardwareWalletOptionsForm(this._walletHardwareRestoreVM);
35 +
36 + final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
37 +
38 + @override
39 + _MoneroHardwareWalletOptionsFormState createState() =>
40 + _MoneroHardwareWalletOptionsFormState(_walletHardwareRestoreVM);
41 +}
42 +
43 +class _MoneroHardwareWalletOptionsFormState
44 + extends State<_MoneroHardwareWalletOptionsForm> {
45 + _MoneroHardwareWalletOptionsFormState(this._walletHardwareRestoreVM)
46 + : _formKey = GlobalKey<FormState>(),
47 + _blockchainHeightKey = GlobalKey<BlockchainHeightState>(),
48 + _blockHeightFocusNode = FocusNode(),
49 + _controller = TextEditingController();
50 +
51 + final GlobalKey<FormState> _formKey;
52 + final GlobalKey<BlockchainHeightState> _blockchainHeightKey;
53 + final FocusNode _blockHeightFocusNode;
54 + final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
55 + final TextEditingController _controller;
56 +
57 + @override
58 + void initState() {
59 + super.initState();
60 + _setEffects(context);
61 + }
62 +
63 + @override
64 + Widget build(BuildContext context) {
65 + return Padding(
66 + padding: EdgeInsets.only(top: 24),
67 + child: ScrollableWithBottomSection(
68 + contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
69 + content: Center(
70 + child: ConstrainedBox(
71 + constraints: BoxConstraints(
72 + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
73 + child: Column(
74 + crossAxisAlignment: CrossAxisAlignment.center,
75 + children: [
76 + Padding(
77 + padding: EdgeInsets.only(top: 0),
78 + child: Form(
79 + key: _formKey,
80 + child: Stack(
81 + alignment: Alignment.centerRight,
82 + children: [
83 + TextFormField(
84 + onChanged: (value) =>
85 + _walletHardwareRestoreVM.name = value,
86 + controller: _controller,
87 + style: TextStyle(
88 + fontSize: 20.0,
89 + fontWeight: FontWeight.w600,
90 + color: Theme.of(context)
91 + .extension<CakeTextTheme>()!
92 + .titleColor,
93 + ),
94 + decoration: InputDecoration(
95 + hintStyle: TextStyle(
96 + fontSize: 18.0,
97 + fontWeight: FontWeight.w500,
98 + color: Theme.of(context)
99 + .extension<NewWalletTheme>()!
100 + .hintTextColor,
101 + ),
102 + hintText: S.of(context).wallet_name,
103 + focusedBorder: UnderlineInputBorder(
104 + borderSide: BorderSide(
105 + color: Theme.of(context)
106 + .extension<NewWalletTheme>()!
107 + .underlineColor,
108 + width: 1.0,
109 + ),
110 + ),
111 + enabledBorder: UnderlineInputBorder(
112 + borderSide: BorderSide(
113 + color: Theme.of(context)
114 + .extension<NewWalletTheme>()!
115 + .underlineColor,
116 + width: 1.0,
117 + ),
118 + ),
119 + suffixIcon: Semantics(
120 + label: S.of(context).generate_name,
121 + child: IconButton(
122 + onPressed: _onGenerateName,
123 + icon: Container(
124 + padding: const EdgeInsets.all(8),
125 + decoration: BoxDecoration(
126 + borderRadius: BorderRadius.circular(6.0),
127 + color: Theme.of(context).hintColor,
128 + ),
129 + width: 34,
130 + height: 34,
131 + child: Image.asset(
132 + 'assets/images/refresh_icon.png',
133 + color: Theme.of(context)
134 + .extension<SendPageTheme>()!
135 + .textFieldButtonIconColor,
136 + ),
137 + ),
138 + ),
139 + ),
140 + ),
141 + validator: WalletNameValidator(),
142 + ),
143 + ],
144 + ),
145 + ),
146 + ),
147 + Padding(
148 + padding: EdgeInsets.only(top: 20),
149 + child: BlockchainHeightWidget(
150 + focusNode: _blockHeightFocusNode,
151 + key: _blockchainHeightKey,
152 + hasDatePicker: true,
153 + walletType: WalletType.monero,
154 + ),
155 + ),
156 + ],
157 + ),
158 + ),
159 + ),
160 + bottomSectionPadding: EdgeInsets.all(24),
161 + bottomSection: Observer(
162 + builder: (context) => LoadingPrimaryButton(
163 + onPressed: _confirmForm,
164 + text: S.of(context).seed_language_next,
165 + color: Colors.green,
166 + textColor: Colors.white,
167 + isDisabled: _walletHardwareRestoreVM.name.isEmpty,
168 + ),
169 + ),
170 + ),
171 + );
172 + }
173 +
174 + Future<void> _onGenerateName() async {
175 + final rName = await generateName();
176 + FocusManager.instance.primaryFocus?.unfocus();
177 +
178 + setState(() {
179 + _controller.text = rName;
180 + _walletHardwareRestoreVM.name = rName;
181 + _controller.selection = TextSelection.fromPosition(
182 + TextPosition(offset: _controller.text.length));
183 + });
184 + }
185 +
186 + Future<void> _confirmForm() async {
187 + showPopUp<void>(
188 + context: context,
189 + builder: (BuildContext context) => AlertWithOneAction(
190 + alertTitle: S.of(context).proceed_on_device,
191 + alertContent: S.of(context).proceed_on_device_description,
192 + buttonText: S.of(context).cancel,
193 + buttonAction: () => Navigator.of(context).pop(),
194 + ),
195 + );
196 +
197 + final options = {'height': _blockchainHeightKey.currentState?.height ?? -1};
198 + await _walletHardwareRestoreVM.create(options: options);
199 + }
200 +
201 + bool _effectsInstalled = false;
202 +
203 + void _setEffects(BuildContext context) {
204 + if (_effectsInstalled) return;
205 +
206 + reaction((_) => _walletHardwareRestoreVM.error, (String? error) {
207 + if (error != null) {
208 + if (error == S.current.ledger_connection_error)
209 + Navigator.of(context).pop();
210 +
211 + WidgetsBinding.instance.addPostFrameCallback((_) {
212 + showPopUp<void>(
213 + context: context,
214 + builder: (BuildContext context) => AlertWithOneAction(
215 + alertTitle: S.of(context).error,
216 + alertContent: error,
217 + buttonText: S.of(context).ok,
218 + buttonAction: () {
219 + _walletHardwareRestoreVM.error = null;
220 + Navigator.of(context).pop();
221 + },
222 + ),
223 + );
224 + });
225 + }
226 + });
227 +
228 + _effectsInstalled = true;
229 + }
230 +}
lib/src/screens/wallet_list/wallet_list_page.dart
+70 -65
@@ -1,44 +1,56 @@
1 +import 'package:another_flushbar/flushbar.dart';
2 +import 'package:cake_wallet/core/auth_service.dart';
3 import 'package:cake_wallet/core/new_wallet_arguments.dart';
4 import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
5 import 'package:cake_wallet/entities/wallet_list_order_types.dart';
6 +import 'package:cake_wallet/generated/i18n.dart';
7 +import 'package:cake_wallet/monero/monero.dart';
8 +import 'package:cake_wallet/routes.dart';
9 +import 'package:cake_wallet/src/screens/auth/auth_page.dart';
10 +import 'package:cake_wallet/src/screens/base_page.dart';
11 +import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
12 import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart';
13 import 'package:cake_wallet/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart';
14 import 'package:cake_wallet/src/screens/wallet_list/edit_wallet_button_widget.dart';
15 import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
16 import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
17 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
18 +import 'package:cake_wallet/src/widgets/primary_button.dart';
19 import 'package:cake_wallet/store/settings_store.dart';
20 import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
11 -import 'package:cake_wallet/src/screens/auth/auth_page.dart';
12 -import 'package:cake_wallet/core/auth_service.dart';
21 import 'package:cake_wallet/themes/extensions/filter_theme.dart';
22 import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
23 import 'package:cake_wallet/utils/responsive_layout_util.dart';
24 import 'package:cake_wallet/utils/show_bar.dart';
25 import 'package:cake_wallet/utils/show_pop_up.dart';
26 import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
19 -import 'package:another_flushbar/flushbar.dart';
20 -import 'package:flutter/material.dart';
21 -import 'package:flutter_mobx/flutter_mobx.dart';
22 -import 'package:cake_wallet/routes.dart';
23 -import 'package:cake_wallet/generated/i18n.dart';
24 -import 'package:cw_core/wallet_type.dart';
27 import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
26 -import 'package:cake_wallet/src/widgets/primary_button.dart';
27 -import 'package:cake_wallet/src/screens/base_page.dart';
28 import 'package:cake_wallet/wallet_type_utils.dart';
29 +import 'package:cw_core/wallet_type.dart';
30 +import 'package:flutter/material.dart';
31 +import 'package:flutter_mobx/flutter_mobx.dart';
32
33 class WalletListPage extends BasePage {
31 - WalletListPage({required this.walletListViewModel, required this.authService});
34 + WalletListPage({
35 + required this.walletListViewModel,
36 + required this.authService,
37 + this.onWalletLoaded,
38 + });
39
40 final WalletListViewModel walletListViewModel;
41 final AuthService authService;
42 + final Function(BuildContext)? onWalletLoaded;
43
44 @override
45 String get title => S.current.wallets;
46
47 @override
40 - Widget body(BuildContext context) =>
41 - WalletListBody(walletListViewModel: walletListViewModel, authService: authService);
48 + Widget body(BuildContext context) => WalletListBody(
49 + walletListViewModel: walletListViewModel,
50 + authService: authService,
51 + onWalletLoaded:
52 + onWalletLoaded ?? (context) => Navigator.of(context).pop(),
53 + );
54
55 @override
56 Widget trailing(BuildContext context) {
@@ -89,10 +101,15 @@ class WalletListPage extends BasePage {
101 }
102
103 class WalletListBody extends StatefulWidget {
92 - WalletListBody({required this.walletListViewModel, required this.authService});
104 + WalletListBody({
105 + required this.walletListViewModel,
106 + required this.authService,
107 + required this.onWalletLoaded,
108 + });
109
110 final WalletListViewModel walletListViewModel;
111 final AuthService authService;
112 + final Function(BuildContext) onWalletLoaded;
113
114 @override
115 WalletListBodyState createState() => WalletListBodyState();
@@ -118,8 +135,8 @@ class WalletListBodyState extends State<WalletListBody> {
135
136 @override
137 Widget build(BuildContext context) {
121 - final newWalletImage =
122 - Image.asset('assets/images/new_wallet.png', height: 12, width: 12, color: Colors.white);
138 + final newWalletImage = Image.asset('assets/images/new_wallet.png',
139 + height: 12, width: 12, color: Colors.white);
140 final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
141 height: 12,
142 width: 12,
@@ -180,8 +197,7 @@ class WalletListBodyState extends State<WalletListBody> {
197 trailingWidget: EditWalletButtonWidget(
198 width: 74,
199 isGroup: true,
183 - isExpanded:
184 - widget.walletListViewModel.expansionTileStateTrack[index]!,
200 + isExpanded: widget.walletListViewModel.expansionTileStateTrack[index]!,
201 onTap: () {
202 final wallet = widget.walletListViewModel
203 .convertWalletInfoToWalletListItem(group.wallets.first);
@@ -198,8 +214,7 @@ class WalletListBodyState extends State<WalletListBody> {
214 },
215 ),
216 childWallets: group.wallets.map((walletInfo) {
201 - return widget.walletListViewModel
202 - .convertWalletInfoToWalletListItem(walletInfo);
217 + return widget.walletListViewModel.convertWalletInfoToWalletListItem(walletInfo);
218 }).toList(),
219 isSelected: false,
220 onChildItemTapped: (wallet) =>
@@ -329,8 +344,7 @@ class WalletListBodyState extends State<WalletListBody> {
344 arguments: NewWalletArguments(
345 type: widget.walletListViewModel.currentWalletType,
346 ),
332 - conditionToDetermineIfToUse2FA:
333 - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
347 + conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
348 );
349 } else {
350 Navigator.of(context).pushNamed(
@@ -345,8 +359,7 @@ class WalletListBodyState extends State<WalletListBody> {
359 widget.authService.authenticateAction(
360 context,
361 route: Routes.newWalletType,
348 - conditionToDetermineIfToUse2FA:
349 - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
362 + conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
363 );
364 } else {
365 Navigator.of(context).pushNamed(Routes.newWalletType);
@@ -367,8 +380,7 @@ class WalletListBodyState extends State<WalletListBody> {
380 context,
381 route: Routes.restoreOptions,
382 arguments: false,
370 - conditionToDetermineIfToUse2FA:
371 - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
383 + conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
384 );
385 } else {
386 Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
@@ -387,39 +399,6 @@ class WalletListBodyState extends State<WalletListBody> {
399 );
400 }
401
390 - Image _imageFor({required WalletType type, bool? isTestnet}) {
391 - switch (type) {
392 - case WalletType.bitcoin:
393 - if (isTestnet == true) {
394 - return tBitcoinIcon;
395 - }
396 - return bitcoinIcon;
397 - case WalletType.monero:
398 - return moneroIcon;
399 - case WalletType.litecoin:
400 - return litecoinIcon;
401 - case WalletType.haven:
402 - return havenIcon;
403 - case WalletType.ethereum:
404 - return ethereumIcon;
405 - case WalletType.bitcoinCash:
406 - return bitcoinCashIcon;
407 - case WalletType.nano:
408 - case WalletType.banano:
409 - return nanoIcon;
410 - case WalletType.polygon:
411 - return polygonIcon;
412 - case WalletType.solana:
413 - return solanaIcon;
414 - case WalletType.tron:
415 - return tronIcon;
416 - case WalletType.wownero:
417 - return wowneroIcon;
418 - case WalletType.none:
419 - return nonWalletTypeIcon;
420 - }
421 - }
422 -
402 Future<void> _loadWallet(WalletListItem wallet) async {
403 if (SettingsStoreBase.walletPasswordDirectInput) {
404 Navigator.of(context).pushNamed(Routes.walletUnlockLoadable,
@@ -438,12 +417,36 @@ class WalletListBodyState extends State<WalletListBody> {
417 await widget.authService.authenticateAction(
418 context,
419 onAuthSuccess: (isAuthenticatedSuccessfully) async {
441 - if (!isAuthenticatedSuccessfully) {
442 - return;
443 - }
420 + if (!isAuthenticatedSuccessfully) return;
421
422 try {
446 - changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
423 + if (widget.walletListViewModel
424 + .requireHardwareWalletConnection(wallet)) {
425 + await Navigator.of(context).pushNamed(
426 + Routes.connectDevices,
427 + arguments: ConnectDevicePageParams(
428 + walletType: WalletType.monero,
429 + onConnectDevice: (context, ledgerVM) async {
430 + monero!.setGlobalLedgerConnection(ledgerVM.connection);
431 + Navigator.of(context).pop();
432 + },
433 + ),
434 + );
435 +
436 + showPopUp<void>(
437 + context: context,
438 + builder: (BuildContext context) => AlertWithOneAction(
439 + alertTitle: S.of(context).proceed_on_device,
440 + alertContent: S.of(context).proceed_on_device_description,
441 + buttonText: S.of(context).cancel,
442 + buttonAction: () => Navigator.of(context).pop()),
443 + );
444 + }
445 +
446 +
447 +
448 + changeProcessText(
449 + S.of(context).wallet_list_loading_wallet(wallet.name));
450 await widget.walletListViewModel.loadWallet(wallet);
451 await hideProgressText();
452 // only pop the wallets route in mobile as it will go back to dashboard page
@@ -451,13 +454,15 @@ class WalletListBodyState extends State<WalletListBody> {
454 if (responsiveLayoutUtil.shouldRenderMobileUI) {
455 WidgetsBinding.instance.addPostFrameCallback((_) {
456 if (this.mounted) {
454 - Navigator.of(context).pop();
457 + widget.onWalletLoaded.call(context);
458 }
459 });
460 }
461 } catch (e) {
462 if (this.mounted) {
460 - changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
463 + changeProcessText(S
464 + .of(context)
465 + .wallet_list_failed_to_load(wallet.name, e.toString()));
466 }
467 }
468 },
lib/view_model/dashboard/dashboard_view_model.dart
+1 -1
@@ -428,7 +428,7 @@ abstract class DashboardViewModelBase with Store {
428 // to not cause work duplication, this will do the job as well, it will be slightly less precise
429 // about what happened - but still enough.
430 // if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0",
431 - if (keys['privateViewKey'] == List.generate(64, (index) => "0").join(""))
431 + if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") && !wallet.isHardwareWallet)
432 "private view key is 0",
433 // if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0",
434 if (keys['publicViewKey'] == List.generate(64, (index) => "0").join(""))
lib/view_model/hardware_wallet/ledger_view_model.dart
+3
@@ -4,6 +4,7 @@ import 'dart:io';
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/generated/i18n.dart';
7 +import 'package:cake_wallet/monero/monero.dart';
8 import 'package:cake_wallet/polygon/polygon.dart';
9 import 'package:cake_wallet/utils/device_info.dart';
10 import 'package:cake_wallet/wallet_type_utils.dart';
@@ -114,6 +115,8 @@ abstract class LedgerViewModelBase with Store {
115
116 void setLedger(WalletBase wallet) {
117 switch (wallet.type) {
118 + case WalletType.monero:
119 + return monero!.setLedgerConnection(wallet, connection);
120 case WalletType.bitcoin:
121 case WalletType.litecoin:
122 return bitcoin!.setLedgerConnection(wallet, connection);
lib/view_model/wallet_hardware_restore_view_model.dart
+13 -2
@@ -1,7 +1,9 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/core/generate_wallet_password.dart';
3 import 'package:cake_wallet/core/wallet_creation_service.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 +import 'package:cake_wallet/monero/monero.dart';
7 import 'package:cake_wallet/polygon/polygon.dart';
8 import 'package:cake_wallet/store/app_store.dart';
9 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
@@ -56,8 +58,8 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
58 List<HardwareAccountData> accounts;
59 switch (type) {
60 case WalletType.bitcoin:
59 - accounts = await bitcoin!
60 - .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
61 + accounts = await bitcoin!
62 + .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
63 break;
64 case WalletType.litecoin:
65 accounts = await bitcoin!
@@ -104,6 +106,15 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
106 case WalletType.polygon:
107 credentials = polygon!.createPolygonHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
108 break;
109 + case WalletType.monero:
110 + final password = walletPassword ?? generateWalletPassword();
111 +
112 + credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
113 + name: name,
114 + ledgerConnection: ledgerViewModel.connection,
115 + password: password,
116 + height: _options['height'] as int? ?? 0,
117 + );
118 default:
119 throw Exception('Unexpected type: ${type.toString()}');
120 }
lib/view_model/wallet_list/wallet_list_view_model.dart
+14 -5
@@ -68,6 +68,10 @@ abstract class WalletListViewModelBase with Store {
68
69 WalletType get currentWalletType => _appStore.wallet!.type;
70
71 + bool requireHardwareWalletConnection(WalletListItem walletItem) =>
72 + _walletLoadingService.requireHardwareWalletConnection(
73 + walletItem.type, walletItem.name);
74 +
75 @action
76 Future<void> loadWallet(WalletListItem walletItem) async {
77 // bool switchingToSameWalletType = walletItem.type == _appStore.wallet?.type;
@@ -87,7 +91,8 @@ abstract class WalletListViewModelBase with Store {
91 singleWalletsList.clear();
92
93 wallets.addAll(
90 - _walletInfoSource.values.map((info) => convertWalletInfoToWalletListItem(info)),
94 + _walletInfoSource.values
95 + .map((info) => convertWalletInfoToWalletListItem(info)),
96 );
97
98 //========== Split into shared seed groups and single wallets list
@@ -95,7 +100,8 @@ abstract class WalletListViewModelBase with Store {
100
101 for (var group in _walletManager.walletGroups) {
102 if (group.wallets.length == 1) {
98 - singleWalletsList.add(convertWalletInfoToWalletListItem(group.wallets.first));
103 + singleWalletsList
104 + .add(convertWalletInfoToWalletListItem(group.wallets.first));
105 } else {
106 multiWalletGroups.add(group);
107 }
@@ -148,9 +154,11 @@ abstract class WalletListViewModelBase with Store {
154 List<WalletInfo> walletInfoSourceCopy = _walletInfoSource.values.toList();
155 await _walletInfoSource.clear();
156 if (ascending) {
151 - walletInfoSourceCopy.sort((a, b) => a.type.toString().compareTo(b.type.toString()));
157 + walletInfoSourceCopy
158 + .sort((a, b) => a.type.toString().compareTo(b.type.toString()));
159 } else {
153 - walletInfoSourceCopy.sort((a, b) => b.type.toString().compareTo(a.type.toString()));
160 + walletInfoSourceCopy
161 + .sort((a, b) => b.type.toString().compareTo(a.type.toString()));
162 }
163 await _walletInfoSource.addAll(walletInfoSourceCopy);
164 updateList();
@@ -213,7 +221,8 @@ abstract class WalletListViewModelBase with Store {
221 name: info.name,
222 type: info.type,
223 key: info.key,
216 - isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
224 + isCurrent: info.name == _appStore.wallet?.name &&
225 + info.type == _appStore.wallet?.type,
226 isEnabled: availableWalletTypes.contains(info.type),
227 isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
228 );
scripts/prepare_moneroc.sh
+2 -2
@@ -6,9 +6,9 @@ cd "$(dirname "$0")"
6
7 if [[ ! -d "monero_c" ]];
8 then
9 - git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip
9 + git clone https://github.com/mrcyjanek/monero_c --branch master
10 cd monero_c
11 - git checkout 6eb571ea498ed7b854934785f00fabfd0dadf75b
11 + git checkout 1d8e0fb30b53c28756f23676d5a3e1a99a9b3051
12 git reset --hard
13 git submodule update --init --force --recursive
14 ./apply_patches.sh monero
tool/configure.dart
+8 -3
@@ -270,20 +270,22 @@ import 'package:cw_core/output_info.dart';
270 import 'package:cake_wallet/view_model/send/output.dart';
271 import 'package:cw_core/wallet_service.dart';
272 import 'package:hive/hive.dart';
273 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
274 import 'package:polyseed/polyseed.dart';""";
275 const moneroCWHeaders = """
276 +import 'package:cw_core/account.dart' as monero_account;
277 import 'package:cw_core/get_height_by_date.dart';
278 import 'package:cw_core/monero_amount_format.dart';
279 import 'package:cw_core/monero_transaction_priority.dart';
280 +import 'package:cw_monero/api/wallet_manager.dart';
281 +import 'package:cw_monero/api/wallet.dart' as monero_wallet_api;
282 +import 'package:cw_monero/ledger.dart';
283 import 'package:cw_monero/monero_unspent.dart';
284 import 'package:cw_monero/api/account_list.dart';
285 import 'package:cw_monero/monero_wallet_service.dart';
281 -import 'package:cw_monero/api/wallet_manager.dart';
286 import 'package:cw_monero/monero_wallet.dart';
287 import 'package:cw_monero/monero_transaction_info.dart';
288 import 'package:cw_monero/monero_transaction_creation_credentials.dart';
285 -import 'package:cw_core/account.dart' as monero_account;
286 -import 'package:cw_monero/api/wallet.dart' as monero_wallet_api;
289 import 'package:cw_monero/mnemonics/english.dart';
290 import 'package:cw_monero/mnemonics/chinese_simplified.dart';
291 import 'package:cw_monero/mnemonics/dutch.dart';
@@ -395,6 +397,7 @@ abstract class Monero {
397 required String language,
398 required int height});
399 WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic});
400 + WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
401 WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password});
402 Map<String, String> getKeys(Object wallet);
403 int? getRestoreHeight(Object wallet);
@@ -411,6 +414,8 @@ abstract class Monero {
414 int getTransactionInfoAccountId(TransactionInfo tx);
415 WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
416 Map<String, String> pendingTransactionInfo(Object transaction);
417 + void setLedgerConnection(Object wallet, ledger.LedgerConnection connection);
418 + void setGlobalLedgerConnection(ledger.LedgerConnection connection);
419 }
420
421 abstract class MoneroSubaddressList {