Cw 1379 add monero support to trezor rebase dev (#3273)

* refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services * refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services * fix: correctly check for Ledger hardware wallet type in `isHardwareWallet` logic * refactor: unify `isConnected` method for hardware wallets, add Trezor-specific support, and update Monero sync logic * refactor: add Trezor transaction signing support, streamline hardware wallet handling, and update Monero dependency * refactor: improve Trezor transaction handling, adjust hardware wallet state logic, and update Monero dependency references * fix: non-hww tx commit() in xmr fix: prod monero_c fix: keyImage import workaround for Trezor/Cupcake chore: add kotlin's .salive to .gitignore * fix: universal_ble builds on iOS chore: remove prints fix: proper CI prebuilt for linux * new trezor connection ui * fix page title * typo * fix page title anim * refactor: enhance Trezor pairing flow, update state management, and simplify UI logic in hardware wallet integration * fix: correct widget indentation in Trezor pairing failure state UI * fix: extract Trezor pairing failure state UI to `_errorBox` widget and update Trezor dependency reference * refactor: simplify hardware wallet pairing error UI, optimize `_errorBox` layout, and streamline widget logic * Update cw_monero/lib/pending_monero_transaction.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Update lib/buy/robinhood/robinhood_buy_provider.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * feat: add USB intent filters, device filter for hardware wallets, and update Trezor dependency reference * Update cw_monero/pubspec.yaml [skip ci] * Update cw_wownero/pubspec.yaml [skip ci] * Update cw_zano/pubspec.yaml [skip ci] * fix: use CryptoCurrency.title instead of amount-refactor-only .symbol getter new_wallet_type_page used curr.symbol, which only exists on the amount-refactor branch (String get symbol => title). On dev the equivalent is curr.title. Adapts the Trezor feature to dev's API without pulling in amount-refactor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix wallet list screen not scrollable minor fixes --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Konstantin Ullrich <konstantin@cakewallet.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Omar Hatem committed Jun 2, 2026 at 02:33 UTC 5b2a8f67559046e89e64d34c841ab2ce0b87e0e5
73 files changed +1851 -451
.github/workflows/pr_test_build_linux.yml
+1 -1
@@ -118,7 +118,7 @@ jobs:
118 pushd scripts/monero_c/release
119 mkdir -p $(git describe --tags)
120 pushd $(git describe --tags)
121 - wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip
121 + wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip
122 unzip release-bundle.zip
123 rm release-bundle.zip
124 popd
.gitignore
+2
@@ -225,3 +225,5 @@ scripts/zcash_lib
225 scripts/android/yttrium
226 scripts/bitbox_flutter
227 scripts/reown_flutter
228 +
229 +*.salive
\ No newline at end of file
android/app/build.gradle
+1 -1
@@ -60,7 +60,7 @@ android {
60
61 defaultConfig {
62 applicationId appProperties['id']
63 - minSdkVersion 24
63 + minSdkVersion 26
64 targetSdkVersion 36
65 versionCode flutterVersionCode.toInteger()
66 versionName flutterVersionName
android/app/src/main/AndroidManifestBase.xml
+8
@@ -128,6 +128,14 @@
128 <data android:scheme="nano-gpt" />
129 </intent-filter>
130
131 +
132 + <intent-filter>
133 + <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
134 + <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
135 + </intent-filter>
136 +
137 + <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
138 + android:resource="@xml/device_filter" />
139 <meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
140 </activity>
141 <meta-data
android/app/src/main/res/xml/device_filter.xml new
+11
@@ -0,0 +1,11 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +
3 +<resources>
4 + <!-- BitBox02 Vendor ID: 0x03eb, Product ID: 0x2403 -->
5 + <usb-device vendor-id="1003" product-id="9219"/>
6 +
7 + <!-- Trezor Devices -->
8 + <usb-device vendor-id="21324" product-id="1"/>
9 + <usb-device vendor-id="4617" product-id="21440"/>
10 + <usb-device vendor-id="4617" product-id="21441"/>
11 +</resources>
assets/images/hardware_wallet/device_ledger_nano_gen_5.svg deleted
-3
@@ -1,3 +0,0 @@
1 -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 -<path d="M16.5 2C17.8807 2 19 3.11929 19 4.5V5C19.2761 5 19.5 5.22386 19.5 5.5V8C19.5 8.24171 19.3286 8.44371 19.1006 8.49023L19 8.5V19.5C19 20.8807 17.8807 22 16.5 22H7.5C6.11929 22 5 20.8807 5 19.5V4.5C5 3.11929 6.11929 2 7.5 2H16.5ZM14.5 19C13.9477 19 13.5 19.4477 13.5 20C13.5 20.5523 13.9477 21 14.5 21H16.5C17.0523 21 17.5 20.5523 17.5 20C17.5 19.4477 17.0523 19 16.5 19H14.5ZM7.5 3.5C6.94772 3.5 6.5 3.94772 6.5 4.5V16.5C6.5 17.0523 6.94772 17.5 7.5 17.5H16.5C17.0523 17.5 17.5 17.0523 17.5 16.5V4.5C17.5 3.94772 17.0523 3.5 16.5 3.5H7.5Z" fill="white"/>
3 -</svg>
assets/images/hardware_wallet/device_trezor_safe_5.svg deleted
-3
@@ -1,3 +0,0 @@
1 -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32">
2 - <path fill="#000" fill-rule="evenodd" d="M7 6a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v12.191a9 9 0 0 1-1.204 4.498l-3.353 5.81A3 3 0 0 1 17.845 30h-3.69a3 3 0 0 1-2.598-1.5l-3.353-5.811A9 9 0 0 1 7 18.19zm4-2a2 2 0 0 0-2 2v12.191a7 7 0 0 0 .937 3.498l3.352 5.81a1 1 0 0 0 .866.501h3.69a1 1 0 0 0 .866-.5l3.352-5.81a7 7 0 0 0 .937-3.5V6a2 2 0 0 0-2-2z" clip-rule="evenodd"/>
3 -</svg>
assets/images/hardware_wallet/ledger_flex.png
Binary files a/assets/images/hardware_wallet/ledger_flex.png and /dev/null differ
assets/images/hardware_wallet/ledger_nano_s.png
Binary files a/assets/images/hardware_wallet/ledger_nano_s.png and /dev/null differ
assets/images/hardware_wallet/ledger_stax.png
Binary files a/assets/images/hardware_wallet/ledger_stax.png and /dev/null differ
cw_core/lib/hardware/device_connection_type.dart
+1 -1
@@ -29,7 +29,7 @@ enum DeviceConnectionType {
29 break;
30 case HardwareWalletType.trezor:
31 isSupported = [
32 - // WalletType.monero,
32 + WalletType.monero,
33 WalletType.bitcoin,
34 WalletType.litecoin,
35 WalletType.ethereum,
cw_core/lib/hardware/hardware_wallet_service.dart
+2 -1
@@ -4,7 +4,8 @@ import 'dart:typed_data';
4 import 'package:cw_core/hardware/hardware_account_data.dart';
5
6 abstract class HardwareWalletService {
7 - Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5});
7 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) =>
8 + throw UnimplementedError();
9
10 Future<Uint8List> signTransaction({required String transaction}) => throw UnimplementedError();
11
cw_core/lib/wallet_info.dart
+1 -1
@@ -44,7 +44,7 @@ enum HardwareWalletType {
44 coldcard,
45 seedsigner,
46 keystone,
47 - trezor,
47 + trezor;
48 }
49
50 enum WalletInfoAddressType {
cw_monero/lib/ledger.dart
+8
@@ -2,6 +2,7 @@ import 'dart:async';
2 import 'dart:ffi';
3 import 'dart:typed_data';
4
5 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
6 import 'package:cw_core/utils/print_verbose.dart';
7 import 'package:ffi/ffi.dart';
8 import 'package:flutter/foundation.dart';
@@ -118,3 +119,10 @@ void _logLedgerCommand(Uint8List command, [bool isResponse = true]) {
119 "> ${_ledgerMoneroCommands[command[1]]} ${toHexString(command.sublist(2))}");
120 }
121 }
122 +
123 +class MoneroLedgerService extends HardwareWalletService {
124 + final LedgerConnection connection;
125 +
126 + MoneroLedgerService(this.connection);
127 +}
128 +
cw_monero/lib/monero_wallet.dart
+35
@@ -37,6 +37,7 @@ import 'package:cw_monero/monero_unspent.dart';
37 import 'package:cw_monero/monero_wallet_addresses.dart';
38 import 'package:cw_monero/monero_wallet_service.dart';
39 import 'package:cw_monero/pending_monero_transaction.dart';
40 +import 'package:cw_monero/trezor.dart';
41 import 'package:flutter/foundation.dart';
42 import 'package:hive/hive.dart';
43 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
@@ -385,8 +386,42 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
386 currentWallet!.hasUnknownKeyImages();
387 }
388
389 + MoneroTrezorService? trezorService;
390 + Future<void> syncTrezor() async {
391 + if (trezorService == null) throw Exception("Trezor not connected");
392 +
393 + final ptr = Pointer<Void>.fromAddress(currentWallet!.ffiAddress());
394 + final tdis = monero.Wallet_exportTrezorTdis(ptr);
395 +
396 + final response = await Trezor(trezorService!).keyImageSync(tdis);
397 +
398 + final success = monero.Wallet_importTrezorEncryptedKeyImagesJson(ptr, response);
399 +
400 + if (!success) throw Exception(monero.Wallet_errorString(ptr));
401 + }
402 +
403 + Future<String> signTrezorTransaction(String json) async {
404 + if (trezorService == null) throw Exception("Trezor not connected");
405 + return Trezor(trezorService!).signTransaction(json);
406 + }
407 +
408 @override
409 Future<PendingTransaction> createTransaction(Object credentials) async {
410 + if (hardwareWalletType == HardwareWalletType.trezor) {
411 + for (int i = 0; i < 2; i++) {
412 + try {
413 + return await _createTransaction(credentials);
414 + } catch (e) {
415 + printV(e);
416 + }
417 + await save();
418 + await Future.delayed(Duration(seconds: i));
419 + }
420 + }
421 + return await _createTransaction(credentials);
422 + }
423 +
424 + Future<PendingTransaction> _createTransaction(Object credentials) async {
425 final _credentials = credentials as MoneroTransactionCreationCredentials;
426 final inputs = <String>[];
427 final outputs = _credentials.outputs;
cw_monero/lib/monero_wallet_service.dart
+26 -12
@@ -3,8 +3,8 @@ import 'dart:ffi';
3 import 'dart:io';
4 import 'dart:isolate';
5
6 -import 'package:collection/collection.dart';
6 import 'package:cw_core/get_height_by_date.dart';
7 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
8 import 'package:cw_core/monero_wallet_utils.dart';
9 import 'package:cw_core/pathForWallet.dart';
10 import 'package:cw_core/unspent_coins_info.dart';
@@ -20,8 +20,8 @@ import 'package:cw_monero/api/wallet_manager.dart';
20 import 'package:cw_monero/bip39_seed.dart';
21 import 'package:cw_monero/ledger.dart';
22 import 'package:cw_monero/monero_wallet.dart';
23 +import 'package:cw_monero/trezor.dart';
24 import 'package:hive/hive.dart';
24 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
25 import 'package:monero/monero.dart' as monero;
26 import 'package:polyseed/polyseed.dart';
27
@@ -46,11 +46,11 @@ class MoneroNewWalletCredentials extends WalletCredentials {
46 class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
47 MoneroRestoreWalletFromHardwareCredentials(
48 {required String name,
49 - required this.ledgerConnection,
49 + required this.hardwareWalletService,
50 int height = 0,
51 String? password})
52 : super(name: name, password: password, height: height);
53 - LedgerConnection ledgerConnection;
53 + HardwareWalletService hardwareWalletService;
54 }
55
56 class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
@@ -193,7 +193,7 @@ class MoneroWalletService extends WalletService<
193 unspentCoinsInfo: unspentCoinsInfoSource,
194 password: password);
195
196 - if (wallet.isHardwareWallet) {
196 + if (wallet.hardwareWalletType == HardwareWalletType.ledger) {
197 wallet.setLedgerConnection(gLedger!);
198 gLedger = null;
199 }
@@ -301,15 +301,29 @@ class MoneroWalletService extends WalletService<
301 try {
302 final path = await pathForWallet(name: credentials.name, type: getType());
303 final password = credentials.password;
304 - final height = credentials.height;
304
306 - enableLedgerExchange(credentials.ledgerConnection);
305 + if (credentials.hardwareWalletService case MoneroLedgerService service) {
306 + enableLedgerExchange(service.connection);
307
308 - await monero_wallet_manager.restoreWalletFromHardwareWallet(
308 + await monero_wallet_manager.restoreWalletFromHardwareWallet(
309 path: path,
310 password: password!,
311 - restoreHeight: height!,
312 - deviceName: 'Ledger');
311 + restoreHeight: credentials.height!,
312 + deviceName: 'Ledger',
313 + );
314 + } else if (credentials.hardwareWalletService case MoneroTrezorService service) {
315 + final watchCredentials = await Trezor(service).getWatchCredentials();
316 +
317 + monero_wallet_manager.restoreWalletFromKeys(
318 + path: path,
319 + password: credentials.password!,
320 + language: "English",
321 + restoreHeight: credentials.height!,
322 + address: watchCredentials.address,
323 + viewKey: watchCredentials.watchKey,
324 + spendKey: "",
325 + );
326 + }
327
328 final wallet = MoneroWallet(
329 walletInfo: credentials.walletInfo!,
@@ -567,7 +581,7 @@ class MoneroWalletService extends WalletService<
581 if (walletInfo == null) {
582 return false;
583 }
570 - return walletInfo.isHardwareWallet;
584 + return walletInfo.hardwareWalletType == HardwareWalletType.ledger;
585 }
586 }
587
@@ -585,4 +599,4 @@ Future<void> closeWalletAwaitIfShould(int wmaddr, int waddr) async {
599 monero.WalletManager_errorString(Pointer.fromAddress(wmaddr));
600 }));
601 }
588 -}
\ No newline at end of file
602 +}
cw_monero/lib/pending_monero_transaction.dart
+32 -11
@@ -1,5 +1,8 @@
1 import 'dart:async';
2 +import 'dart:ffi';
3
4 +import 'package:cw_core/utils/print_verbose.dart';
5 +import 'package:cw_core/wallet_info.dart';
6 import 'package:cw_monero/api/account_list.dart';
7 import 'package:cw_monero/api/structs/pending_transaction.dart';
8 import 'package:cw_monero/api/transaction_history.dart'
@@ -10,6 +13,7 @@ import 'package:cw_core/amount_converter.dart';
13 import 'package:cw_core/pending_transaction.dart';
14 import 'package:cw_monero/api/wallet.dart';
15 import 'package:cw_monero/monero_wallet.dart';
16 +import 'package:monero/monero.dart' as monero;
17
18 class DoubleSpendException implements Exception {
19 DoubleSpendException();
@@ -43,22 +47,39 @@ class PendingMoneroTransaction with PendingTransaction {
47 CryptoCurrency.xmr, pendingTransactionDescription.fee);
48
49 @override
46 - bool shouldCommitUR() => isViewOnly;
50 + bool shouldCommitUR() => isViewOnly && wallet.hardwareWalletType != HardwareWalletType.trezor;
51
52 @override
53 Future<void> commit() async {
50 - try {
51 - await monero_transaction_history.commitTransactionFromPointerAddress(
52 - address: pendingTransactionDescription.pointerAddress,
53 - useUR: false);
54 - } catch (e) {
55 - final message = e.toString();
54 + if (wallet.hardwareWalletType == HardwareWalletType.trezor) {
55 + final ptr = Pointer<Void>.fromAddress(pendingTransactionDescription.pointerAddress);
56 + final ret = await monero.PendingTransaction_commitTrezor(ptr, 0);
57
57 - if (message.contains('Reason: double spend')) {
58 - throw DoubleSpendException();
59 - }
58 + final json = await wallet.signTrezorTransaction(ret);
59
61 - rethrow;
60 + final wptr = Pointer<Void>.fromAddress(currentWallet!.ffiAddress());
61 +
62 + final suc = await monero.Wallet_submitTransactionHex(wptr, json);
63 +
64 + if (!suc) {
65 + final err = monero.UnsignedTransaction_errorString(ptr);
66 + printV(err);
67 + throw err;
68 + }
69 + } else {
70 + try {
71 + await monero_transaction_history.commitTransactionFromPointerAddress(
72 + address: pendingTransactionDescription.pointerAddress,
73 + useUR: false);
74 + } catch (e) {
75 + final message = e.toString();
76 +
77 + if (message.contains('Reason: double spend')) {
78 + throw DoubleSpendException();
79 + }
80 +
81 + rethrow;
82 + }
83 }
84 storeSync(force: true);
85 unawaited(() async {
cw_monero/lib/trezor.dart new
+53
@@ -0,0 +1,53 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
4 +import 'package:trezor_flutter/trezor_flutter.dart';
5 +
6 +class MoneroTrezorService extends HardwareWalletService {
7 + final TrezorClient client;
8 +
9 + MoneroTrezorService(this.client);
10 +}
11 +
12 +class MoneroTrezorWatchCredentials {
13 + final String watchKey;
14 + final String address;
15 +
16 + const MoneroTrezorWatchCredentials(this.watchKey, this.address);
17 +}
18 +
19 +class Trezor {
20 + final MoneroTrezorService service;
21 +
22 + Trezor(this.service);
23 +
24 + Future<MoneroTrezorWatchCredentials> getWatchCredentials() async {
25 + final credentials = await TrezorMonero(service.client).getWatchCredentials();
26 +
27 + return MoneroTrezorWatchCredentials(credentials.$1, credentials.$2);
28 + }
29 +
30 + Future<String> keyImageSync(String tdis) async {
31 + final tdisMap = jsonDecode(tdis) as Map<String, dynamic>;
32 + final tdisList = tdisMap["tdis"] as List<dynamic>;
33 +
34 + final txIds = <MoneroKeyImageTxData>[];
35 + for (final tdi in tdisList) {
36 + txIds.add(MoneroKeyImageTxData(
37 + outKey: tdi["out_key"],
38 + txPubKey: tdi["tx_pub_key"],
39 + internalOutputIndex: tdi["internal_output_index"],
40 + subAddrMajor: tdi["sub_addr_major"],
41 + subAddrMinor: tdi["sub_addr_minor"],
42 + additionalTxPubKeys: (tdi["additional_tx_pub_keys"] as List?)?.map((e) => e as String).toList() ?? []
43 + ));
44 + }
45 + final keyImages = await TrezorMonero(service.client).syncKeyImages(txIds);
46 +
47 + return jsonEncode(keyImages.toMap());
48 + }
49 +
50 + Future<String> signTransaction(String json) async {
51 + return TrezorMonero(service.client).signTransaction(jsonDecode(json));
52 + }
53 +}
cw_monero/pubspec.lock
+45 -11
@@ -296,6 +296,14 @@ packages:
296 url: "https://pub.dev"
297 source: hosted
298 version: "1.3.3"
299 + fast_base58:
300 + dependency: transitive
301 + description:
302 + name: fast_base58
303 + sha256: "611f65633b734f27a850b51371b3eba993a5165650e12e8e7b02959f3768ba06"
304 + url: "https://pub.dev"
305 + source: hosted
306 + version: "0.2.1"
307 ffi:
308 dependency: "direct main"
309 description:
@@ -342,10 +350,10 @@ packages:
350 dependency: transitive
351 description:
352 name: flutter_web_bluetooth
345 - sha256: "1363831def5eed1e1064d1eca04e8ccb35446e8f758579c3c519e156b77926da"
353 + sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8
354 url: "https://pub.dev"
355 source: hosted
348 - version: "1.0.0"
356 + version: "1.1.0"
357 frontend_server_client:
358 dependency: transitive
359 description:
@@ -494,18 +502,18 @@ packages:
502 dependency: "direct main"
503 description:
504 name: ledger_flutter_plus
497 - sha256: "1c03f3c4a9754b5f0170a9eb9552ec54fa86e985f8ee71a255ee2c5629b53d31"
505 + sha256: "59c5a6062505c721c2ca909fa085c52cffe93de995174b8418b54a6acf52eb70"
506 url: "https://pub.dev"
507 source: hosted
500 - version: "1.5.1"
508 + version: "1.5.6"
509 ledger_usb_plus:
510 dependency: transitive
511 description:
512 name: ledger_usb_plus
505 - sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
513 + sha256: "911df2d1d55cdae612ff2c9db140cf71a9b28a67ba16adfdc4983cd9f02c531e"
514 url: "https://pub.dev"
515 source: hosted
508 - version: "1.0.4"
516 + version: "1.0.8"
517 logging:
518 dependency: transitive
519 description:
@@ -582,9 +590,9 @@ packages:
590 dependency: "direct main"
591 description:
592 path: "impls/monero.dart"
585 - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
586 - resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
587 - url: "https://github.com/mrcyjanek/monero_c"
593 + ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
594 + resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
595 + url: "http://github.com/mrcyjanek/monero_c.git"
596 source: git
597 version: "0.0.0"
598 mutex:
@@ -724,6 +732,14 @@ packages:
732 url: "https://pub.dev"
733 source: hosted
734 version: "1.5.1"
735 + protobuf:
736 + dependency: transitive
737 + description:
738 + name: protobuf
739 + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
740 + url: "https://pub.dev"
741 + source: hosted
742 + version: "3.1.0"
743 provider:
744 dependency: transitive
745 description:
@@ -962,6 +978,24 @@ packages:
978 relative: true
979 source: path
980 version: "0.0.1"
981 + trezor_flutter:
982 + dependency: "direct main"
983 + description:
984 + path: trezor-flutter
985 + ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
986 + resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
987 + url: "https://github.com/cake-tech/trezor-flutter.git"
988 + source: git
989 + version: "0.1.0"
990 + trezor_usb_transport:
991 + dependency: transitive
992 + description:
993 + path: trezor_usb_transport
994 + ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
995 + resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
996 + url: "https://github.com/cake-tech/trezor-flutter.git"
997 + source: git
998 + version: "0.0.1"
999 tuple:
1000 dependency: transitive
1001 description:
@@ -982,10 +1016,10 @@ packages:
1016 dependency: transitive
1017 description:
1018 name: universal_ble
985 - sha256: "1fad089150a29db82b3b7d60327e18c5ad6b3a5bb509defc1c690b0a76b9c098"
1019 + sha256: "8325ca9f5cce2fba2b4efef74a4fcfe7144f303b409d6fc6ed16197b00a1a241"
1020 url: "https://pub.dev"
1021 source: hosted
988 - version: "0.15.0"
1022 + version: "1.2.0"
1023 universal_platform:
1024 dependency: transitive
1025 description:
cw_monero/pubspec.yaml
+7 -2
@@ -25,11 +25,16 @@ dependencies:
25 path: ../cw_core
26 monero:
27 git:
28 - url: https://github.com/mrcyjanek/monero_c
29 - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
28 + url: https://github.com/mrcyjanek/monero_c.git
29 + ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
30 path: impls/monero.dart
31 mutex: ^3.1.0
32 ledger_flutter_plus: ^1.4.1
33 + trezor_flutter:
34 + git:
35 + url: https://github.com/cake-tech/trezor-flutter.git
36 + ref: 4bf54339a1caefd4c53a4a8873a9686ec13a9107
37 + path: trezor-flutter
38
39 dev_dependencies:
40 flutter_test:
cw_wownero/pubspec.lock
+3 -3
@@ -502,9 +502,9 @@ packages:
502 dependency: "direct main"
503 description:
504 path: "impls/monero.dart"
505 - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
506 - resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
507 - url: "https://github.com/mrcyjanek/monero_c"
505 + ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
506 + resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
507 + url: "http://github.com/mrcyjanek/monero_c.git"
508 source: git
509 version: "0.0.0"
510 mutex:
cw_wownero/pubspec.yaml
+2 -2
@@ -23,8 +23,8 @@ dependencies:
23 path: ../cw_core
24 monero:
25 git:
26 - url: https://github.com/mrcyjanek/monero_c
27 - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash
26 + url: https://github.com/mrcyjanek/monero_c.git
27 + ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
28 path: impls/monero.dart
29 mutex: ^3.1.0
30
cw_zano/pubspec.lock
+3 -3
@@ -507,9 +507,9 @@ packages:
507 dependency: "direct main"
508 description:
509 path: "impls/monero.dart"
510 - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
511 - resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
512 - url: "https://github.com/mrcyjanek/monero_c"
510 + ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
511 + resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
512 + url: "http://github.com/mrcyjanek/monero_c.git"
513 source: git
514 version: "0.0.0"
515 nested:
cw_zano/pubspec.yaml
+2 -2
@@ -24,8 +24,8 @@ dependencies:
24 fluttertoast: ^8.2.12
25 monero:
26 git:
27 - url: https://github.com/mrcyjanek/monero_c
28 - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash
27 + url: https://github.com/mrcyjanek/monero_c.git
28 + ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
29 path: impls/monero.dart
30 dev_dependencies:
31 flutter_test:
integration_test/components/common_test_flows.dart
+1 -4
@@ -231,12 +231,9 @@ class CommonTestFlows {
231
232 await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
233
234 + // Tapping the wallet type navigates directly (no separate Next button).
235 _tester.printToConsole('Tapping wallet type: ${type.name}');
236 await _newWalletTypePageRobot.selectWalletType(type);
236 - await _tester.pumpAndSettle(Duration(milliseconds: 500));
237 -
238 - _tester.printToConsole('Pressing next button');
239 - await _newWalletTypePageRobot.onNextButtonPressed();
237
238 await _tester.pumpAndSettle(Duration(milliseconds: 2000));
239
integration_test/robots/new_wallet_type_page_robot.dart
+2 -4
@@ -77,11 +77,9 @@ class NewWalletTypePageRobot {
77 tester.printToConsole('Wallet type ${type.name} is now visible and tappable');
78 }
79
80 + // Tapping a wallet type now navigates directly (the page no longer has a
81 + // separate "Next" button — selection happens on tap).
82 Future<void> selectWalletType(WalletType type) async {
83 await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key');
84 }
83 -
84 - Future<void> onNextButtonPressed() async {
85 - await commonTestCases.tapItemByKey('new_wallet_type_next_button_key');
86 - }
85 }
ios/Podfile.lock
+1
@@ -295,6 +295,7 @@ SPEC CHECKSUMS:
295 universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
296 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
297 wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
298 + warp_api: 997d8fb3e777669bc6260d0c12e56cd40b9db1d1
299 YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
300
301 PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
lib/buy/dfx/dfx_buy_provider.dart
+1 -1
@@ -336,7 +336,7 @@ class DFXBuyProvider extends BuyProvider {
336 required String cryptoCurrencyAddress,
337 String? countryCode}) async {
338 if (wallet.isHardwareWallet) {
339 - if (!hardwareWalletVM!.isConnected) {
339 + if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) {
340 await Navigator.of(context).pushNamed(Routes.connectDevices,
341 arguments: ConnectDevicePageParams(
342 walletType: wallet.walletInfo.type,
lib/buy/robinhood/robinhood_buy_provider.dart
+1 -1
@@ -173,7 +173,7 @@ class RobinhoodBuyProvider extends BuyProvider {
173 required String cryptoCurrencyAddress,
174 String? countryCode}) async {
175 if (wallet.isHardwareWallet) {
176 - if (!hardwareWalletVM!.isConnected) {
176 + if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) {
177 await Navigator.of(context).pushNamed(Routes.connectDevices,
178 arguments: ConnectDevicePageParams(
179 walletType: wallet.walletInfo.type,
lib/di.dart
+3 -3
@@ -240,7 +240,7 @@ import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
240 import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
241 import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
242 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
243 -import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
243 +import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
244 import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
245 import 'package:cake_wallet/view_model/link_view_model.dart';
246 import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
@@ -414,7 +414,7 @@ Future<void> setup({
414 switch(type) {
415 case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
416 case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
417 - case HardwareWalletType.trezor: return getIt<TrezorViewModel>();
417 + case HardwareWalletType.trezor: return getIt<TrezorConnectViewModel>();
418 case HardwareWalletType.cupcake:
419 case HardwareWalletType.coldcard:
420 case HardwareWalletType.seedsigner:
@@ -429,7 +429,7 @@ Future<void> setup({
429
430 getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
431 appName: "Cake Wallet"));
432 - getIt.registerLazySingleton(() => TrezorViewModel(getIt<TrezorConnect>()));
432 + getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt<TrezorConnect>()));
433
434
435 getIt.registerFactory<KeyService>(() => KeyService(getIt.get<SecureStorage>()));
lib/entities/hardware_wallet/hardware_wallet_device.dart
+52 -3
@@ -1,5 +1,6 @@
1 import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
2 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
3 +import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
4
5 abstract class HardwareWalletDevice {
6 String get name;
@@ -41,6 +42,21 @@ class BitboxHardwareWalletDevice extends HardwareWalletDevice {
42 HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
43 }
44
45 +class TrezorHardwareWalletDevice extends HardwareWalletDevice {
46 + final trezor.TrezorDevice device;
47 +
48 + TrezorHardwareWalletDevice(this.device);
49 +
50 + @override
51 + String get name => device.name;
52 +
53 + @override
54 + HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
55 +
56 + @override
57 + HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
58 +}
59 +
60 enum HardwareWalletDeviceType {
61 ledgerBlue,
62 ledgerNanoS,
@@ -50,7 +66,12 @@ enum HardwareWalletDeviceType {
66 ledgerStax,
67 ledgerFlex,
68 BitBox02,
53 - BitBox02Nova;
69 + BitBox02Nova,
70 + trezorModelOne,
71 + trezorModelT,
72 + trezorSafe3,
73 + trezorSafe5,
74 + trezorSafe7;
75 }
76
77 enum HardwareWalletConnectionType {
@@ -60,7 +81,7 @@ enum HardwareWalletConnectionType {
81 qr;
82 }
83
63 -extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
84 +extension LedgerToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
85 HardwareWalletDeviceType toGeneric() {
86 switch (this) {
87 case ledger.LedgerDeviceType.blue:
@@ -81,7 +102,24 @@ extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
102 }
103 }
104
84 -extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
105 +extension TrezorToGenericHardwareWalletDeviceType on trezor.TrezorDeviceType {
106 + HardwareWalletDeviceType toGeneric() {
107 + switch (this) {
108 + case trezor.TrezorDeviceType.modelOne:
109 + return HardwareWalletDeviceType.trezorModelOne;
110 + case trezor.TrezorDeviceType.modelT:
111 + return HardwareWalletDeviceType.trezorModelT;
112 + case trezor.TrezorDeviceType.safe3:
113 + return HardwareWalletDeviceType.trezorSafe3;
114 + case trezor.TrezorDeviceType.safe5:
115 + return HardwareWalletDeviceType.trezorSafe5;
116 + case trezor.TrezorDeviceType.safe7:
117 + return HardwareWalletDeviceType.trezorSafe7;
118 + }
119 + }
120 +}
121 +
122 +extension LedgerToGenericHardwareWalletConnectionType on ledger.ConnectionType {
123 HardwareWalletConnectionType toGeneric() {
124 switch (this) {
125 case ledger.ConnectionType.usb:
@@ -91,3 +129,14 @@ extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
129 }
130 }
131 }
132 +
133 +extension TrezorToGenericHardwareWalletConnectionType on trezor.ConnectionType {
134 + HardwareWalletConnectionType toGeneric() {
135 + switch (this) {
136 + case trezor.ConnectionType.usb:
137 + return HardwareWalletConnectionType.usb;
138 + case trezor.ConnectionType.ble:
139 + return HardwareWalletConnectionType.ble;
140 + }
141 + }
142 +}
lib/monero/cw_monero.dart
+32 -21
@@ -236,13 +236,14 @@ class CWMonero extends Monero {
236 required String name,
237 required String password,
238 required int height,
239 - required ledger.LedgerConnection ledgerConnection,
239 + required HardwareWalletService hardwareWalletService,
240 }) =>
241 MoneroRestoreWalletFromHardwareCredentials(
242 - name: name,
243 - password: password,
244 - height: height,
245 - ledgerConnection: ledgerConnection);
242 + name: name,
243 + password: password,
244 + height: height,
245 + hardwareWalletService: hardwareWalletService,
246 + );
247
248 @override
249 WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
@@ -416,9 +417,7 @@ class CWMonero extends Monero {
417 }
418
419 @override
419 - void monerocCheck() {
420 - checkIfMoneroCIsFine();
421 - }
420 + void monerocCheck() => checkIfMoneroCIsFine();
421
422 @override
423 Future<void> setLedgerConnection(Object wallet, ledger.LedgerConnection connection) async {
@@ -427,29 +426,41 @@ class CWMonero extends Monero {
426 }
427
428 @override
430 - void resetLedgerConnection() {
431 - disableLedgerExchange();
432 - }
429 + void resetLedgerConnection() => disableLedgerExchange();
430
431 @override
435 - void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
436 - gLedger = connection;
437 - }
432 + void setGlobalLedgerConnection(ledger.LedgerConnection connection) => gLedger = connection;
433
434 @override
435 String? getLastLedgerCommand() => latestLedgerCommand;
436
442 - bool isViewOnly() {
443 - return isViewOnlyBySpendKey(null);
437 + @override
438 + void setHardwareWalletService(Object wallet, HardwareWalletService service) {
439 + final moneroWallet = wallet as MoneroWallet;
440 + moneroWallet.trezorService = service as MoneroTrezorService;
441 }
442
443 +
444 @override
447 - Map<String, List<int>> debugCallLength() {
448 - return monero_wallet_api.debugCallLength();
449 - }
445 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
446 + MoneroLedgerService(connection);
447 +
448 + @override
449 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client) =>
450 + MoneroTrezorService(client);
451
452 @override
452 - Map<String, dynamic> getWalletCacheDebug() {
453 - return monero_wallet_api.getWalletCacheDebug();
453 + Future<void> syncTrezor(Object wallet) async {
454 + final moneroWallet = wallet as MoneroWallet;
455 +
456 + return moneroWallet.syncTrezor();
457 }
458 +
459 + bool isViewOnly() => isViewOnlyBySpendKey(null);
460 +
461 + @override
462 + Map<String, List<int>> debugCallLength() => monero_wallet_api.debugCallLength();
463 +
464 + @override
465 + Map<String, dynamic> getWalletCacheDebug() => monero_wallet_api.getWalletCacheDebug();
466 }
lib/new-ui/pages/send_page.dart
+9 -4
@@ -726,12 +726,13 @@ class _NewSendPageState extends State<NewSendPage> {
726 }
727
728 if (widget.sendViewModel.wallet.isHardwareWallet) {
729 - if (!widget.sendViewModel.hardwareWalletViewModel!.isConnected) {
729 + if (!widget.sendViewModel.hardwareWalletViewModel!
730 + .isConnected(widget.sendViewModel.walletType)) {
731 await Navigator.of(context).pushNamed(Routes.connectDevices,
732 arguments: ConnectDevicePageParams(
733 walletType: widget.sendViewModel.walletType,
734 hardwareWalletType: widget.sendViewModel.wallet.walletInfo.hardwareWalletType!,
734 - onConnectDevice: (BuildContext context, _) {
735 + onConnectDevice: (_, __) {
736 widget.sendViewModel.hardwareWalletViewModel!
737 .initWallet(widget.sendViewModel.wallet);
738 Navigator.of(context).pop();
@@ -748,8 +749,12 @@ class _NewSendPageState extends State<NewSendPage> {
749 amount += item.formattedCryptoAmount;
750 }
751 if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
751 - await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
752 - arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet));
752 + if (widget.sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor) {
753 + await monero!.syncTrezor(widget.sendViewModel.wallet);
754 + } else {
755 + await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
756 + arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet));
757 + }
758 await Future.delayed(Duration(seconds: 1)); // wait for monero to refresh the state
759 }
760 if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
lib/new-ui/widgets/coins_page/wallet_info.dart
+3 -3
@@ -86,11 +86,11 @@ class WalletInfoBar extends StatelessWidget {
86 case null:
87 return null;
88 case HardwareWalletType.bitbox:
89 - return "assets/images/hardware_wallet/device_bitbox.svg";
89 + return "assets/new-ui/hardware_wallets/device_bitbox.svg";
90 case HardwareWalletType.ledger:
91 - return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
91 + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
92 case HardwareWalletType.trezor:
93 - return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
93 + return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
94 case HardwareWalletType.cupcake:
95 return "assets/images/cupcake.svg";
96 case HardwareWalletType.coldcard:
lib/new-ui/widgets/digit_input.dart new
+182
@@ -0,0 +1,182 @@
1 +import 'package:flutter/material.dart';
2 +import 'package:flutter/services.dart';
3 +
4 +class DigitInputController {
5 + String _text = "";
6 +
7 + String get text => _text;
8 +
9 + set text(String newText) {
10 + _text = newText;
11 + for (final listener in _listeners) {
12 + listener.call();
13 + }
14 + }
15 +
16 + final List<void Function()> _listeners = [];
17 +
18 + void addListener(void Function() listener) => _listeners.add(listener);
19 +}
20 +
21 +class DigitInputPill extends StatelessWidget {
22 + const DigitInputPill({super.key, this.digit, required this.highlighted});
23 +
24 + final String? digit;
25 + final bool highlighted;
26 +
27 + @override
28 + Widget build(BuildContext context) {
29 + return Container(
30 + width: 51,
31 + height: 84,
32 + decoration: BoxDecoration(
33 + color: Theme.of(context).colorScheme.surfaceContainerHigh,
34 + borderRadius: BorderRadius.circular(16),
35 + border:
36 + highlighted ? Border.all(color: Theme.of(context).colorScheme.primary, width: 2) : null,
37 + ),
38 + child: Center(
39 + child: Padding(
40 + padding: EdgeInsets.symmetric(vertical: 16),
41 + child: Text(digit ?? " ", style: TextStyle(fontSize: 40)),
42 + ),
43 + ),
44 + );
45 + }
46 +}
47 +
48 +class DigitInput extends StatefulWidget {
49 + const DigitInput({
50 + super.key,
51 + required this.controller,
52 + required this.desiredLength,
53 + this.breakAt = 3,
54 + });
55 +
56 + final DigitInputController controller;
57 + final int desiredLength;
58 + final int breakAt;
59 +
60 + @override
61 + State<DigitInput> createState() => _DigitInputState();
62 +}
63 +
64 +class _DigitInputState extends State<DigitInput> implements TextInputClient {
65 + TextInputConnection? _textInputConnection;
66 +
67 + @override
68 + void initState() {
69 + super.initState();
70 + _openVirtualKeyboard();
71 + }
72 +
73 + void _openVirtualKeyboard() {
74 + if (_textInputConnection == null || !_textInputConnection!.attached) {
75 + _textInputConnection = TextInput.attach(
76 + this,
77 + const TextInputConfiguration(
78 + inputType: TextInputType.numberWithOptions(
79 + signed: false,
80 + decimal: false,
81 + ),
82 + inputAction: TextInputAction.none,
83 + ),
84 + );
85 + }
86 +
87 + _textInputConnection!.setEditingState(
88 + TextEditingValue(
89 + text: widget.controller.text,
90 + selection: TextSelection.collapsed(offset: widget.controller.text.length),
91 + ),
92 + );
93 + _textInputConnection!.show();
94 + }
95 +
96 + void _closeVirtualKeyboard() => _textInputConnection?.close();
97 +
98 + @override
99 + void dispose() {
100 + _closeVirtualKeyboard();
101 + super.dispose();
102 + }
103 +
104 + @override
105 + Widget build(BuildContext context) {
106 + return GestureDetector(
107 + onTap: _openVirtualKeyboard,
108 + child: SizedBox(
109 + height: 84,
110 + child: ListView.separated(
111 + scrollDirection: Axis.horizontal,
112 + shrinkWrap: true,
113 + physics: NeverScrollableScrollPhysics(),
114 + itemBuilder: (context, index) => DigitInputPill(
115 + digit: widget.controller.text.length > index ? widget.controller.text[index] : null,
116 + highlighted: widget.controller.text.length == index,
117 + ),
118 + separatorBuilder: (context, index) =>
119 + SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4),
120 + itemCount: widget.desiredLength,
121 + ),
122 + ),
123 + );
124 + }
125 +
126 + @override
127 + void updateEditingValue(TextEditingValue value) {
128 + final RegExp numberRegExp = RegExp(r'^\d{0,6}$');
129 +
130 + if (numberRegExp.hasMatch(value.text)) {
131 + setState(() => widget.controller.text = value.text);
132 + } else {
133 + // flutter keeps the text editing state regardless of whether you actually used it or not.
134 + // so, if validation fails, we gotta set it back to what it was explicitly
135 + _textInputConnection?.setEditingState(
136 + TextEditingValue(
137 + text: widget.controller.text,
138 + selection: TextSelection.collapsed(offset: widget.controller.text.length),
139 + ),
140 + );
141 + }
142 + }
143 +
144 + @override
145 + void performAction(TextInputAction action) {}
146 +
147 + @override
148 + TextEditingValue? get currentTextEditingValue => TextEditingValue(text: widget.controller.text);
149 +
150 + @override
151 + void connectionClosed() => _textInputConnection = null;
152 +
153 + @override
154 + AutofillScope? get currentAutofillScope => null;
155 +
156 + @override
157 + void performPrivateCommand(String action, Map<String, dynamic> data) {}
158 +
159 + @override
160 + void showAutocorrectionPromptRect(int start, int end) {}
161 +
162 + @override
163 + void updateFloatingCursor(RawFloatingCursorPoint point) {}
164 +
165 + @override
166 + void insertTextPlaceholder(Size size) {}
167 +
168 + @override
169 + void removeTextPlaceholder() {}
170 +
171 + @override
172 + void showToolbar() {}
173 +
174 + @override
175 + void performSelector(String selectorName) {}
176 +
177 + @override
178 + void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {}
179 +
180 + @override
181 + void insertContent(KeyboardInsertedContent content) {}
182 +}
lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart new
+60
@@ -0,0 +1,60 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
3 +import 'package:cw_core/wallet_info.dart';
4 +import 'package:flutter/material.dart';
5 +
6 +class HardwareWalletProceedOnDeviceMessage extends StatelessWidget {
7 + const HardwareWalletProceedOnDeviceMessage({super.key, required this.hardwareWalletType});
8 +
9 + final HardwareWalletType hardwareWalletType;
10 +
11 + @override
12 + Widget build(BuildContext context) {
13 + return Container(
14 + width: double.infinity,
15 + decoration: BoxDecoration(
16 + border: Border.all(
17 + color: Theme.of(context).colorScheme.primary,
18 + ),
19 + borderRadius: BorderRadius.circular(16),
20 + ),
21 + child: Padding(
22 + padding: const EdgeInsets.all(12.0),
23 + child: Column(spacing: 8, children: [
24 + if (hardwareWalletIcon != null)
25 + CakeImageWidget(
26 + imageUrl: hardwareWalletIcon!,
27 + width: 36,
28 + height: 36,
29 + colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
30 + ),
31 + Text(
32 + S.of(context).proceed_on_device,
33 + style: TextStyle(
34 + fontSize: 16,
35 + fontWeight: FontWeight.w400,
36 + color: Theme.of(context).colorScheme.onSurface,
37 + ),
38 + )
39 + ]),
40 + ),
41 + );
42 + }
43 +
44 + String? get hardwareWalletIcon {
45 + switch (hardwareWalletType) {
46 + case HardwareWalletType.bitbox:
47 + return "assets/new-ui/hardware_wallets/device_bitbox.svg";
48 + case HardwareWalletType.ledger:
49 + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
50 + case HardwareWalletType.trezor:
51 + return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
52 + case HardwareWalletType.cupcake:
53 + return "assets/images/cupcake.svg";
54 + case HardwareWalletType.coldcard:
55 + case HardwareWalletType.seedsigner:
56 + case HardwareWalletType.keystone:
57 + return "assets/images/hardware_wallet/device_qr.svg";
58 + }
59 + }
60 +}
lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart new
+338
@@ -0,0 +1,338 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/new-ui/widgets/digit_input.dart';
3 +import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
4 +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
5 +import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
6 +import 'package:cake_wallet/new-ui/widgets/send_page/directional_switcher.dart';
7 +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
8 +import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
9 +import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
10 +import 'package:cake_wallet/utils/show_pop_up.dart';
11 +import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
12 +import 'package:cw_core/wallet_info.dart';
13 +import 'package:flutter/cupertino.dart';
14 +import 'package:flutter/material.dart';
15 +import 'package:mobx/mobx.dart';
16 +
17 +class HardwareWalletProceedOnDeviceSheet extends StatefulWidget {
18 + const HardwareWalletProceedOnDeviceSheet({
19 + super.key,
20 + required this.hardwareWalletType,
21 + required this.trezorConnectVM,
22 + required this.onRetry,
23 + });
24 +
25 + final HardwareWalletType hardwareWalletType;
26 + final TrezorConnectViewModelBase trezorConnectVM;
27 + final void Function() onRetry;
28 +
29 + @override
30 + State<HardwareWalletProceedOnDeviceSheet> createState() =>
31 + _HardwareWalletProceedOnDeviceSheetState();
32 +}
33 +
34 +class _HardwareWalletProceedOnDeviceSheetState extends State<HardwareWalletProceedOnDeviceSheet> {
35 + TrezorParingState _paringState = TrezorParingState.initial;
36 +
37 + final DigitInputController _controller = DigitInputController();
38 +
39 + static const pinOpenDuration = Duration(milliseconds: 300);
40 +
41 + // you should probably check if there can be other pin lengths
42 + static const pinLength = 6;
43 +
44 + ReactionDisposer? paringStateReaction;
45 +
46 + @override
47 + void initState() {
48 + super.initState();
49 + _controller.addListener(() => setState(() {}));
50 +
51 + _paringState = widget.trezorConnectVM.paringState;
52 + paringStateReaction = reaction((_) => widget.trezorConnectVM.paringState, (paringState) {
53 + if (paringState is SuccessTrezorParingState) {
54 + if (mounted) Navigator.of(context).pop();
55 + return;
56 + }
57 + setState(() => _paringState = paringState);
58 + });
59 + }
60 +
61 + @override
62 + void dispose() {
63 + super.dispose();
64 + paringStateReaction?.reaction.dispose();
65 + }
66 +
67 + bool get _isAwaitingPin => _paringState == TrezorParingState.enterPin;
68 +
69 + @override
70 + Widget build(BuildContext context) {
71 + return PopScope(
72 + canPop: false,
73 + child: SafeArea(
74 + bottom: false,
75 + child: Container(
76 + width: MediaQuery.of(context).size.width,
77 + decoration: BoxDecoration(
78 + color: Theme.of(context).colorScheme.surface,
79 + borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
80 + ),
81 + child: SafeArea(
82 + child: Padding(
83 + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
84 + child: Column(
85 + children: [
86 + ModalTopBar(
87 + title: "",
88 + leadingWidget: AnimatedSwitcher(
89 + layoutBuilder: (currentChild, previousChildren) {
90 + return Stack(
91 + alignment: Alignment.centerLeft,
92 + children: <Widget>[
93 + ...previousChildren,
94 + if (currentChild != null) currentChild,
95 + ],
96 + );
97 + },
98 + duration: pinOpenDuration,
99 + child: Text(
100 + key: ValueKey(pageTitle),
101 + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
102 + pageTitle,
103 + ),
104 + ),
105 + trailingIcon: Icon(
106 + Icons.close,
107 + color: Theme.of(context).colorScheme.onSurfaceVariant,
108 + ),
109 + onTrailingPressed: () => showPopUp(
110 + context: context,
111 + builder: (context) => AlertWithTwoActions(
112 + alertTitle: S.of(context).are_you_sure_exit,
113 + alertContent: S.of(context).hww_exit_desc,
114 + leftButtonText: S.of(context).cancel,
115 + rightButtonText: S.of(context).yes_exit,
116 + rightAlertButtonStyle: AlertButtonStyle.error(context),
117 + actionLeftButton: Navigator.of(context).pop,
118 + actionRightButton: () {
119 + Navigator.of(context).pop();
120 + Navigator.of(context).pop();
121 + },
122 + ),
123 + ),
124 + ),
125 + Expanded(
126 + child: DirectionalAnimatedSwitcher(
127 + duration: Duration(milliseconds: 400),
128 + child: content,
129 + ),
130 + ),
131 + Padding(
132 + padding: EdgeInsets.only(bottom: 24, right: 24),
133 + child: Row(
134 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
135 + children: [
136 + SizedBox(),
137 + AnimatedOpacity(
138 + curve: Curves.easeOutQuad,
139 + opacity: hasFullPin ? 1 : 0,
140 + duration: pinOpenDuration,
141 + child: ModernButton(
142 + backgroundColor: Theme.of(context).colorScheme.primary,
143 + iconColor: Theme.of(context).colorScheme.onPrimary,
144 + size: 36,
145 + icon: Icon(Icons.arrow_forward),
146 + onPressed: () =>
147 + widget.trezorConnectVM.setParingPin(_controller.text.trim()),
148 + ),
149 + ),
150 + ],
151 + ),
152 + )
153 + ],
154 + ),
155 + )),
156 + ),
157 + ),
158 + );
159 + }
160 +
161 + Widget get content {
162 + if (_paringState is InitialTrezorParingState || _paringState is EnterPinTrezorParingState) {
163 + return _enterPinCode();
164 + }
165 +
166 + if (_paringState is VerifyingPinTrezorParingState) return _verifyingCode();
167 +
168 + if (_paringState is FailTrezorParingState)
169 + return _errorBox((_paringState as FailTrezorParingState).message);
170 +
171 + return SizedBox.shrink(key: ValueKey(3));
172 + }
173 +
174 + void retry() {
175 + _controller.text = "";
176 + widget.onRetry();
177 + }
178 +
179 + String get pageTitle {
180 + if (_paringState is InitialTrezorParingState) return S.of(context).device_confirmation;
181 + if (_isAwaitingPin) return S.of(context).pairing_code;
182 + return "";
183 + }
184 +
185 + bool get hasFullPin => _controller.text.length == pinLength && _isAwaitingPin;
186 +
187 + String? get hardwareWalletIcon {
188 + switch (widget.hardwareWalletType) {
189 + case HardwareWalletType.bitbox:
190 + return "assets/new-ui/hardware_wallets/device_bitbox.svg";
191 + case HardwareWalletType.ledger:
192 + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
193 + case HardwareWalletType.trezor:
194 + return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
195 + case HardwareWalletType.cupcake:
196 + return "assets/images/cupcake.svg";
197 + case HardwareWalletType.coldcard:
198 + case HardwareWalletType.seedsigner:
199 + case HardwareWalletType.keystone:
200 + return "assets/images/hardware_wallet/device_qr.svg";
201 + }
202 + }
203 +
204 + Widget _verifyingCode() => Container(
205 + key: ValueKey(1),
206 + width: MediaQuery.of(context).size.width,
207 + child: Column(
208 + spacing: 12,
209 + mainAxisAlignment: MainAxisAlignment.center,
210 + children: [
211 + const CupertinoActivityIndicator(radius: 36),
212 + const SizedBox(),
213 + Text(
214 + "${S.of(context).verifying_code}...",
215 + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
216 + ),
217 + Text(
218 + S.of(context).this_can_take_few_seconds,
219 + style: TextStyle(
220 + fontWeight: FontWeight.w400,
221 + fontSize: 16,
222 + color: Theme.of(context).colorScheme.onSurfaceVariant,
223 + ),
224 + )
225 + ],
226 + ),
227 + );
228 +
229 + Widget _errorBox(String errorText) => Container(
230 + key: ValueKey(2),
231 + width: MediaQuery.of(context).size.width,
232 + child: Padding(
233 + padding: const EdgeInsets.symmetric(horizontal: 18),
234 + child: Column(
235 + mainAxisAlignment: MainAxisAlignment.center,
236 + spacing: 12,
237 + children: [
238 + const Spacer(),
239 + Icon(
240 + Icons.error_outline,
241 + size: 80,
242 + color: Theme.of(context).colorScheme.error,
243 + ),
244 + Text(
245 + S.of(context).pairing_error,
246 + style: TextStyle(
247 + fontSize: 20,
248 + fontWeight: FontWeight.w500,
249 + color: Theme.of(context).colorScheme.onSurface,
250 + ),
251 + textAlign: TextAlign.center,
252 + ),
253 + Text(
254 + errorText,
255 + style: TextStyle(
256 + fontSize: 16,
257 + fontWeight: FontWeight.w400,
258 + color: Theme.of(context).colorScheme.onSurfaceVariant,
259 + ),
260 + textAlign: TextAlign.center,
261 + ),
262 + const Spacer(),
263 + NewPrimaryButton(
264 + onPressed: retry,
265 + text: S.of(context).try_again,
266 + color: Theme.of(context).colorScheme.primary,
267 + textColor: Theme.of(context).colorScheme.onPrimary,
268 + )
269 + ],
270 + ),
271 + ),
272 + );
273 +
274 + Widget _enterPinCode() => Column(
275 + key: ValueKey(0),
276 + spacing: 12,
277 + mainAxisAlignment: MainAxisAlignment.center,
278 + children: [
279 + AnimatedContainer(
280 + duration: pinOpenDuration,
281 + curve: Curves.easeOutCubic,
282 + width: _isAwaitingPin ? 40 : 100,
283 + height: _isAwaitingPin ? 40 : 100,
284 + child: CakeImageWidget(
285 + imageUrl: hardwareWalletIcon,
286 + colorFilter: ColorFilter.mode(
287 + Theme.of(context).colorScheme.onSurfaceVariant,
288 + BlendMode.srcIn,
289 + ),
290 + ),
291 + ),
292 + AnimatedSwitcher(
293 + duration: pinOpenDuration,
294 + switchInCurve: Curves.easeInCubic,
295 + switchOutCurve: Curves.easeOutCubic,
296 + child: _isAwaitingPin
297 + ? Column(
298 + spacing: 24,
299 + key: ValueKey(1),
300 + children: [
301 + Text(
302 + textAlign: TextAlign.center,
303 + "${S.of(context).pairing_code_desc_1}\n${S.of(context).pairing_code_desc_2}",
304 + ),
305 + DigitInput(
306 + controller: _controller,
307 + desiredLength: pinLength,
308 + )
309 + ],
310 + )
311 + : Column(
312 + key: ValueKey(0),
313 + spacing: 12,
314 + children: [
315 + Text(
316 + S.of(context).proceed_on_device,
317 + style: TextStyle(
318 + fontWeight: FontWeight.w500,
319 + fontSize: 20,
320 + ),
321 + ),
322 + Padding(
323 + padding: EdgeInsets.symmetric(horizontal: 20),
324 + child: Text(
325 + S.of(context).proceed_on_device_description,
326 + textAlign: TextAlign.center,
327 + style: TextStyle(
328 + fontSize: 16,
329 + color: Theme.of(context).colorScheme.onSurfaceVariant,
330 + ),
331 + ),
332 + )
333 + ],
334 + ),
335 + )
336 + ],
337 + );
338 +}
lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart new
+106
@@ -0,0 +1,106 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
3 +import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
4 +import 'package:flutter/material.dart';
5 +
6 +class HardwareWalletTrezorParingSheet extends StatefulWidget {
7 + const HardwareWalletTrezorParingSheet({super.key});
8 +
9 + @override
10 + State<HardwareWalletTrezorParingSheet> createState() => _HardwareWalletTrezorParingSheetState();
11 +}
12 +
13 +class _HardwareWalletTrezorParingSheetState extends State<HardwareWalletTrezorParingSheet> {
14 + final TextEditingController _paringCodeController = TextEditingController();
15 +
16 + @override
17 + Widget build(BuildContext context) {
18 + return Padding(
19 + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
20 + child: SafeArea(
21 + child: Container(
22 + decoration: BoxDecoration(
23 + borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
24 + color: Theme.of(context).colorScheme.surface,
25 + ),
26 + child: Column(
27 + mainAxisSize: MainAxisSize.min,
28 + crossAxisAlignment: CrossAxisAlignment.start,
29 + children: [
30 + ModalTopBar(
31 + title: "Paring code",
32 + onLeadingPressed: Navigator.of(context).pop,
33 + onTrailingPressed: () {},
34 + leadingIcon: Icon(Icons.close),
35 + ),
36 + Padding(
37 + padding: const EdgeInsets.all(18.0),
38 + child: Column(
39 + mainAxisSize: MainAxisSize.min,
40 + crossAxisAlignment: CrossAxisAlignment.start,
41 + spacing: 12,
42 + children: [
43 + SizedBox(),
44 + Row(
45 + mainAxisAlignment: MainAxisAlignment.center,
46 + children: [
47 + Expanded(
48 + flex: 75,
49 + child: AnimatedContainer(
50 + duration: Duration(milliseconds: 300),
51 + height: 60,
52 + decoration: BoxDecoration(
53 + color: Theme.of(context).colorScheme.surfaceContainer,
54 + borderRadius: BorderRadius.only(
55 + topLeft: Radius.circular(16),
56 + bottomLeft: Radius.circular(16),
57 + ),
58 + border: Border.all(
59 + color: Theme.of(context).colorScheme.surfaceContainer,
60 + width: 2,
61 + ),
62 + ),
63 + child: TextField(
64 + textAlign: TextAlign.left,
65 + textAlignVertical: TextAlignVertical.center,
66 + controller: _paringCodeController,
67 + keyboardType:
68 + TextInputType.numberWithOptions(signed: false, decimal: false),
69 + decoration: InputDecoration(
70 + hint: Text(
71 + "000 000",
72 + textAlign: TextAlign.center,
73 + style: TextStyle(
74 + fontSize: 16,
75 + color: Theme.of(
76 + context,
77 + ).colorScheme.onSurface.withValues(alpha: 0.5),
78 + ),
79 + ),
80 + border: InputBorder.none,
81 + filled: true,
82 + fillColor: Colors.transparent,
83 + ),
84 + style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
85 + ),
86 + ),
87 + ),
88 + ],
89 + ),
90 + SizedBox(),
91 + NewPrimaryButton(
92 + text: S.of(context).continue_text,
93 + onPressed: () => Navigator.of(context).pop(_paringCodeController.text),
94 + color: Theme.of(context).colorScheme.primary,
95 + textColor: Theme.of(context).colorScheme.onPrimary,
96 + )
97 + ],
98 + ),
99 + )
100 + ],
101 + ),
102 + ),
103 + ),
104 + );
105 + }
106 +}
lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
+2 -59
@@ -1,16 +1,14 @@
1 import 'package:cake_wallet/core/execution_state.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/new-ui/widgets/confirm_swiper.dart';
4 +import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart';
5 import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
6 import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
7 import 'package:cake_wallet/view_model/send/send_view_model.dart';
8 import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
8 -import 'package:cw_core/wallet_info.dart';
9 import 'package:flutter/cupertino.dart';
10 import 'package:flutter/material.dart';
11 import 'package:flutter_mobx/flutter_mobx.dart';
12 -import 'package:flutter_svg/svg.dart';
13 -import 'package:vector_graphics/vector_graphics.dart';
12
13 class SendConfirmBottomWidget extends StatelessWidget {
14 const SendConfirmBottomWidget({super.key, required this.sendViewModel});
@@ -61,7 +59,7 @@ class SendConfirmBottomWidget extends StatelessWidget {
59 text: "${S.of(context).signing_transaction}...",
60 );
61 case IsAwaitingDeviceResponseState:
64 - return HardwareWalletConfirmationMessage(
62 + return HardwareWalletProceedOnDeviceMessage(
63 hardwareWalletType: sendViewModel.wallet.hardwareWalletType!);
64 case TransactionCommitting:
65 return LoadingBottomWidget(
@@ -173,58 +171,3 @@ class TransactionErrorActions extends StatelessWidget {
171 );
172 }
173 }
176 -
177 -class HardwareWalletConfirmationMessage extends StatelessWidget {
178 - const HardwareWalletConfirmationMessage({super.key, required this.hardwareWalletType});
179 -
180 - final HardwareWalletType hardwareWalletType;
181 -
182 - @override
183 - Widget build(BuildContext context) {
184 - return Container(
185 - width: double.infinity,
186 - decoration: BoxDecoration(
187 - border: Border.all(
188 - color: Theme.of(context).colorScheme.primary,
189 - ),
190 - borderRadius: BorderRadius.circular(16),
191 - ),
192 - child: Padding(
193 - padding: const EdgeInsets.all(12.0),
194 - child: Column(spacing: 8, children: [
195 - if (hardwareWalletIcon != null)
196 - CakeImageWidget(imageUrl:
197 - hardwareWalletIcon!,
198 - width: 36,
199 - height: 36,
200 - colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
201 - ),
202 - Text(
203 - S.of(context).proceed_on_device,
204 - style: TextStyle(
205 - fontSize: 16,
206 - fontWeight: FontWeight.w400,
207 - color: Theme.of(context).colorScheme.onSurface),
208 - )
209 - ]),
210 - ),
211 - );
212 - }
213 -
214 - String? get hardwareWalletIcon {
215 - switch (hardwareWalletType) {
216 - case HardwareWalletType.bitbox:
217 - return "assets/images/hardware_wallet/device_bitbox.svg";
218 - case HardwareWalletType.ledger:
219 - return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
220 - case HardwareWalletType.trezor:
221 - return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
222 - case HardwareWalletType.cupcake:
223 - return "assets/images/cupcake.svg";
224 - case HardwareWalletType.coldcard:
225 - case HardwareWalletType.seedsigner:
226 - case HardwareWalletType.keystone:
227 - return "assets/images/hardware_wallet/device_qr.svg";
228 - }
229 - }
230 -}
lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart
+1 -1
@@ -41,7 +41,7 @@ class _SwapConfirmSheetState extends State<SwapConfirmSheet> {
41 final sendVM = widget.exchangeTradeViewModel.sendViewModel;
42
43 if (sendVM.wallet.isHardwareWallet) {
44 - if (!sendVM.hardwareWalletViewModel!.isConnected) {
44 + if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
45 await Navigator.of(context).pushNamed(Routes.connectDevices,
46 arguments: ConnectDevicePageParams(
47 walletType: sendVM.walletType,
lib/router.dart
+1 -1
@@ -371,7 +371,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
371 (_) => getIt.get<NewWalletTypePage>(
372 param1: NewWalletTypeArguments(
373 onTypeSelected: (BuildContext context, WalletType type) {
374 - if (hardwareWalletType == HardwareWalletType.trezor) {
374 + if (hardwareWalletType == HardwareWalletType.trezor && type != WalletType.monero) {
375 Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
376 arguments: [type, hardwareWalletType]);
377 return;
lib/src/screens/connect_device/connect_device_page.dart
+97 -75
@@ -3,10 +3,13 @@ import 'dart:io';
3
4 import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 +import 'package:cake_wallet/main.dart';
7 +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
8 import 'package:cake_wallet/routes.dart';
9 import 'package:cake_wallet/src/screens/base_page.dart';
10 import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
11 import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart';
12 +import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
13 import 'package:cake_wallet/src/widgets/primary_button.dart';
14 import 'package:cake_wallet/themes/core/material_base_theme.dart';
15 import 'package:cake_wallet/utils/responsive_layout_util.dart';
@@ -91,6 +94,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
94 var bleDevices = <HardwareWalletDevice>[];
95 var usbDevices = <HardwareWalletDevice>[];
96
97 + List<HardwareWalletDevice> get allDevices => [...bleDevices, ...usbDevices];
98 +
99 late Timer? _usbRefreshTimer = null;
100 late Timer? _bleRefreshTimer = null;
101 late Timer? _bleStateTimer = null;
@@ -158,24 +163,38 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
163
164 Future<void> _connectToDevice(HardwareWalletDevice device) async {
165 final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
161 - if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM);
166 +
167 + WidgetsBinding.instance.addPostFrameCallback((_) {
168 + if (isConnected) widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM);
169 + });
170 }
171
172 String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
173 switch (deviceType) {
174 case HardwareWalletDeviceType.ledgerNanoX:
167 - return 'assets/images/hardware_wallet/ledger_nano_x.png';
175 + return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
176 case HardwareWalletDeviceType.ledgerNanoGen5:
169 - return 'assets/images/hardware_wallet/device_ledger_nano_gen_5.svg';
177 + return 'assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg';
178 case HardwareWalletDeviceType.ledgerStax:
171 - return 'assets/images/hardware_wallet/ledger_stax.png';
179 + return 'assets/new-ui/hardware_wallets/ledger_stax.svg';
180 case HardwareWalletDeviceType.ledgerFlex:
173 - return 'assets/images/hardware_wallet/ledger_flex.png';
181 + return 'assets/new-ui/hardware_wallets/ledger_flex.svg';
182 case HardwareWalletDeviceType.BitBox02:
175 - return 'assets/images/hardware_wallet/device_bitbox.svg';
183 + case HardwareWalletDeviceType.BitBox02Nova:
184 + return 'assets/new-ui/hardware_wallets/device_bitbox.svg';
185 + case HardwareWalletDeviceType.trezorModelOne:
186 + return 'assets/new-ui/hardware_wallets/device_trezor_model_one.svg';
187 + case HardwareWalletDeviceType.trezorModelT:
188 + return 'assets/new-ui/hardware_wallets/device_trezor_model_t.svg';
189 + case HardwareWalletDeviceType.trezorSafe3:
190 + return 'assets/new-ui/hardware_wallets/device_trezor_safe_3.svg';
191 + case HardwareWalletDeviceType.trezorSafe5:
192 + return 'assets/new-ui/hardware_wallets/device_trezor_safe_5.svg';
193 + case HardwareWalletDeviceType.trezorSafe7:
194 + return 'assets/new-ui/hardware_wallets/device_trezor_safe_7.svg';
195
196 default:
178 - return 'assets/images/hardware_wallet/ledger_nano_x.png';
197 + return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
198 }
199 }
200
@@ -197,25 +216,15 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
216 Expanded(
217 child: SingleChildScrollView(
218 child: Column(
219 + spacing: 24,
220 children: [
201 - Padding(
202 - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
203 - child: Text(
204 - description,
205 - style: Theme.of(context).textTheme.titleMedium,
206 - textAlign: TextAlign.center,
207 - ),
221 + CakeImageWidget(
222 + imageUrl: "assets/new-ui/hardware_wallet.svg",
223 ),
209 - Offstage(
210 - offstage: !longWait,
211 - child: Padding(
212 - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
213 - child: Text(
214 - S.of(context).if_you_dont_see_your_device,
215 - style: Theme.of(context).textTheme.titleMedium,
216 - textAlign: TextAlign.center,
217 - ),
218 - ),
224 + Text(
225 + description,
226 + style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
227 + textAlign: TextAlign.center,
228 ),
229 Observer(
230 builder: (_) => Offstage(
@@ -231,56 +240,69 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
240 ),
241 ),
242 ),
234 - if (bleDevices.length > 0) ...[
235 - Padding(
236 - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
237 - child: Container(
238 - width: double.infinity,
239 - child: Text(
240 - S.of(context).bluetooth,
241 - style: Theme.of(context).textTheme.bodyMedium,
242 - ),
243 - ),
244 - ),
245 - ...bleDevices
246 - .map(
247 - (device) => Padding(
248 - padding: EdgeInsets.only(bottom: 20),
249 - child: DeviceTile(
250 - onPressed: () => _connectToDevice(device),
251 - title: device.name,
252 - leading: _getDeviceTileLeading(device.type),
253 - connectionType: device.connectionType,
243 + Container(
244 + decoration: BoxDecoration(
245 + color: Theme.of(context).colorScheme.surfaceContainer,
246 + borderRadius: BorderRadius.circular(18)),
247 + child: ListView.separated(
248 + shrinkWrap: true,
249 + itemBuilder: (context, index) {
250 + final item = allDevices[index];
251 + return GestureDetector(
252 + onTap: () => _connectToDevice(item),
253 + child: SizedBox(
254 + height: 48,
255 + child: Padding(
256 + padding: const EdgeInsets.symmetric(horizontal: 12.0),
257 + child: Row(
258 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
259 + children: [
260 + Row(
261 + spacing: 12,
262 + children: [
263 + CakeImageWidget(
264 + imageUrl: _getDeviceTileLeading(item.type),
265 + width: 24,
266 + height: 24,
267 + colorFilter: ColorFilter.mode(
268 + Theme.of(context).colorScheme.onSurfaceVariant,
269 + BlendMode.srcIn),
270 + ),
271 + Text(item.name)
272 + ],
273 + ),
274 + Row(
275 + spacing: 12,
276 + children: [
277 + if (item.connectionType ==
278 + HardwareWalletConnectionType.ble)
279 + CakeImageWidget(
280 + imageUrl: "assets/new-ui/bluetooth.svg",
281 + width: 24,
282 + height: 24,
283 + colorFilter: ColorFilter.mode(
284 + Theme.of(context).colorScheme.primary,
285 + BlendMode.srcIn),
286 + ),
287 + CakeImageWidget(
288 + imageUrl: "assets/new-ui/arrow_forward.svg",
289 + height: 16,
290 + colorFilter: ColorFilter.mode(
291 + Theme.of(context).colorScheme.onSurfaceVariant,
292 + BlendMode.srcIn))
293 + ],
294 + )
295 + ],
296 + ),
297 + ),
298 ),
255 - ),
256 - )
257 - .toList()
258 - ],
259 - if (usbDevices.length > 0) ...[
260 - Padding(
261 - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
262 - child: Container(
263 - width: double.infinity,
264 - child: Text(
265 - S.of(context).usb,
266 - style: Theme.of(context).textTheme.bodyMedium,
267 - ),
268 - ),
269 - ),
270 - ...usbDevices
271 - .map(
272 - (device) => Padding(
273 - padding: EdgeInsets.only(bottom: 20),
274 - child: DeviceTile(
275 - onPressed: () => _connectToDevice(device),
276 - title: device.name,
277 - leading: _getDeviceTileLeading(device.type),
278 - connectionType: device.connectionType,
279 - ),
280 - ),
281 - )
282 - .toList(),
283 - ],
299 + );
300 + },
301 + separatorBuilder: (context, index) => Container(
302 + height: 1,
303 + color: Theme.of(context).colorScheme.surfaceContainerHigh),
304 + itemCount: allDevices.length),
305 + ),
306 if (widget.allowChangeWallet) ...[
307 PrimaryButton(
308 text: S.of(context).wallets,
@@ -293,10 +315,10 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
315 ),
316 ),
317 ),
296 - PrimaryButton(
318 + NewPrimaryButton(
319 text: S.of(context).how_to_connect,
320 color: Theme.of(context).colorScheme.surfaceContainer,
299 - textColor: Theme.of(context).colorScheme.onSecondaryContainer,
321 + textColor: Theme.of(context).colorScheme.primary,
322 onPressed: () => _onHowToConnect(context),
323 )
324 ],
lib/src/screens/connect_device/widgets/device_tile.dart
+7 -8
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
2 +import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
3 import 'package:flutter/material.dart';
4 import 'package:flutter_svg/flutter_svg.dart';
5
@@ -43,14 +44,12 @@ class DeviceTile extends StatelessWidget {
44 mainAxisAlignment: MainAxisAlignment.center,
45 crossAxisAlignment: CrossAxisAlignment.center,
46 children: <Widget>[
46 - if (leading != null && !leading!.endsWith(".svg"))
47 - Image.asset(
48 - leading!,
49 - height: 30,
50 - color: Theme.of(context).colorScheme.onSurface,
51 - ),
52 - if (leading != null && leading!.endsWith(".svg"))
53 - SvgPicture.asset(leading!, height: 30),
47 + CakeImageWidget(
48 + height: 30,
49 + imageUrl: leading,
50 + colorFilter:
51 + ColorFilter.mode(Theme.of(context).colorScheme.onSurface, BlendMode.srcIn),
52 + ),
53 Expanded(
54 child: Padding(
55 padding: EdgeInsets.only(left: 16),
lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+3 -3
@@ -34,11 +34,11 @@ class CryptoBalanceWidget extends StatelessWidget {
34 case null:
35 return null;
36 case HardwareWalletType.bitbox:
37 - return "assets/images/hardware_wallet/device_bitbox.svg";
37 + return "assets/new-ui/hardware_wallets/device_bitbox.svg";
38 case HardwareWalletType.ledger:
39 - return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
39 + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
40 case HardwareWalletType.trezor:
41 - return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
41 + return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
42 case HardwareWalletType.cupcake:
43 return "assets/images/cupcake.svg";
44 case HardwareWalletType.coldcard:
lib/src/screens/exchange_trade/exchange_trade_page.dart
+1 -1
@@ -213,7 +213,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
213 final sendVM = widget.exchangeTradeViewModel.sendViewModel;
214
215 if (sendVM.wallet.isHardwareWallet) {
216 - if (!sendVM.hardwareWalletViewModel!.isConnected) {
216 + if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
217 await Navigator.of(context).pushNamed(Routes.connectDevices,
218 arguments: ConnectDevicePageParams(
219 walletType: sendVM.walletType,
lib/src/screens/integrations/deuro/savings_page.dart
+1 -1
@@ -160,7 +160,7 @@ class DEuroSavingsPage extends BasePage {
160
161 Future<void> _requireHardwareWallet(BuildContext context) async {
162 if (_dEuroViewModel.wallet.isHardwareWallet) {
163 - if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected) {
163 + if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected(_dEuroViewModel.wallet.type)) {
164 await Navigator.of(context).pushNamed(Routes.connectDevices,
165 arguments: ConnectDevicePageParams(
166 walletType: _dEuroViewModel.wallet.type,
lib/src/screens/new_wallet/new_wallet_type_page.dart
+119 -105
@@ -6,14 +6,8 @@ import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/reactions/wallet_utils.dart';
7 import 'package:cake_wallet/routes.dart';
8 import 'package:cake_wallet/src/screens/base_page.dart';
9 -import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
10 -import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
9 import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
12 -import 'package:cake_wallet/src/widgets/primary_button.dart';
13 -import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
14 -import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
10 import 'package:cake_wallet/utils/responsive_layout_util.dart';
16 -import 'package:cake_wallet/utils/show_pop_up.dart';
11 import 'package:cake_wallet/wallet_types.g.dart';
12 import 'package:cw_core/currency_for_wallet_type.dart';
13 import 'package:cw_core/hardware/device_connection_type.dart';
@@ -21,10 +15,38 @@ import 'package:cw_core/wallet_info.dart';
15 import 'package:cw_core/wallet_type.dart';
16 import 'package:flutter/material.dart';
17
18 +class NewWalletTypePageHeader extends StatelessWidget {
19 + const NewWalletTypePageHeader({super.key});
20 +
21 + @override
22 + Widget build(BuildContext context) => const SizedBox(
23 + width: 138,
24 + height: 75,
25 + child: Stack(
26 + children: [
27 + Positioned(
28 + left: 0,
29 + child: CakeImageWidget(
30 + imageUrl: "assets/new-ui/hardware_wallet.svg",
31 + width: 75,
32 + height: 75,
33 + ),
34 + ),
35 + Positioned(
36 + left: 63,
37 + child: CakeImageWidget(
38 + imageUrl: "assets/new-ui/cake_coin.svg",
39 + width: 75,
40 + height: 75,
41 + ),
42 + ),
43 + ],
44 + ),
45 + );
46 +}
47 +
48 class NewWalletTypePage extends BasePage {
25 - NewWalletTypePage({
26 - required this.newWalletTypeArguments,
27 - });
49 + NewWalletTypePage({required this.newWalletTypeArguments});
50
51 final NewWalletTypeArguments newWalletTypeArguments;
52
@@ -38,23 +60,21 @@ class NewWalletTypePage extends BasePage {
60
61 @override
62 Function(BuildContext)? get pushToNextWidget => (context) {
41 - FocusScopeNode currentFocus = FocusScope.of(context);
42 - if (!currentFocus.hasPrimaryFocus) {
43 - currentFocus.focusedChild?.unfocus();
44 - }
45 - };
63 + final currentFocus = FocusScope.of(context);
64 + if (!currentFocus.hasPrimaryFocus) currentFocus.focusedChild?.unfocus();
65 + };
66
67 @override
68 Widget body(BuildContext context) => WalletTypeForm(
49 - walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
50 - isCreate: newWalletTypeArguments.isCreate,
51 - onTypeSelected: newWalletTypeArguments.onTypeSelected,
52 - hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
53 - );
69 + walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
70 + isCreate: newWalletTypeArguments.isCreate,
71 + onTypeSelected: newWalletTypeArguments.onTypeSelected,
72 + hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
73 + );
74 }
75
76 class WalletTypeForm extends StatefulWidget {
57 - WalletTypeForm({
77 + const WalletTypeForm({
78 required this.walletImage,
79 required this.isCreate,
80 this.onTypeSelected,
@@ -77,9 +97,6 @@ class WalletTypeFormState extends State<WalletTypeForm> {
97
98 static const aspectRatioImage = 1.22;
99
80 - final TextEditingController searchController = TextEditingController();
81 -
82 - WalletType? selected;
100 List<WalletType> types;
101 List<WalletType> filteredTypes = [];
102
@@ -87,21 +104,12 @@ class WalletTypeFormState extends State<WalletTypeForm> {
104 void initState() {
105 types = filteredTypes = availableWalletTypes
106 .where((element) =>
90 - !widget.isHardwareWallet ||
91 - DeviceConnectionType.supportedConnectionTypes(
92 - element, widget.hardwareWalletType!, Platform.isIOS)
93 - .isNotEmpty)
107 + !widget.isHardwareWallet ||
108 + DeviceConnectionType.supportedConnectionTypes(
109 + element, widget.hardwareWalletType!, Platform.isIOS)
110 + .isNotEmpty)
111 .toList();
112 super.initState();
96 -
97 - searchController.addListener(() {
98 - setState(() {
99 - filteredTypes = List.from(types.where((type) => walletTypeToDisplayName(type)
100 - .toLowerCase()
101 - .contains(searchController.text.toLowerCase())));
102 - return;
103 - });
104 - });
113 }
114
115 @override
@@ -110,96 +118,102 @@ class WalletTypeFormState extends State<WalletTypeForm> {
118 child: ConstrainedBox(
119 constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
120 child: Column(
121 + spacing: 24,
122 children: [
114 - Padding(
115 - padding: EdgeInsets.only(top: 48),
116 - child: Text(
117 - S.of(context).choose_wallet_currency,
118 - textAlign: TextAlign.center,
119 - style: Theme.of(context).textTheme.titleMedium?.copyWith(
120 - color: Theme.of(context).colorScheme.onSurfaceVariant,
121 - ),
122 - ),
123 - ),
124 - Padding(
125 - padding: EdgeInsets.fromLTRB(24, 24, 24, 12),
126 - child: SearchBarWidget(searchController: searchController),
123 + const NewWalletTypePageHeader(),
124 + Text(
125 + S.of(context).choose_wallet_currency,
126 + textAlign: TextAlign.center,
127 ),
128 - Expanded(
129 - child: ScrollableWithBottomSection(
130 - contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
131 - scrollableKey: ValueKey('new_wallet_type_scrollable_key'),
132 - content: Column(
133 - crossAxisAlignment: CrossAxisAlignment.center,
134 - children: <Widget>[
135 - ...filteredTypes.map(
136 - (type) => Padding(
137 - padding: EdgeInsets.only(top: 12),
138 - child: SelectButton(
139 - key: ValueKey('new_wallet_type_${type.name}_button_key'),
140 - image: CakeImageWidget(
141 - imageUrl: getCryptoCurrencyIconForWalletListItem(type),
142 - height: 24,
143 - width: 24,
128 + Flexible(
129 + child: Padding(
130 + padding: const EdgeInsets.symmetric(horizontal: 18),
131 + child: Container(
132 + decoration: BoxDecoration(
133 + color: Theme.of(context).colorScheme.surfaceContainer,
134 + borderRadius: BorderRadius.circular(18),
135 + ),
136 + child: ListView.separated(
137 + key: const ValueKey('new_wallet_type_scrollable_key'),
138 + itemCount: filteredTypes.length,
139 + shrinkWrap: true,
140 + itemBuilder: (context, index) {
141 + final item = filteredTypes[index];
142 + final curr = getCryptoCurrencyForWalletListItem(item);
143 +
144 + return GestureDetector(
145 + key: ValueKey('new_wallet_type_${item.name}_button_key'),
146 + behavior: HitTestBehavior.opaque,
147 + onTap: () {
148 + onTypeSelected(item);
149 + },
150 + child: SizedBox(
151 + height: 48,
152 + child: Padding(
153 + padding: const EdgeInsets.symmetric(horizontal: 12.0),
154 + child: Row(
155 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
156 + children: [
157 + Row(
158 + children: [
159 + CakeImageWidget(
160 + height: 24,
161 + width: 24,
162 + imageUrl: curr.iconPath,
163 + ),
164 + const SizedBox(width: 12),
165 + Text(curr.fullName ?? curr.name),
166 + const SizedBox(width: 4),
167 + Text(
168 + curr.title,
169 + style: TextStyle(
170 + color: Theme.of(context).colorScheme.onSurfaceVariant,
171 + ),
172 + )
173 + ],
174 + ),
175 + CakeImageWidget(
176 + imageUrl: "assets/new-ui/arrow_forward.svg",
177 + height: 16,
178 + colorFilter: ColorFilter.mode(
179 + Theme.of(context).colorScheme.onSurfaceVariant,
180 + BlendMode.srcIn,
181 + ),
182 + )
183 + ],
184 + ),
185 ),
145 - text: walletTypeToDisplayName(type),
146 - showTrailingIcon: false,
147 - height: 54,
148 - isSelected: selected == type,
149 - onTap: () => setState(() => selected = type),
150 - deviceConnectionTypes: widget.isHardwareWallet
151 - ? DeviceConnectionType.supportedConnectionTypes(
152 - type, widget.hardwareWalletType!, Platform.isIOS)
153 - : [],
186 ),
155 - ),
187 + );
188 + },
189 + separatorBuilder: (context, index) => Container(
190 + height: 1,
191 + color: Theme.of(context).colorScheme.surfaceContainerHigh,
192 ),
157 - ],
158 - ),
159 - bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
160 - bottomSection: PrimaryButton(
161 - key: ValueKey('new_wallet_type_next_button_key'),
162 - onPressed: () => onTypeSelected(),
163 - text: S.of(context).seed_language_next,
164 - color: Theme.of(context).colorScheme.primary,
165 - textColor: Theme.of(context).colorScheme.onPrimary,
166 - isDisabled: selected == null,
193 + ),
194 ),
195 ),
169 - ),
196 + )
197 ],
198 ),
199 ),
200 );
201 }
202
176 - Future<void> onTypeSelected() async {
177 - if (selected == null) throw Exception('Wallet Type is not selected yet.');
178 -
179 - if (selected == WalletType.haven && widget.isCreate) {
180 - return await showPopUp<void>(
181 - context: context,
182 - builder: (BuildContext context) => PopUpCancellableAlertDialog(
183 - contentText: S.of(context).pause_wallet_creation,
184 - actionButtonText: S.of(context).ok,
185 - buttonAction: () => Navigator.of(context).pop(),
186 - ),
187 - );
188 - }
189 -
203 + Future<void> onTypeSelected(WalletType selected) async {
204 // If it's a restore flow, trigger the external callback
205 // If it's not a BIP39 Wallet or if there are no other wallets, route to the newWallet page
206 // Any other scenario, route to pre-existing seed page
207 final walletList = await WalletInfo.getAll();
208 if (!widget.isCreate) {
195 - widget.onTypeSelected!(context, selected!);
196 - } else if (!isBIP39Wallet(selected!) || walletList.isEmpty) {
209 + widget.onTypeSelected!(context, selected);
210 + } else if (!isBIP39Wallet(selected) || walletList.isEmpty) {
211 Navigator.of(context).pushNamed(
212 Routes.newWallet,
199 - arguments: NewWalletArguments(type: selected!),
213 + arguments: NewWalletArguments(type: selected),
214 );
215 } else {
202 - Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected!);
216 + Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected);
217 }
218 }
205 -}
\ No newline at end of file
219 +}
lib/src/screens/send/send_page.dart
+1 -1
@@ -426,7 +426,7 @@ class SendPage extends BasePage {
426 }
427
428 if (sendViewModel.wallet.isHardwareWallet) {
429 - if (!sendViewModel.hardwareWalletViewModel!.isConnected) {
429 + if (!sendViewModel.hardwareWalletViewModel!.isConnected(sendViewModel.walletType)) {
430 await Navigator.of(context).pushNamed(Routes.connectDevices,
431 arguments: ConnectDevicePageParams(
432 walletType: sendViewModel.walletType,
lib/view_model/hardware_wallet/bitbox_view_model.dart
+1 -1
@@ -78,7 +78,7 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
78 bool _isConnecting = false;
79
80 @override
81 - bool get isConnected => false;
81 + bool isConnected(WalletType type) => false;
82
83 @override
84 HardwareWalletService getHardwareWalletService(WalletType type) {
lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
+2 -1
@@ -6,10 +6,11 @@ import 'package:cw_core/wallet_type.dart';
6
7 abstract class HardwareWalletViewModel {
8 HardwareWalletType get hardwareWalletType;
9 - bool get isConnected;
9 bool get isBleEnabled;
10 bool get hasBluetooth;
11
12 + bool isConnected(WalletType type);
13 +
14 Future<void> updateBleState();
15
16 Stream<HardwareWalletDevice> scanForBleDevices();
lib/view_model/hardware_wallet/ledger_view_model.dart
+5 -3
@@ -114,7 +114,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
114 if (_isConnecting) return false;
115 _isConnecting = true;
116 _connectingWalletType = type;
117 - if (isConnected) {
117 + if (isConnected(type)) {
118 try {
119 await _connection!.disconnect().catchError((_) {});
120 } catch (_) {}
@@ -126,7 +126,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
126
127 if (_connectionChangeSubscription == null) {
128 _connectionChangeSubscription = ledger
129 - .deviceStateChanges
129 + .deviceStateChanges(device.device.id)
130 .listen(_connectionChangeListener);
131 }
132
@@ -172,7 +172,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
172 }
173
174 @override
175 - bool get isConnected => _connection != null && !(_connection!.isDisconnected);
175 + bool isConnected(WalletType type) => _connection != null && !(_connection!.isDisconnected);
176
177 sdk.LedgerConnection get connection => _connection!;
178
@@ -196,6 +196,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
196 @override
197 HardwareWalletService getHardwareWalletService(WalletType type) {
198 switch (type) {
199 + case WalletType.monero:
200 + return monero!.getLedgerHardwareWalletService(connection);
201 case WalletType.bitcoin:
202 return bitcoin!.getLedgerHardwareWalletService(connection, true);
203 case WalletType.litecoin:
lib/view_model/hardware_wallet/trezor_connect_view_model.dart new
+258
@@ -0,0 +1,258 @@
1 +import 'dart:async';
2 +import 'dart:io';
3 +
4 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
6 +import 'package:cake_wallet/evm/evm.dart';
7 +import 'package:cake_wallet/main.dart';
8 +import 'package:cake_wallet/monero/monero.dart';
9 +import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart';
10 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
11 +import 'package:cake_wallet/wallet_type_utils.dart';
12 +import 'package:cw_core/hardware/device_connection_type.dart';
13 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
14 +import 'package:cw_core/utils/print_verbose.dart';
15 +import 'package:cw_core/wallet_base.dart';
16 +import 'package:cw_core/wallet_info.dart';
17 +import 'package:cw_core/wallet_type.dart';
18 +import 'package:device_info_plus/device_info_plus.dart';
19 +import 'package:flutter/material.dart';
20 +import 'package:mobx/mobx.dart';
21 +import 'package:permission_handler/permission_handler.dart';
22 +import 'package:trezor_connect/trezor_connect.dart' as connect_sdk;
23 +import 'package:trezor_flutter/trezor_flutter.dart' as sdk;
24 +
25 +part 'trezor_connect_view_model.g.dart';
26 +
27 +class TrezorConnectViewModel = TrezorConnectViewModelBase with _$TrezorConnectViewModel;
28 +
29 +abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with Store {
30 + final connect_sdk.TrezorConnect trezorConnect;
31 +
32 + late final sdk.TrezorInterface trezorBLE;
33 + late final sdk.TrezorInterface trezorUSB;
34 +
35 + sdk.ThpState _state = sdk.ThpState();
36 +
37 + TrezorConnectViewModelBase(this.trezorConnect) {
38 + if (_doesSupportHardwareWallets) {
39 + reaction((_) => isBleEnabled, (_) {
40 + if (isBleEnabled) _initBLE();
41 + });
42 + updateBleState();
43 +
44 + if (!Platform.isIOS) {
45 + trezorUSB = sdk.TrezorInterface.usb();
46 + }
47 + }
48 + }
49 +
50 + bool get _doesSupportHardwareWallets {
51 + if (isMoneroOnly) {
52 + return DeviceConnectionType.supportedConnectionTypes(
53 + WalletType.monero, HardwareWalletType.trezor, Platform.isIOS)
54 + .isNotEmpty;
55 + }
56 +
57 + return true;
58 + }
59 +
60 + bool _bleIsInitialized = false;
61 +
62 + Future<void> _initBLE() async {
63 + if (isBleEnabled && !_bleIsInitialized) {
64 + trezorBLE = sdk.TrezorInterface.ble(
65 + onPermissionRequest: (_) async {
66 + if (Platform.isMacOS) return true;
67 +
68 + Map<Permission, PermissionStatus> statuses = await [
69 + Permission.bluetoothScan,
70 + Permission.bluetoothConnect,
71 + Permission.bluetoothAdvertise,
72 + ].request();
73 +
74 + return statuses.values.where((status) => status.isDenied).isEmpty;
75 + },
76 + bleOptions: sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)),
77 + );
78 + _bleIsInitialized = true;
79 + }
80 + }
81 +
82 + @override
83 + HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
84 +
85 + @override
86 + @observable
87 + bool isBleEnabled = false;
88 +
89 + @override
90 + bool get hasBluetooth => true;
91 +
92 + @override
93 + Future<void> updateBleState() async {
94 + final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
95 +
96 + final newState = bleState == sdk.AvailabilityState.poweredOn;
97 +
98 + if (newState != isBleEnabled) isBleEnabled = newState;
99 + }
100 +
101 + @override
102 + Stream<HardwareWalletDevice> scanForBleDevices() =>
103 + trezorBLE.scan().map((d) => TrezorHardwareWalletDevice(d));
104 +
105 + @override
106 + Future<List<HardwareWalletDevice>> getAllUsbDevices() => trezorUSB.devices
107 + .then((devices) => devices.map((d) => TrezorHardwareWalletDevice(d)).toList());
108 +
109 + @override
110 + Future<void> stopScanning() async {
111 + if (_bleIsInitialized) await trezorBLE.stopScanning();
112 + if (!Platform.isIOS) await trezorUSB.stopScanning();
113 + }
114 +
115 + bool _isConnecting = false;
116 + sdk.TrezorClient? _client;
117 +
118 + Completer<String?>? _pinCompleter;
119 +
120 + @observable
121 + TrezorParingState paringState = TrezorParingState.initial;
122 +
123 + void setParingPin(String pin) => _pinCompleter?.complete(pin);
124 +
125 + @override
126 + Future<bool> connectDevice(HardwareWalletDevice device, WalletType type,
127 + [bool isRetry = false]) async {
128 + if (!(device is TrezorHardwareWalletDevice)) return false;
129 + if (_isConnecting) return false;
130 + _isConnecting = true;
131 + paringState = TrezorParingState.initial;
132 +
133 + try {
134 + final trezorInterface =
135 + device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB;
136 + final connection = await trezorInterface.connect(device.device);
137 +
138 + if (!isRetry) {
139 + showModalBottomSheet(
140 + context: navigatorKey.currentContext!,
141 + isScrollControlled: true,
142 + isDismissible: false,
143 + enableDrag: false,
144 + useSafeArea: true,
145 + builder: (context) =>
146 + HardwareWalletProceedOnDeviceSheet(
147 + hardwareWalletType: hardwareWalletType,
148 + trezorConnectVM: this,
149 + onRetry: () => connectDevice(device, type, true)
150 + ),
151 + );
152 + }
153 +
154 + Future<String> onPinCode() async {
155 + _pinCompleter = Completer<String?>();
156 + paringState = TrezorParingState.enterPin;
157 +
158 + final res = await _pinCompleter!.future;
159 + paringState = TrezorParingState.verifyingPin;
160 + if (res == null) throw Exception();
161 + return res;
162 + }
163 +
164 + final deviceInfo = await _deviceName;
165 +
166 + _client = sdk.TrezorClient.getClientForConnection(
167 + connection,
168 + _state,
169 + "Cake Wallet",
170 + deviceInfo,
171 + onPinCode,
172 + );
173 +
174 + await _client!.createChannel();
175 +
176 + paringState = TrezorParingState.success;
177 + return true;
178 + } catch (e) {
179 + await _client?.connection.disconnect();
180 + _client = null;
181 + _state = sdk.ThpState();
182 + // rethrow;
183 + paringState = TrezorParingState.fail(e.toString());
184 + printV(e);
185 + return false;
186 + } finally {
187 + _isConnecting = false;
188 + }
189 + }
190 +
191 + Future<String> get _deviceName async {
192 + final deviceInfo = DeviceInfoPlugin();
193 + if (Platform.isAndroid) return (await deviceInfo.androidInfo).model;
194 + if (Platform.isIOS) return (await deviceInfo.iosInfo).name;
195 + return "Computer";
196 + }
197 +
198 + @override
199 + bool isConnected(WalletType type) => type == WalletType.monero
200 + ? _client != null && _client?.connection.isDisconnected == false
201 + : true;
202 +
203 + @override
204 + HardwareWalletService getHardwareWalletService(WalletType type) {
205 + switch (type) {
206 + case WalletType.monero:
207 + return monero!.getTrezorHardwareWalletService(_client!);
208 + case WalletType.bitcoin:
209 + return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
210 + case WalletType.litecoin:
211 + return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
212 + case WalletType.ethereum:
213 + case WalletType.polygon:
214 + return evm!.getTrezorHardwareWalletService(trezorConnect);
215 + default:
216 + throw UnimplementedError();
217 + }
218 + }
219 +
220 + @override
221 + Future<void> initWallet(WalletBase wallet) async {
222 + switch (wallet.type) {
223 + case WalletType.monero:
224 + return monero!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
225 + case WalletType.bitcoin:
226 + case WalletType.litecoin:
227 + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
228 + case WalletType.ethereum:
229 + case WalletType.polygon:
230 + return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
231 + default:
232 + throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
233 + }
234 + }
235 +}
236 +
237 +
238 +abstract class TrezorParingState {
239 + static TrezorParingState initial = InitialTrezorParingState();
240 + static TrezorParingState enterPin = EnterPinTrezorParingState();
241 + static TrezorParingState verifyingPin = VerifyingPinTrezorParingState();
242 + static TrezorParingState success = SuccessTrezorParingState();
243 + static TrezorParingState fail(String message) => FailTrezorParingState(message);
244 +}
245 +
246 +class InitialTrezorParingState extends TrezorParingState {}
247 +
248 +class EnterPinTrezorParingState extends TrezorParingState {}
249 +
250 +class VerifyingPinTrezorParingState extends TrezorParingState {}
251 +
252 +class SuccessTrezorParingState extends TrezorParingState {}
253 +
254 +class FailTrezorParingState extends TrezorParingState {
255 + final String message;
256 +
257 + FailTrezorParingState(this.message);
258 +}
lib/view_model/hardware_wallet/trezor_view_model.dart deleted
-79
@@ -1,79 +0,0 @@
1 -import 'dart:async';
2 -
3 -import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 -import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
5 -import 'package:cake_wallet/evm/evm.dart';
6 -import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
7 -import 'package:cw_core/hardware/hardware_wallet_service.dart';
8 -import 'package:cw_core/wallet_base.dart';
9 -import 'package:cw_core/wallet_info.dart';
10 -import 'package:cw_core/wallet_type.dart';
11 -import 'package:trezor_connect/trezor_connect.dart' as sdk;
12 -import 'package:mobx/mobx.dart';
13 -
14 -part 'trezor_view_model.g.dart';
15 -
16 -class TrezorViewModel = TrezorViewModelBase with _$TrezorViewModel;
17 -
18 -abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
19 - final sdk.TrezorConnect trezorConnect;
20 -
21 - TrezorViewModelBase(this.trezorConnect);
22 -
23 - @override
24 - HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
25 -
26 - @override
27 - @observable
28 - bool isBleEnabled = false;
29 -
30 - @override
31 - bool get hasBluetooth => false;
32 -
33 - @override
34 - Future<void> updateBleState() async {}
35 -
36 - @override
37 - Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
38 -
39 - @override
40 - Future<List<HardwareWalletDevice>> getAllUsbDevices() async => [];
41 -
42 - @override
43 - Future<void> stopScanning() async {}
44 -
45 - @override
46 - Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async => true;
47 -
48 - @override
49 - bool get isConnected => true;
50 -
51 - @override
52 - HardwareWalletService getHardwareWalletService(WalletType type) {
53 - switch (type) {
54 - case WalletType.bitcoin:
55 - return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
56 - case WalletType.litecoin:
57 - return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
58 - case WalletType.ethereum:
59 - case WalletType.polygon:
60 - return evm!.getTrezorHardwareWalletService(trezorConnect);
61 - default:
62 - throw UnimplementedError();
63 - }
64 - }
65 -
66 - @override
67 - Future<void> initWallet(WalletBase wallet) async {
68 - switch (wallet.type) {
69 - case WalletType.bitcoin:
70 - case WalletType.litecoin:
71 - return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
72 - case WalletType.ethereum:
73 - case WalletType.polygon:
74 - return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
75 - default:
76 - throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
77 - }
78 - }
79 -}
lib/view_model/send/send_view_model.dart
+6 -2
@@ -619,7 +619,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
619 if (!(state is IsExecutingState)) state = IsExecutingState();
620
621 if (wallet.isHardwareWallet) {
622 - state = IsAwaitingDeviceResponseState();
622 if (walletType == WalletType.monero) {
623 _ledgerTxStateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
624 if (monero!.getLastLedgerCommand() == "INS_CLSAG") {
@@ -627,6 +626,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
626 state = IsDeviceSigningResponseState();
627 }
628 });
629 + } else {
630 + state = IsAwaitingDeviceResponseState();
631 }
632 }
633
@@ -970,8 +971,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
971 throw Exception("Pending transaction doesn't exist. It should not be happened.");
972 }
973
974 +
975 try {
974 - state = TransactionCommitting();
976 + state = wallet.isHardwareWallet && walletType == WalletType.monero
977 + ? IsAwaitingDeviceResponseState()
978 + : TransactionCommitting();
979
980 if (ocpRequest != null) {
981 await _handleOcpRequest();
lib/view_model/wallet_hardware_restore_view_model.dart
+1 -1
@@ -89,7 +89,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
89
90 credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
91 name: name,
92 - ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection,
92 + hardwareWalletService: hardwareWalletVM.getHardwareWalletService(type),
93 password: password,
94 height: _options['height'] as int? ?? 0,
95 );
pubspec_base.yaml
+7 -2
@@ -125,7 +125,7 @@ dependencies:
125 ledger_flutter_plus:
126 git:
127 url: https://github.com/vespr-wallet/ledger-flutter-plus
128 - ref: 60817d4b20144f9da9029f5034790272795b9d38
128 + ref: 0ccfbe26298e8974dfd4e891f34719c8348562f9
129 trezor_connect:
130 git:
131 url: https://github.com/cake-tech/trezor_connect
@@ -203,7 +203,7 @@ dependency_overrides:
203 ledger_flutter_plus:
204 git:
205 url: https://github.com/cake-tech/ledger-flutter-plus
206 - ref: 5237e5b3d5f07696eefd0aaee9c32478ed399d20
206 + ref: 7bcb00380783f8611f68f5cd63fec96261fea2c2
207 ledger_usb_plus:
208 git:
209 url: https://github.com/konstantinullrich/ledger-usb-plus
@@ -227,6 +227,10 @@ dependency_overrides:
227 decimal: ^2.3.3
228 flutter_rust_bridge: 2.11.1
229 flutter_secure_storage_windows: 3.1.2
230 + universal_ble:
231 + git:
232 + url: https://github.com/cake-tech/universal_ble.git
233 + ref: f7044558ad111a156987614971e08c583c80175c
234
235 flutter_icons:
236 image_path: "assets/images/app_logo.png"
@@ -290,6 +294,7 @@ flutter:
294 - assets/new-ui/card_icons/outline_icons/
295 - assets/new-ui/card_icons/symbol_icons/
296 - assets/new-ui/crypto_full_icons/
297 + - assets/new-ui/hardware_wallets/
298 - assets/new-ui/node_speed_badges/
299
300 fonts:
res/pictures/bluetooth.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M11.2495 21.6537V13.8037L6.39945 18.6537L5.3457 17.5999L10.9457 11.9999L5.3457 6.39994L6.39945 5.34619L11.2495 10.1962V2.34619H11.9995L17.3532 7.69994L13.0532 11.9999L17.3532 16.2999L11.9995 21.6537H11.2495ZM12.7495 10.1962L15.2457 7.69994L12.7495 5.23444V10.1962ZM12.7495 18.7654L15.2457 16.2999L12.7495 13.8037V18.7654Z" fill="white"/>
3 +</svg>
res/pictures/cake_coin.svg new
+60
@@ -0,0 +1,60 @@
1 +<svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M37.5 1C57.6584 1 74 17.3416 74 37.5C74 57.6584 57.6584 74 37.5 74C17.3416 74 1 57.6584 1 37.5C1 17.3416 17.3416 1 37.5 1Z" fill="url(#paint0_linear_19960_1035188)"/>
3 + <path d="M37.5 1C57.6584 1 74 17.3416 74 37.5C74 57.6584 57.6584 74 37.5 74C17.3416 74 1 57.6584 1 37.5C1 17.3416 17.3416 1 37.5 1Z" fill="url(#paint1_linear_19960_1035188)" fill-opacity="0.25"/>
4 + <path d="M63.1728 37.4961C63.1728 23.3155 51.6777 11.8195 37.4971 11.8193C23.3163 11.8193 11.8203 23.3154 11.8203 37.4961C11.8205 51.6767 23.3164 63.1719 37.4971 63.1719V66.1719L36.7568 66.1631C21.5073 65.7768 9.21662 53.4858 8.83008 38.2363L8.82031 37.4961C8.82031 21.6585 21.6595 8.81934 37.4971 8.81934L38.2373 8.8291C53.7326 9.22188 66.1728 21.9061 66.1728 37.4961L66.1641 38.2363C65.7713 53.7316 53.0869 66.1717 37.4971 66.1719V63.1719C51.6776 63.1717 63.1727 51.6766 63.1728 37.4961Z" fill="#D58308" fill-opacity="0.2"/>
5 + <path d="M65.1728 37.4961C65.1728 22.2109 52.7822 9.81949 37.4971 9.81934C22.2118 9.81934 9.82031 22.2108 9.82031 37.4961C9.82047 52.7812 22.2119 65.1719 37.4971 65.1719V66.1719L36.7568 66.1631C21.5073 65.7768 9.21662 53.4858 8.83008 38.2363L8.82031 37.4961C8.82031 21.6585 21.6595 8.81934 37.4971 8.81934L38.2373 8.8291C53.7326 9.22188 66.1728 21.9061 66.1728 37.4961L66.1641 38.2363C65.7713 53.7316 53.0869 66.1717 37.4971 66.1719V65.1719C52.7821 65.1717 65.1727 52.7812 65.1728 37.4961Z" fill="url(#paint2_linear_19960_1035188)" fill-opacity="0.5"/>
6 + <path d="M48.2033 35.5459C47.8236 35.9243 47.5076 36.3135 47.2941 36.8839C47.0805 37.4542 46.9748 38.0753 46.8228 39.0575C46.6049 40.4558 46.1799 41.0057 45.8435 41.3528C45.1564 42.0612 44.1803 42.0213 42.9907 41.7863C42.2433 41.6385 42.0491 41.8164 41.1604 42.5841L36.4752 47.2223L36.4731 54.2637C36.4731 54.3898 36.6263 54.4535 36.7158 54.364L50.5534 40.5539C50.74 40.3674 50.8457 40.1151 50.8457 39.851L50.8629 32.8721L48.2033 35.5448V35.5459Z" fill="url(#paint3_linear_19960_1035188)"/>
7 + <path d="M48.2033 35.5459C47.8236 35.9243 47.5076 36.3135 47.2941 36.8839C47.0805 37.4542 46.9748 38.0753 46.8228 39.0575C46.6049 40.4558 46.1799 41.0057 45.8435 41.3528C45.1564 42.0612 44.1803 42.0213 42.9907 41.7863C42.2433 41.6385 42.0491 41.8164 41.1604 42.5841L36.4752 47.2223L36.4731 54.2637C36.4731 54.3898 36.6263 54.4535 36.7158 54.364L50.5534 40.5539C50.74 40.3674 50.8457 40.1151 50.8457 39.851L50.8629 32.8721L48.2033 35.5448V35.5459Z" fill="url(#paint4_linear_19960_1035188)" fill-opacity="0.2"/>
8 + <path d="M42.6209 39.9512C43.5225 39.9771 44.1513 40.6294 44.8934 40.1614C45.6354 39.6935 45.6149 39.1728 45.7583 38.3124C45.9028 37.451 46.1887 36.4494 46.4432 35.8456C46.6988 35.2408 47.2694 34.4677 47.8496 33.9039L50.8404 30.7934L50.8598 27.6592C50.8598 27.4673 50.629 27.3703 50.4931 27.5061L36.8917 41.1027C36.6242 41.37 36.4743 41.7323 36.4743 42.1097V45.1953L41.0689 40.5852C41.0689 40.5852 41.7181 39.9264 42.6198 39.9523L42.6209 39.9512Z" fill="url(#paint5_linear_19960_1035188)"/>
9 + <path d="M42.6209 39.9512C43.5225 39.9771 44.1513 40.6294 44.8934 40.1614C45.6354 39.6935 45.6149 39.1728 45.7583 38.3124C45.9028 37.451 46.1887 36.4494 46.4432 35.8456C46.6988 35.2408 47.2694 34.4677 47.8496 33.9039L50.8404 30.7934L50.8598 27.6592C50.8598 27.4673 50.629 27.3703 50.4931 27.5061L36.8917 41.1027C36.6242 41.37 36.4743 41.7323 36.4743 42.1097V45.1953L41.0689 40.5852C41.0689 40.5852 41.7181 39.9264 42.6198 39.9523L42.6209 39.9512Z" fill="url(#paint6_linear_19960_1035188)" fill-opacity="0.2"/>
10 + <path d="M50.8641 25.0892L36.4743 39.5116V36.4324C36.4743 36.0551 36.6242 35.6928 36.8917 35.4255L50.4964 21.8257C50.6312 21.6909 50.8631 21.7858 50.8631 21.9777V25.0892H50.8641Z" fill="url(#paint7_linear_19960_1035188)"/>
11 + <path d="M50.8641 25.0892L36.4743 39.5116V36.4324C36.4743 36.0551 36.6242 35.6928 36.8917 35.4255L50.4964 21.8257C50.6312 21.6909 50.8631 21.7858 50.8631 21.9777V25.0892H50.8641Z" fill="url(#paint8_linear_19960_1035188)" fill-opacity="0.2"/>
12 + <path d="M49.7562 19.6088L25.3952 24.9143C24.5227 25.1041 23.9004 25.8761 23.9004 26.7688V44.5508C23.9004 44.8149 24.005 45.0682 24.1927 45.2558L34.4431 55.5026C34.6523 55.7118 35.0104 55.564 35.0104 55.2676V36.2101C35.0115 35.5816 35.2617 34.9778 35.7061 34.5336L50.0625 20.1683C50.2966 19.9344 50.0808 19.5387 49.7573 19.6088H49.7562Z" fill="url(#paint9_linear_19960_1035188)"/>
13 + <path d="M49.7562 19.6088L25.3952 24.9143C24.5227 25.1041 23.9004 25.8761 23.9004 26.7688V44.5508C23.9004 44.8149 24.005 45.0682 24.1927 45.2558L34.4431 55.5026C34.6523 55.7118 35.0104 55.564 35.0104 55.2676V36.2101C35.0115 35.5816 35.2617 34.9778 35.7061 34.5336L50.0625 20.1683C50.2966 19.9344 50.0808 19.5387 49.7573 19.6088H49.7562Z" fill="url(#paint10_linear_19960_1035188)" fill-opacity="0.2"/>
14 + <defs>
15 + <linearGradient id="paint0_linear_19960_1035188" x1="37.5" y1="0" x2="37.5" y2="75" gradientUnits="userSpaceOnUse">
16 + <stop stop-color="#FFCC35"/>
17 + <stop offset="1" stop-color="#FFAE00"/>
18 + </linearGradient>
19 + <linearGradient id="paint1_linear_19960_1035188" x1="37.5" y1="0" x2="37.5" y2="75" gradientUnits="userSpaceOnUse">
20 + <stop stop-color="white"/>
21 + <stop offset="0.25" stop-color="white" stop-opacity="0"/>
22 + </linearGradient>
23 + <linearGradient id="paint2_linear_19960_1035188" x1="37.4968" y1="8.81934" x2="37.4968" y2="66.1723" gradientUnits="userSpaceOnUse">
24 + <stop stop-color="#935800"/>
25 + <stop offset="1" stop-color="#D58308" stop-opacity="0"/>
26 + </linearGradient>
27 + <linearGradient id="paint3_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
28 + <stop stop-color="#D08C00"/>
29 + <stop offset="0.5" stop-color="#E19803"/>
30 + </linearGradient>
31 + <linearGradient id="paint4_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
32 + <stop stop-color="#5A3B00"/>
33 + <stop offset="0.25" stop-color="white" stop-opacity="0"/>
34 + </linearGradient>
35 + <linearGradient id="paint5_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
36 + <stop stop-color="#D08C00"/>
37 + <stop offset="0.5" stop-color="#E19803"/>
38 + </linearGradient>
39 + <linearGradient id="paint6_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
40 + <stop stop-color="#5A3B00"/>
41 + <stop offset="0.25" stop-color="white" stop-opacity="0"/>
42 + </linearGradient>
43 + <linearGradient id="paint7_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
44 + <stop stop-color="#D08C00"/>
45 + <stop offset="0.5" stop-color="#E19803"/>
46 + </linearGradient>
47 + <linearGradient id="paint8_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
48 + <stop stop-color="#5A3B00"/>
49 + <stop offset="0.25" stop-color="white" stop-opacity="0"/>
50 + </linearGradient>
51 + <linearGradient id="paint9_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
52 + <stop stop-color="#D08C00"/>
53 + <stop offset="0.5" stop-color="#E19803"/>
54 + </linearGradient>
55 + <linearGradient id="paint10_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
56 + <stop stop-color="#5A3B00"/>
57 + <stop offset="0.25" stop-color="white" stop-opacity="0"/>
58 + </linearGradient>
59 + </defs>
60 +</svg>
res/pictures/hardware_wallet.svg new
+191
@@ -0,0 +1,191 @@
1 +<svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <g clip-path="url(#clip0_19960_1035187)">
3 + <g clip-path="url(#clip1_19960_1035187)">
4 + <path d="M26.5632 1.37614C27.7392 0.200141 29.6352 0.200141 30.8112 1.37614L71.1192 41.6841C76.9752 47.5401 76.9752 57.0441 71.1192 62.9001C65.2632 68.7561 55.7592 68.7561 49.9032 62.9001L9.58317 22.5921C8.40717 21.4161 8.40717 19.5201 9.58317 18.3441L26.5632 1.37614Z" fill="url(#paint0_linear_19960_1035187)"/>
5 + <path d="M26.5632 1.37614C27.7392 0.200141 29.6352 0.200141 30.8112 1.37614L71.1192 41.6841C76.9752 47.5401 76.9752 57.0441 71.1192 62.9001C65.2632 68.7561 55.7592 68.7561 49.9032 62.9001L9.58317 22.5921C8.40717 21.4161 8.40717 19.5201 9.58317 18.3441L26.5632 1.37614Z" fill="url(#paint1_linear_19960_1035187)"/>
6 + <path d="M26.5632 1.37614C27.7392 0.200141 29.6352 0.200141 30.8112 1.37614L71.1192 41.6841C76.9752 47.5401 76.9752 57.0441 71.1192 62.9001C65.2632 68.7561 55.7592 68.7561 49.9032 62.9001L9.58317 22.5921C8.40717 21.4161 8.40717 19.5201 9.58317 18.3441L26.5632 1.37614Z" fill="url(#paint2_linear_19960_1035187)"/>
7 + <path d="M26.5632 1.37614C27.7392 0.200141 29.6352 0.200141 30.8112 1.37614L71.1192 41.6841C76.9752 47.5401 76.9752 57.0441 71.1192 62.9001C65.2632 68.7561 55.7592 68.7561 49.9032 62.9001L9.58317 22.5921C8.40717 21.4161 8.40717 19.5201 9.58317 18.3441L26.5632 1.37614Z" fill="url(#paint3_linear_19960_1035187)" fill-opacity="0.5"/>
8 + <path d="M26.5632 1.37614C27.7392 0.200141 29.6352 0.200141 30.8112 1.37614L71.1192 41.6841C76.9752 47.5401 76.9752 57.0441 71.1192 62.9001C65.2632 68.7561 55.7592 68.7561 49.9032 62.9001L9.58317 22.5921C8.40717 21.4161 8.40717 19.5201 9.58317 18.3441L26.5632 1.37614Z" fill="url(#paint4_linear_19960_1035187)" fill-opacity="0.3"/>
9 + <path d="M26.5632 1.37614C27.7392 0.200141 29.6352 0.200141 30.8112 1.37614L71.1192 41.6841C76.9752 47.5401 76.9752 57.0441 71.1192 62.9001C65.2632 68.7561 55.7592 68.7561 49.9032 62.9001L9.58317 22.5921C8.40717 21.4161 8.40717 19.5201 9.58317 18.3441L26.5632 1.37614Z" fill="url(#paint5_linear_19960_1035187)" fill-opacity="0.4"/>
10 + <rect x="44.2656" y="24.5062" width="32.76" height="16.44" rx="1.68" transform="rotate(45 44.2656 24.5062)" fill="#09479E"/>
11 + <rect x="44.2656" y="24.5062" width="32.76" height="16.44" rx="1.68" transform="rotate(45 44.2656 24.5062)" fill="url(#paint6_linear_19960_1035187)"/>
12 + <rect x="44.2656" y="24.5062" width="32.76" height="16.44" rx="1.68" transform="rotate(45 44.2656 24.5062)" stroke="url(#paint7_linear_19960_1035187)" stroke-width="0.72"/>
13 + <mask id="mask0_19960_1035187" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="32" y="24" width="36" height="35">
14 + <rect x="44.2656" y="24.5062" width="32.76" height="16.44" rx="1.68" transform="rotate(45 44.2656 24.5062)" fill="#09479E" stroke="url(#paint8_linear_19960_1035187)" stroke-width="0.72"/>
15 + </mask>
16 + <g mask="url(#mask0_19960_1035187)">
17 + <rect x="58.5298" y="28.9963" width="9.6" height="29.0002" transform="rotate(75 58.5298 28.9963)" fill="url(#paint9_linear_19960_1035187)" fill-opacity="0.2" stroke="url(#paint10_linear_19960_1035187)" stroke-width="0.12"/>
18 + </g>
19 + <rect x="44.2656" y="24.5062" width="32.76" height="16.44" rx="1.68" transform="rotate(45 44.2656 24.5062)" stroke="url(#paint11_linear_19960_1035187)" stroke-width="0.72"/>
20 + <path d="M26.9023 1.71582C27.8909 0.727272 29.4831 0.727273 30.4717 1.71582L70.7793 42.0234C76.4478 47.692 76.4478 56.892 70.7793 62.5605C65.1107 68.2289 55.9107 68.229 50.2422 62.5605L9.92285 22.2529C8.99611 21.3262 8.93817 19.869 9.74902 18.876L9.92285 18.6836L26.9023 1.71582Z" stroke="url(#paint12_linear_19960_1035187)" stroke-width="0.96"/>
21 + <path d="M26.9023 1.71582C27.8909 0.727272 29.4831 0.727273 30.4717 1.71582L70.7793 42.0234C76.4478 47.692 76.4478 56.892 70.7793 62.5605C65.1107 68.2289 55.9107 68.229 50.2422 62.5605L9.92285 22.2529C8.99611 21.3262 8.93817 19.869 9.74902 18.876L9.92285 18.6836L26.9023 1.71582Z" stroke="url(#paint13_linear_19960_1035187)" stroke-opacity="0.5" stroke-width="0.96"/>
22 + <circle cx="28.7422" cy="20.5524" r="8.04" transform="rotate(45 28.7422 20.5524)" stroke="url(#paint14_linear_19960_1035187)" stroke-width="1.08"/>
23 + <g filter="url(#filter0_d_19960_1035187)">
24 + <circle cx="28.7422" cy="20.5524" r="7.26" transform="rotate(45 28.7422 20.5524)" stroke="url(#paint15_linear_19960_1035187)" stroke-width="0.48" shape-rendering="crispEdges"/>
25 + </g>
26 + <path d="M0.5 40.292C0.5 38.636 1.844 37.292 3.5 37.292H60.5C68.78 37.292 75.5 44.012 75.5 52.292C75.5 60.572 68.78 67.292 60.5 67.292H3.5C1.844 67.292 0.5 65.948 0.5 64.292V40.292Z" fill="url(#paint16_linear_19960_1035187)"/>
27 + <path d="M0.5 40.292C0.5 38.636 1.844 37.292 3.5 37.292H60.5C68.78 37.292 75.5 44.012 75.5 52.292C75.5 60.572 68.78 67.292 60.5 67.292H3.5C1.844 67.292 0.5 65.948 0.5 64.292V40.292Z" fill="url(#paint17_linear_19960_1035187)"/>
28 + <path d="M0.5 40.292C0.5 38.636 1.844 37.292 3.5 37.292H60.5C68.78 37.292 75.5 44.012 75.5 52.292C75.5 60.572 68.78 67.292 60.5 67.292H3.5C1.844 67.292 0.5 65.948 0.5 64.292V40.292Z" fill="url(#paint18_linear_19960_1035187)" fill-opacity="0.5"/>
29 + <path d="M0.5 40.292C0.5 38.636 1.844 37.292 3.5 37.292H60.5C68.78 37.292 75.5 44.012 75.5 52.292C75.5 60.572 68.78 67.292 60.5 67.292H3.5C1.844 67.292 0.5 65.948 0.5 64.292V40.292Z" fill="url(#paint19_linear_19960_1035187)" fill-opacity="0.3"/>
30 + <mask id="mask1_19960_1035187" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="37" width="76" height="31">
31 + <path d="M0.5 40.292C0.5 38.636 1.844 37.292 3.5 37.292H60.5C68.78 37.292 75.5 44.012 75.5 52.292C75.5 60.572 68.78 67.292 60.5 67.292H3.5C1.844 67.292 0.5 65.948 0.5 64.292V40.292Z" fill="url(#paint20_linear_19960_1035187)"/>
32 + <path d="M0.5 40.292C0.5 38.636 1.844 37.292 3.5 37.292H60.5C68.78 37.292 75.5 44.012 75.5 52.292C75.5 60.572 68.78 67.292 60.5 67.292H3.5C1.844 67.292 0.5 65.948 0.5 64.292V40.292Z" fill="url(#paint21_linear_19960_1035187)"/>
33 + </mask>
34 + <g mask="url(#mask1_19960_1035187)">
35 + <rect x="20.6016" y="23.8003" width="21.4265" height="49.5049" transform="rotate(20.9508 20.6016 23.8003)" fill="url(#paint22_linear_19960_1035187)" fill-opacity="0.3"/>
36 + </g>
37 + <path d="M3.5 37.6523H60.5C68.5812 37.6523 75.1396 44.2108 75.1396 52.292C75.1396 60.3732 68.5812 66.9316 60.5 66.9316H3.5C2.04282 66.9316 0.860355 65.7492 0.860352 64.292V40.292C0.860352 38.8348 2.04282 37.6523 3.5 37.6523Z" stroke="url(#paint23_linear_19960_1035187)" stroke-width="0.72"/>
38 + <path d="M3.5 37.6523H60.5C68.5812 37.6523 75.1396 44.2108 75.1396 52.292C75.1396 60.3732 68.5812 66.9316 60.5 66.9316H3.5C2.04282 66.9316 0.860355 65.7492 0.860352 64.292V40.292C0.860352 38.8348 2.04282 37.6523 3.5 37.6523Z" stroke="url(#paint24_linear_19960_1035187)" stroke-opacity="0.3" stroke-width="0.72"/>
39 + <circle cx="60.5605" cy="52.2798" r="8.04" stroke="url(#paint25_linear_19960_1035187)" stroke-width="1.08"/>
40 + <g filter="url(#filter1_d_19960_1035187)">
41 + <circle cx="60.5605" cy="52.2798" r="7.5" fill="url(#paint26_linear_19960_1035187)" fill-opacity="0.5" shape-rendering="crispEdges"/>
42 + <circle cx="60.5605" cy="52.2798" r="7.5" fill="url(#paint27_linear_19960_1035187)" fill-opacity="0.2" shape-rendering="crispEdges"/>
43 + <circle cx="60.5605" cy="52.2798" r="7.25955" stroke="url(#paint28_linear_19960_1035187)" stroke-width="0.480902" shape-rendering="crispEdges"/>
44 + </g>
45 + </g>
46 + </g>
47 + <defs>
48 + <filter id="filter0_d_19960_1035187" x="19.1589" y="12.6356" width="22.5" height="22.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
49 + <feFlood flood-opacity="0" result="BackgroundImageFix"/>
50 + <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
51 + <feOffset dx="1.66667" dy="3.33333"/>
52 + <feGaussianBlur stdDeviation="1.875"/>
53 + <feComposite in2="hardAlpha" operator="out"/>
54 + <feColorMatrix type="matrix" values="0 0 0 0 0.0353643 0 0 0 0 0.224702 0 0 0 0 0.490385 0 0 0 1 0"/>
55 + <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_19960_1035187"/>
56 + <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_19960_1035187" result="shape"/>
57 + </filter>
58 + <filter id="filter1_d_19960_1035187" x="50.9772" y="44.3631" width="22.5" height="22.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
59 + <feFlood flood-opacity="0" result="BackgroundImageFix"/>
60 + <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
61 + <feOffset dx="1.66667" dy="3.33333"/>
62 + <feGaussianBlur stdDeviation="1.875"/>
63 + <feComposite in2="hardAlpha" operator="out"/>
64 + <feColorMatrix type="matrix" values="0 0 0 0 0.34956 0 0 0 0 0.30834 0 0 0 0 0.442308 0 0 0 0.5 0"/>
65 + <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_19960_1035187"/>
66 + <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_19960_1035187" result="shape"/>
67 + </filter>
68 + <linearGradient id="paint0_linear_19960_1035187" x1="18.3192" y1="5.66012" x2="48.4992" y2="40.8201" gradientUnits="userSpaceOnUse">
69 + <stop stop-color="#EFEFF8"/>
70 + <stop offset="1" stop-color="#C6C6E6"/>
71 + </linearGradient>
72 + <linearGradient id="paint1_linear_19960_1035187" x1="34.1959" y1="0.49414" x2="67.9747" y2="49.3316" gradientUnits="userSpaceOnUse">
73 + <stop stop-color="#FE6412"/>
74 + <stop offset="1" stop-color="#EB4600"/>
75 + </linearGradient>
76 + <linearGradient id="paint2_linear_19960_1035187" x1="19.3392" y1="0.500117" x2="61.0992" y2="50.2401" gradientUnits="userSpaceOnUse">
77 + <stop stop-color="#4EBEFF"/>
78 + <stop offset="1" stop-color="#2982FF"/>
79 + </linearGradient>
80 + <linearGradient id="paint3_linear_19960_1035187" x1="46.7493" y1="-9.91652" x2="52.9993" y2="15.0835" gradientUnits="userSpaceOnUse">
81 + <stop stop-color="white"/>
82 + <stop offset="1" stop-color="white" stop-opacity="0"/>
83 + </linearGradient>
84 + <linearGradient id="paint4_linear_19960_1035187" x1="33.8325" y1="17.1668" x2="55.4992" y2="49.6668" gradientUnits="userSpaceOnUse">
85 + <stop stop-color="#000938" stop-opacity="0"/>
86 + <stop offset="1" stop-color="#000938"/>
87 + </linearGradient>
88 + <linearGradient id="paint5_linear_19960_1035187" x1="30.4991" y1="20.3751" x2="12.4991" y2="47.7501" gradientUnits="userSpaceOnUse">
89 + <stop offset="0.176297" stop-color="#EFEFF8" stop-opacity="0"/>
90 + <stop offset="0.571544" stop-color="#EFEFF8"/>
91 + </linearGradient>
92 + <linearGradient id="paint6_linear_19960_1035187" x1="61.0056" y1="23.9971" x2="61.0056" y2="41.1571" gradientUnits="userSpaceOnUse">
93 + <stop stop-color="#09479E"/>
94 + <stop offset="1" stop-color="#001D67"/>
95 + </linearGradient>
96 + <linearGradient id="paint7_linear_19960_1035187" x1="49.3656" y1="23.2171" x2="64.2035" y2="38.4576" gradientUnits="userSpaceOnUse">
97 + <stop stop-color="#002A66"/>
98 + <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
99 + </linearGradient>
100 + <linearGradient id="paint8_linear_19960_1035187" x1="49.3656" y1="23.2171" x2="64.2035" y2="38.4576" gradientUnits="userSpaceOnUse">
101 + <stop stop-color="#002A66"/>
102 + <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
103 + </linearGradient>
104 + <linearGradient id="paint9_linear_19960_1035187" x1="58.5723" y1="43.4829" x2="68.2923" y2="43.4829" gradientUnits="userSpaceOnUse">
105 + <stop stop-color="#DBEDFF"/>
106 + <stop offset="1" stop-color="#DBEDFF" stop-opacity="0"/>
107 + </linearGradient>
108 + <linearGradient id="paint10_linear_19960_1035187" x1="60.0529" y1="27.5992" x2="68.6402" y2="29.1082" gradientUnits="userSpaceOnUse">
109 + <stop stop-color="#002A66"/>
110 + <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
111 + </linearGradient>
112 + <linearGradient id="paint11_linear_19960_1035187" x1="49.3656" y1="23.2171" x2="64.2035" y2="38.4576" gradientUnits="userSpaceOnUse">
113 + <stop stop-color="#002A66"/>
114 + <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
115 + </linearGradient>
116 + <linearGradient id="paint12_linear_19960_1035187" x1="42.1062" y1="0.494141" x2="34.0992" y2="38.6001" gradientUnits="userSpaceOnUse">
117 + <stop stop-color="white" stop-opacity="0.5"/>
118 + <stop offset="1" stop-color="white" stop-opacity="0"/>
119 + </linearGradient>
120 + <linearGradient id="paint13_linear_19960_1035187" x1="32.6592" y1="8.54012" x2="21.6192" y2="36.3201" gradientUnits="userSpaceOnUse">
121 + <stop stop-color="#195BA1" stop-opacity="0"/>
122 + <stop offset="1" stop-color="#195BA1"/>
123 + </linearGradient>
124 + <linearGradient id="paint14_linear_19960_1035187" x1="21.2422" y1="14.3964" x2="36.646" y2="16.1247" gradientUnits="userSpaceOnUse">
125 + <stop stop-color="#2D72BB" stop-opacity="0.2"/>
126 + <stop offset="1" stop-color="#185AA0"/>
127 + </linearGradient>
128 + <linearGradient id="paint15_linear_19960_1035187" x1="21.9572" y1="17.7316" x2="35.7882" y2="23.3319" gradientUnits="userSpaceOnUse">
129 + <stop stop-color="white" stop-opacity="0.3"/>
130 + <stop offset="1" stop-color="white" stop-opacity="0"/>
131 + </linearGradient>
132 + <linearGradient id="paint16_linear_19960_1035187" x1="29.12" y1="37.292" x2="32.8635" y2="62.2832" gradientUnits="userSpaceOnUse">
133 + <stop stop-color="#EB4600"/>
134 + <stop offset="1" stop-color="#FE6412"/>
135 + </linearGradient>
136 + <linearGradient id="paint17_linear_19960_1035187" x1="0.5" y1="39.98" x2="72.8" y2="60.26" gradientUnits="userSpaceOnUse">
137 + <stop stop-color="#EFEFF8"/>
138 + <stop offset="1" stop-color="#C6C6E6"/>
139 + </linearGradient>
140 + <linearGradient id="paint18_linear_19960_1035187" x1="38" y1="43" x2="35.5" y2="75.9167" gradientUnits="userSpaceOnUse">
141 + <stop stop-color="#7D6493" stop-opacity="0"/>
142 + <stop offset="1" stop-color="#7D6493"/>
143 + </linearGradient>
144 + <linearGradient id="paint19_linear_19960_1035187" x1="61.3333" y1="28.4167" x2="47.5833" y2="58" gradientUnits="userSpaceOnUse">
145 + <stop stop-color="#46B0FF"/>
146 + <stop offset="1" stop-color="#46B0FF" stop-opacity="0"/>
147 + </linearGradient>
148 + <linearGradient id="paint20_linear_19960_1035187" x1="29.12" y1="37.292" x2="32.8635" y2="62.2832" gradientUnits="userSpaceOnUse">
149 + <stop stop-color="#EB4600"/>
150 + <stop offset="1" stop-color="#FE6412"/>
151 + </linearGradient>
152 + <linearGradient id="paint21_linear_19960_1035187" x1="0.5" y1="39.98" x2="72.8" y2="60.26" gradientUnits="userSpaceOnUse">
153 + <stop stop-color="#EFEFF8"/>
154 + <stop offset="1" stop-color="#C6C6E6"/>
155 + </linearGradient>
156 + <linearGradient id="paint22_linear_19960_1035187" x1="30.974" y1="33.6949" x2="32.8091" y2="66.4546" gradientUnits="userSpaceOnUse">
157 + <stop stop-color="white"/>
158 + <stop offset="1" stop-color="white" stop-opacity="0"/>
159 + </linearGradient>
160 + <linearGradient id="paint23_linear_19960_1035187" x1="38" y1="37.292" x2="38" y2="67.292" gradientUnits="userSpaceOnUse">
161 + <stop stop-color="white"/>
162 + <stop offset="1" stop-color="white" stop-opacity="0"/>
163 + </linearGradient>
164 + <linearGradient id="paint24_linear_19960_1035187" x1="38" y1="37.292" x2="38" y2="67.292" gradientUnits="userSpaceOnUse">
165 + <stop stop-color="#312938" stop-opacity="0"/>
166 + <stop offset="1" stop-color="#312938"/>
167 + </linearGradient>
168 + <linearGradient id="paint25_linear_19960_1035187" x1="53.0605" y1="46.1238" x2="68.4643" y2="47.8521" gradientUnits="userSpaceOnUse">
169 + <stop stop-color="#6E6EA8"/>
170 + <stop offset="1" stop-color="#312938"/>
171 + </linearGradient>
172 + <linearGradient id="paint26_linear_19960_1035187" x1="56.3337" y1="45.9165" x2="63.0003" y2="57.5831" gradientUnits="userSpaceOnUse">
173 + <stop stop-color="white"/>
174 + <stop offset="1" stop-color="white" stop-opacity="0"/>
175 + </linearGradient>
176 + <linearGradient id="paint27_linear_19960_1035187" x1="60.5605" y1="44.7798" x2="65.5005" y2="56.3331" gradientUnits="userSpaceOnUse">
177 + <stop stop-color="#564A84" stop-opacity="0"/>
178 + <stop offset="1" stop-color="#564A84"/>
179 + </linearGradient>
180 + <linearGradient id="paint28_linear_19960_1035187" x1="53.7756" y1="49.459" x2="67.6066" y2="55.0593" gradientUnits="userSpaceOnUse">
181 + <stop stop-color="white"/>
182 + <stop offset="1" stop-color="white" stop-opacity="0"/>
183 + </linearGradient>
184 + <clipPath id="clip0_19960_1035187">
185 + <rect width="75" height="75" fill="white"/>
186 + </clipPath>
187 + <clipPath id="clip1_19960_1035187">
188 + <rect width="75" height="66.792" fill="white" transform="translate(0.5 0.5)"/>
189 + </clipPath>
190 + </defs>
191 +</svg>
res/pictures/hardware_wallets/device_bitbox.svg renamed
+1 -1
@@ -1,3 +1,3 @@
1 <svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
2 -<path d="M67.0791 26C72.5581 26 77 30.4419 77 35.9209V39H90V58H77V61.0791C77 66.5581 72.5581 71 67.0791 71H15.9209L15.4102 70.9873C10.1684 70.7216 6 66.3869 6 61.0791V35.9209C6 30.6131 10.1684 26.2784 15.4102 26.0127L15.9209 26H67.0791ZM15.9209 33C14.3582 33 13.082 34.2273 13.0039 35.7705L13 35.9209V61.0791C13 62.6921 14.3079 64 15.9209 64H67.0791C68.6921 64 70 62.6921 70 61.0791V35.9209C70 34.3079 68.6921 33 67.0791 33H15.9209Z" fill="#D7E2F7"/>
2 +<path d="M67.0791 26C72.5581 26 77 30.4419 77 35.9209V39H90V58H77V61.0791C77 66.5581 72.5581 71 67.0791 71H15.9209L15.4102 70.9873C10.1684 70.7216 6 66.3869 6 61.0791V35.9209C6 30.6131 10.1684 26.2784 15.4102 26.0127L15.9209 26H67.0791ZM15.9209 33C14.3582 33 13.082 34.2273 13.0039 35.7705L13 35.9209V61.0791C13 62.6921 14.3079 64 15.9209 64H67.0791C68.6921 64 70 62.6921 70 61.0791V35.9209C70 34.3079 68.6921 33 67.0791 33H15.9209Z" fill="white"/>
3 </svg>
res/pictures/hardware_wallets/device_ledger_flex.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M15.7886 19.8887C16.3406 19.8889 16.7884 20.3366 16.7886 20.8887C16.7886 20.9262 16.7858 20.9636 16.7817 21H16.7886V23H6.45459V21H6.46143C6.4574 20.9636 6.45459 20.9262 6.45459 20.8887C6.45481 20.3366 6.90254 19.8888 7.45459 19.8887H15.7886ZM18.3989 5.33301C18.3987 4.0445 17.3545 3 16.0659 3H7.17725C5.88869 3 4.84346 4.0445 4.84326 5.33301V23H2.84326V5.33301C2.84346 2.93992 4.78411 1 7.17725 1H16.0659C18.459 1 20.3987 2.93992 20.3989 5.33301V5.46582C20.968 5.57023 21.3989 6.06778 21.3989 6.66699V9.55566C21.3989 10.1548 20.9679 10.6514 20.3989 10.7559V23H18.3989V5.33301Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M17.3503 0C19.0071 0 20.3503 1.34315 20.3503 3V3.6C20.6816 3.6 20.9503 3.86863 20.9503 4.2V7.2C20.9503 7.49005 20.7446 7.73245 20.471 7.78828L20.3503 7.8V21C20.3503 22.6568 19.0071 24 17.3503 24H6.55029C4.89344 24 3.55029 22.6568 3.55029 21V3C3.55029 1.34315 4.89344 0 6.55029 0H17.3503ZM14.9503 20.4C14.2875 20.4 13.7503 20.9372 13.7503 21.6C13.7503 22.2628 14.2875 22.8 14.9503 22.8H17.3503C18.0131 22.8 18.5503 22.2628 18.5503 21.6C18.5503 20.9372 18.0131 20.4 17.3503 20.4H14.9503ZM6.55029 1.8C5.88756 1.8 5.35029 2.33726 5.35029 3V17.4C5.35029 18.0628 5.88756 18.6 6.55029 18.6H17.3503C18.0131 18.6 18.5503 18.0628 18.5503 17.4V3C18.5503 2.33726 18.0131 1.8 17.3503 1.8H6.55029Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_ledger_nano_s.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M0 20.4286H17.4952C19.9571 20.4286 22 18.3857 22 15.8976C22 14.7452 21.581 13.6452 20.8214 12.8595L15.1643 7.1762L15.8714 6.46906L14.0119 4.60954L13.3048 5.31667L11.2095 3.24762L11.9429 2.51429L10.0833 0.654762L9.34999 1.3881L7.9881 0L1.72857 6.25954L6.8881 11.3929H0V20.4286ZM1.96429 18.4643V13.3571H17.4952C18.8833 13.3571 20.0357 14.5095 20.0357 15.8976C20.0357 17.3119 18.8833 18.4643 17.4952 18.4643H1.96429ZM4.50476 6.25954L7.9881 2.77619L16.5786 11.3929H15.7929H9.66428L4.50476 6.25954ZM15.9762 15.8976C15.9762 16.6309 16.5786 17.2857 17.3643 17.2857C18.1238 17.2857 18.7262 16.6309 18.7262 15.8976C18.7262 15.1381 18.1238 14.5357 17.3643 14.5357C16.5786 14.5357 15.9762 15.1381 15.9762 15.8976Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_ledger_nano_x.svg renamed
+1 -1
@@ -1,3 +1,3 @@
1 <svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
2 -<path d="M7.76001 86.33H72.556C81.674 86.33 89.24 78.764 89.24 69.549C89.24 65.281 87.688 61.207 84.875 58.297L37.345 10.67L14.162 33.8531L33.271 52.865H7.76001V86.33ZM15.035 79.055V60.14H72.556C77.697 60.14 81.965 64.408 81.965 69.549C81.965 74.787 77.697 79.055 72.556 79.055H15.035ZM24.444 33.8531L37.345 20.952L69.161 52.865H66.251H43.553L24.444 33.8531ZM32.592 34.1441C32.592 36.8601 34.823 39.2851 37.733 39.2851C40.546 39.2851 42.777 36.8601 42.777 34.1441C42.777 31.3311 40.546 29.1001 37.733 29.1001C34.823 29.1001 32.592 31.3311 32.592 34.1441ZM66.93 69.549C66.93 72.265 69.161 74.69 72.071 74.69C74.884 74.69 77.115 72.265 77.115 69.549C77.115 66.736 74.884 64.505 72.071 64.505C69.161 64.505 66.93 66.736 66.93 69.549Z" fill="#D7E2F7"/>
2 +<path d="M7.76001 86.33H72.556C81.674 86.33 89.24 78.764 89.24 69.549C89.24 65.281 87.688 61.207 84.875 58.297L37.345 10.67L14.162 33.8531L33.271 52.865H7.76001V86.33ZM15.035 79.055V60.14H72.556C77.697 60.14 81.965 64.408 81.965 69.549C81.965 74.787 77.697 79.055 72.556 79.055H15.035ZM24.444 33.8531L37.345 20.952L69.161 52.865H66.251H43.553L24.444 33.8531ZM32.592 34.1441C32.592 36.8601 34.823 39.2851 37.733 39.2851C40.546 39.2851 42.777 36.8601 42.777 34.1441C42.777 31.3311 40.546 29.1001 37.733 29.1001C34.823 29.1001 32.592 31.3311 32.592 34.1441ZM66.93 69.549C66.93 72.265 69.161 74.69 72.071 74.69C74.884 74.69 77.115 72.265 77.115 69.549C77.115 66.736 74.884 64.505 72.071 64.505C69.161 64.505 66.93 66.736 66.93 69.549Z" fill="white"/>
3 </svg>
res/pictures/hardware_wallets/device_ledger_stax.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M17.2876 4.09623C17.2876 3.31191 16.6517 2.67602 15.8674 2.67602H5.4143V21.324H15.8674C16.6517 21.324 17.2876 20.6881 17.2876 19.9038V4.09623ZM18.9636 5.51864C19.5292 5.54359 19.9803 6.00899 19.9803 6.58049V9.51574C19.9803 10.0872 19.5292 10.5515 18.9636 10.5765V19.9038C18.9636 21.6139 17.5775 23 15.8674 23H5.14085C4.36614 23 3.73828 22.372 3.73828 21.5974V2.40257C3.73828 1.6279 4.36618 1 5.14085 1H15.8674C17.5775 1 18.9636 2.38619 18.9636 4.09623V5.51864Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_trezor_model_one.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path fill-rule="evenodd" clip-rule="evenodd" d="M7.4873 0.655234C10.4323 -0.218431 13.5677 -0.218392 16.5127 0.655234C17.1731 0.851269 17.7531 1.25547 18.165 1.80758C18.577 2.35968 18.7989 3.03083 18.7988 3.71969V14.8994C18.7986 15.7851 18.6448 16.6641 18.3438 17.497L16.8057 21.7548C16.5693 22.4091 16.1376 22.9755 15.5693 23.3769C15.001 23.7783 14.3227 23.9961 13.627 24H10.3721C9.67638 23.9959 8.99793 23.7783 8.42969 23.3769C7.86158 22.9756 7.43065 22.4091 7.19434 21.7548L5.65625 17.497C5.35543 16.6639 5.20116 15.7851 5.20117 14.8994V3.71969C5.20109 3.03082 5.42396 2.35968 5.83594 1.80758C6.2479 1.25564 6.82704 0.851231 7.4873 0.655234ZM16.0283 2.28512C13.3994 1.50548 10.6006 1.50544 7.97168 2.28512C7.33598 2.47294 6.90137 3.05679 6.90137 3.71969V12.1015H10.3008C10.526 12.1016 10.7421 12.1913 10.9014 12.3505C11.0606 12.5099 11.1503 12.7259 11.1504 12.9511C11.1504 13.1765 11.0607 13.3933 10.9014 13.5527C10.7421 13.7119 10.526 13.8006 10.3008 13.8007H6.90137V14.8994C6.9013 15.5883 7.02094 16.2719 7.25488 16.9199L8.79297 21.1777C8.9117 21.5061 9.12832 21.7905 9.41406 21.9912C9.69995 22.1919 10.0413 22.2996 10.3906 22.2998H13.6094C13.9587 22.2996 14.3001 22.1919 14.5859 21.9912C14.8717 21.7904 15.0883 21.5061 15.207 21.1777L16.7461 16.9199C16.98 16.2719 17.0997 15.5882 17.0996 14.8994V13.8007H13.7002C13.4748 13.8007 13.258 13.712 13.0986 13.5527C12.9393 13.3933 12.8496 13.1765 12.8496 12.9511C12.8497 12.7259 12.9394 12.5099 13.0986 12.3505C13.258 12.1912 13.4748 12.1015 13.7002 12.1015H17.0996V3.71969C17.0995 3.39721 16.9947 3.08255 16.8018 2.82418C16.6088 2.56599 16.3374 2.37669 16.0283 2.28512ZM14.125 5.30269C14.4629 5.30278 14.7874 5.43681 15.0264 5.67574C15.2654 5.91481 15.3994 6.23999 15.3994 6.57809V9.12691C15.3994 9.46486 15.2652 9.78924 15.0264 10.0283C14.7874 10.2673 14.463 10.4022 14.125 10.4023H9.875C9.53702 10.4022 9.21263 10.2673 8.97363 10.0283C8.73476 9.78924 8.60059 9.46486 8.60059 9.12691V6.57809C8.60059 6.23999 8.73456 5.91481 8.97363 5.67574C9.21262 5.43681 9.53707 5.30278 9.875 5.30269H14.125ZM10.2998 8.70211H13.7002V7.00289H10.2998V8.70211Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_trezor_model_t.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M16.2856 0C17.195 0 18.0675 0.360924 18.7104 1.00391C19.3534 1.64688 19.7144 2.51943 19.7144 3.42871V12.7871C19.7143 14.8347 19.2982 16.8608 18.4917 18.7422C18.0912 19.6743 17.5819 20.5558 16.9741 21.3682L15.771 22.9717C15.5315 23.2909 15.2207 23.55 14.8638 23.7285C14.5068 23.9069 14.1134 24 13.7144 24H10.2856C9.88644 24 9.49231 23.907 9.13525 23.7285C8.77834 23.55 8.46747 23.2909 8.22803 22.9717L7.02588 21.3682C6.41841 20.5557 5.90858 19.6743 5.5083 18.7422C4.70075 16.8608 4.285 14.8345 4.28564 12.7871V3.42871C4.28564 2.5194 4.64657 1.64689 5.28955 1.00391C5.93253 0.360924 6.80504 0 7.71436 0H16.2856ZM7.71436 1.71387C7.2597 1.71387 6.82393 1.89531 6.50244 2.2168C6.18095 2.53829 5.99951 2.97405 5.99951 3.42871V12.7871C5.99955 14.6023 6.36877 16.3986 7.0835 18.0664C7.42973 18.8737 7.87078 19.6371 8.39697 20.3398L9.6001 21.9424C9.67986 22.0487 9.78298 22.1358 9.90186 22.1953C10.0209 22.2548 10.1526 22.2861 10.2856 22.2861H13.7144C13.8472 22.2861 13.9783 22.2547 14.0972 22.1953C14.2162 22.1358 14.3201 22.0488 14.3999 21.9424L15.6021 20.3398C16.1283 19.637 16.5702 18.8738 16.9165 18.0664C17.6322 16.3984 18.0005 14.6022 17.9995 12.7871V3.42871C17.9995 2.97409 17.819 2.53828 17.4976 2.2168C17.1761 1.89531 16.7403 1.71387 16.2856 1.71387H7.71436ZM14.9995 5.14258C15.3404 5.14258 15.6676 5.27852 15.9087 5.51953C16.1498 5.76065 16.2856 6.08772 16.2856 6.42871V12.4287C16.2856 12.7697 16.1498 13.0968 15.9087 13.3379C15.6676 13.5788 15.3404 13.7139 14.9995 13.7139H8.99951C8.65884 13.7137 8.33228 13.5787 8.09131 13.3379C7.85022 13.0968 7.71439 12.7697 7.71436 12.4287V6.42871C7.71436 6.08772 7.85019 5.76065 8.09131 5.51953C8.33229 5.27864 8.65878 5.14271 8.99951 5.14258H14.9995ZM9.42822 12H14.5718V6.85742H9.42822V12Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_trezor_safe_3.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M17.1431 0C17.825 7.38484e-05 18.4793 0.270759 18.9614 0.75293C19.4436 1.23513 19.7143 1.88939 19.7144 2.57129V13.2578C19.7143 14.3894 19.4651 15.5076 18.9849 16.5322L16.1772 22.5205C15.9697 22.9628 15.6404 23.3366 15.228 23.5986C14.8157 23.8605 14.3376 24.0001 13.8491 24H10.1509C9.66231 24.0001 9.18339 23.8606 8.771 23.5986C8.35878 23.3366 8.0292 22.9627 7.82178 22.5205L5.01514 16.5322C4.53484 15.5076 4.28567 14.3894 4.28564 13.2578V2.57129C4.28568 1.88935 4.55637 1.23513 5.03857 0.75293C5.52078 0.270726 6.175 3.69506e-05 6.85693 0H17.1431ZM5.99951 12V13.2578C5.99957 14.1379 6.19335 15.0078 6.56689 15.8047L9.37451 21.792C9.44362 21.9396 9.55337 22.0649 9.69092 22.1523C9.82836 22.2397 9.98804 22.2861 10.1509 22.2861H13.8491C14.012 22.2861 14.1717 22.2397 14.3091 22.1523C14.4466 22.0649 14.5564 21.9396 14.6255 21.792L17.4321 15.8047C17.8058 15.0077 17.9994 14.138 17.9995 13.2578V12H5.99951ZM15.4282 13.7139C15.6556 13.7139 15.8739 13.8051 16.0347 13.9658C16.1952 14.1265 16.2856 14.3441 16.2856 14.5713C16.2856 14.7986 16.1954 15.017 16.0347 15.1777C15.8739 15.3384 15.6555 15.4287 15.4282 15.4287H8.57178C8.3445 15.4287 8.12607 15.3384 7.96533 15.1777C7.80459 15.017 7.71436 14.7986 7.71436 14.5713C7.71439 14.3441 7.80477 14.1265 7.96533 13.9658C8.12608 13.8051 8.34445 13.7139 8.57178 13.7139H15.4282ZM6.85693 1.71387C6.62966 1.7139 6.41218 1.80511 6.25146 1.96582C6.09075 2.12653 5.99955 2.34401 5.99951 2.57129V10.2861H17.9995V2.57129C17.9995 2.34404 17.9092 2.12653 17.7485 1.96582C17.5879 1.80514 17.3703 1.71394 17.1431 1.71387H6.85693Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_trezor_safe_5.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path fill-rule="evenodd" clip-rule="evenodd" d="M4.28564 3.42857C4.28564 2.51926 4.64687 1.64719 5.28985 1.00421C5.93283 0.361224 6.8049 0 7.71422 0H16.2856C17.195 0 18.067 0.361224 18.71 1.00421C19.353 1.64719 19.7142 2.51926 19.7142 3.42857V13.878C19.7144 15.2314 19.3585 16.5611 18.6822 17.7334L15.8082 22.7134C15.5826 23.1045 15.2581 23.4293 14.8672 23.6551C14.4763 23.881 14.0328 23.9999 13.5814 24H10.4185C9.96713 24 9.52372 23.8812 9.13282 23.6555C8.74193 23.4298 8.41733 23.1052 8.19164 22.7143L5.31765 17.7334C4.64125 16.5608 4.28533 15.2309 4.28564 13.8771V3.42857ZM7.71422 1.71429C7.25956 1.71429 6.82352 1.8949 6.50203 2.21639C6.18054 2.53788 5.99993 2.97391 5.99993 3.42857V13.878C6.00002 14.9306 6.277 15.9646 6.80307 16.8763L9.67622 21.8563C9.75134 21.9867 9.85949 22.0951 9.98979 22.1705C10.1201 22.2459 10.268 22.2856 10.4185 22.2857H13.5814C13.7318 22.2857 13.8796 22.2461 14.0099 22.1709C14.1402 22.0956 14.2484 21.9874 14.3236 21.8571L17.1968 16.8771C17.7232 15.965 18.0001 14.9303 17.9999 13.8771V3.42857C17.9999 2.97391 17.8193 2.53788 17.4978 2.21639C17.1763 1.8949 16.7403 1.71429 16.2856 1.71429H7.71422Z" fill="white"/>
3 +</svg>
res/pictures/hardware_wallets/device_trezor_safe_7.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path fill-rule="evenodd" clip-rule="evenodd" d="M16.8571 0C17.5391 0 18.1932 0.270918 18.6754 0.753154C19.1577 1.23539 19.4286 1.88944 19.4286 2.57143V14.208C19.4287 15.3244 19.1866 16.4275 18.7189 17.4411L16.3806 22.506C16.1748 22.9519 15.8457 23.3296 15.432 23.5943C15.0184 23.8591 14.5377 23.9998 14.0466 24H9.382C8.89091 23.9998 8.41014 23.8591 7.99653 23.5943C7.58291 23.3296 7.25377 22.9519 7.048 22.506L4.70971 17.4403C4.24235 16.4274 4.00021 15.3252 4 14.2097V2.57143C4 1.88944 4.27092 1.23539 4.75315 0.753154C5.23539 0.270918 5.88944 0 6.57143 0H16.8571ZM8.60457 21.7877C8.67312 21.9363 8.78275 22.0621 8.92051 22.1503C9.05826 22.2386 9.2184 22.2856 9.382 22.2857H14.0466C14.2102 22.2856 14.3703 22.2386 14.5081 22.1503C14.6458 22.0621 14.7555 21.9363 14.824 21.7877L16.1766 18.8571H7.252L8.60457 21.7877ZM6.57143 1.71429C6.3441 1.71429 6.12608 1.80459 5.96534 1.96534C5.80459 2.12608 5.71429 2.3441 5.71429 2.57143V14.208C5.71429 15.0763 5.90286 15.9343 6.26714 16.7229L6.46086 17.1429H16.8571C16.8931 17.1429 16.9289 17.1451 16.9643 17.1497L17.1614 16.7229C17.5257 15.9343 17.7143 15.0771 17.7143 14.208V2.57143C17.7143 2.3441 17.624 2.12608 17.4632 1.96534C17.3025 1.80459 17.0845 1.71429 16.8571 1.71429H6.57143Z" fill="white"/>
3 +</svg>
res/values/strings_en.arb
+11 -1
@@ -74,6 +74,7 @@
74 "approve_tokens": "Approve tokens",
75 "apps": "Apps",
76 "arbiscan_history": "ArbiScan History",
77 + "are_you_sure_exit": "Are you sure you want to exit?",
78 "arrive_in_this_address": "${currency} ${tag}will arrive in this address",
79 "ascending": "Ascending",
80 "ask_each_time": "Ask each time",
@@ -330,6 +331,7 @@
331 "deuro_savings_welcome_description": "dEURO is a fully decentralized, collateralized stablecoin pegged to the Euro — secure, versatile, and censorship-resistant without having to rely on oracles. 1 dEURO = 1 EURO.",
332 "deuro_tooltip_no_eth": "In order to enable dEURO, you must have ETH in your wallet.",
333 "deuro_tx_commited_content": "It might take a couple of seconds for the transaction to confirm and be reflected on screen",
334 + "device_confirmation": "Device Confirmation",
335 "device_is_signing": "Device is signing",
336 "dfx_option_description": "Buy crypto with EUR & CHF. For retail and corporate customers in Europe",
337 "did_you_back_up_seeds": "Did you back up all your seeds?",
@@ -529,6 +531,7 @@
531 "how_to_connect": "How to Connect",
532 "how_to_use": "How to use",
533 "how_to_use_card": "How to use this card",
534 + "hww_exit_desc": "This will ed the ongoing pairing process with your hardware device",
535 "i_understand": "I understand",
536 "icon_style": "Icon style",
537 "id": "ID: ",
@@ -722,6 +725,10 @@
725 "overshot": "Overshot",
726 "overwrite_amount": "Overwrite amount",
727 "pairingInvalidEvent": "Pairing Invalid Event",
728 + "pairing_code": "Pairing Code",
729 + "pairing_code_desc_1": "Your device will show a pairing code.",
730 + "pairing_code_desc_2": "Input here to continue",
731 + "pairing_error": "Pairing Error",
732 "passphrase": "Passphrase (Optional)",
733 "passphrase_view_keys": "Passphrase",
734 "passphrases_doesnt_match": "Passphrases do not match, please try again",
@@ -1138,6 +1145,7 @@
1145 "testnet_coins_no_value": "Testnet coins have no value",
1146 "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
1147 "third_intro_title": "Yat plays nicely with others",
1148 + "this_can_take_few_seconds": "This can take a few seconds",
1149 "this_pair_is_not_supported_warning": "This pair is not supported with the currently selected exchange(s). Please select another exchange.",
1150 "thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address",
1151 "thorchain_taproot_address_not_supported": "The THORChain provider does not support Taproot addresses. Please change the address or select a different provider.",
@@ -1287,6 +1295,7 @@
1295 "value_type": "Value Type",
1296 "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
1297 "verification": "Verification",
1298 + "verifying_code": "Verifying Code",
1299 "verify_message": "Verify Message",
1300 "verify_seed": "Verify Seed",
1301 "verify_with_2fa": "Verify with Cake 2FA",
@@ -1394,6 +1403,7 @@
1403 "yat_popup_title": "Your wallet address can be emojified.",
1404 "yes": "Yes",
1405 "yesterday": "Yesterday",
1406 + "yes_exit": "Yes. Exit",
1407 "you_now_have_debit_card": "You now have a debit card",
1408 "you_pay": "You Pay",
1409 "you_will_get": "Convert to",
@@ -1408,4 +1418,4 @@
1418 "zcash_card_missing_funds": "Missing funds?",
1419 "zcash_card_scan": "Scan",
1420 "zcash_card_warning": "Do not close the app until the procedure completes, if you do so this process will need to restart from scratch."
1411 -}
\ No newline at end of file
1421 +}
scripts/prepare_moneroc.sh
+2 -2
@@ -14,9 +14,9 @@ else
14 fi
15
16 # NOTE: Make sure to update monero_c prebuilds link in workflow files
17 -# https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip
17 +# https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip
18 git fetch -a
19 -git checkout bc8d1a0b75b97156d71579581b4cdfe58c777ed2
19 +git checkout 3bfb3856a838f2bf6b729501837bb0295dedf25d
20 git reset --hard
21 git submodule update --init --force --recursive
22
tool/configure.dart
+8 -1
@@ -334,8 +334,10 @@ import 'package:cake_wallet/view_model/send/output.dart';
334 import 'package:cw_core/wallet_service.dart';
335 import 'package:hive/hive.dart';
336 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
337 +import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
338 import 'package:polyseed/polyseed.dart';""";
339 const moneroCWHeaders = """
340 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
341 import 'package:cw_core/account.dart' as monero_account;
342 import 'package:cw_core/get_height_by_date.dart';
343 import 'package:cw_core/monero_amount_format.dart';
@@ -345,6 +347,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet_api;
347 import 'package:cw_monero/ledger.dart';
348 import 'package:cw_monero/monero_unspent.dart';
349 import 'package:cw_monero/api/account_list.dart';
350 +import 'package:cw_monero/trezor.dart';
351 import 'package:cw_monero/monero_wallet_service.dart';
352 import 'package:cw_monero/monero_wallet.dart';
353 import 'package:cw_monero/monero_transaction_info.dart';
@@ -463,7 +466,7 @@ abstract class Monero {
466 HardwareWalletType? hardwareWalletType,
467 required int height});
468 WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
466 - WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
469 + WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService});
470 WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic});
471 Map<String, String> getKeys(Object wallet);
472 int? getRestoreHeight(Object wallet);
@@ -484,6 +487,10 @@ WalletCredentials createMoneroNewWalletCredentials({required String name, requir
487 void resetLedgerConnection();
488 void setGlobalLedgerConnection(ledger.LedgerConnection connection);
489 String? getLastLedgerCommand();
490 + void setHardwareWalletService(Object wallet, HardwareWalletService service);
491 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
492 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client);
493 + Future<void> syncTrezor(Object wallet);
494 Map<String, List<int>> debugCallLength();
495 Map<String, dynamic> getWalletCacheDebug();
496 }