* v6.4.5 Release candidate

* Apply patches for BLE session, locktime prompt, and monero coincontrol on hww [skip ci]

* include the fix for bluetooth prompt showing on app start

* include the fix for bluetooth prompt showing on app start
This commit is contained in:
Omar Hatem
2026-09-20 02:40:48 +02:00
committed by GitHub
parent bbff4b0994
commit 9fe23970c7
74 changed files with 296 additions and 85 deletions
@@ -27,7 +27,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
String get title => 'Exolix';
@override
bool get isAvailable => true;
bool get isAvailable => false;
@override
bool get isEnabled => true;
+19
View File
@@ -48,6 +48,7 @@ import "package:cake_wallet/src/widgets/standard_checkbox.dart";
import "package:cake_wallet/utils/payment_request.dart";
import "package:cake_wallet/utils/show_pop_up.dart";
import "package:cake_wallet/view_model/contact_list/contact_list_view_model.dart";
import "package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart";
import "package:cake_wallet/view_model/link_view_model.dart";
import "package:cake_wallet/view_model/send/output.dart";
import "package:cake_wallet/view_model/send/send_view_model.dart";
@@ -861,8 +862,23 @@ class _NewSendPageState extends State<NewSendPage> {
}
if (widget.sendViewModel.wallet.isHardwareWallet) {
final trezorVM = widget.sendViewModel.hardwareWalletViewModel;
if (trezorVM is TrezorConnectViewModel &&
widget.sendViewModel.walletType == WalletType.bitcoin &&
trezorVM.isConnected(WalletType.bitcoin) &&
!trezorVM.isSessionFor(widget.sendViewModel.wallet)) {
// The live session belongs to another wallet (passphrase); open one for this wallet
await trezorVM.dropSession();
}
if (!widget.sendViewModel.hardwareWalletViewModel!
.isConnected(widget.sendViewModel.walletType)) {
final hardwareWalletVM = widget.sendViewModel.hardwareWalletViewModel;
if (hardwareWalletVM is TrezorConnectViewModel &&
widget.sendViewModel.walletType == WalletType.bitcoin) {
hardwareWalletVM.prepareReconnect(widget.sendViewModel.wallet);
}
await Navigator.of(context).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
@@ -875,6 +891,9 @@ class _NewSendPageState extends State<NewSendPage> {
isReconnect: false,
),
);
if (hardwareWalletVM is TrezorConnectViewModel) {
hardwareWalletVM.cancelReconnect();
}
// Recheck to handle tap-backs
if (!widget.sendViewModel.hardwareWalletViewModel!
@@ -4,10 +4,17 @@ import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
class HardwareWalletProceedOnDeviceMessage extends StatelessWidget {
const HardwareWalletProceedOnDeviceMessage({super.key, required this.hardwareWalletType});
const HardwareWalletProceedOnDeviceMessage({
super.key,
required this.hardwareWalletType,
this.note,
});
final HardwareWalletType hardwareWalletType;
/// Optional hint about what the device is going to ask
final String? note;
@override
Widget build(BuildContext context) {
return Container(
@@ -35,7 +42,17 @@ class HardwareWalletProceedOnDeviceMessage extends StatelessWidget {
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface,
),
)
),
if (note != null)
Text(
note!,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
]),
),
);
@@ -6,6 +6,8 @@ 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:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -61,7 +63,12 @@ class SendConfirmBottomWidget extends StatelessWidget {
);
case IsAwaitingDeviceResponseState:
return HardwareWalletProceedOnDeviceMessage(
hardwareWalletType: sendViewModel.wallet.hardwareWalletType!);
hardwareWalletType: sendViewModel.wallet.hardwareWalletType!,
note: sendViewModel.walletType == WalletType.bitcoin &&
sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor
? S.of(context).trezor_locktime_notice
: null,
);
case TransactionCommitting:
return LoadingBottomWidget(
text: "${S.of(context).sending}...",
@@ -149,6 +149,49 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
Completer<String?>? _pinCompleter;
Completer<TrezorDeviceSettings>? _settingsCompleter;
/// Settings that successfully opened a session, kept in memory (never persisted) per wallet id
/// so a reconnect during a send doesn't ask for the wallet options / passphrase again.
final Map<String, TrezorDeviceSettings> _sessionSettings = {};
TrezorDeviceSettings? _lastUsedSettings;
String? _reconnectWalletId;
/// The wallet the live session (and with it the passphrase) belongs to, null if unknown
String? _sessionWalletId;
/// Call right before opening the connect page to reconnect the device for [wallet].
/// Only then the settings of the previous session are reused; creating or restoring a wallet
/// always asks for them.
void prepareReconnect(WalletBase wallet) => _reconnectWalletId = wallet.id;
/// Call when the connect page opened after [prepareReconnect] is closed, so an attempt that was
/// backed out of can never leak its settings into a later, unrelated connect (e.g. a restore).
void cancelReconnect() => _reconnectWalletId = null;
/// Call once a wallet was created / restored with the live session
void bindSessionToWallet(WalletBase wallet) {
_sessionWalletId = wallet.id;
final settings = _lastUsedSettings;
if (settings != null) {
_sessionSettings[wallet.id] = settings;
_lastUsedSettings = null;
}
}
/// False if the live session is known to belong to another wallet, e.g. one with a different
/// passphrase. Using it would only fail when signing.
bool isSessionFor(WalletBase wallet) =>
_sessionWalletId == null || _sessionWalletId == wallet.id;
Future<void> dropSession() async {
final client = _client;
_client = null;
_sessionWalletId = null;
_lastUsedSettings = null;
try {
await client?.connection.disconnect();
} catch (_) {}
}
@observable
TrezorParingState paringState = TrezorParingState.initial;
@@ -172,6 +215,10 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
isConnecting = true;
paringState = TrezorParingState.initial;
final reconnectWalletId = _reconnectWalletId;
_reconnectWalletId = null;
final reusedSettings = reconnectWalletId != null ? _sessionSettings[reconnectWalletId] : null;
try {
final trezorInterface =
device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB;
@@ -226,10 +273,15 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
_state!.pairingCredentials.any((c) => c.autoconnect == true);
final isAutoPairingAvailable = _client is sdk.TrezorClientV2 && !hasAutoPairingCredentials;
paringState =
TrezorParingState.awaitingSettings(isAutoPairingAvailable: isAutoPairingAvailable);
_settingsCompleter = Completer<TrezorDeviceSettings>();
final settings = await _settingsCompleter!.future;
final TrezorDeviceSettings settings;
if (reusedSettings != null) {
settings = reusedSettings;
} else {
paringState =
TrezorParingState.awaitingSettings(isAutoPairingAvailable: isAutoPairingAvailable);
_settingsCompleter = Completer<TrezorDeviceSettings>();
settings = await _settingsCompleter!.future;
}
paringState = TrezorParingState.awaitingPassphrase;
if (settings.enableAutoParing && isAutoPairingAvailable) {
@@ -249,18 +301,28 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
await _client!.createSession(passphrase);
}
_lastUsedSettings = settings;
_sessionWalletId = reconnectWalletId;
paringState = TrezorParingState.success;
return true;
} catch (e) {
await _client?.connection.disconnect();
_client = null;
_state = sdk.ThpState();
_sessionWalletId = null;
_lastUsedSettings = null;
// Drop the (possibly half way through a handshake) state, the next attempt reloads the
// saved pairing credentials instead of asking for the pairing code again.
_state = null;
if (reconnectWalletId != null) {
_sessionSettings.remove(reconnectWalletId);
}
// rethrow;
paringState = TrezorParingState.fail(e.toString());
printV(e);
return false;
} finally {
isConnecting = false;
_reconnectWalletId = null;
}
}
@@ -299,6 +361,13 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
@override
Future<void> initWallet(WalletBase wallet) async {
// Only a session that was opened for this wallet may hand its settings to it
final lastUsedSettings = _lastUsedSettings;
if (lastUsedSettings != null && _sessionWalletId == wallet.id) {
_sessionSettings[wallet.id] = lastUsedSettings;
_lastUsedSettings = null;
}
switch (wallet.type) {
case WalletType.monero:
return monero!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
@@ -354,10 +423,30 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
}
}
void _connectionChangeListener(sdk.BleConnectionState event) {
printV("Ledger Device State Changed: $event");
if (event == sdk.BleConnectionState.disconnected && !isConnecting) {
Future<void> _connectionChangeListener(sdk.BleConnectionState event) async {
printV("Trezor Device State Changed: $event");
if (event != sdk.BleConnectionState.disconnected || isConnecting) {
return;
}
final client = _client;
if (client == null) {
return;
}
// The events aren't device specific, so check if it's actually our Trezor that is gone
if (client.connection.connectionType == sdk.ConnectionType.ble) {
try {
final state = await sdk.UniversalBle.getConnectionState(client.connection.device.id);
if (state == sdk.BleConnectionState.connected) {
return;
}
} catch (_) {}
}
if (identical(_client, client)) {
_client = null;
_sessionWalletId = null;
}
}
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
@@ -103,6 +104,12 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
@override
Future<WalletBase> process(WalletCredentials credentials) async {
walletCreationService.changeWalletType(type: type);
return walletCreationService.restoreFromHardwareWallet(credentials);
final wallet = await walletCreationService.restoreFromHardwareWallet(credentials);
final hardwareWalletVM = this.hardwareWalletVM;
if (hardwareWalletVM is TrezorConnectViewModel && type == WalletType.bitcoin) {
hardwareWalletVM.bindSessionToWallet(wallet);
}
return wallet;
}
}