Cw 1379 add monero support to trezor rebase dev (#3273)
What changed, and why it matters
This is a large feature commit that adds Trezor hardware wallet support for Monero to the Cake Wallet app, alongside Ledger and BitBox. It also refreshes the hardware wallet connection UI, updates dependencies, and bumps the Android minimum SDK. The changes are mostly new feature code rather than a fix for a known security flaw. There are no explicit security claims in the commit message or diff, and no independent vulnerability disclosure is referenced.
Review the Trezor signing and key-image sync implementation for correct validation of device responses, ensure the new dependency versions do not introduce known regressions, and verify that the USB/BLE permission and intent-filter changes do not expose unintended attack surface. Treat this as a normal feature/security review rather than an urgent patch.
Security signals we found
New hardware wallet signing path added for Monero via Trezor
Dependency updates for cryptographic/Bluetooth/USB libraries
Android USB intent filters and device filters added for hardware wallets
Refactored hardware wallet connection state checks across send, swap, buy, and dEuro flows
New Trezor pairing UI with PIN entry and state management
Evidence from the diff
The commit introduces a new trezor_flutter SDK integration, a MoneroTrezorService, Trezor-specific key image sync and transaction signing paths in cw_monero, and a new pairing/connect UI. It refactors hardware wallet abstractions so Ledger and Trezor share a common HardwareWalletService. Dependency versions for monero_c, ledger_flutter_plus, ledger_usb_plus, universal_ble, and flutter_web_bluetooth are updated. Android minSdkVersion is raised from 24 to 26 and USB device filters for Trezor/BitBox are added. Several UI call sites now pass WalletType to isConnected().
Changed components
cw_monero/lib/trezor.dartcw_monero/lib/monero_wallet.dartcw_monero/lib/monero_wallet_service.dartcw_monero/lib/pending_monero_transaction.dartlib/view_model/hardware_wallet/trezor_connect_view_model.dartlib/view_model/hardware_wallet/ledger_view_model.dartlib/view_model/hardware_wallet/hardware_wallet_view_model.dartlib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dartandroid/app/src/main/AndroidManifestBase.xmlandroid/app/src/main/res/xml/device_filter.xmlcw_monero/pubspec.yamlpubspec_base.yamlInspect captured patch +1855 / −455
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 36fb28a..99a489b 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -118,7 +118,7 @@ jobs:
pushd scripts/monero_c/release
mkdir -p $(git describe --tags)
pushd $(git describe --tags)
- wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip
+ wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip
unzip release-bundle.zip
rm release-bundle.zip
popd
diff --git a/.gitignore b/.gitignore
index 9fce14f..268167b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -225,3 +225,5 @@ scripts/zcash_lib
scripts/android/yttrium
scripts/bitbox_flutter
scripts/reown_flutter
+
+*.salive
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6c15402..26f5544 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -60,7 +60,7 @@ android {
defaultConfig {
applicationId appProperties['id']
- minSdkVersion 24
+ minSdkVersion 26
targetSdkVersion 36
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index a7364d6..0f15c75 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -128,6 +128,14 @@
<data android:scheme="nano-gpt" />
</intent-filter>
+
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
+ </intent-filter>
+
+ <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+ android:resource="@xml/device_filter" />
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
</activity>
<meta-data
diff --git a/android/app/src/main/res/xml/device_filter.xml b/android/app/src/main/res/xml/device_filter.xml
new file mode 100644
index 0000000..fc0ade5
--- /dev/null
+++ b/android/app/src/main/res/xml/device_filter.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+ <!-- BitBox02 Vendor ID: 0x03eb, Product ID: 0x2403 -->
+ <usb-device vendor-id="1003" product-id="9219"/>
+
+ <!-- Trezor Devices -->
+ <usb-device vendor-id="21324" product-id="1"/>
+ <usb-device vendor-id="4617" product-id="21440"/>
+ <usb-device vendor-id="4617" product-id="21441"/>
+</resources>
diff --git a/assets/images/hardware_wallet/device_bitbox.svg b/assets/images/hardware_wallet/device_bitbox.svg
deleted file mode 100644
index 52368aa..0000000
--- a/assets/images/hardware_wallet/device_bitbox.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
-<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"/>
-</svg>
diff --git a/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg b/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg
deleted file mode 100644
index 9d2fc8b..0000000
--- a/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<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"/>
-</svg>
diff --git a/assets/images/hardware_wallet/device_ledger_nano_x.svg b/assets/images/hardware_wallet/device_ledger_nano_x.svg
deleted file mode 100644
index c605249..0000000
--- a/assets/images/hardware_wallet/device_ledger_nano_x.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
-<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"/>
-</svg>
diff --git a/assets/images/hardware_wallet/device_trezor_safe_5.svg b/assets/images/hardware_wallet/device_trezor_safe_5.svg
deleted file mode 100644
index 14c0329..0000000
--- a/assets/images/hardware_wallet/device_trezor_safe_5.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32">
- <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"/>
-</svg>
diff --git a/assets/images/hardware_wallet/ledger_flex.png b/assets/images/hardware_wallet/ledger_flex.png
deleted file mode 100644
index fa39f24..0000000
Binary files a/assets/images/hardware_wallet/ledger_flex.png and /dev/null differ
diff --git a/assets/images/hardware_wallet/ledger_nano_s.png b/assets/images/hardware_wallet/ledger_nano_s.png
deleted file mode 100644
index 02777ae..0000000
Binary files a/assets/images/hardware_wallet/ledger_nano_s.png and /dev/null differ
diff --git a/assets/images/hardware_wallet/ledger_stax.png b/assets/images/hardware_wallet/ledger_stax.png
deleted file mode 100644
index 06c9c84..0000000
Binary files a/assets/images/hardware_wallet/ledger_stax.png and /dev/null differ
diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart
index 39e0400..3b1dd93 100644
--- a/cw_core/lib/hardware/device_connection_type.dart
+++ b/cw_core/lib/hardware/device_connection_type.dart
@@ -29,7 +29,7 @@ enum DeviceConnectionType {
break;
case HardwareWalletType.trezor:
isSupported = [
- // WalletType.monero,
+ WalletType.monero,
WalletType.bitcoin,
WalletType.litecoin,
WalletType.ethereum,
diff --git a/cw_core/lib/hardware/hardware_wallet_service.dart b/cw_core/lib/hardware/hardware_wallet_service.dart
index 19125ba..94cbdc4 100644
--- a/cw_core/lib/hardware/hardware_wallet_service.dart
+++ b/cw_core/lib/hardware/hardware_wallet_service.dart
@@ -4,7 +4,8 @@ import 'dart:typed_data';
import 'package:cw_core/hardware/hardware_account_data.dart';
abstract class HardwareWalletService {
- Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5});
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) =>
+ throw UnimplementedError();
Future<Uint8List> signTransaction({required String transaction}) => throw UnimplementedError();
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index 1ce3972..5877ad2 100644
--- a/cw_core/lib/wallet_info.dart
+++ b/cw_core/lib/wallet_info.dart
@@ -44,7 +44,7 @@ enum HardwareWalletType {
coldcard,
seedsigner,
keystone,
- trezor,
+ trezor;
}
enum WalletInfoAddressType {
diff --git a/cw_monero/lib/ledger.dart b/cw_monero/lib/ledger.dart
index 7707acd..7c1742a 100644
--- a/cw_monero/lib/ledger.dart
+++ b/cw_monero/lib/ledger.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:ffi';
import 'dart:typed_data';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
@@ -118,3 +119,10 @@ void _logLedgerCommand(Uint8List command, [bool isResponse = true]) {
"> ${_ledgerMoneroCommands[command[1]]} ${toHexString(command.sublist(2))}");
}
}
+
+class MoneroLedgerService extends HardwareWalletService {
+ final LedgerConnection connection;
+
+ MoneroLedgerService(this.connection);
+}
+
diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart
index e315f24..30c5507 100644
--- a/cw_monero/lib/monero_wallet.dart
+++ b/cw_monero/lib/monero_wallet.dart
@@ -37,6 +37,7 @@ import 'package:cw_monero/monero_unspent.dart';
import 'package:cw_monero/monero_wallet_addresses.dart';
import 'package:cw_monero/monero_wallet_service.dart';
import 'package:cw_monero/pending_monero_transaction.dart';
+import 'package:cw_monero/trezor.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
@@ -385,8 +386,42 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
currentWallet!.hasUnknownKeyImages();
}
+ MoneroTrezorService? trezorService;
+ Future<void> syncTrezor() async {
+ if (trezorService == null) throw Exception("Trezor not connected");
+
+ final ptr = Pointer<Void>.fromAddress(currentWallet!.ffiAddress());
+ final tdis = monero.Wallet_exportTrezorTdis(ptr);
+
+ final response = await Trezor(trezorService!).keyImageSync(tdis);
+
+ final success = monero.Wallet_importTrezorEncryptedKeyImagesJson(ptr, response);
+
+ if (!success) throw Exception(monero.Wallet_errorString(ptr));
+ }
+
+ Future<String> signTrezorTransaction(String json) async {
+ if (trezorService == null) throw Exception("Trezor not connected");
+ return Trezor(trezorService!).signTransaction(json);
+ }
+
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
+ if (hardwareWalletType == HardwareWalletType.trezor) {
+ for (int i = 0; i < 2; i++) {
+ try {
+ return await _createTransaction(credentials);
+ } catch (e) {
+ printV(e);
+ }
+ await save();
+ await Future.delayed(Duration(seconds: i));
+ }
+ }
+ return await _createTransaction(credentials);
+ }
+
+ Future<PendingTransaction> _createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
final inputs = <String>[];
final outputs = _credentials.outputs;
diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart
index 2a153c5..1b0e9e3 100644
--- a/cw_monero/lib/monero_wallet_service.dart
+++ b/cw_monero/lib/monero_wallet_service.dart
@@ -3,8 +3,8 @@ import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
-import 'package:collection/collection.dart';
import 'package:cw_core/get_height_by_date.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/monero_wallet_utils.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/unspent_coins_info.dart';
@@ -20,8 +20,8 @@ import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/bip39_seed.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_wallet.dart';
+import 'package:cw_monero/trezor.dart';
import 'package:hive/hive.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:monero/monero.dart' as monero;
import 'package:polyseed/polyseed.dart';
@@ -46,11 +46,11 @@ class MoneroNewWalletCredentials extends WalletCredentials {
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
MoneroRestoreWalletFromHardwareCredentials(
{required String name,
- required this.ledgerConnection,
+ required this.hardwareWalletService,
int height = 0,
String? password})
: super(name: name, password: password, height: height);
- LedgerConnection ledgerConnection;
+ HardwareWalletService hardwareWalletService;
}
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
@@ -193,7 +193,7 @@ class MoneroWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource,
password: password);
- if (wallet.isHardwareWallet) {
+ if (wallet.hardwareWalletType == HardwareWalletType.ledger) {
wallet.setLedgerConnection(gLedger!);
gLedger = null;
}
@@ -301,15 +301,29 @@ class MoneroWalletService extends WalletService<
try {
final path = await pathForWallet(name: credentials.name, type: getType());
final password = credentials.password;
- final height = credentials.height;
- enableLedgerExchange(credentials.ledgerConnection);
+ if (credentials.hardwareWalletService case MoneroLedgerService service) {
+ enableLedgerExchange(service.connection);
- await monero_wallet_manager.restoreWalletFromHardwareWallet(
+ await monero_wallet_manager.restoreWalletFromHardwareWallet(
path: path,
password: password!,
- restoreHeight: height!,
- deviceName: 'Ledger');
+ restoreHeight: credentials.height!,
+ deviceName: 'Ledger',
+ );
+ } else if (credentials.hardwareWalletService case MoneroTrezorService service) {
+ final watchCredentials = await Trezor(service).getWatchCredentials();
+
+ monero_wallet_manager.restoreWalletFromKeys(
+ path: path,
+ password: credentials.password!,
+ language: "English",
+ restoreHeight: credentials.height!,
+ address: watchCredentials.address,
+ viewKey: watchCredentials.watchKey,
+ spendKey: "",
+ );
+ }
final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!,
@@ -567,7 +581,7 @@ class MoneroWalletService extends WalletService<
if (walletInfo == null) {
return false;
}
- return walletInfo.isHardwareWallet;
+ return walletInfo.hardwareWalletType == HardwareWalletType.ledger;
}
}
@@ -585,4 +599,4 @@ Future<void> closeWalletAwaitIfShould(int wmaddr, int waddr) async {
monero.WalletManager_errorString(Pointer.fromAddress(wmaddr));
}));
}
-}
\ No newline at end of file
+}
diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart
index e1d191e..c32a2cc 100644
--- a/cw_monero/lib/pending_monero_transaction.dart
+++ b/cw_monero/lib/pending_monero_transaction.dart
@@ -1,5 +1,8 @@
import 'dart:async';
+import 'dart:ffi';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/transaction_history.dart'
@@ -10,6 +13,7 @@ import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/monero_wallet.dart';
+import 'package:monero/monero.dart' as monero;
class DoubleSpendException implements Exception {
DoubleSpendException();
@@ -43,22 +47,39 @@ class PendingMoneroTransaction with PendingTransaction {
CryptoCurrency.xmr, pendingTransactionDescription.fee);
@override
- bool shouldCommitUR() => isViewOnly;
+ bool shouldCommitUR() => isViewOnly && wallet.hardwareWalletType != HardwareWalletType.trezor;
@override
Future<void> commit() async {
- try {
- await monero_transaction_history.commitTransactionFromPointerAddress(
- address: pendingTransactionDescription.pointerAddress,
- useUR: false);
- } catch (e) {
- final message = e.toString();
+ if (wallet.hardwareWalletType == HardwareWalletType.trezor) {
+ final ptr = Pointer<Void>.fromAddress(pendingTransactionDescription.pointerAddress);
+ final ret = await monero.PendingTransaction_commitTrezor(ptr, 0);
- if (message.contains('Reason: double spend')) {
- throw DoubleSpendException();
- }
+ final json = await wallet.signTrezorTransaction(ret);
- rethrow;
+ final wptr = Pointer<Void>.fromAddress(currentWallet!.ffiAddress());
+
+ final suc = await monero.Wallet_submitTransactionHex(wptr, json);
+
+ if (!suc) {
+ final err = monero.UnsignedTransaction_errorString(ptr);
+ printV(err);
+ throw err;
+ }
+ } else {
+ try {
+ await monero_transaction_history.commitTransactionFromPointerAddress(
+ address: pendingTransactionDescription.pointerAddress,
+ useUR: false);
+ } catch (e) {
+ final message = e.toString();
+
+ if (message.contains('Reason: double spend')) {
+ throw DoubleSpendException();
+ }
+
+ rethrow;
+ }
}
storeSync(force: true);
unawaited(() async {
diff --git a/cw_monero/lib/trezor.dart b/cw_monero/lib/trezor.dart
new file mode 100644
index 0000000..f2651d0
--- /dev/null
+++ b/cw_monero/lib/trezor.dart
@@ -0,0 +1,53 @@
+import 'dart:convert';
+
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:trezor_flutter/trezor_flutter.dart';
+
+class MoneroTrezorService extends HardwareWalletService {
+ final TrezorClient client;
+
+ MoneroTrezorService(this.client);
+}
+
+class MoneroTrezorWatchCredentials {
+ final String watchKey;
+ final String address;
+
+ const MoneroTrezorWatchCredentials(this.watchKey, this.address);
+}
+
+class Trezor {
+ final MoneroTrezorService service;
+
+ Trezor(this.service);
+
+ Future<MoneroTrezorWatchCredentials> getWatchCredentials() async {
+ final credentials = await TrezorMonero(service.client).getWatchCredentials();
+
+ return MoneroTrezorWatchCredentials(credentials.$1, credentials.$2);
+ }
+
+ Future<String> keyImageSync(String tdis) async {
+ final tdisMap = jsonDecode(tdis) as Map<String, dynamic>;
+ final tdisList = tdisMap["tdis"] as List<dynamic>;
+
+ final txIds = <MoneroKeyImageTxData>[];
+ for (final tdi in tdisList) {
+ txIds.add(MoneroKeyImageTxData(
+ outKey: tdi["out_key"],
+ txPubKey: tdi["tx_pub_key"],
+ internalOutputIndex: tdi["internal_output_index"],
+ subAddrMajor: tdi["sub_addr_major"],
+ subAddrMinor: tdi["sub_addr_minor"],
+ additionalTxPubKeys: (tdi["additional_tx_pub_keys"] as List?)?.map((e) => e as String).toList() ?? []
+ ));
+ }
+ final keyImages = await TrezorMonero(service.client).syncKeyImages(txIds);
+
+ return jsonEncode(keyImages.toMap());
+ }
+
+ Future<String> signTransaction(String json) async {
+ return TrezorMonero(service.client).signTransaction(jsonDecode(json));
+ }
+}
diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock
index 6f6174d..d00f214 100644
--- a/cw_monero/pubspec.lock
+++ b/cw_monero/pubspec.lock
@@ -296,6 +296,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
+ fast_base58:
+ dependency: transitive
+ description:
+ name: fast_base58
+ sha256: "611f65633b734f27a850b51371b3eba993a5165650e12e8e7b02959f3768ba06"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1"
ffi:
dependency: "direct main"
description:
@@ -342,10 +350,10 @@ packages:
dependency: transitive
description:
name: flutter_web_bluetooth
- sha256: "1363831def5eed1e1064d1eca04e8ccb35446e8f758579c3c519e156b77926da"
+ sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8
url: "https://pub.dev"
source: hosted
- version: "1.0.0"
+ version: "1.1.0"
frontend_server_client:
dependency: transitive
description:
@@ -494,18 +502,18 @@ packages:
dependency: "direct main"
description:
name: ledger_flutter_plus
- sha256: "1c03f3c4a9754b5f0170a9eb9552ec54fa86e985f8ee71a255ee2c5629b53d31"
+ sha256: "59c5a6062505c721c2ca909fa085c52cffe93de995174b8418b54a6acf52eb70"
url: "https://pub.dev"
source: hosted
- version: "1.5.1"
+ version: "1.5.6"
ledger_usb_plus:
dependency: transitive
description:
name: ledger_usb_plus
- sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
+ sha256: "911df2d1d55cdae612ff2c9db140cf71a9b28a67ba16adfdc4983cd9f02c531e"
url: "https://pub.dev"
source: hosted
- version: "1.0.4"
+ version: "1.0.8"
logging:
dependency: transitive
description:
@@ -582,9 +590,9 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- url: "https://github.com/mrcyjanek/monero_c"
+ ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ url: "http://github.com/mrcyjanek/monero_c.git"
source: git
version: "0.0.0"
mutex:
@@ -724,6 +732,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
+ protobuf:
+ dependency: transitive
+ description:
+ name: protobuf
+ sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
provider:
dependency: transitive
description:
@@ -962,6 +978,24 @@ packages:
relative: true
source: path
version: "0.0.1"
+ trezor_flutter:
+ dependency: "direct main"
+ description:
+ path: trezor-flutter
+ ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ url: "https://github.com/cake-tech/trezor-flutter.git"
+ source: git
+ version: "0.1.0"
+ trezor_usb_transport:
+ dependency: transitive
+ description:
+ path: trezor_usb_transport
+ ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ url: "https://github.com/cake-tech/trezor-flutter.git"
+ source: git
+ version: "0.0.1"
tuple:
dependency: transitive
description:
@@ -982,10 +1016,10 @@ packages:
dependency: transitive
description:
name: universal_ble
- sha256: "1fad089150a29db82b3b7d60327e18c5ad6b3a5bb509defc1c690b0a76b9c098"
+ sha256: "8325ca9f5cce2fba2b4efef74a4fcfe7144f303b409d6fc6ed16197b00a1a241"
url: "https://pub.dev"
source: hosted
- version: "0.15.0"
+ version: "1.2.0"
universal_platform:
dependency: transitive
description:
diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml
index 7f38c0e..4c10e94 100644
--- a/cw_monero/pubspec.yaml
+++ b/cw_monero/pubspec.yaml
@@ -25,11 +25,16 @@ dependencies:
path: ../cw_core
monero:
git:
- url: https://github.com/mrcyjanek/monero_c
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
+ url: https://github.com/mrcyjanek/monero_c.git
+ ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
path: impls/monero.dart
mutex: ^3.1.0
ledger_flutter_plus: ^1.4.1
+ trezor_flutter:
+ git:
+ url: https://github.com/cake-tech/trezor-flutter.git
+ ref: 4bf54339a1caefd4c53a4a8873a9686ec13a9107
+ path: trezor-flutter
dev_dependencies:
flutter_test:
diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock
index 734af93..55cfb8f 100644
--- a/cw_wownero/pubspec.lock
+++ b/cw_wownero/pubspec.lock
@@ -502,9 +502,9 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- url: "https://github.com/mrcyjanek/monero_c"
+ ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ url: "http://github.com/mrcyjanek/monero_c.git"
source: git
version: "0.0.0"
mutex:
diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml
index a23fc39..ca9c70b 100644
--- a/cw_wownero/pubspec.yaml
+++ b/cw_wownero/pubspec.yaml
@@ -23,8 +23,8 @@ dependencies:
path: ../cw_core
monero:
git:
- url: https://github.com/mrcyjanek/monero_c
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash
+ url: https://github.com/mrcyjanek/monero_c.git
+ ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
path: impls/monero.dart
mutex: ^3.1.0
diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock
index 662ccde..6e2916d 100644
--- a/cw_zano/pubspec.lock
+++ b/cw_zano/pubspec.lock
@@ -507,9 +507,9 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- url: "https://github.com/mrcyjanek/monero_c"
+ ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ url: "http://github.com/mrcyjanek/monero_c.git"
source: git
version: "0.0.0"
nested:
diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml
index 8eb5d3e..16585b7 100644
--- a/cw_zano/pubspec.yaml
+++ b/cw_zano/pubspec.yaml
@@ -24,8 +24,8 @@ dependencies:
fluttertoast: ^8.2.12
monero:
git:
- url: https://github.com/mrcyjanek/monero_c
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash
+ url: https://github.com/mrcyjanek/monero_c.git
+ ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
path: impls/monero.dart
dev_dependencies:
flutter_test:
diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart
index d885e3e..888c2ce 100644
--- a/integration_test/components/common_test_flows.dart
+++ b/integration_test/components/common_test_flows.dart
@@ -231,12 +231,9 @@ class CommonTestFlows {
await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
+ // Tapping the wallet type navigates directly (no separate Next button).
_tester.printToConsole('Tapping wallet type: ${type.name}');
await _newWalletTypePageRobot.selectWalletType(type);
- await _tester.pumpAndSettle(Duration(milliseconds: 500));
-
- _tester.printToConsole('Pressing next button');
- await _newWalletTypePageRobot.onNextButtonPressed();
await _tester.pumpAndSettle(Duration(milliseconds: 2000));
diff --git a/integration_test/robots/new_wallet_type_page_robot.dart b/integration_test/robots/new_wallet_type_page_robot.dart
index fc3e94f..11785b6 100644
--- a/integration_test/robots/new_wallet_type_page_robot.dart
+++ b/integration_test/robots/new_wallet_type_page_robot.dart
@@ -77,11 +77,9 @@ class NewWalletTypePageRobot {
tester.printToConsole('Wallet type ${type.name} is now visible and tappable');
}
+ // Tapping a wallet type now navigates directly (the page no longer has a
+ // separate "Next" button — selection happens on tap).
Future<void> selectWalletType(WalletType type) async {
await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key');
}
-
- Future<void> onNextButtonPressed() async {
- await commonTestCases.tapItemByKey('new_wallet_type_next_button_key');
- }
}
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index e661632..2d32cb8 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -295,6 +295,7 @@ SPEC CHECKSUMS:
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
+ warp_api: 997d8fb3e777669bc6260d0c12e56cd40b9db1d1
YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart
index f927b70..582cccd 100644
--- a/lib/buy/dfx/dfx_buy_provider.dart
+++ b/lib/buy/dfx/dfx_buy_provider.dart
@@ -336,7 +336,7 @@ class DFXBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
- if (!hardwareWalletVM!.isConnected) {
+ if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart
index 7cd9928..2c26fa4 100644
--- a/lib/buy/robinhood/robinhood_buy_provider.dart
+++ b/lib/buy/robinhood/robinhood_buy_provider.dart
@@ -173,7 +173,7 @@ class RobinhoodBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
- if (!hardwareWalletVM!.isConnected) {
+ if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
diff --git a/lib/di.dart b/lib/di.dart
index 991e2f7..cd7ee62 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -240,7 +240,7 @@ import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
@@ -414,7 +414,7 @@ Future<void> setup({
switch(type) {
case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
- case HardwareWalletType.trezor: return getIt<TrezorViewModel>();
+ case HardwareWalletType.trezor: return getIt<TrezorConnectViewModel>();
case HardwareWalletType.cupcake:
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
@@ -429,7 +429,7 @@ Future<void> setup({
getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
appName: "Cake Wallet"));
- getIt.registerLazySingleton(() => TrezorViewModel(getIt<TrezorConnect>()));
+ getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt<TrezorConnect>()));
getIt.registerFactory<KeyService>(() => KeyService(getIt.get<SecureStorage>()));
diff --git a/lib/entities/hardware_wallet/hardware_wallet_device.dart b/lib/entities/hardware_wallet/hardware_wallet_device.dart
index 646f5a2..757e680 100644
--- a/lib/entities/hardware_wallet/hardware_wallet_device.dart
+++ b/lib/entities/hardware_wallet/hardware_wallet_device.dart
@@ -1,5 +1,6 @@
import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
+import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
abstract class HardwareWalletDevice {
String get name;
@@ -41,6 +42,21 @@ class BitboxHardwareWalletDevice extends HardwareWalletDevice {
HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
}
+class TrezorHardwareWalletDevice extends HardwareWalletDevice {
+ final trezor.TrezorDevice device;
+
+ TrezorHardwareWalletDevice(this.device);
+
+ @override
+ String get name => device.name;
+
+ @override
+ HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
+
+ @override
+ HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
+}
+
enum HardwareWalletDeviceType {
ledgerBlue,
ledgerNanoS,
@@ -50,7 +66,12 @@ enum HardwareWalletDeviceType {
ledgerStax,
ledgerFlex,
BitBox02,
- BitBox02Nova;
+ BitBox02Nova,
+ trezorModelOne,
+ trezorModelT,
+ trezorSafe3,
+ trezorSafe5,
+ trezorSafe7;
}
enum HardwareWalletConnectionType {
@@ -60,7 +81,7 @@ enum HardwareWalletConnectionType {
qr;
}
-extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
+extension LedgerToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
HardwareWalletDeviceType toGeneric() {
switch (this) {
case ledger.LedgerDeviceType.blue:
@@ -81,7 +102,24 @@ extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
}
}
-extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
+extension TrezorToGenericHardwareWalletDeviceType on trezor.TrezorDeviceType {
+ HardwareWalletDeviceType toGeneric() {
+ switch (this) {
+ case trezor.TrezorDeviceType.modelOne:
+ return HardwareWalletDeviceType.trezorModelOne;
+ case trezor.TrezorDeviceType.modelT:
+ return HardwareWalletDeviceType.trezorModelT;
+ case trezor.TrezorDeviceType.safe3:
+ return HardwareWalletDeviceType.trezorSafe3;
+ case trezor.TrezorDeviceType.safe5:
+ return HardwareWalletDeviceType.trezorSafe5;
+ case trezor.TrezorDeviceType.safe7:
+ return HardwareWalletDeviceType.trezorSafe7;
+ }
+ }
+}
+
+extension LedgerToGenericHardwareWalletConnectionType on ledger.ConnectionType {
HardwareWalletConnectionType toGeneric() {
switch (this) {
case ledger.ConnectionType.usb:
@@ -91,3 +129,14 @@ extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
}
}
}
+
+extension TrezorToGenericHardwareWalletConnectionType on trezor.ConnectionType {
+ HardwareWalletConnectionType toGeneric() {
+ switch (this) {
+ case trezor.ConnectionType.usb:
+ return HardwareWalletConnectionType.usb;
+ case trezor.ConnectionType.ble:
+ return HardwareWalletConnectionType.ble;
+ }
+ }
+}
diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart
index 08143b3..0a378e2 100644
--- a/lib/monero/cw_monero.dart
+++ b/lib/monero/cw_monero.dart
@@ -236,13 +236,14 @@ class CWMonero extends Monero {
required String name,
required String password,
required int height,
- required ledger.LedgerConnection ledgerConnection,
+ required HardwareWalletService hardwareWalletService,
}) =>
MoneroRestoreWalletFromHardwareCredentials(
- name: name,
- password: password,
- height: height,
- ledgerConnection: ledgerConnection);
+ name: name,
+ password: password,
+ height: height,
+ hardwareWalletService: hardwareWalletService,
+ );
@override
WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
@@ -416,9 +417,7 @@ class CWMonero extends Monero {
}
@override
- void monerocCheck() {
- checkIfMoneroCIsFine();
- }
+ void monerocCheck() => checkIfMoneroCIsFine();
@override
Future<void> setLedgerConnection(Object wallet, ledger.LedgerConnection connection) async {
@@ -427,29 +426,41 @@ class CWMonero extends Monero {
}
@override
- void resetLedgerConnection() {
- disableLedgerExchange();
- }
+ void resetLedgerConnection() => disableLedgerExchange();
@override
- void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
- gLedger = connection;
- }
+ void setGlobalLedgerConnection(ledger.LedgerConnection connection) => gLedger = connection;
@override
String? getLastLedgerCommand() => latestLedgerCommand;
- bool isViewOnly() {
- return isViewOnlyBySpendKey(null);
+ @override
+ void setHardwareWalletService(Object wallet, HardwareWalletService service) {
+ final moneroWallet = wallet as MoneroWallet;
+ moneroWallet.trezorService = service as MoneroTrezorService;
}
+
@override
- Map<String, List<int>> debugCallLength() {
- return monero_wallet_api.debugCallLength();
- }
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
+ MoneroLedgerService(connection);
+
+ @override
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client) =>
+ MoneroTrezorService(client);
@override
- Map<String, dynamic> getWalletCacheDebug() {
- return monero_wallet_api.getWalletCacheDebug();
+ Future<void> syncTrezor(Object wallet) async {
+ final moneroWallet = wallet as MoneroWallet;
+
+ return moneroWallet.syncTrezor();
}
+
+ bool isViewOnly() => isViewOnlyBySpendKey(null);
+
+ @override
+ Map<String, List<int>> debugCallLength() => monero_wallet_api.debugCallLength();
+
+ @override
+ Map<String, dynamic> getWalletCacheDebug() => monero_wallet_api.getWalletCacheDebug();
}
diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart
index c3f5da9..9292d1a 100644
--- a/lib/new-ui/pages/send_page.dart
+++ b/lib/new-ui/pages/send_page.dart
@@ -726,12 +726,13 @@ class _NewSendPageState extends State<NewSendPage> {
}
if (widget.sendViewModel.wallet.isHardwareWallet) {
- if (!widget.sendViewModel.hardwareWalletViewModel!.isConnected) {
+ if (!widget.sendViewModel.hardwareWalletViewModel!
+ .isConnected(widget.sendViewModel.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: widget.sendViewModel.walletType,
hardwareWalletType: widget.sendViewModel.wallet.walletInfo.hardwareWalletType!,
- onConnectDevice: (BuildContext context, _) {
+ onConnectDevice: (_, __) {
widget.sendViewModel.hardwareWalletViewModel!
.initWallet(widget.sendViewModel.wallet);
Navigator.of(context).pop();
@@ -748,8 +749,12 @@ class _NewSendPageState extends State<NewSendPage> {
amount += item.formattedCryptoAmount;
}
if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
- await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
- arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet));
+ if (widget.sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor) {
+ await monero!.syncTrezor(widget.sendViewModel.wallet);
+ } else {
+ await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
+ arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet));
+ }
await Future.delayed(Duration(seconds: 1)); // wait for monero to refresh the state
}
if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
diff --git a/lib/new-ui/widgets/coins_page/wallet_info.dart b/lib/new-ui/widgets/coins_page/wallet_info.dart
index 21c83db..4963513 100644
--- a/lib/new-ui/widgets/coins_page/wallet_info.dart
+++ b/lib/new-ui/widgets/coins_page/wallet_info.dart
@@ -86,11 +86,11 @@ class WalletInfoBar extends StatelessWidget {
case null:
return null;
case HardwareWalletType.bitbox:
- return "assets/images/hardware_wallet/device_bitbox.svg";
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
case HardwareWalletType.ledger:
- return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
case HardwareWalletType.trezor:
- return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
case HardwareWalletType.cupcake:
return "assets/images/cupcake.svg";
case HardwareWalletType.coldcard:
diff --git a/lib/new-ui/widgets/digit_input.dart b/lib/new-ui/widgets/digit_input.dart
new file mode 100644
index 0000000..d6889db
--- /dev/null
+++ b/lib/new-ui/widgets/digit_input.dart
@@ -0,0 +1,182 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+class DigitInputController {
+ String _text = "";
+
+ String get text => _text;
+
+ set text(String newText) {
+ _text = newText;
+ for (final listener in _listeners) {
+ listener.call();
+ }
+ }
+
+ final List<void Function()> _listeners = [];
+
+ void addListener(void Function() listener) => _listeners.add(listener);
+}
+
+class DigitInputPill extends StatelessWidget {
+ const DigitInputPill({super.key, this.digit, required this.highlighted});
+
+ final String? digit;
+ final bool highlighted;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: 51,
+ height: 84,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
+ borderRadius: BorderRadius.circular(16),
+ border:
+ highlighted ? Border.all(color: Theme.of(context).colorScheme.primary, width: 2) : null,
+ ),
+ child: Center(
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 16),
+ child: Text(digit ?? " ", style: TextStyle(fontSize: 40)),
+ ),
+ ),
+ );
+ }
+}
+
+class DigitInput extends StatefulWidget {
+ const DigitInput({
+ super.key,
+ required this.controller,
+ required this.desiredLength,
+ this.breakAt = 3,
+ });
+
+ final DigitInputController controller;
+ final int desiredLength;
+ final int breakAt;
+
+ @override
+ State<DigitInput> createState() => _DigitInputState();
+}
+
+class _DigitInputState extends State<DigitInput> implements TextInputClient {
+ TextInputConnection? _textInputConnection;
+
+ @override
+ void initState() {
+ super.initState();
+ _openVirtualKeyboard();
+ }
+
+ void _openVirtualKeyboard() {
+ if (_textInputConnection == null || !_textInputConnection!.attached) {
+ _textInputConnection = TextInput.attach(
+ this,
+ const TextInputConfiguration(
+ inputType: TextInputType.numberWithOptions(
+ signed: false,
+ decimal: false,
+ ),
+ inputAction: TextInputAction.none,
+ ),
+ );
+ }
+
+ _textInputConnection!.setEditingState(
+ TextEditingValue(
+ text: widget.controller.text,
+ selection: TextSelection.collapsed(offset: widget.controller.text.length),
+ ),
+ );
+ _textInputConnection!.show();
+ }
+
+ void _closeVirtualKeyboard() => _textInputConnection?.close();
+
+ @override
+ void dispose() {
+ _closeVirtualKeyboard();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: _openVirtualKeyboard,
+ child: SizedBox(
+ height: 84,
+ child: ListView.separated(
+ scrollDirection: Axis.horizontal,
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ itemBuilder: (context, index) => DigitInputPill(
+ digit: widget.controller.text.length > index ? widget.controller.text[index] : null,
+ highlighted: widget.controller.text.length == index,
+ ),
+ separatorBuilder: (context, index) =>
+ SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4),
+ itemCount: widget.desiredLength,
+ ),
+ ),
+ );
+ }
+
+ @override
+ void updateEditingValue(TextEditingValue value) {
+ final RegExp numberRegExp = RegExp(r'^\d{0,6}$');
+
+ if (numberRegExp.hasMatch(value.text)) {
+ setState(() => widget.controller.text = value.text);
+ } else {
+ // flutter keeps the text editing state regardless of whether you actually used it or not.
+ // so, if validation fails, we gotta set it back to what it was explicitly
+ _textInputConnection?.setEditingState(
+ TextEditingValue(
+ text: widget.controller.text,
+ selection: TextSelection.collapsed(offset: widget.controller.text.length),
+ ),
+ );
+ }
+ }
+
+ @override
+ void performAction(TextInputAction action) {}
+
+ @override
+ TextEditingValue? get currentTextEditingValue => TextEditingValue(text: widget.controller.text);
+
+ @override
+ void connectionClosed() => _textInputConnection = null;
+
+ @override
+ AutofillScope? get currentAutofillScope => null;
+
+ @override
+ void performPrivateCommand(String action, Map<String, dynamic> data) {}
+
+ @override
+ void showAutocorrectionPromptRect(int start, int end) {}
+
+ @override
+ void updateFloatingCursor(RawFloatingCursorPoint point) {}
+
+ @override
+ void insertTextPlaceholder(Size size) {}
+
+ @override
+ void removeTextPlaceholder() {}
+
+ @override
+ void showToolbar() {}
+
+ @override
+ void performSelector(String selectorName) {}
+
+ @override
+ void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {}
+
+ @override
+ void insertContent(KeyboardInsertedContent content) {}
+}
diff --git a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart
new file mode 100644
index 0000000..100325d
--- /dev/null
+++ b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart
@@ -0,0 +1,60 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:flutter/material.dart';
+
+class HardwareWalletProceedOnDeviceMessage extends StatelessWidget {
+ const HardwareWalletProceedOnDeviceMessage({super.key, required this.hardwareWalletType});
+
+ final HardwareWalletType hardwareWalletType;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: double.infinity,
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ borderRadius: BorderRadius.circular(16),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Column(spacing: 8, children: [
+ if (hardwareWalletIcon != null)
+ CakeImageWidget(
+ imageUrl: hardwareWalletIcon!,
+ width: 36,
+ height: 36,
+ colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
+ ),
+ Text(
+ S.of(context).proceed_on_device,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ color: Theme.of(context).colorScheme.onSurface,
+ ),
+ )
+ ]),
+ ),
+ );
+ }
+
+ String? get hardwareWalletIcon {
+ switch (hardwareWalletType) {
+ case HardwareWalletType.bitbox:
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
+ case HardwareWalletType.ledger:
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
+ case HardwareWalletType.trezor:
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
+ case HardwareWalletType.cupcake:
+ return "assets/images/cupcake.svg";
+ case HardwareWalletType.coldcard:
+ case HardwareWalletType.seedsigner:
+ case HardwareWalletType.keystone:
+ return "assets/images/hardware_wallet/device_qr.svg";
+ }
+ }
+}
diff --git a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart
new file mode 100644
index 0000000..2a06f37
--- /dev/null
+++ b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart
@@ -0,0 +1,338 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/digit_input.dart';
+import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:cake_wallet/new-ui/widgets/send_page/directional_switcher.dart';
+import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
+import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cake_wallet/utils/show_pop_up.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:mobx/mobx.dart';
+
+class HardwareWalletProceedOnDeviceSheet extends StatefulWidget {
+ const HardwareWalletProceedOnDeviceSheet({
+ super.key,
+ required this.hardwareWalletType,
+ required this.trezorConnectVM,
+ required this.onRetry,
+ });
+
+ final HardwareWalletType hardwareWalletType;
+ final TrezorConnectViewModelBase trezorConnectVM;
+ final void Function() onRetry;
+
+ @override
+ State<HardwareWalletProceedOnDeviceSheet> createState() =>
+ _HardwareWalletProceedOnDeviceSheetState();
+}
+
+class _HardwareWalletProceedOnDeviceSheetState extends State<HardwareWalletProceedOnDeviceSheet> {
+ TrezorParingState _paringState = TrezorParingState.initial;
+
+ final DigitInputController _controller = DigitInputController();
+
+ static const pinOpenDuration = Duration(milliseconds: 300);
+
+ // you should probably check if there can be other pin lengths
+ static const pinLength = 6;
+
+ ReactionDisposer? paringStateReaction;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller.addListener(() => setState(() {}));
+
+ _paringState = widget.trezorConnectVM.paringState;
+ paringStateReaction = reaction((_) => widget.trezorConnectVM.paringState, (paringState) {
+ if (paringState is SuccessTrezorParingState) {
+ if (mounted) Navigator.of(context).pop();
+ return;
+ }
+ setState(() => _paringState = paringState);
+ });
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ paringStateReaction?.reaction.dispose();
+ }
+
+ bool get _isAwaitingPin => _paringState == TrezorParingState.enterPin;
+
+ @override
+ Widget build(BuildContext context) {
+ return PopScope(
+ canPop: false,
+ child: SafeArea(
+ bottom: false,
+ child: Container(
+ width: MediaQuery.of(context).size.width,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
+ ),
+ child: SafeArea(
+ child: Padding(
+ padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+ child: Column(
+ children: [
+ ModalTopBar(
+ title: "",
+ leadingWidget: AnimatedSwitcher(
+ layoutBuilder: (currentChild, previousChildren) {
+ return Stack(
+ alignment: Alignment.centerLeft,
+ children: <Widget>[
+ ...previousChildren,
+ if (currentChild != null) currentChild,
+ ],
+ );
+ },
+ duration: pinOpenDuration,
+ child: Text(
+ key: ValueKey(pageTitle),
+ style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
+ pageTitle,
+ ),
+ ),
+ trailingIcon: Icon(
+ Icons.close,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ onTrailingPressed: () => showPopUp(
+ context: context,
+ builder: (context) => AlertWithTwoActions(
+ alertTitle: S.of(context).are_you_sure_exit,
+ alertContent: S.of(context).hww_exit_desc,
+ leftButtonText: S.of(context).cancel,
+ rightButtonText: S.of(context).yes_exit,
+ rightAlertButtonStyle: AlertButtonStyle.error(context),
+ actionLeftButton: Navigator.of(context).pop,
+ actionRightButton: () {
+ Navigator.of(context).pop();
+ Navigator.of(context).pop();
+ },
+ ),
+ ),
+ ),
+ Expanded(
+ child: DirectionalAnimatedSwitcher(
+ duration: Duration(milliseconds: 400),
+ child: content,
+ ),
+ ),
+ Padding(
+ padding: EdgeInsets.only(bottom: 24, right: 24),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ SizedBox(),
+ AnimatedOpacity(
+ curve: Curves.easeOutQuad,
+ opacity: hasFullPin ? 1 : 0,
+ duration: pinOpenDuration,
+ child: ModernButton(
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ iconColor: Theme.of(context).colorScheme.onPrimary,
+ size: 36,
+ icon: Icon(Icons.arrow_forward),
+ onPressed: () =>
+ widget.trezorConnectVM.setParingPin(_controller.text.trim()),
+ ),
+ ),
+ ],
+ ),
+ )
+ ],
+ ),
+ )),
+ ),
+ ),
+ );
+ }
+
+ Widget get content {
+ if (_paringState is InitialTrezorParingState || _paringState is EnterPinTrezorParingState) {
+ return _enterPinCode();
+ }
+
+ if (_paringState is VerifyingPinTrezorParingState) return _verifyingCode();
+
+ if (_paringState is FailTrezorParingState)
+ return _errorBox((_paringState as FailTrezorParingState).message);
+
+ return SizedBox.shrink(key: ValueKey(3));
+ }
+
+ void retry() {
+ _controller.text = "";
+ widget.onRetry();
+ }
+
+ String get pageTitle {
+ if (_paringState is InitialTrezorParingState) return S.of(context).device_confirmation;
+ if (_isAwaitingPin) return S.of(context).pairing_code;
+ return "";
+ }
+
+ bool get hasFullPin => _controller.text.length == pinLength && _isAwaitingPin;
+
+ String? get hardwareWalletIcon {
+ switch (widget.hardwareWalletType) {
+ case HardwareWalletType.bitbox:
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
+ case HardwareWalletType.ledger:
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
+ case HardwareWalletType.trezor:
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
+ case HardwareWalletType.cupcake:
+ return "assets/images/cupcake.svg";
+ case HardwareWalletType.coldcard:
+ case HardwareWalletType.seedsigner:
+ case HardwareWalletType.keystone:
+ return "assets/images/hardware_wallet/device_qr.svg";
+ }
+ }
+
+ Widget _verifyingCode() => Container(
+ key: ValueKey(1),
+ width: MediaQuery.of(context).size.width,
+ child: Column(
+ spacing: 12,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const CupertinoActivityIndicator(radius: 36),
+ const SizedBox(),
+ Text(
+ "${S.of(context).verifying_code}...",
+ style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
+ ),
+ Text(
+ S.of(context).this_can_take_few_seconds,
+ style: TextStyle(
+ fontWeight: FontWeight.w400,
+ fontSize: 16,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ )
+ ],
+ ),
+ );
+
+ Widget _errorBox(String errorText) => Container(
+ key: ValueKey(2),
+ width: MediaQuery.of(context).size.width,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 18),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 12,
+ children: [
+ const Spacer(),
+ Icon(
+ Icons.error_outline,
+ size: 80,
+ color: Theme.of(context).colorScheme.error,
+ ),
+ Text(
+ S.of(context).pairing_error,
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).colorScheme.onSurface,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ Text(
+ errorText,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const Spacer(),
+ NewPrimaryButton(
+ onPressed: retry,
+ text: S.of(context).try_again,
+ color: Theme.of(context).colorScheme.primary,
+ textColor: Theme.of(context).colorScheme.onPrimary,
+ )
+ ],
+ ),
+ ),
+ );
+
+ Widget _enterPinCode() => Column(
+ key: ValueKey(0),
+ spacing: 12,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ AnimatedContainer(
+ duration: pinOpenDuration,
+ curve: Curves.easeOutCubic,
+ width: _isAwaitingPin ? 40 : 100,
+ height: _isAwaitingPin ? 40 : 100,
+ child: CakeImageWidget(
+ imageUrl: hardwareWalletIcon,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn,
+ ),
+ ),
+ ),
+ AnimatedSwitcher(
+ duration: pinOpenDuration,
+ switchInCurve: Curves.easeInCubic,
+ switchOutCurve: Curves.easeOutCubic,
+ child: _isAwaitingPin
+ ? Column(
+ spacing: 24,
+ key: ValueKey(1),
+ children: [
+ Text(
+ textAlign: TextAlign.center,
+ "${S.of(context).pairing_code_desc_1}\n${S.of(context).pairing_code_desc_2}",
+ ),
+ DigitInput(
+ controller: _controller,
+ desiredLength: pinLength,
+ )
+ ],
+ )
+ : Column(
+ key: ValueKey(0),
+ spacing: 12,
+ children: [
+ Text(
+ S.of(context).proceed_on_device,
+ style: TextStyle(
+ fontWeight: FontWeight.w500,
+ fontSize: 20,
+ ),
+ ),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 20),
+ child: Text(
+ S.of(context).proceed_on_device_description,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ ),
+ )
+ ],
+ ),
+ )
+ ],
+ );
+}
diff --git a/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart
new file mode 100644
index 0000000..9b429ed
--- /dev/null
+++ b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart
@@ -0,0 +1,106 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:flutter/material.dart';
+
+class HardwareWalletTrezorParingSheet extends StatefulWidget {
+ const HardwareWalletTrezorParingSheet({super.key});
+
+ @override
+ State<HardwareWalletTrezorParingSheet> createState() => _HardwareWalletTrezorParingSheetState();
+}
+
+class _HardwareWalletTrezorParingSheetState extends State<HardwareWalletTrezorParingSheet> {
+ final TextEditingController _paringCodeController = TextEditingController();
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+ child: SafeArea(
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
+ color: Theme.of(context).colorScheme.surface,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ModalTopBar(
+ title: "Paring code",
+ onLeadingPressed: Navigator.of(context).pop,
+ onTrailingPressed: () {},
+ leadingIcon: Icon(Icons.close),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(18.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 12,
+ children: [
+ SizedBox(),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Expanded(
+ flex: 75,
+ child: AnimatedContainer(
+ duration: Duration(milliseconds: 300),
+ height: 60,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(16),
+ bottomLeft: Radius.circular(16),
+ ),
+ border: Border.all(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ width: 2,
+ ),
+ ),
+ child: TextField(
+ textAlign: TextAlign.left,
+ textAlignVertical: TextAlignVertical.center,
+ controller: _paringCodeController,
+ keyboardType:
+ TextInputType.numberWithOptions(signed: false, decimal: false),
+ decoration: InputDecoration(
+ hint: Text(
+ "000 000",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Theme.of(
+ context,
+ ).colorScheme.onSurface.withValues(alpha: 0.5),
+ ),
+ ),
+ border: InputBorder.none,
+ filled: true,
+ fillColor: Colors.transparent,
+ ),
+ style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
+ ),
+ ),
+ ),
+ ],
+ ),
+ SizedBox(),
+ NewPrimaryButton(
+ text: S.of(context).continue_text,
+ onPressed: () => Navigator.of(context).pop(_paringCodeController.text),
+ color: Theme.of(context).colorScheme.primary,
+ textColor: Theme.of(context).colorScheme.onPrimary,
+ )
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart b/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
index 68f8c68..6715799 100644
--- a/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
+++ b/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
@@ -1,16 +1,14 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/confirm_swiper.dart';
+import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart';
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
-import 'package:cw_core/wallet_info.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:vector_graphics/vector_graphics.dart';
class SendConfirmBottomWidget extends StatelessWidget {
const SendConfirmBottomWidget({super.key, required this.sendViewModel});
@@ -61,7 +59,7 @@ class SendConfirmBottomWidget extends StatelessWidget {
text: "${S.of(context).signing_transaction}...",
);
case IsAwaitingDeviceResponseState:
- return HardwareWalletConfirmationMessage(
+ return HardwareWalletProceedOnDeviceMessage(
hardwareWalletType: sendViewModel.wallet.hardwareWalletType!);
case TransactionCommitting:
return LoadingBottomWidget(
@@ -173,58 +171,3 @@ class TransactionErrorActions extends StatelessWidget {
);
}
}
-
-class HardwareWalletConfirmationMessage extends StatelessWidget {
- const HardwareWalletConfirmationMessage({super.key, required this.hardwareWalletType});
-
- final HardwareWalletType hardwareWalletType;
-
- @override
- Widget build(BuildContext context) {
- return Container(
- width: double.infinity,
- decoration: BoxDecoration(
- border: Border.all(
- color: Theme.of(context).colorScheme.primary,
- ),
- borderRadius: BorderRadius.circular(16),
- ),
- child: Padding(
- padding: const EdgeInsets.all(12.0),
- child: Column(spacing: 8, children: [
- if (hardwareWalletIcon != null)
- CakeImageWidget(imageUrl:
- hardwareWalletIcon!,
- width: 36,
- height: 36,
- colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
- ),
- Text(
- S.of(context).proceed_on_device,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w400,
- color: Theme.of(context).colorScheme.onSurface),
- )
- ]),
- ),
- );
- }
-
- String? get hardwareWalletIcon {
- switch (hardwareWalletType) {
- case HardwareWalletType.bitbox:
- return "assets/images/hardware_wallet/device_bitbox.svg";
- case HardwareWalletType.ledger:
- return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
- case HardwareWalletType.trezor:
- return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
- case HardwareWalletType.cupcake:
- return "assets/images/cupcake.svg";
- case HardwareWalletType.coldcard:
- case HardwareWalletType.seedsigner:
- case HardwareWalletType.keystone:
- return "assets/images/hardware_wallet/device_qr.svg";
- }
- }
-}
diff --git a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart
index 9ccfb14..3676cd6 100644
--- a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart
+++ b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart
@@ -41,7 +41,7 @@ class _SwapConfirmSheetState extends State<SwapConfirmSheet> {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
if (sendVM.wallet.isHardwareWallet) {
- if (!sendVM.hardwareWalletViewModel!.isConnected) {
+ if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendVM.walletType,
diff --git a/lib/router.dart b/lib/router.dart
index a5af558..8936517 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -371,7 +371,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
(_) => getIt.get<NewWalletTypePage>(
param1: NewWalletTypeArguments(
onTypeSelected: (BuildContext context, WalletType type) {
- if (hardwareWalletType == HardwareWalletType.trezor) {
+ if (hardwareWalletType == HardwareWalletType.trezor && type != WalletType.monero) {
Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
arguments: [type, hardwareWalletType]);
return;
diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart
index 2122db4..22e466c 100644
--- a/lib/src/screens/connect_device/connect_device_page.dart
+++ b/lib/src/screens/connect_device/connect_device_page.dart
@@ -3,10 +3,13 @@ import 'dart:io';
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/main.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
@@ -91,6 +94,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
var bleDevices = <HardwareWalletDevice>[];
var usbDevices = <HardwareWalletDevice>[];
+ List<HardwareWalletDevice> get allDevices => [...bleDevices, ...usbDevices];
+
late Timer? _usbRefreshTimer = null;
late Timer? _bleRefreshTimer = null;
late Timer? _bleStateTimer = null;
@@ -158,24 +163,38 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Future<void> _connectToDevice(HardwareWalletDevice device) async {
final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
- if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM);
+
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (isConnected) widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM);
+ });
}
String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
switch (deviceType) {
case HardwareWalletDeviceType.ledgerNanoX:
- return 'assets/images/hardware_wallet/ledger_nano_x.png';
+ return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
case HardwareWalletDeviceType.ledgerNanoGen5:
- return 'assets/images/hardware_wallet/device_ledger_nano_gen_5.svg';
+ return 'assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg';
case HardwareWalletDeviceType.ledgerStax:
- return 'assets/images/hardware_wallet/ledger_stax.png';
+ return 'assets/new-ui/hardware_wallets/ledger_stax.svg';
case HardwareWalletDeviceType.ledgerFlex:
- return 'assets/images/hardware_wallet/ledger_flex.png';
+ return 'assets/new-ui/hardware_wallets/ledger_flex.svg';
case HardwareWalletDeviceType.BitBox02:
- return 'assets/images/hardware_wallet/device_bitbox.svg';
+ case HardwareWalletDeviceType.BitBox02Nova:
+ return 'assets/new-ui/hardware_wallets/device_bitbox.svg';
+ case HardwareWalletDeviceType.trezorModelOne:
+ return 'assets/new-ui/hardware_wallets/device_trezor_model_one.svg';
+ case HardwareWalletDeviceType.trezorModelT:
+ return 'assets/new-ui/hardware_wallets/device_trezor_model_t.svg';
+ case HardwareWalletDeviceType.trezorSafe3:
+ return 'assets/new-ui/hardware_wallets/device_trezor_safe_3.svg';
+ case HardwareWalletDeviceType.trezorSafe5:
+ return 'assets/new-ui/hardware_wallets/device_trezor_safe_5.svg';
+ case HardwareWalletDeviceType.trezorSafe7:
+ return 'assets/new-ui/hardware_wallets/device_trezor_safe_7.svg';
default:
- return 'assets/images/hardware_wallet/ledger_nano_x.png';
+ return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
}
}
@@ -197,25 +216,15 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Expanded(
child: SingleChildScrollView(
child: Column(
+ spacing: 24,
children: [
- Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Text(
- description,
- style: Theme.of(context).textTheme.titleMedium,
- textAlign: TextAlign.center,
- ),
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/hardware_wallet.svg",
),
- Offstage(
- offstage: !longWait,
- child: Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Text(
- S.of(context).if_you_dont_see_your_device,
- style: Theme.of(context).textTheme.titleMedium,
- textAlign: TextAlign.center,
- ),
- ),
+ Text(
+ description,
+ style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
+ textAlign: TextAlign.center,
),
Observer(
builder: (_) => Offstage(
@@ -231,56 +240,69 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
),
),
),
- if (bleDevices.length > 0) ...[
- Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Container(
- width: double.infinity,
- child: Text(
- S.of(context).bluetooth,
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- ),
- ),
- ...bleDevices
- .map(
- (device) => Padding(
- padding: EdgeInsets.only(bottom: 20),
- child: DeviceTile(
- onPressed: () => _connectToDevice(device),
- title: device.name,
- leading: _getDeviceTileLeading(device.type),
- connectionType: device.connectionType,
+ Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(18)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ final item = allDevices[index];
+ return GestureDetector(
+ onTap: () => _connectToDevice(item),
+ child: SizedBox(
+ height: 48,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ spacing: 12,
+ children: [
+ CakeImageWidget(
+ imageUrl: _getDeviceTileLeading(item.type),
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn),
+ ),
+ Text(item.name)
+ ],
+ ),
+ Row(
+ spacing: 12,
+ children: [
+ if (item.connectionType ==
+ HardwareWalletConnectionType.ble)
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/bluetooth.svg",
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.primary,
+ BlendMode.srcIn),
+ ),
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/arrow_forward.svg",
+ height: 16,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn))
+ ],
+ )
+ ],
+ ),
+ ),
),
- ),
- )
- .toList()
- ],
- if (usbDevices.length > 0) ...[
- Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Container(
- width: double.infinity,
- child: Text(
- S.of(context).usb,
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- ),
- ),
- ...usbDevices
- .map(
- (device) => Padding(
- padding: EdgeInsets.only(bottom: 20),
- child: DeviceTile(
- onPressed: () => _connectToDevice(device),
- title: device.name,
- leading: _getDeviceTileLeading(device.type),
- connectionType: device.connectionType,
- ),
- ),
- )
- .toList(),
- ],
+ );
+ },
+ separatorBuilder: (context, index) => Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh),
+ itemCount: allDevices.length),
+ ),
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
@@ -293,10 +315,10 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
),
),
),
- PrimaryButton(
+ NewPrimaryButton(
text: S.of(context).how_to_connect,
color: Theme.of(context).colorScheme.surfaceContainer,
- textColor: Theme.of(context).colorScheme.onSecondaryContainer,
+ textColor: Theme.of(context).colorScheme.primary,
onPressed: () => _onHowToConnect(context),
)
],
diff --git a/lib/src/screens/connect_device/widgets/device_tile.dart b/lib/src/screens/connect_device/widgets/device_tile.dart
index 803a648..4014e50 100644
--- a/lib/src/screens/connect_device/widgets/device_tile.dart
+++ b/lib/src/screens/connect_device/widgets/device_tile.dart
@@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -43,14 +44,12 @@ class DeviceTile extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
- if (leading != null && !leading!.endsWith(".svg"))
- Image.asset(
- leading!,
- height: 30,
- color: Theme.of(context).colorScheme.onSurface,
- ),
- if (leading != null && leading!.endsWith(".svg"))
- SvgPicture.asset(leading!, height: 30),
+ CakeImageWidget(
+ height: 30,
+ imageUrl: leading,
+ colorFilter:
+ ColorFilter.mode(Theme.of(context).colorScheme.onSurface, BlendMode.srcIn),
+ ),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
diff --git a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
index a6ea7b3..de05bcc 100644
--- a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+++ b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
@@ -34,11 +34,11 @@ class CryptoBalanceWidget extends StatelessWidget {
case null:
return null;
case HardwareWalletType.bitbox:
- return "assets/images/hardware_wallet/device_bitbox.svg";
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
case HardwareWalletType.ledger:
- return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
case HardwareWalletType.trezor:
- return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
case HardwareWalletType.cupcake:
return "assets/images/cupcake.svg";
case HardwareWalletType.coldcard:
diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart
index d7fd319..6ebd252 100644
--- a/lib/src/screens/exchange_trade/exchange_trade_page.dart
+++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart
@@ -213,7 +213,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
if (sendVM.wallet.isHardwareWallet) {
- if (!sendVM.hardwareWalletViewModel!.isConnected) {
+ if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendVM.walletType,
diff --git a/lib/src/screens/integrations/deuro/savings_page.dart b/lib/src/screens/integrations/deuro/savings_page.dart
index 4a5d62a..a084378 100644
--- a/lib/src/screens/integrations/deuro/savings_page.dart
+++ b/lib/src/screens/integrations/deuro/savings_page.dart
@@ -160,7 +160,7 @@ class DEuroSavingsPage extends BasePage {
Future<void> _requireHardwareWallet(BuildContext context) async {
if (_dEuroViewModel.wallet.isHardwareWallet) {
- if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected) {
+ if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected(_dEuroViewModel.wallet.type)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: _dEuroViewModel.wallet.type,
diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart
index fff2068..4b170dc 100644
--- a/lib/src/screens/new_wallet/new_wallet_type_page.dart
+++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart
@@ -6,14 +6,8 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
-import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
-import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
-import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
-import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
-import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
@@ -21,10 +15,38 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
+class NewWalletTypePageHeader extends StatelessWidget {
+ const NewWalletTypePageHeader({super.key});
+
+ @override
+ Widget build(BuildContext context) => const SizedBox(
+ width: 138,
+ height: 75,
+ child: Stack(
+ children: [
+ Positioned(
+ left: 0,
+ child: CakeImageWidget(
+ imageUrl: "assets/new-ui/hardware_wallet.svg",
+ width: 75,
+ height: 75,
+ ),
+ ),
+ Positioned(
+ left: 63,
+ child: CakeImageWidget(
+ imageUrl: "assets/new-ui/cake_coin.svg",
+ width: 75,
+ height: 75,
+ ),
+ ),
+ ],
+ ),
+ );
+}
+
class NewWalletTypePage extends BasePage {
- NewWalletTypePage({
- required this.newWalletTypeArguments,
- });
+ NewWalletTypePage({required this.newWalletTypeArguments});
final NewWalletTypeArguments newWalletTypeArguments;
@@ -38,23 +60,21 @@ class NewWalletTypePage extends BasePage {
@override
Function(BuildContext)? get pushToNextWidget => (context) {
- FocusScopeNode currentFocus = FocusScope.of(context);
- if (!currentFocus.hasPrimaryFocus) {
- currentFocus.focusedChild?.unfocus();
- }
- };
+ final currentFocus = FocusScope.of(context);
+ if (!currentFocus.hasPrimaryFocus) currentFocus.focusedChild?.unfocus();
+ };
@override
Widget body(BuildContext context) => WalletTypeForm(
- walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
- isCreate: newWalletTypeArguments.isCreate,
- onTypeSelected: newWalletTypeArguments.onTypeSelected,
- hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
- );
+ walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
+ isCreate: newWalletTypeArguments.isCreate,
+ onTypeSelected: newWalletTypeArguments.onTypeSelected,
+ hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
+ );
}
class WalletTypeForm extends StatefulWidget {
- WalletTypeForm({
+ const WalletTypeForm({
required this.walletImage,
required this.isCreate,
this.onTypeSelected,
@@ -77,9 +97,6 @@ class WalletTypeFormState extends State<WalletTypeForm> {
static const aspectRatioImage = 1.22;
- final TextEditingController searchController = TextEditingController();
-
- WalletType? selected;
List<WalletType> types;
List<WalletType> filteredTypes = [];
@@ -87,21 +104,12 @@ class WalletTypeFormState extends State<WalletTypeForm> {
void initState() {
types = filteredTypes = availableWalletTypes
.where((element) =>
- !widget.isHardwareWallet ||
- DeviceConnectionType.supportedConnectionTypes(
- element, widget.hardwareWalletType!, Platform.isIOS)
- .isNotEmpty)
+ !widget.isHardwareWallet ||
+ DeviceConnectionType.supportedConnectionTypes(
+ element, widget.hardwareWalletType!, Platform.isIOS)
+ .isNotEmpty)
.toList();
super.initState();
-
- searchController.addListener(() {
- setState(() {
- filteredTypes = List.from(types.where((type) => walletTypeToDisplayName(type)
- .toLowerCase()
- .contains(searchController.text.toLowerCase())));
- return;
- });
- });
}
@override
@@ -110,96 +118,102 @@ class WalletTypeFormState extends State<WalletTypeForm> {
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
+ spacing: 24,
children: [
- Padding(
- padding: EdgeInsets.only(top: 48),
- child: Text(
- S.of(context).choose_wallet_currency,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.titleMedium?.copyWith(
- color: Theme.of(context).colorScheme.onSurfaceVariant,
- ),
- ),
- ),
- Padding(
- padding: EdgeInsets.fromLTRB(24, 24, 24, 12),
- child: SearchBarWidget(searchController: searchController),
+ const NewWalletTypePageHeader(),
+ Text(
+ S.of(context).choose_wallet_currency,
+ textAlign: TextAlign.center,
),
- Expanded(
- child: ScrollableWithBottomSection(
- contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
- scrollableKey: ValueKey('new_wallet_type_scrollable_key'),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: <Widget>[
- ...filteredTypes.map(
- (type) => Padding(
- padding: EdgeInsets.only(top: 12),
- child: SelectButton(
- key: ValueKey('new_wallet_type_${type.name}_button_key'),
- image: CakeImageWidget(
- imageUrl: getCryptoCurrencyIconForWalletListItem(type),
- height: 24,
- width: 24,
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 18),
+ child: Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(18),
+ ),
+ child: ListView.separated(
+ key: const ValueKey('new_wallet_type_scrollable_key'),
+ itemCount: filteredTypes.length,
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ final item = filteredTypes[index];
+ final curr = getCryptoCurrencyForWalletListItem(item);
+
+ return GestureDetector(
+ key: ValueKey('new_wallet_type_${item.name}_button_key'),
+ behavior: HitTestBehavior.opaque,
+ onTap: () {
+ onTypeSelected(item);
+ },
+ child: SizedBox(
+ height: 48,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ CakeImageWidget(
+ height: 24,
+ width: 24,
+ imageUrl: curr.iconPath,
+ ),
+ const SizedBox(width: 12),
+ Text(curr.fullName ?? curr.name),
+ const SizedBox(width: 4),
+ Text(
+ curr.title,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ )
+ ],
+ ),
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/arrow_forward.svg",
+ height: 16,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn,
+ ),
+ )
+ ],
+ ),
),
- text: walletTypeToDisplayName(type),
- showTrailingIcon: false,
- height: 54,
- isSelected: selected == type,
- onTap: () => setState(() => selected = type),
- deviceConnectionTypes: widget.isHardwareWallet
- ? DeviceConnectionType.supportedConnectionTypes(
- type, widget.hardwareWalletType!, Platform.isIOS)
- : [],
),
- ),
+ );
+ },
+ separatorBuilder: (context, index) => Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
- ],
- ),
- bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
- bottomSection: PrimaryButton(
- key: ValueKey('new_wallet_type_next_button_key'),
- onPressed: () => onTypeSelected(),
- text: S.of(context).seed_language_next,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- isDisabled: selected == null,
+ ),
),
),
- ),
+ )
],
),
),
);
}
- Future<void> onTypeSelected() async {
- if (selected == null) throw Exception('Wallet Type is not selected yet.');
-
- if (selected == WalletType.haven && widget.isCreate) {
- return await showPopUp<void>(
- context: context,
- builder: (BuildContext context) => PopUpCancellableAlertDialog(
- contentText: S.of(context).pause_wallet_creation,
- actionButtonText: S.of(context).ok,
- buttonAction: () => Navigator.of(context).pop(),
- ),
- );
- }
-
+ Future<void> onTypeSelected(WalletType selected) async {
// If it's a restore flow, trigger the external callback
// If it's not a BIP39 Wallet or if there are no other wallets, route to the newWallet page
// Any other scenario, route to pre-existing seed page
final walletList = await WalletInfo.getAll();
if (!widget.isCreate) {
- widget.onTypeSelected!(context, selected!);
- } else if (!isBIP39Wallet(selected!) || walletList.isEmpty) {
+ widget.onTypeSelected!(context, selected);
+ } else if (!isBIP39Wallet(selected) || walletList.isEmpty) {
Navigator.of(context).pushNamed(
Routes.newWallet,
- arguments: NewWalletArguments(type: selected!),
+ arguments: NewWalletArguments(type: selected),
);
} else {
- Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected!);
+ Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected);
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart
index 7e44e41..db0d5e8 100644
--- a/lib/src/screens/send/send_page.dart
+++ b/lib/src/screens/send/send_page.dart
@@ -426,7 +426,7 @@ class SendPage extends BasePage {
}
if (sendViewModel.wallet.isHardwareWallet) {
- if (!sendViewModel.hardwareWalletViewModel!.isConnected) {
+ if (!sendViewModel.hardwareWalletViewModel!.isConnected(sendViewModel.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
diff --git a/lib/view_model/hardware_wallet/bitbox_view_model.dart b/lib/view_model/hardware_wallet/bitbox_view_model.dart
index 07a412c..b518154 100644
--- a/lib/view_model/hardware_wallet/bitbox_view_model.dart
+++ b/lib/view_model/hardware_wallet/bitbox_view_model.dart
@@ -78,7 +78,7 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
bool _isConnecting = false;
@override
- bool get isConnected => false;
+ bool isConnected(WalletType type) => false;
@override
HardwareWalletService getHardwareWalletService(WalletType type) {
diff --git a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
index 4c79eda..de64079 100644
--- a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
+++ b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
@@ -6,10 +6,11 @@ import 'package:cw_core/wallet_type.dart';
abstract class HardwareWalletViewModel {
HardwareWalletType get hardwareWalletType;
- bool get isConnected;
bool get isBleEnabled;
bool get hasBluetooth;
+ bool isConnected(WalletType type);
+
Future<void> updateBleState();
Stream<HardwareWalletDevice> scanForBleDevices();
diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart
index 793f0df..c074622 100644
--- a/lib/view_model/hardware_wallet/ledger_view_model.dart
+++ b/lib/view_model/hardware_wallet/ledger_view_model.dart
@@ -114,7 +114,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
if (_isConnecting) return false;
_isConnecting = true;
_connectingWalletType = type;
- if (isConnected) {
+ if (isConnected(type)) {
try {
await _connection!.disconnect().catchError((_) {});
} catch (_) {}
@@ -126,7 +126,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
if (_connectionChangeSubscription == null) {
_connectionChangeSubscription = ledger
- .deviceStateChanges
+ .deviceStateChanges(device.device.id)
.listen(_connectionChangeListener);
}
@@ -172,7 +172,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
}
@override
- bool get isConnected => _connection != null && !(_connection!.isDisconnected);
+ bool isConnected(WalletType type) => _connection != null && !(_connection!.isDisconnected);
sdk.LedgerConnection get connection => _connection!;
@@ -196,6 +196,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
@override
HardwareWalletService getHardwareWalletService(WalletType type) {
switch (type) {
+ case WalletType.monero:
+ return monero!.getLedgerHardwareWalletService(connection);
case WalletType.bitcoin:
return bitcoin!.getLedgerHardwareWalletService(connection, true);
case WalletType.litecoin:
diff --git a/lib/view_model/hardware_wallet/trezor_connect_view_model.dart b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart
new file mode 100644
index 0000000..3fd4514
--- /dev/null
+++ b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart
@@ -0,0 +1,258 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:cake_wallet/bitcoin/bitcoin.dart';
+import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
+import 'package:cake_wallet/evm/evm.dart';
+import 'package:cake_wallet/main.dart';
+import 'package:cake_wallet/monero/monero.dart';
+import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
+import 'package:cake_wallet/wallet_type_utils.dart';
+import 'package:cw_core/hardware/device_connection_type.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:device_info_plus/device_info_plus.dart';
+import 'package:flutter/material.dart';
+import 'package:mobx/mobx.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:trezor_connect/trezor_connect.dart' as connect_sdk;
+import 'package:trezor_flutter/trezor_flutter.dart' as sdk;
+
+part 'trezor_connect_view_model.g.dart';
+
+class TrezorConnectViewModel = TrezorConnectViewModelBase with _$TrezorConnectViewModel;
+
+abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with Store {
+ final connect_sdk.TrezorConnect trezorConnect;
+
+ late final sdk.TrezorInterface trezorBLE;
+ late final sdk.TrezorInterface trezorUSB;
+
+ sdk.ThpState _state = sdk.ThpState();
+
+ TrezorConnectViewModelBase(this.trezorConnect) {
+ if (_doesSupportHardwareWallets) {
+ reaction((_) => isBleEnabled, (_) {
+ if (isBleEnabled) _initBLE();
+ });
+ updateBleState();
+
+ if (!Platform.isIOS) {
+ trezorUSB = sdk.TrezorInterface.usb();
+ }
+ }
+ }
+
+ bool get _doesSupportHardwareWallets {
+ if (isMoneroOnly) {
+ return DeviceConnectionType.supportedConnectionTypes(
+ WalletType.monero, HardwareWalletType.trezor, Platform.isIOS)
+ .isNotEmpty;
+ }
+
+ return true;
+ }
+
+ bool _bleIsInitialized = false;
+
+ Future<void> _initBLE() async {
+ if (isBleEnabled && !_bleIsInitialized) {
+ trezorBLE = sdk.TrezorInterface.ble(
+ onPermissionRequest: (_) async {
+ if (Platform.isMacOS) return true;
+
+ Map<Permission, PermissionStatus> statuses = await [
+ Permission.bluetoothScan,
+ Permission.bluetoothConnect,
+ Permission.bluetoothAdvertise,
+ ].request();
+
+ return statuses.values.where((status) => status.isDenied).isEmpty;
+ },
+ bleOptions: sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)),
+ );
+ _bleIsInitialized = true;
+ }
+ }
+
+ @override
+ HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
+
+ @override
+ @observable
+ bool isBleEnabled = false;
+
+ @override
+ bool get hasBluetooth => true;
+
+ @override
+ Future<void> updateBleState() async {
+ final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
+
+ final newState = bleState == sdk.AvailabilityState.poweredOn;
+
+ if (newState != isBleEnabled) isBleEnabled = newState;
+ }
+
+ @override
+ Stream<HardwareWalletDevice> scanForBleDevices() =>
+ trezorBLE.scan().map((d) => TrezorHardwareWalletDevice(d));
+
+ @override
+ Future<List<HardwareWalletDevice>> getAllUsbDevices() => trezorUSB.devices
+ .then((devices) => devices.map((d) => TrezorHardwareWalletDevice(d)).toList());
+
+ @override
+ Future<void> stopScanning() async {
+ if (_bleIsInitialized) await trezorBLE.stopScanning();
+ if (!Platform.isIOS) await trezorUSB.stopScanning();
+ }
+
+ bool _isConnecting = false;
+ sdk.TrezorClient? _client;
+
+ Completer<String?>? _pinCompleter;
+
+ @observable
+ TrezorParingState paringState = TrezorParingState.initial;
+
+ void setParingPin(String pin) => _pinCompleter?.complete(pin);
+
+ @override
+ Future<bool> connectDevice(HardwareWalletDevice device, WalletType type,
+ [bool isRetry = false]) async {
+ if (!(device is TrezorHardwareWalletDevice)) return false;
+ if (_isConnecting) return false;
+ _isConnecting = true;
+ paringState = TrezorParingState.initial;
+
+ try {
+ final trezorInterface =
+ device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB;
+ final connection = await trezorInterface.connect(device.device);
+
+ if (!isRetry) {
+ showModalBottomSheet(
+ context: navigatorKey.currentContext!,
+ isScrollControlled: true,
+ isDismissible: false,
+ enableDrag: false,
+ useSafeArea: true,
+ builder: (context) =>
+ HardwareWalletProceedOnDeviceSheet(
+ hardwareWalletType: hardwareWalletType,
+ trezorConnectVM: this,
+ onRetry: () => connectDevice(device, type, true)
+ ),
+ );
+ }
+
+ Future<String> onPinCode() async {
+ _pinCompleter = Completer<String?>();
+ paringState = TrezorParingState.enterPin;
+
+ final res = await _pinCompleter!.future;
+ paringState = TrezorParingState.verifyingPin;
+ if (res == null) throw Exception();
+ return res;
+ }
+
+ final deviceInfo = await _deviceName;
+
+ _client = sdk.TrezorClient.getClientForConnection(
+ connection,
+ _state,
+ "Cake Wallet",
+ deviceInfo,
+ onPinCode,
+ );
+
+ await _client!.createChannel();
+
+ paringState = TrezorParingState.success;
+ return true;
+ } catch (e) {
+ await _client?.connection.disconnect();
+ _client = null;
+ _state = sdk.ThpState();
+ // rethrow;
+ paringState = TrezorParingState.fail(e.toString());
+ printV(e);
+ return false;
+ } finally {
+ _isConnecting = false;
+ }
+ }
+
+ Future<String> get _deviceName async {
+ final deviceInfo = DeviceInfoPlugin();
+ if (Platform.isAndroid) return (await deviceInfo.androidInfo).model;
+ if (Platform.isIOS) return (await deviceInfo.iosInfo).name;
+ return "Computer";
+ }
+
+ @override
+ bool isConnected(WalletType type) => type == WalletType.monero
+ ? _client != null && _client?.connection.isDisconnected == false
+ : true;
+
+ @override
+ HardwareWalletService getHardwareWalletService(WalletType type) {
+ switch (type) {
+ case WalletType.monero:
+ return monero!.getTrezorHardwareWalletService(_client!);
+ case WalletType.bitcoin:
+ return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
+ case WalletType.litecoin:
+ return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
+ case WalletType.ethereum:
+ case WalletType.polygon:
+ return evm!.getTrezorHardwareWalletService(trezorConnect);
+ default:
+ throw UnimplementedError();
+ }
+ }
+
+ @override
+ Future<void> initWallet(WalletBase wallet) async {
+ switch (wallet.type) {
+ case WalletType.monero:
+ return monero!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ case WalletType.bitcoin:
+ case WalletType.litecoin:
+ return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ case WalletType.ethereum:
+ case WalletType.polygon:
+ return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ default:
+ throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
+ }
+ }
+}
+
+
+abstract class TrezorParingState {
+ static TrezorParingState initial = InitialTrezorParingState();
+ static TrezorParingState enterPin = EnterPinTrezorParingState();
+ static TrezorParingState verifyingPin = VerifyingPinTrezorParingState();
+ static TrezorParingState success = SuccessTrezorParingState();
+ static TrezorParingState fail(String message) => FailTrezorParingState(message);
+}
+
+class InitialTrezorParingState extends TrezorParingState {}
+
+class EnterPinTrezorParingState extends TrezorParingState {}
+
+class VerifyingPinTrezorParingState extends TrezorParingState {}
+
+class SuccessTrezorParingState extends TrezorParingState {}
+
+class FailTrezorParingState extends TrezorParingState {
+ final String message;
+
+ FailTrezorParingState(this.message);
+}
diff --git a/lib/view_model/hardware_wallet/trezor_view_model.dart b/lib/view_model/hardware_wallet/trezor_view_model.dart
deleted file mode 100644
index 8a56fb1..0000000
--- a/lib/view_model/hardware_wallet/trezor_view_model.dart
+++ /dev/null
@@ -1,79 +0,0 @@
-import 'dart:async';
-
-import 'package:cake_wallet/bitcoin/bitcoin.dart';
-import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
-import 'package:cake_wallet/evm/evm.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
-import 'package:cw_core/hardware/hardware_wallet_service.dart';
-import 'package:cw_core/wallet_base.dart';
-import 'package:cw_core/wallet_info.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:trezor_connect/trezor_connect.dart' as sdk;
-import 'package:mobx/mobx.dart';
-
-part 'trezor_view_model.g.dart';
-
-class TrezorViewModel = TrezorViewModelBase with _$TrezorViewModel;
-
-abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
- final sdk.TrezorConnect trezorConnect;
-
- TrezorViewModelBase(this.trezorConnect);
-
- @override
- HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
-
- @override
- @observable
- bool isBleEnabled = false;
-
- @override
- bool get hasBluetooth => false;
-
- @override
- Future<void> updateBleState() async {}
-
- @override
- Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
-
- @override
- Future<List<HardwareWalletDevice>> getAllUsbDevices() async => [];
-
- @override
- Future<void> stopScanning() async {}
-
- @override
- Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async => true;
-
- @override
- bool get isConnected => true;
-
- @override
- HardwareWalletService getHardwareWalletService(WalletType type) {
- switch (type) {
- case WalletType.bitcoin:
- return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
- case WalletType.litecoin:
- return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
- case WalletType.ethereum:
- case WalletType.polygon:
- return evm!.getTrezorHardwareWalletService(trezorConnect);
- default:
- throw UnimplementedError();
- }
- }
-
- @override
- Future<void> initWallet(WalletBase wallet) async {
- switch (wallet.type) {
- case WalletType.bitcoin:
- case WalletType.litecoin:
- return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
- case WalletType.ethereum:
- case WalletType.polygon:
- return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
- default:
- throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
- }
- }
-}
diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart
index 35883f5..a51423e 100644
--- a/lib/view_model/send/send_view_model.dart
+++ b/lib/view_model/send/send_view_model.dart
@@ -619,7 +619,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (!(state is IsExecutingState)) state = IsExecutingState();
if (wallet.isHardwareWallet) {
- state = IsAwaitingDeviceResponseState();
if (walletType == WalletType.monero) {
_ledgerTxStateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
if (monero!.getLastLedgerCommand() == "INS_CLSAG") {
@@ -627,6 +626,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = IsDeviceSigningResponseState();
}
});
+ } else {
+ state = IsAwaitingDeviceResponseState();
}
}
@@ -970,8 +971,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
throw Exception("Pending transaction doesn't exist. It should not be happened.");
}
+
try {
- state = TransactionCommitting();
+ state = wallet.isHardwareWallet && walletType == WalletType.monero
+ ? IsAwaitingDeviceResponseState()
+ : TransactionCommitting();
if (ocpRequest != null) {
await _handleOcpRequest();
diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart
index ac89b2a..1baabd1 100644
--- a/lib/view_model/wallet_hardware_restore_view_model.dart
+++ b/lib/view_model/wallet_hardware_restore_view_model.dart
@@ -89,7 +89,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
name: name,
- ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection,
+ hardwareWalletService: hardwareWalletVM.getHardwareWalletService(type),
password: password,
height: _options['height'] as int? ?? 0,
);
diff --git a/pubspec_base.yaml b/pubspec_base.yaml
index a04b093..fb35f35 100644
--- a/pubspec_base.yaml
+++ b/pubspec_base.yaml
@@ -125,7 +125,7 @@ dependencies:
ledger_flutter_plus:
git:
url: https://github.com/vespr-wallet/ledger-flutter-plus
- ref: 60817d4b20144f9da9029f5034790272795b9d38
+ ref: 0ccfbe26298e8974dfd4e891f34719c8348562f9
trezor_connect:
git:
url: https://github.com/cake-tech/trezor_connect
@@ -203,7 +203,7 @@ dependency_overrides:
ledger_flutter_plus:
git:
url: https://github.com/cake-tech/ledger-flutter-plus
- ref: 5237e5b3d5f07696eefd0aaee9c32478ed399d20
+ ref: 7bcb00380783f8611f68f5cd63fec96261fea2c2
ledger_usb_plus:
git:
url: https://github.com/konstantinullrich/ledger-usb-plus
@@ -227,6 +227,10 @@ dependency_overrides:
decimal: ^2.3.3
flutter_rust_bridge: 2.11.1
flutter_secure_storage_windows: 3.1.2
+ universal_ble:
+ git:
+ url: https://github.com/cake-tech/universal_ble.git
+ ref: f7044558ad111a156987614971e08c583c80175c
flutter_icons:
image_path: "assets/images/app_logo.png"
@@ -290,6 +294,7 @@ flutter:
- assets/new-ui/card_icons/outline_icons/
- assets/new-ui/card_icons/symbol_icons/
- assets/new-ui/crypto_full_icons/
+ - assets/new-ui/hardware_wallets/
- assets/new-ui/node_speed_badges/
fonts:
diff --git a/res/pictures/bluetooth.svg b/res/pictures/bluetooth.svg
new file mode 100644
index 0000000..ddee93c
--- /dev/null
+++ b/res/pictures/bluetooth.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <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"/>
+</svg>
diff --git a/res/pictures/cake_coin.svg b/res/pictures/cake_coin.svg
new file mode 100644
index 0000000..03d468a
--- /dev/null
+++ b/res/pictures/cake_coin.svg
@@ -0,0 +1,60 @@
+<svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <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)"/>
+ <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"/>
+ <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"/>
+ <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"/>
+ <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)"/>
+ <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"/>
+ <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)"/>
+ <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"/>
+ <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)"/>
+ <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"/>
+ <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)"/>
+ <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"/>
+ <defs>
+ <linearGradient id="paint0_linear_19960_1035188" x1="37.5" y1="0" x2="37.5" y2="75" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#FFCC35"/>
+ <stop offset="1" stop-color="#FFAE00"/>
+ </linearGradient>
+ <linearGradient id="paint1_linear_19960_1035188" x1="37.5" y1="0" x2="37.5" y2="75" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white"/>
+ <stop offset="0.25" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint2_linear_19960_1035188" x1="37.4968" y1="8.81934" x2="37.4968" y2="66.1723" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#935800"/>
+ <stop offset="1" stop-color="#D58308" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint3_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#D08C00"/>
+ <stop offset="0.5" stop-color="#E19803"/>
+ </linearGradient>
+ <linearGradient id="paint4_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#5A3B00"/>
+ <stop offset="0.25" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint5_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#D08C00"/>
+ <stop offset="0.5" stop-color="#E19803"/>
+ </linearGradient>
+ <linearGradient id="paint6_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#5A3B00"/>
+ <stop offset="0.25" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint7_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#D08C00"/>
+ <stop offset="0.5" stop-color="#E19803"/>
+ </linearGradient>
+ <linearGradient id="paint8_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#5A3B00"/>
+ <stop offset="0.25" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint9_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#D08C00"/>
+ <stop offset="0.5" stop-color="#E19803"/>
+ </linearGradient>
+ <linearGradient id="paint10_linear_19960_1035188" x1="37.3823" y1="19.6006" x2="37.3823" y2="55.6006" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#5A3B00"/>
+ <stop offset="0.25" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ </defs>
+</svg>
diff --git a/res/pictures/hardware_wallet.svg b/res/pictures/hardware_wallet.svg
new file mode 100644
index 0000000..56f7c89
--- /dev/null
+++ b/res/pictures/hardware_wallet.svg
@@ -0,0 +1,191 @@
+<svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <g clip-path="url(#clip0_19960_1035187)">
+ <g clip-path="url(#clip1_19960_1035187)">
+ <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)"/>
+ <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)"/>
+ <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)"/>
+ <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"/>
+ <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"/>
+ <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"/>
+ <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"/>
+ <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)"/>
+ <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"/>
+ <mask id="mask0_19960_1035187" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="32" y="24" width="36" height="35">
+ <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"/>
+ </mask>
+ <g mask="url(#mask0_19960_1035187)">
+ <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"/>
+ </g>
+ <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"/>
+ <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"/>
+ <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"/>
+ <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"/>
+ <g filter="url(#filter0_d_19960_1035187)">
+ <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"/>
+ </g>
+ <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)"/>
+ <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)"/>
+ <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"/>
+ <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"/>
+ <mask id="mask1_19960_1035187" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="37" width="76" height="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)"/>
+ <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)"/>
+ </mask>
+ <g mask="url(#mask1_19960_1035187)">
+ <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"/>
+ </g>
+ <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"/>
+ <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"/>
+ <circle cx="60.5605" cy="52.2798" r="8.04" stroke="url(#paint25_linear_19960_1035187)" stroke-width="1.08"/>
+ <g filter="url(#filter1_d_19960_1035187)">
+ <circle cx="60.5605" cy="52.2798" r="7.5" fill="url(#paint26_linear_19960_1035187)" fill-opacity="0.5" shape-rendering="crispEdges"/>
+ <circle cx="60.5605" cy="52.2798" r="7.5" fill="url(#paint27_linear_19960_1035187)" fill-opacity="0.2" shape-rendering="crispEdges"/>
+ <circle cx="60.5605" cy="52.2798" r="7.25955" stroke="url(#paint28_linear_19960_1035187)" stroke-width="0.480902" shape-rendering="crispEdges"/>
+ </g>
+ </g>
+ </g>
+ <defs>
+ <filter id="filter0_d_19960_1035187" x="19.1589" y="12.6356" width="22.5" height="22.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
+ <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"/>
+ <feOffset dx="1.66667" dy="3.33333"/>
+ <feGaussianBlur stdDeviation="1.875"/>
+ <feComposite in2="hardAlpha" operator="out"/>
+ <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"/>
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_19960_1035187"/>
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_19960_1035187" result="shape"/>
+ </filter>
+ <filter id="filter1_d_19960_1035187" x="50.9772" y="44.3631" width="22.5" height="22.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
+ <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"/>
+ <feOffset dx="1.66667" dy="3.33333"/>
+ <feGaussianBlur stdDeviation="1.875"/>
+ <feComposite in2="hardAlpha" operator="out"/>
+ <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"/>
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_19960_1035187"/>
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_19960_1035187" result="shape"/>
+ </filter>
+ <linearGradient id="paint0_linear_19960_1035187" x1="18.3192" y1="5.66012" x2="48.4992" y2="40.8201" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#EFEFF8"/>
+ <stop offset="1" stop-color="#C6C6E6"/>
+ </linearGradient>
+ <linearGradient id="paint1_linear_19960_1035187" x1="34.1959" y1="0.49414" x2="67.9747" y2="49.3316" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#FE6412"/>
+ <stop offset="1" stop-color="#EB4600"/>
+ </linearGradient>
+ <linearGradient id="paint2_linear_19960_1035187" x1="19.3392" y1="0.500117" x2="61.0992" y2="50.2401" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#4EBEFF"/>
+ <stop offset="1" stop-color="#2982FF"/>
+ </linearGradient>
+ <linearGradient id="paint3_linear_19960_1035187" x1="46.7493" y1="-9.91652" x2="52.9993" y2="15.0835" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white"/>
+ <stop offset="1" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint4_linear_19960_1035187" x1="33.8325" y1="17.1668" x2="55.4992" y2="49.6668" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#000938" stop-opacity="0"/>
+ <stop offset="1" stop-color="#000938"/>
+ </linearGradient>
+ <linearGradient id="paint5_linear_19960_1035187" x1="30.4991" y1="20.3751" x2="12.4991" y2="47.7501" gradientUnits="userSpaceOnUse">
+ <stop offset="0.176297" stop-color="#EFEFF8" stop-opacity="0"/>
+ <stop offset="0.571544" stop-color="#EFEFF8"/>
+ </linearGradient>
+ <linearGradient id="paint6_linear_19960_1035187" x1="61.0056" y1="23.9971" x2="61.0056" y2="41.1571" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#09479E"/>
+ <stop offset="1" stop-color="#001D67"/>
+ </linearGradient>
+ <linearGradient id="paint7_linear_19960_1035187" x1="49.3656" y1="23.2171" x2="64.2035" y2="38.4576" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#002A66"/>
+ <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint8_linear_19960_1035187" x1="49.3656" y1="23.2171" x2="64.2035" y2="38.4576" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#002A66"/>
+ <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint9_linear_19960_1035187" x1="58.5723" y1="43.4829" x2="68.2923" y2="43.4829" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#DBEDFF"/>
+ <stop offset="1" stop-color="#DBEDFF" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint10_linear_19960_1035187" x1="60.0529" y1="27.5992" x2="68.6402" y2="29.1082" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#002A66"/>
+ <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint11_linear_19960_1035187" x1="49.3656" y1="23.2171" x2="64.2035" y2="38.4576" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#002A66"/>
+ <stop offset="1" stop-color="#002A66" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint12_linear_19960_1035187" x1="42.1062" y1="0.494141" x2="34.0992" y2="38.6001" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white" stop-opacity="0.5"/>
+ <stop offset="1" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint13_linear_19960_1035187" x1="32.6592" y1="8.54012" x2="21.6192" y2="36.3201" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#195BA1" stop-opacity="0"/>
+ <stop offset="1" stop-color="#195BA1"/>
+ </linearGradient>
+ <linearGradient id="paint14_linear_19960_1035187" x1="21.2422" y1="14.3964" x2="36.646" y2="16.1247" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#2D72BB" stop-opacity="0.2"/>
+ <stop offset="1" stop-color="#185AA0"/>
+ </linearGradient>
+ <linearGradient id="paint15_linear_19960_1035187" x1="21.9572" y1="17.7316" x2="35.7882" y2="23.3319" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white" stop-opacity="0.3"/>
+ <stop offset="1" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint16_linear_19960_1035187" x1="29.12" y1="37.292" x2="32.8635" y2="62.2832" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#EB4600"/>
+ <stop offset="1" stop-color="#FE6412"/>
+ </linearGradient>
+ <linearGradient id="paint17_linear_19960_1035187" x1="0.5" y1="39.98" x2="72.8" y2="60.26" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#EFEFF8"/>
+ <stop offset="1" stop-color="#C6C6E6"/>
+ </linearGradient>
+ <linearGradient id="paint18_linear_19960_1035187" x1="38" y1="43" x2="35.5" y2="75.9167" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#7D6493" stop-opacity="0"/>
+ <stop offset="1" stop-color="#7D6493"/>
+ </linearGradient>
+ <linearGradient id="paint19_linear_19960_1035187" x1="61.3333" y1="28.4167" x2="47.5833" y2="58" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#46B0FF"/>
+ <stop offset="1" stop-color="#46B0FF" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint20_linear_19960_1035187" x1="29.12" y1="37.292" x2="32.8635" y2="62.2832" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#EB4600"/>
+ <stop offset="1" stop-color="#FE6412"/>
+ </linearGradient>
+ <linearGradient id="paint21_linear_19960_1035187" x1="0.5" y1="39.98" x2="72.8" y2="60.26" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#EFEFF8"/>
+ <stop offset="1" stop-color="#C6C6E6"/>
+ </linearGradient>
+ <linearGradient id="paint22_linear_19960_1035187" x1="30.974" y1="33.6949" x2="32.8091" y2="66.4546" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white"/>
+ <stop offset="1" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint23_linear_19960_1035187" x1="38" y1="37.292" x2="38" y2="67.292" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white"/>
+ <stop offset="1" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint24_linear_19960_1035187" x1="38" y1="37.292" x2="38" y2="67.292" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#312938" stop-opacity="0"/>
+ <stop offset="1" stop-color="#312938"/>
+ </linearGradient>
+ <linearGradient id="paint25_linear_19960_1035187" x1="53.0605" y1="46.1238" x2="68.4643" y2="47.8521" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#6E6EA8"/>
+ <stop offset="1" stop-color="#312938"/>
+ </linearGradient>
+ <linearGradient id="paint26_linear_19960_1035187" x1="56.3337" y1="45.9165" x2="63.0003" y2="57.5831" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white"/>
+ <stop offset="1" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <linearGradient id="paint27_linear_19960_1035187" x1="60.5605" y1="44.7798" x2="65.5005" y2="56.3331" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#564A84" stop-opacity="0"/>
+ <stop offset="1" stop-color="#564A84"/>
+ </linearGradient>
+ <linearGradient id="paint28_linear_19960_1035187" x1="53.7756" y1="49.459" x2="67.6066" y2="55.0593" gradientUnits="userSpaceOnUse">
+ <stop stop-color="white"/>
+ <stop offset="1" stop-color="white" stop-opacity="0"/>
+ </linearGradient>
+ <clipPath id="clip0_19960_1035187">
+ <rect width="75" height="75" fill="white"/>
+ </clipPath>
+ <clipPath id="clip1_19960_1035187">
+ <rect width="75" height="66.792" fill="white" transform="translate(0.5 0.5)"/>
+ </clipPath>
+ </defs>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_bitbox.svg b/res/pictures/hardware_wallets/device_bitbox.svg
new file mode 100644
index 0000000..fe41717
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_bitbox.svg
@@ -0,0 +1,3 @@
+<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_ledger_flex.svg b/res/pictures/hardware_wallets/device_ledger_flex.svg
new file mode 100644
index 0000000..9c8e9f7
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_flex.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg b/res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg
new file mode 100644
index 0000000..fe1124e
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_ledger_nano_s.svg b/res/pictures/hardware_wallets/device_ledger_nano_s.svg
new file mode 100644
index 0000000..a803274
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_nano_s.svg
@@ -0,0 +1,3 @@
+<svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_ledger_nano_x.svg b/res/pictures/hardware_wallets/device_ledger_nano_x.svg
new file mode 100644
index 0000000..538bb3f
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_nano_x.svg
@@ -0,0 +1,3 @@
+<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_ledger_stax.svg b/res/pictures/hardware_wallets/device_ledger_stax.svg
new file mode 100644
index 0000000..2704570
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_stax.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_trezor_model_one.svg b/res/pictures/hardware_wallets/device_trezor_model_one.svg
new file mode 100644
index 0000000..5d1c779
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_model_one.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_trezor_model_t.svg b/res/pictures/hardware_wallets/device_trezor_model_t.svg
new file mode 100644
index 0000000..fb437c9
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_model_t.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_trezor_safe_3.svg b/res/pictures/hardware_wallets/device_trezor_safe_3.svg
new file mode 100644
index 0000000..338c727
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_safe_3.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_trezor_safe_5.svg b/res/pictures/hardware_wallets/device_trezor_safe_5.svg
new file mode 100644
index 0000000..5ee539f
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_safe_5.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/pictures/hardware_wallets/device_trezor_safe_7.svg b/res/pictures/hardware_wallets/device_trezor_safe_7.svg
new file mode 100644
index 0000000..8d442f4
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_safe_7.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index f882887..f0b4256 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -74,6 +74,7 @@
"approve_tokens": "Approve tokens",
"apps": "Apps",
"arbiscan_history": "ArbiScan History",
+ "are_you_sure_exit": "Are you sure you want to exit?",
"arrive_in_this_address": "${currency} ${tag}will arrive in this address",
"ascending": "Ascending",
"ask_each_time": "Ask each time",
@@ -330,6 +331,7 @@
"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.",
"deuro_tooltip_no_eth": "In order to enable dEURO, you must have ETH in your wallet.",
"deuro_tx_commited_content": "It might take a couple of seconds for the transaction to confirm and be reflected on screen",
+ "device_confirmation": "Device Confirmation",
"device_is_signing": "Device is signing",
"dfx_option_description": "Buy crypto with EUR & CHF. For retail and corporate customers in Europe",
"did_you_back_up_seeds": "Did you back up all your seeds?",
@@ -529,6 +531,7 @@
"how_to_connect": "How to Connect",
"how_to_use": "How to use",
"how_to_use_card": "How to use this card",
+ "hww_exit_desc": "This will ed the ongoing pairing process with your hardware device",
"i_understand": "I understand",
"icon_style": "Icon style",
"id": "ID: ",
@@ -722,6 +725,10 @@
"overshot": "Overshot",
"overwrite_amount": "Overwrite amount",
"pairingInvalidEvent": "Pairing Invalid Event",
+ "pairing_code": "Pairing Code",
+ "pairing_code_desc_1": "Your device will show a pairing code.",
+ "pairing_code_desc_2": "Input here to continue",
+ "pairing_error": "Pairing Error",
"passphrase": "Passphrase (Optional)",
"passphrase_view_keys": "Passphrase",
"passphrases_doesnt_match": "Passphrases do not match, please try again",
@@ -1138,6 +1145,7 @@
"testnet_coins_no_value": "Testnet coins have no value",
"third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
"third_intro_title": "Yat plays nicely with others",
+ "this_can_take_few_seconds": "This can take a few seconds",
"this_pair_is_not_supported_warning": "This pair is not supported with the currently selected exchange(s). Please select another exchange.",
"thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address",
"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 @@
"value_type": "Value Type",
"variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
"verification": "Verification",
+ "verifying_code": "Verifying Code",
"verify_message": "Verify Message",
"verify_seed": "Verify Seed",
"verify_with_2fa": "Verify with Cake 2FA",
@@ -1394,6 +1403,7 @@
"yat_popup_title": "Your wallet address can be emojified.",
"yes": "Yes",
"yesterday": "Yesterday",
+ "yes_exit": "Yes. Exit",
"you_now_have_debit_card": "You now have a debit card",
"you_pay": "You Pay",
"you_will_get": "Convert to",
@@ -1408,4 +1418,4 @@
"zcash_card_missing_funds": "Missing funds?",
"zcash_card_scan": "Scan",
"zcash_card_warning": "Do not close the app until the procedure completes, if you do so this process will need to restart from scratch."
-}
\ No newline at end of file
+}
diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh
index 2d7b06f..0a328f5 100755
--- a/scripts/prepare_moneroc.sh
+++ b/scripts/prepare_moneroc.sh
@@ -14,9 +14,9 @@ else
fi
# NOTE: Make sure to update monero_c prebuilds link in workflow files
-# https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip
+# https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip
git fetch -a
-git checkout bc8d1a0b75b97156d71579581b4cdfe58c777ed2
+git checkout 3bfb3856a838f2bf6b729501837bb0295dedf25d
git reset --hard
git submodule update --init --force --recursive
diff --git a/tool/configure.dart b/tool/configure.dart
index 8085632..6a5385f 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -334,8 +334,10 @@ import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
+import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
import 'package:polyseed/polyseed.dart';""";
const moneroCWHeaders = """
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/account.dart' as monero_account;
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/monero_amount_format.dart';
@@ -345,6 +347,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet_api;
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_unspent.dart';
import 'package:cw_monero/api/account_list.dart';
+import 'package:cw_monero/trezor.dart';
import 'package:cw_monero/monero_wallet_service.dart';
import 'package:cw_monero/monero_wallet.dart';
import 'package:cw_monero/monero_transaction_info.dart';
@@ -463,7 +466,7 @@ abstract class Monero {
HardwareWalletType? hardwareWalletType,
required int height});
WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
- WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
+ WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService});
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic});
Map<String, String> getKeys(Object wallet);
int? getRestoreHeight(Object wallet);
@@ -484,6 +487,10 @@ WalletCredentials createMoneroNewWalletCredentials({required String name, requir
void resetLedgerConnection();
void setGlobalLedgerConnection(ledger.LedgerConnection connection);
String? getLastLedgerCommand();
+ void setHardwareWalletService(Object wallet, HardwareWalletService service);
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client);
+ Future<void> syncTrezor(Object wallet);
Map<String, List<int>> debugCallLength();
Map<String, dynamic> getWalletCacheDebug();
}
Why this scored 33/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.