LCW-1128-Add-support-for-Trezor (#2565)

* feat: add support for Trezor hardware wallet integration Restore Wallets

* feat: add Trezor hardware wallet support, implement RLP decoding utility, and update transaction signing logic

* feat: add bitcoin trezor signing

* chore: update Trezor Connect dependency to specific commit hash in pubspec_base.yaml

* feat: add trezor support for litecoin

* feat: add colorFilter for device manufacturer icons to align with theme styles

* refactor: optimize Trezor callback handling

* feat: add BitBox and Trezor support for Bitcoin and Litecoin, update transaction signing logic, and improve modularity in hardware wallet services

* refactor: simplify constructor assertions in `BaseBottomSheetWidget` and cleanup Trezor chainId handling logic

* feat: conditionally render device manufacturers based on platform and hide SeedSigner for now
This commit is contained in:
Konstantin Ullrich
2025-10-11 02:12:27 +03:00
committed by GitHub
parent 75ca159d2c
commit 8b76391a2e
38 changed files with 996 additions and 184 deletions
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@@ -29,28 +31,62 @@ class SelectDeviceManufacturerPage extends BasePage {
@override
AppBarStyle get appBarStyle => AppBarStyle.regular;
ColorFilter get _colorFilter =>
ColorFilter.mode(currentTheme.colorScheme.onSurface, BlendMode.srcIn);
List<_DeviceManufacturer> get availableManufacturers => [
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25),
image: SvgPicture.asset(
'assets/images/hardware_wallet/ledger_man.svg',
height: 25,
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.ledger,
),
// _DeviceManufacturer(
// image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
// hardwareWalletType: HardwareWalletType.bitbox,
// tag: S.current.new_tag,
// ),
if (Platform.isAndroid) ...[
_DeviceManufacturer(
image: SvgPicture.asset(
'assets/images/hardware_wallet/trezor_man.svg',
height: 25,
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.trezor,
tag: S.current.new_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset(
'assets/images/hardware_wallet/bitbox_man.svg',
height: 25,
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.bitbox,
tag: S.current.new_tag,
),
],
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25),
image: SvgPicture.asset(
'assets/images/hardware_wallet/cupcake_man.svg',
height: 25,
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.cupcake,
tag: S.current.new_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25),
image: SvgPicture.asset(
'assets/images/hardware_wallet/coldcard_man.svg',
height: 25,
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.coldcard,
tag: S.current.new_tag,
),
// _DeviceManufacturer(
// image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
// image: SvgPicture.asset(
// 'assets/images/hardware_wallet/seedsigner_man.svg',
// height: 25,
// colorFilter: _colorFilter,
// ),
// hardwareWalletType: HardwareWalletType.seedsigner,
// tag: S.current.new_tag,
// ),
@@ -61,26 +97,40 @@ class SelectDeviceManufacturerPage extends BasePage {
.toList();
List<_DeviceManufacturer> get comingManufacturers => [
if (!Platform.isAndroid) ...[
_DeviceManufacturer(
image: SvgPicture.asset(
'assets/images/hardware_wallet/bitbox_man.svg',
height: 25,
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.bitbox,
tag: S.current.coming_soon_tag,
),
],
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25),
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
hardwareWalletType: HardwareWalletType.bitbox,
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
image: SvgPicture.asset(
'assets/images/hardware_wallet/seedsigner_man.svg',
height: 25,
colorFilter: _colorFilter,
),
hardwareWalletType: HardwareWalletType.seedsigner,
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25),
image: SvgPicture.asset(
'assets/images/hardware_wallet/foundation_man.svg',
height: 25,
colorFilter: _colorFilter,
),
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25),
image: SvgPicture.asset(
'assets/images/hardware_wallet/keystone_man.svg',
height: 25,
colorFilter: _colorFilter,
),
tag: S.current.coming_soon_tag,
),
];
@@ -90,48 +140,52 @@ class SelectDeviceManufacturerPage extends BasePage {
child: Center(
child: Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: Column(
children: [
...availableManufacturers.map(
(manufacturer) => Padding(
padding: EdgeInsets.only(top: 10),
child: ManufacturerOptionTile(
image: manufacturer.image,
tag: manufacturer.tag,
onPressed: () {
if (onSelect != null)
return onSelect!.call(context, manufacturer.hardwareWalletType!);
if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
_onScanQRCode(context, manufacturer.hardwareWalletType!);
} else if (manufacturer.hardwareWalletType != null) {
Navigator.pushNamed(context, Routes.connectHardwareWallet,
arguments: [manufacturer.hardwareWalletType]);
}
},
),
),
),
if (showUnavailable) ...[
Padding(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
),
...comingManufacturers.map(
height: double.infinity,
child: SingleChildScrollView(
child: Column(
children: [
...availableManufacturers.map(
(manufacturer) => Padding(
padding: EdgeInsets.only(top: 10),
child: ManufacturerOptionTile(
image: manufacturer.image,
tag: manufacturer.tag,
onPressed: () =>
Fluttertoast.showToast(msg: 'One more tap and it might work'),
// Ester egg
isUnavailable: true,
onPressed: () {
if (onSelect != null)
return onSelect!.call(context, manufacturer.hardwareWalletType!);
if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
_onScanQRCode(context, manufacturer.hardwareWalletType!);
} else if (manufacturer.hardwareWalletType != null) {
Navigator.pushNamed(context, Routes.connectHardwareWallet,
arguments: [manufacturer.hardwareWalletType]);
}
},
),
),
),
]
],
if (showUnavailable) ...[
Padding(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
),
...comingManufacturers.map(
(manufacturer) => Padding(
padding: EdgeInsets.only(top: 10),
child: ManufacturerOptionTile(
image: manufacturer.image,
tag: manufacturer.tag,
onPressed: () =>
Fluttertoast.showToast(msg: 'One more tap and it might work'),
// Ester egg
isUnavailable: true,
),
),
),
]
],
),
),
),
),
@@ -33,10 +33,12 @@ class CryptoBalanceWidget extends StatelessWidget {
switch (dashboardViewModel.wallet.hardwareWalletType) {
case null:
return null;
case HardwareWalletType.ledger:
return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
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:
+17 -8
View File
@@ -1,26 +1,28 @@
import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/node_switching_service.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/trade_monitor.dart';
import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/socket_health_logger.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:mobx/mobx.dart';
import 'package:trezor_connect/trezor_connect.dart';
import 'package:uni_links/uni_links.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cw_core/utils/socket_health_logger.dart';
class Root extends StatefulWidget {
Root({
@@ -33,6 +35,7 @@ class Root extends StatefulWidget {
required this.linkViewModel,
required this.tradeMonitor,
required this.nodeSwitchingService,
required this.trezorConnect,
}) : super(key: key);
final AuthenticationStore authenticationStore;
@@ -43,6 +46,7 @@ class Root extends StatefulWidget {
final LinkViewModel linkViewModel;
final TradeMonitor tradeMonitor;
final NodeSwitchingService nodeSwitchingService;
final TrezorConnect trezorConnect;
@override
RootState createState() => RootState();
@@ -110,6 +114,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
widget.linkViewModel.currentLink = uri;
if (uri.toString().startsWith(widget.trezorConnect.callbackBackUri)) {
widget.trezorConnect.handleCallback(uri);
return;
}
bool requireAuth = await widget.authService.requireAuth();
if (!requireAuth && widget.authenticationStore.state == AuthenticationState.allowed) {
@@ -23,8 +23,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
this.leftActionButtonKey,
this.rightActionButtonKey,
required this.maxHeight,
}) : assert(footerType == FooterType.none,
'currentTheme is required unless footerType is none');
});
final String titleText;
final String? titleIconPath;