From 5cccde53f679c11d7485be8d019a078016175a36 Mon Sep 17 00:00:00 2001
From: Omar
Date: Sun, 31 May 2026 22:44:31 +0200
Subject: [PATCH 1/6] update discord link [skip ci]
---
lib/view_model/support_view_model.dart | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart
index 5459e523b..052ab6478 100644
--- a/lib/view_model/support_view_model.dart
+++ b/lib/view_model/support_view_model.dart
@@ -39,8 +39,8 @@ abstract class SupportViewModelBase with Store {
LinkListItem(
title: 'Discord',
icon: 'assets/images/discord.png',
- linkTitle: 'discord.gg/pwmWa6aFpX',
- link: 'https://discord.gg/pwmWa6aFpX'),
+ linkTitle: 'discord.gg/2vqYJdXG2H',
+ link: 'https://discord.gg/2vqYJdXG2H'),
LinkListItem(
title: 'Telegram',
icon: 'assets/images/Telegram.png',
From e13d997622e1a662741296fcae3dde80ae21cfb0 Mon Sep 17 00:00:00 2001
From: David Adegoke <64401859+Blazebrain@users.noreply.github.com>
Date: Mon, 1 Jun 2026 16:27:24 +0100
Subject: [PATCH 2/6] Refactor WalletConnect UI components and enhance
transaction approval and signing flow (#3233)
* Refactor WalletConnect UI components and enhance transaction approval and signing flow
* Refactor WalletConnect UI components and enhance transaction approval and signing flow
* Remove unused ui components
* Enhance pairing details page and remove unused ui components
* adjust buttons on details page
* Add warning banner for scam dApps on connection requests
* General cleanup
* Parse and display estimated network fee for WalletConnect approval requests
* Revamp WalletConnect pair listing view
* Remove unused action buttons and walletkit methods
* update svg and sync with dev
---
.../list_item/list_item_regular_row.dart | 6 +-
.../services/bottom_sheet_service.dart | 4 -
.../chain_service/eth/evm_chain_service.dart | 88 ++++-
.../solana/solana_chain_service.dart | 1 -
.../wallet_connect_key_service.dart | 5 -
.../services/walletkit_service.dart | 81 ++---
.../wallet_connect/utils/method_utils.dart | 73 +++-
.../utils/namespace_model_builder.dart | 76 -----
.../utils/wc_permissions_mapper.dart | 80 +++++
.../wc_connections_listing_view.dart | 316 +++++++++---------
.../wc_pairing_detail_page.dart | 255 ++++++--------
.../enter_wallet_connect_uri_widget.dart | 8 +-
.../widgets/wc_connection_item_widget.dart | 94 ------
.../widgets/wc_connection_request_sheet.dart | 94 ++++++
.../widgets/wc_connection_request_widget.dart | 100 ------
.../widgets/wc_connection_widget.dart | 45 ---
.../wallet_connect/widgets/wc_dapp_card.dart | 132 ++++++++
.../wallet_connect/widgets/wc_hero_card.dart | 51 +++
.../widgets/wc_message_card.dart | 86 +++++
.../widgets/wc_pairing_item_widget.dart | 89 -----
.../widgets/wc_permissions_card.dart | 64 ++++
.../widgets/wc_request_widget.dart | 69 ----
.../widgets/wc_scam_banner.dart | 56 ++++
.../wc_session_auth_request_widget.dart | 60 ----
.../widgets/wc_sheet_header.dart | 50 +++
.../widgets/wc_signing_request_sheet.dart | 111 ++++++
.../widgets/wc_verify_context_widget.dart | 131 --------
.../widgets/wc_wallet_card.dart | 95 ++++++
lib/src/widgets/cake_image_widget.dart | 16 +-
.../list_item_regular_row_widget.dart | 11 +-
.../new_list_row/new_list_section.dart | 2 +
res/pictures/global_view.svg | 26 ++
res/pictures/green_check.svg | 21 ++
res/pictures/pencil.svg | 1 +
res/pictures/walletconnect_icon.svg | 29 ++
res/values/strings_ar.arb | 33 +-
res/values/strings_bg.arb | 33 +-
res/values/strings_cs.arb | 33 +-
res/values/strings_de.arb | 35 +-
res/values/strings_en.arb | 33 +-
res/values/strings_es.arb | 33 +-
res/values/strings_fa.arb | 33 +-
res/values/strings_fr.arb | 33 +-
res/values/strings_gn.arb | 25 ++
res/values/strings_ha.arb | 33 +-
res/values/strings_hi.arb | 33 +-
res/values/strings_hr.arb | 33 +-
res/values/strings_hy.arb | 32 +-
res/values/strings_id.arb | 32 +-
res/values/strings_it.arb | 32 +-
res/values/strings_ja.arb | 32 +-
res/values/strings_ko.arb | 32 +-
res/values/strings_my.arb | 32 +-
res/values/strings_nl.arb | 32 +-
res/values/strings_pl.arb | 32 +-
res/values/strings_pt.arb | 32 +-
res/values/strings_pt_BR.arb | 32 +-
res/values/strings_ru.arb | 32 +-
res/values/strings_th.arb | 32 +-
res/values/strings_tl.arb | 32 +-
res/values/strings_tr.arb | 32 +-
res/values/strings_uk.arb | 32 +-
res/values/strings_ur.arb | 32 +-
res/values/strings_vi.arb | 32 +-
res/values/strings_yo.arb | 32 +-
res/values/strings_zh.arb | 32 +-
res/values/strings_zh_tw.arb | 32 +-
67 files changed, 2127 insertions(+), 1329 deletions(-)
delete mode 100644 lib/src/screens/wallet_connect/utils/namespace_model_builder.dart
create mode 100644 lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart
delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart
delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart
delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_dapp_card.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_hero_card.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_message_card.dart
delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_permissions_card.dart
delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_request_widget.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_scam_banner.dart
delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_sheet_header.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart
delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart
create mode 100644 lib/src/screens/wallet_connect/widgets/wc_wallet_card.dart
create mode 100644 res/pictures/global_view.svg
create mode 100644 res/pictures/green_check.svg
create mode 100644 res/pictures/pencil.svg
create mode 100644 res/pictures/walletconnect_icon.svg
diff --git a/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart b/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart
index f6df44a25..e5fdc4595 100644
--- a/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart
+++ b/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart
@@ -16,7 +16,9 @@ class ListItemRegularRow extends ListItem {
this.truncateTrailingText = false,
this.foregroundColor,
this.trailingIconSize,
- this.copyableText
+ this.copyableText,
+ this.leadingIconErrorWidget,
+ this.leadingIconSize,
});
final String? subtitle;
@@ -31,4 +33,6 @@ class ListItemRegularRow extends ListItem {
final bool truncateTrailingText;
final Color? foregroundColor;
final double? trailingIconSize;
+ final Widget? leadingIconErrorWidget;
+ final double? leadingIconSize;
}
diff --git a/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart
index 44637ac1b..b8e21c9b8 100644
--- a/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart
+++ b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart
@@ -29,7 +29,6 @@ class BottomSheetServiceImpl implements BottomSheetService {
int closeAfter = 0,
bool isModalDismissible = false,
}) async {
- // Create the bottom sheet queue item
final completer = Completer();
final queueItem = BottomSheetQueueItemModel(
widget: widget,
@@ -38,15 +37,12 @@ class BottomSheetServiceImpl implements BottomSheetService {
isModalDismissible: isModalDismissible,
);
- // If the current sheet it null, set it to the queue item
if (currentSheet.value == null) {
currentSheet.value = queueItem;
} else {
- // Otherwise, add it to the queue
queue.add(queueItem);
}
- // Return the future
return await completer.future;
}
diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart
index 15d2bdc00..c8618d85f 100644
--- a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart
+++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart
@@ -1,6 +1,10 @@
import 'dart:convert';
+import 'package:cake_wallet/di.dart';
+import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:eth_sig_util/util/utils.dart';
@@ -106,7 +110,6 @@ class EvmChainServiceImpl {
if (isApproved) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@@ -155,7 +158,6 @@ class EvmChainServiceImpl {
if (isApproved) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@@ -297,7 +299,6 @@ class EvmChainServiceImpl {
if (transaction is Transaction) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@@ -309,7 +310,6 @@ class EvmChainServiceImpl {
chainId: int.parse(chainId),
);
- // Sign the transaction
final signedTx = bytesToHex(signature, include0x: true);
response = response.copyWith(result: signedTx);
} on RPCError catch (e) {
@@ -349,7 +349,6 @@ class EvmChainServiceImpl {
);
if (transaction is Transaction) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
final chainId = getChainId().split(':').last;
@@ -440,30 +439,26 @@ class EvmChainServiceImpl {
transaction = await _applyWCBufferedFees(transaction);
- final gweiGasPrice =
- (transaction.gasPrice?.getInWei ?? transaction.maxFeePerGas?.getInWei ?? BigInt.zero) /
- BigInt.from(1000000000);
+ final nativeCurrency = evm?.getChainInfoByChainId(reference.chainId ?? 1)?.currency;
+ final nativeSymbol = nativeCurrency?.title ?? 'ETH';
final amount = (transaction.value?.getInWei ?? BigInt.zero) / BigInt.from(1e18);
- final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} ETH\n'
+ final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} $nativeSymbol\n'
'${S.current.from}: ${transaction.from?.hex}\n'
'${S.current.to}: ${transaction.to?.hex}';
+ final feeRows = _buildFeeExtraModels(transaction, nativeCurrency, nativeSymbol);
+
if (await MethodsUtils.requestApproval(
txMessageText,
title: title,
method: method,
chainId: chainId,
- address: address,
+ address: address ?? transaction.from?.hex ?? '',
transportType: transportType,
verifyContext: verifyContext,
- extraModels: [
- WCConnectionModel(
- title: S.current.gas_price,
- elements: ['${gweiGasPrice.toStringAsFixed(2)} GWEI'],
- ),
- ],
+ extraModels: feeRows,
)) {
return transaction;
}
@@ -471,6 +466,66 @@ class EvmChainServiceImpl {
return JsonRpcError(code: 5002, message: S.current.user_rejected_method);
}
+ List _buildFeeExtraModels(
+ Transaction transaction,
+ CryptoCurrency? nativeCurrency,
+ String nativeSymbol,
+ ) {
+ final gasLimit = transaction.maxGas;
+ if (gasLimit == null || gasLimit <= 0) return const [];
+
+ final gasLimitBig = BigInt.from(gasLimit);
+ final isEip1559 = transaction.isEIP1559;
+ final perGasWei =
+ isEip1559 ? transaction.maxFeePerGas?.getInWei : transaction.gasPrice?.getInWei;
+ if (perGasWei == null || perGasWei <= BigInt.zero) return const [];
+
+ final feeWei = gasLimitBig * perGasWei;
+ final feeNative = feeWei.toDouble() / 1e18;
+
+ final label = isEip1559 ? S.current.wc_max_network_fee : S.current.wc_network_fee;
+
+ return [
+ WCConnectionModel(
+ title: label,
+ elements: [_formatFeeLine(feeNative, nativeSymbol, nativeCurrency)],
+ ),
+ ];
+ }
+
+ String _formatFeeLine(
+ double feeNative,
+ String nativeSymbol,
+ CryptoCurrency? nativeCurrency,
+ ) {
+ final cryptoPart = '${_formatNativeAmount(feeNative)} $nativeSymbol';
+
+ if (nativeCurrency == null) return cryptoPart;
+
+ try {
+ final fiatStore = getIt.get();
+ final price = fiatStore.prices[nativeCurrency];
+ if (price == null || price <= 0) return cryptoPart;
+
+ final fiatSymbol = appStore.settingsStore.fiatCurrency.title;
+ final fiatValue = calculateFiatAmount(
+ price: price,
+ cryptoAmount: feeNative.toString(),
+ );
+ if (fiatValue.isEmpty || fiatValue == '0.00') return cryptoPart;
+
+ return '$cryptoPart (~ $fiatValue $fiatSymbol)';
+ } catch (_) {
+ return cryptoPart;
+ }
+ }
+
+ String _formatNativeAmount(double value) {
+ if (value == 0) return '0';
+ if (value >= 0.0001) return value.toStringAsFixed(6);
+ return value.toStringAsExponential(4);
+ }
+
Future _ensureWCTransactionHasGasLimit(Transaction transaction) async {
final hasGasLimit = transaction.maxGas != null && transaction.maxGas! > 0;
if (hasGasLimit) return transaction;
@@ -637,7 +692,6 @@ $messageDetails''';
if (value == null) continue;
if (types.containsKey(fieldType)) {
- // Handle nested types
final nestedFields = types[fieldType] as List;
if (fieldType == 'Person') {
// Special formatting for Person type
diff --git a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart
index f7a57b51d..b5d12f357 100644
--- a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart
+++ b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart
@@ -80,7 +80,6 @@ class SolanaChainService {
error: JsonRpcError(code: error.code, message: error.message),
);
}
- //
} catch (e) {
debugPrint('solanaSignMessage error $e');
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
diff --git a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
index 498585f0e..38772da10 100644
--- a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
+++ b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
@@ -6,12 +6,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletConnectKeyService {
- /// Returns a list of all the keys.
List getKeys(WalletBase wallet);
-
- /// Returns a list of all the keys for a given chain id.
- /// If the chain is not found, returns an empty list.
- /// - [chain]: The chain to get the keys for.
List getKeysForChain(WalletBase wallet);
}
diff --git a/lib/src/screens/wallet_connect/services/walletkit_service.dart b/lib/src/screens/wallet_connect/services/walletkit_service.dart
index 5e215ea7e..7d5a0ca67 100644
--- a/lib/src/screens/wallet_connect/services/walletkit_service.dart
+++ b/lib/src/screens/wallet_connect/services/walletkit_service.dart
@@ -20,10 +20,9 @@ import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chai
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart';
import 'package:cake_wallet/src/screens/wallet_connect/utils/eth_utils.dart';
import 'package:cake_wallet/src/screens/wallet_connect/utils/method_utils.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'bottom_sheet_service.dart';
@@ -56,8 +55,6 @@ abstract class WalletKitServiceBase with Store {
bool isInitialized;
/// The list of requests from the dapp
- /// Potential types include, but aren't limited to:
- /// [SessionProposalEvent], [SessionAuthRequest]
@observable
ObservableList pairings;
@@ -69,7 +66,6 @@ abstract class WalletKitServiceBase with Store {
@action
void create() {
- // Create the walletkit client
_walletKit = ReownWalletKit(
core: ReownCore(
projectId: secrets.walletConnectProjectId,
@@ -85,7 +81,6 @@ abstract class WalletKitServiceBase with Store {
_walletKit.core.addLogListener(_logListener);
- // Setup our listeners
log('Created instance of walletKit');
_walletKit.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid);
@@ -288,16 +283,12 @@ abstract class WalletKitServiceBase with Store {
if (args != null) {
final proposer = args.params.proposer;
final result = (await _bottomSheetHandler.queueBottomSheet(
- widget: WCRequestWidget(
+ widget: WCConnectionRequestSheet(
+ proposalData: args.params,
+ requester: proposer,
verifyContext: args.verifyContext,
- child: WCConnectionRequestWidget(
- proposalData: args.params,
- verifyContext: args.verifyContext,
- requester: proposer,
- walletKeyService: walletKeyService,
- walletKit: walletKit,
- appStore: appStore,
- ),
+ walletKeyService: walletKeyService,
+ appStore: appStore,
),
)) ??
WCBottomSheetResult.reject;
@@ -400,10 +391,8 @@ abstract class WalletKitServiceBase with Store {
debugPrint('_onPairingCreate $args');
if (args != null && args.topic != null && args.topic!.isNotEmpty) {
- // Save the pairing topic when pairing is created
savePairingTopicToLocalStorage(args.topic!);
- // Refresh pairings to show the new pairing in the list
_refreshPairings();
}
}
@@ -440,16 +429,28 @@ abstract class WalletKitServiceBase with Store {
formattedMessages.add({iss: message});
}
+ final requesterMetadata = args.requester.metadata;
+ final requesterIcon = requesterMetadata.icons.isNotEmpty
+ ? requesterMetadata.icons.first
+ : null;
+ final chainKeysForAuth = walletKeyService.getKeysForChain(appStore.wallet!);
+ final addressForAuth =
+ chainKeysForAuth.isNotEmpty ? chainKeysForAuth.first.publicKey : '';
+ final combinedMessageBody =
+ formattedMessages.map((m) => m.values.first as String).join('\n\n');
+
final WCBottomSheetResult result = (await _bottomSheetHandler.queueBottomSheet(
- widget: WCSessionAuthRequestWidget(
- child: WCConnectionRequestWidget(
- sessionAuthPayload: newAuthPayload,
- verifyContext: args.verifyContext,
- requester: args.requester,
- walletKeyService: walletKeyService,
- walletKit: _walletKit,
- appStore: appStore,
- ),
+ widget: WCSigningRequestSheet(
+ title: S.current.wc_signing_request_title,
+ swipeLabel: S.current.wc_swipe_to_sign,
+ dappName: requesterMetadata.name,
+ dappIconUrl: requesterIcon,
+ dappSubtitle: requesterMetadata.url,
+ message: combinedMessageBody,
+ walletName: appStore.wallet?.name ?? '',
+ address: addressForAuth,
+ verifyContext: args.verifyContext,
+ signAllCount: formattedMessages.length,
),
) as WCBottomSheetResult?) ??
WCBottomSheetResult.reject;
@@ -458,10 +459,8 @@ abstract class WalletKitServiceBase with Store {
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
final privateKey = '0x${chainKeys.first.privateKey}';
final credentials = EthPrivateKey.fromHex(privateKey);
- //
final messageToSign = formattedMessages.length;
final count = (result == WCBottomSheetResult.one) ? 1 : messageToSign;
- //
final List cacaos = [];
for (var i = 0; i < count; i++) {
final iss = formattedMessages[i].keys.first;
@@ -572,19 +571,6 @@ abstract class WalletKitServiceBase with Store {
_refreshPairings();
}
- @action
- Future updateSession({
- required String topic,
- required Map namespaces,
- }) async {
- await walletKit.updateSession(topic: topic, namespaces: namespaces);
- }
-
- @action
- Future extendSession({required String topic}) async {
- await walletKit.extendSession(topic: topic);
- }
-
@action
Future pairWithUri(Uri uri) async {
try {
@@ -633,7 +619,7 @@ abstract class WalletKitServiceBase with Store {
@action
List getSessionsForPairingInfo(PairingInfo pairing) {
- return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
+ return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
}
String getKeyForStoringTopicsForWallet() {
@@ -664,18 +650,14 @@ abstract class WalletKitServiceBase with Store {
}
List getPairingTopicsForWallet(String key) {
- // Get the JSON-encoded string from shared preferences
final jsonString = sharedPreferences.getString(key);
- // If the string is null, return an empty list
if (jsonString == null) {
return [];
}
- // Decode the JSON string to a list of strings
final List jsonList = jsonDecode(jsonString) as List;
- // Cast each item to a string
return jsonList.map((item) => item as String).toList();
}
@@ -691,12 +673,10 @@ abstract class WalletKitServiceBase with Store {
}
Future savePairingTopicToLocalStorage(String pairingTopic) async {
- // Get key specific to the current wallet
final key = getKeyForStoringTopicsForWallet();
if (key.isEmpty) return;
- // Get all pairing topics attached to this key
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic);
@@ -704,13 +684,10 @@ abstract class WalletKitServiceBase with Store {
'Is Pairing Topic Saved: $isPairingTopicAlreadySaved, Key: $key, Topic: $pairingTopic');
if (!isPairingTopicAlreadySaved) {
- // Update the list with the most recent pairing topic
pairingTopicsForWallet.add(pairingTopic);
- // Convert the list of updated pairing topics to a JSON-encoded string
final jsonString = jsonEncode(pairingTopicsForWallet);
- // Save the encoded string to shared preferences
await sharedPreferences.setString(key, jsonString);
}
}
diff --git a/lib/src/screens/wallet_connect/utils/method_utils.dart b/lib/src/screens/wallet_connect/utils/method_utils.dart
index d79adfede..b22c57259 100644
--- a/lib/src/screens/wallet_connect/utils/method_utils.dart
+++ b/lib/src/screens/wallet_connect/utils/method_utils.dart
@@ -3,8 +3,9 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_message_card.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart';
+import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/themes/core/custom_theme_colors.dart';
import 'package:flutter/material.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
@@ -12,6 +13,14 @@ import 'package:reown_walletkit/reown_walletkit.dart';
class MethodsUtils {
static final walletKit = getIt.get().walletKit;
static final bottomSheetService = getIt.get();
+
+ static const _transactionMethods = {
+ 'eth_sendTransaction',
+ 'eth_signTransaction',
+ 'solana_signTransaction',
+ 'solana_signAllTransactions',
+ 'solana_signAndSendTransaction',
+ };
static Future requestApproval(
String text, {
@@ -23,23 +32,53 @@ class MethodsUtils {
List extraModels = const [],
VerifyContext? verifyContext,
}) async {
+ final appStore = getIt.get();
+ final pending = walletKit.pendingRequests.getAll();
+ final session = pending.isNotEmpty
+ ? walletKit.sessions.get(pending.last.topic)
+ : null;
+ final dAppMetadata = session?.peer.metadata;
+
+ final isTransaction = method != null && _transactionMethods.contains(method);
+ final resolvedTitle = title ??
+ (isTransaction
+ ? S.current.wc_approve_request_title
+ : S.current.wc_signing_request_title);
+ final swipeLabel =
+ isTransaction ? S.current.wc_swipe_to_approve : S.current.wc_swipe_to_sign;
+
+ final extraRows = [];
+ if (method != null && method.isNotEmpty) {
+ extraRows.add(WCMessageRow(label: S.current.method, value: method));
+ }
+ if (chainId != null && chainId.isNotEmpty) {
+ extraRows.add(WCMessageRow(label: S.current.chain_id, value: chainId));
+ }
+ if (transportType.isNotEmpty) {
+ extraRows.add(WCMessageRow(
+ label: S.current.transport_type,
+ value: transportType.toUpperCase(),
+ ));
+ }
+ for (final model in extraModels) {
+ if (model.title == null) continue;
+ final value = model.elements?.join(', ') ?? model.text ?? '';
+ extraRows.add(WCMessageRow(label: model.title!, value: value));
+ }
+
final WCBottomSheetResult result = (await bottomSheetService.queueBottomSheet(
- widget: WCRequestWidget(
+ widget: WCSigningRequestSheet(
+ title: resolvedTitle,
+ swipeLabel: swipeLabel,
+ dappName: dAppMetadata?.name ?? '',
+ dappIconUrl:
+ (dAppMetadata?.icons.isNotEmpty ?? false) ? dAppMetadata!.icons.first : null,
+ dappSubtitle: method ?? dAppMetadata?.url ?? '',
+ message: text,
+ walletName: appStore.wallet?.name ?? '',
+ address: address ?? '',
verifyContext: verifyContext,
- child: WCConnectionWidget(
- title: title ?? S.current.approve_request,
- info: [
- WCConnectionModel(
- title: '${S.current.method}: $method\n'
- '${S.current.transport_type}: ${transportType.toUpperCase()}\n'
- '${S.current.chain_id}: $chainId\n'
- '${S.current.address}: $address\n\n'
- '${S.current.message}:',
- elements: [text],
- ),
- ...extraModels,
- ],
- ),
+ extraRows: extraRows,
),
) as WCBottomSheetResult?) ??
WCBottomSheetResult.reject;
diff --git a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart
deleted file mode 100644
index 26cb3bbcf..000000000
--- a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart
+++ /dev/null
@@ -1,76 +0,0 @@
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart';
-import 'package:flutter/material.dart';
-import 'package:reown_walletkit/reown_walletkit.dart';
-
-class ConnectionWidgetBuilder {
- static List buildFromRequiredNamespaces(
- Map generatedNamespaces,
- ) {
- final List views = [];
- for (final key in generatedNamespaces.keys) {
- final namespaces = generatedNamespaces[key]!;
- final chains = NamespaceUtils.getChainsFromAccounts(namespaces.accounts);
-
- final List models = [];
-
- // If the chains property is present, add the chain data to the models
- models.add(WCConnectionModel(title: S.current.chains, elements: chains));
- models.add(WCConnectionModel(title: S.current.methods, elements: namespaces.methods));
-
- if (namespaces.events.isNotEmpty) {
- models.add(WCConnectionModel(title: S.current.events, elements: namespaces.events));
- }
-
- views.add(WCConnectionWidget(title: key, info: models));
- }
-
- return views;
- }
-
- static List buildFromNamespaces(
- String topic,
- Map namespaces,
- BuildContext context,
- ) {
- final List views = [];
- for (final key in namespaces.keys) {
- final ns = namespaces[key]!;
- final List models = [];
-
- // If the chains property is present, add the chain data to the models
- models.add(WCConnectionModel(title: S.current.accounts, elements: ns.accounts));
- models.add(WCConnectionModel(title: S.current.methods, elements: ns.methods));
-
- if (ns.events.isNotEmpty) {
- models.add(WCConnectionModel(title: S.current.events, elements: ns.events));
- }
-
- views.add(WCConnectionWidget(title: key, info: models));
- }
-
- return views;
- }
-
- static Map updateNamespaces(
- Map currentNamespaces,
- String namespace,
- List newChains,
- ) {
- final updatedNamespaces = Map.from(currentNamespaces);
-
- final accounts = currentNamespaces[namespace]!.accounts;
- final address = NamespaceUtils.getAccount(accounts.first);
- final newAccounts = newChains.map((c) => '$c:$address').toList();
-
- final newNamespaces = currentNamespaces[namespace]!.copyWith(
- chains: NamespaceUtils.getChainsFromAccounts(accounts)..addAll(newChains),
- accounts: List.from(accounts)..addAll(newAccounts),
- );
-
- updatedNamespaces[namespace] = newNamespaces;
-
- return updatedNamespaces;
- }
-}
diff --git a/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart
new file mode 100644
index 000000000..6a84f826e
--- /dev/null
+++ b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart
@@ -0,0 +1,80 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:reown_walletkit/reown_walletkit.dart';
+
+class WCPermission {
+ const WCPermission({required this.iconUrl, required this.label});
+
+ final String iconUrl;
+ final String label;
+}
+
+class WCPermissionsMapper {
+ static const _transactionMethods = {
+ 'eth_sendTransaction',
+ 'eth_signTransaction',
+ 'solana_signTransaction',
+ 'solana_signAllTransactions',
+ 'solana_signAndSendTransaction',
+ };
+
+ static const _messageSigningMethods = {
+ 'personal_sign',
+ 'eth_sign',
+ 'eth_signTypedData',
+ 'eth_signTypedData_v1',
+ 'eth_signTypedData_v3',
+ 'eth_signTypedData_v4',
+ 'solana_signMessage',
+ };
+
+ static const _chainAdminMethods = {
+ 'wallet_switchEthereumChain',
+ 'wallet_addEthereumChain',
+ };
+
+ static List fromGeneratedNamespaces(Map generatedNamespaces) {
+ final methods = {};
+ for (final ns in generatedNamespaces.values) {
+ methods.addAll(ns.methods);
+ }
+
+ final permissions = [
+ WCPermission(iconUrl: "assets/new-ui/global_view.svg", label: S.current.wc_permission_view_balance),
+ ];
+
+ final wantsTransactionApproval = methods.any(_transactionMethods.contains);
+ if (wantsTransactionApproval) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/green_check.svg",
+ label: S.current.wc_permission_request_approval,
+ ));
+ }
+
+ final wantsMessageSigning = methods.any(_messageSigningMethods.contains);
+ if (wantsMessageSigning) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/pencil.svg",
+ label: S.current.wc_permission_sign_messages,
+ ));
+ }
+
+ final wantsChainAdmin = methods.any(_chainAdminMethods.contains);
+ if (wantsChainAdmin) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/exchange_providers.svg",
+ label: S.current.wc_permission_switch_chains,
+ ));
+ }
+
+ final known = {..._transactionMethods, ..._messageSigningMethods, ..._chainAdminMethods};
+ final unknown = methods.where((m) => !known.contains(m)).toList()..sort();
+ for (final method in unknown) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/help.svg",
+ label: S.current.wc_permission_other(method),
+ ));
+ }
+
+ return permissions;
+ }
+}
diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart
index 5bc60cfd6..a6caff932 100644
--- a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart
+++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart
@@ -1,122 +1,86 @@
+import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
+import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/src/screens/base_page.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/src/screens/wallet_connect/services/walletkit_service.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_hero_card.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/utils/device_info.dart';
+import 'package:cake_wallet/utils/permission_handler.dart';
+import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
-import 'package:cake_wallet/entities/qr_scanner.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
-import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:cake_wallet/utils/permission_handler.dart';
-import 'package:url_launcher/url_launcher.dart';
-import 'widgets/wc_pairing_item_widget.dart';
import 'wc_pairing_detail_page.dart';
class WalletConnectConnectionsView extends StatelessWidget {
- final WalletKitService walletKitService;
-
- WalletConnectConnectionsView({required this.walletKitService, Uri? launchUri, Key? key})
- : super(key: key) {
+ WalletConnectConnectionsView({
+ required this.walletKitService,
+ Uri? launchUri,
+ Key? key,
+ }) : super(key: key) {
_triggerPairingFromDeeplink(launchUri);
}
+ final WalletKitService walletKitService;
+
void _triggerPairingFromDeeplink(Uri? launchUri) async {
if (launchUri == null) return;
- if(launchUri.scheme == "wc") {
+ if (launchUri.scheme == 'wc') {
await walletKitService.pairWithUri(launchUri);
return;
}
- final actualLinkList = launchUri.query.split("uri=");
-
+ final actualLinkList = launchUri.query.split('uri=');
if (actualLinkList.length <= 1) return;
- final query = actualLinkList[1];
-
- final decoded = Uri.decodeComponent(query).trim();
-
+ final decoded = Uri.decodeComponent(actualLinkList[1]).trim();
final sanitized = decoded.startsWith('@') ? decoded.substring(1) : decoded;
-
final uriData = Uri.tryParse(sanitized);
-
- if (uriData == null || (uriData.scheme.isEmpty)) return;
+ if (uriData == null || uriData.scheme.isEmpty) return;
await walletKitService.pairWithUri(uriData);
}
- @override
- Widget build(BuildContext context) {
- return WCPairingsWidget(walletKitService: walletKitService);
- }
-}
-
-class WCPairingsWidget extends BasePage {
- WCPairingsWidget({required this.walletKitService, Key? key})
- : walletKit = walletKitService.walletKit;
-
- final ReownWalletKit walletKit;
- final WalletKitService walletKitService;
-
- @override
- String get title => S.current.walletConnect;
-
- Future _onScanQrCode(BuildContext context, ReownWalletKit web3Wallet) async {
- final String? uri;
-
- if (DeviceInfo.instance.isMobile) {
- bool isCameraPermissionGranted =
- await PermissionHandler.checkPermission(Permission.camera, context);
- if (!isCameraPermissionGranted) return;
- uri = await presentQRScanner(context);
- } else {
- uri = await _showEnterWalletConnectURIPopUp(context);
- }
+ Future _onScanQrCode(BuildContext context) async {
+ final isCameraPermissionGranted =
+ await PermissionHandler.checkPermission(Permission.camera, context);
+ if (!isCameraPermissionGranted) return;
+ final uri = await presentQRScanner(context);
await _handleWalletConnectURI(uri, context);
}
- Future _showEnterWalletConnectURIPopUp(BuildContext context) async {
- final walletConnectURI = await showPopUp(
+ Future _onPasteLink(BuildContext context) async {
+ final uri = await showPopUp(
context: context,
- builder: (BuildContext context) {
- return EnterWalletConnectURIWrapperWidget();
- },
+ builder: (BuildContext context) => EnterWalletConnectURIWrapperWidget(),
);
- return walletConnectURI;
+ await _handleWalletConnectURI(uri, context);
}
- Future _handleWalletConnectURI(
- String? walletConnectURI,
- BuildContext context,
- ) async {
+ Future _handleWalletConnectURI(String? walletConnectURI, BuildContext context) async {
if (walletConnectURI == null) return _invalidUriToast(context, S.current.nullURIError);
- log('_onFoundUri: $walletConnectURI');
- // Accept either a raw WC URI or a full URL containing `uri=` parameter
String input = walletConnectURI.trim();
-
if (input.contains('uri=')) {
final parts = input.split('uri=');
- if (parts.length > 1) {
- input = Uri.decodeComponent(parts.last);
- }
+ if (parts.length > 1) input = Uri.decodeComponent(parts.last);
}
+ if (input.startsWith('@')) input = input.substring(1);
- // Some scanners may prefix with '@', strip it
- if (input.startsWith('@')) {
- input = input.substring(1);
- }
- final Uri? uriData = Uri.tryParse(input);
- final bool hasValidScheme = uriData != null && uriData.scheme.isNotEmpty;
- if (!hasValidScheme) {
+ final uriData = Uri.tryParse(input);
+ if (uriData == null || uriData.scheme.isEmpty) {
return _invalidUriToast(context, S.current.invalid_input);
}
+
await walletKitService.pairWithUri(uriData);
}
@@ -135,88 +99,132 @@ class WCPairingsWidget extends BasePage {
);
}
- @override
- Widget body(BuildContext context) {
- return Observer(
- builder: (context) {
- return Column(
- children: [
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 24),
- child: Column(
- children: [
- SizedBox(height: 24),
- Text(
- S.current.connectWalletPrompt,
- style: Theme.of(context).textTheme.bodyMedium!.copyWith(
- fontSize: 16.0,
- fontWeight: FontWeight.normal,
- color: Theme.of(context).colorScheme.onSurface,
- ),
- ),
- SizedBox(height: 16),
- PrimaryButton(
- text: S.current.newConnection,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- onPressed: () => _onScanQrCode(context, walletKit),
- ),
- SizedBox(height: 4),
- TextButton(
- onPressed: () async {
- final uri = await _showEnterWalletConnectURIPopUp(context);
- await _handleWalletConnectURI(uri, context);
- },
- child: Text(
- 'Click to paste WalletConnect Link',
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- ),
- ],
- ),
- ),
- SizedBox(height: 16),
- Expanded(
- child: Visibility(
- visible: walletKitService.pairings.isEmpty,
- child: Center(
- child: Text(
- S.current.activeConnectionsPrompt,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.bodyMedium!.copyWith(
- fontSize: 16.0,
- fontWeight: FontWeight.normal,
- color: Theme.of(context).colorScheme.onSurface,
- ),
- ),
- ),
- replacement: ListView.builder(
- itemCount: walletKitService.pairings.length,
- itemBuilder: (BuildContext context, int index) {
- final pairing = walletKitService.pairings[index];
- return WCPairingItemWidget(
- key: ValueKey(pairing.topic),
- pairing: pairing,
- onTap: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => WalletConnectPairingDetailsPage(
- pairing: pairing,
- walletKitService: walletKitService,
- ),
- ),
- );
- },
- );
- },
- ),
- ),
- ),
- SizedBox(height: 48),
- ],
- );
- },
+ void _openPairingDetails(BuildContext context, PairingInfo pairing) {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => WalletConnectPairingDetailsPage(
+ pairing: pairing,
+ walletKitService: walletKitService,
+ ),
+ ),
);
}
-}
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final colors = theme.colorScheme;
+ final isMobile = DeviceInfo.instance.isMobile;
+
+ return Scaffold(
+ backgroundColor: colors.surface,
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const SizedBox(height: 8),
+ Align(
+ alignment: Alignment.centerLeft,
+ child: ModernButton(
+ size: 40,
+ onPressed: () => Navigator.of(context).maybePop(),
+ icon: Icon(Icons.arrow_back_ios_new, size: 16),
+ iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ ),
+ const SizedBox(height: 16),
+ Expanded(
+ child: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const WCHeroCard(),
+ const SizedBox(height: 24),
+ Observer(
+ builder: (_) {
+ if (walletKitService.pairings.isEmpty) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 32),
+ child: Center(
+ child: Text(
+ S.of(context).activeConnectionsPrompt,
+ textAlign: TextAlign.center,
+ style: theme.textTheme.bodyMedium?.copyWith(
+ color: colors.onSurfaceVariant,
+ ),
+ ),
+ ),
+ );
+ }
+
+ final items = [];
+ for (final pairing in walletKitService.pairings) {
+ final metadata = pairing.peerMetadata;
+ if (metadata == null) continue;
+ items.add(
+ ListItemRegularRow(
+ keyValue: pairing.topic,
+ label: metadata.name,
+ subtitle: metadata.url,
+ iconPath: metadata.icons.isNotEmpty
+ ? metadata.icons.first
+ : 'assets/new-ui/walletconnect_icon.svg',
+ onTap: () => _openPairingDetails(context, pairing),
+ leadingIconSize: 36,
+ leadingIconErrorWidget: CakeImageWidget(
+ imageUrl: 'assets/new-ui/walletconnect_icon.svg',
+ width: 36,
+ height: 36,
+ ),
+ ),
+ );
+ }
+
+ if (items.isEmpty) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 32),
+ child: Center(
+ child: Text(
+ S.of(context).activeConnectionsPrompt,
+ textAlign: TextAlign.center,
+ style: theme.textTheme.bodyMedium?.copyWith(
+ color: colors.onSurfaceVariant,
+ ),
+ ),
+ ),
+ );
+ }
+
+ return NewListSections(sections: {'': items});
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+ NewPrimaryButton(
+ onPressed: () => _onPasteLink(context),
+ text: S.of(context).wc_paste_link,
+ color: colors.surfaceContainerHigh,
+ textColor: colors.primary,
+ ),
+ if (isMobile) ...[
+ const SizedBox(height: 12),
+ NewPrimaryButton(
+ onPressed: () => _onScanQrCode(context),
+ text: S.of(context).wc_scan_qr,
+ color: colors.primary,
+ textColor: colors.onPrimary,
+ ),
+ ],
+ const SizedBox(height: 16),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
index 44f0681bd..0a01560e2 100644
--- a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
+++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
@@ -1,15 +1,18 @@
+import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/utils/wc_permissions_mapper.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_dapp_card.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_permissions_card.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_wallet_card.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
+import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:flutter/material.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
-import 'utils/namespace_model_builder.dart';
-
class WalletConnectPairingDetailsPage extends StatefulWidget {
final PairingInfo pairing;
final WalletKitService walletKitService;
@@ -25,180 +28,100 @@ class WalletConnectPairingDetailsPage extends StatefulWidget {
}
class WalletConnectPairingDetailsPageState extends State {
- List sessionWidgets = [];
late String expiryDate;
+ List sessions = const [];
+
@override
void initState() {
super.initState();
initDateTime();
WidgetsBinding.instance.addPostFrameCallback((_) {
- initSessions();
+ if (!mounted) return;
+ setState(() {
+ sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing);
+ });
});
}
void initDateTime() {
- DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000);
- int year = dateTime.year;
- int month = dateTime.month;
- int day = dateTime.day;
-
- expiryDate = '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
- }
-
- void initSessions() {
- List sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing);
-
- for (final SessionData session in sessions) {
- List namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces(
- session.topic,
- session.namespaces,
- context,
- );
- // Loop through and add the namespace widgets, but put 20 pixels between each one
- for (int i = 0; i < namespaceWidget.length; i++) {
- sessionWidgets.add(namespaceWidget[i]);
- if (i != namespaceWidget.length - 1) {
- sessionWidgets.add(const SizedBox(height: 20.0));
- }
- }
-
- sessionWidgets.add(const SizedBox.square(dimension: 10.0));
- sessionWidgets.add(
- PrimaryButton(
- onPressed: () async {
- try {
- await widget.walletKitService.extendSession(
- topic: session.topic,
- );
- } catch (e) {
- debugPrint('${e.toString()}');
- }
- },
- text: S.current.extend_session,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- ),
- );
- sessionWidgets.add(const SizedBox.square(dimension: 10.0));
- sessionWidgets.add(
- PrimaryButton(
- onPressed: () async {
- try {
- await widget.walletKitService.updateSession(
- topic: session.topic,
- namespaces: session.namespaces,
- );
- } catch (e) {
- debugPrint('${e.toString()}');
- }
- },
- text: S.current.update_session,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- ),
- );
- sessionWidgets.add(const SizedBox.square(dimension: 10.0));
- sessionWidgets.add(
- PrimaryButton(
- onPressed: () async {
- try {
- await widget.walletKitService.disconnectSession(
- topic: session.topic,
- );
- } catch (e) {
- debugPrint('${e.toString()}');
- }
- },
- text: S.current.disconnect_session,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- ),
- );
- }
+ final dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000);
+ expiryDate = '${dateTime.year}-'
+ '${dateTime.month.toString().padLeft(2, '0')}-'
+ '${dateTime.day.toString().padLeft(2, '0')}';
}
@override
Widget build(BuildContext context) {
return WCCDetailsWidget(
- widget.pairing,
- expiryDate,
- sessionWidgets,
- widget.walletKitService,
+ pairing: widget.pairing,
+ expiryDate: expiryDate,
+ sessions: sessions,
+ walletKitService: widget.walletKitService,
);
}
}
class WCCDetailsWidget extends BasePage {
- WCCDetailsWidget(
- this.pairing,
- this.expiryDate,
- this.sessionWidgets,
- this.walletKitService,
- );
+ WCCDetailsWidget({
+ required this.pairing,
+ required this.expiryDate,
+ required this.sessions,
+ required this.walletKitService,
+ });
final PairingInfo pairing;
final String expiryDate;
- final List sessionWidgets;
+ final List sessions;
final WalletKitService walletKitService;
@override
Widget body(BuildContext context) {
- return Scaffold(
- body: SingleChildScrollView(
- child: Container(
- padding: const EdgeInsets.all(8),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Flexible(
- child: CircleAvatar(
- backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty
- ? NetworkImage(pairing.peerMetadata!.icons[0])
- : AssetImage(
- CakeTor.instance!.enabled
- ? 'assets/images/tor_logo.svg'
- : 'assets/images/app_logo.png')) as ImageProvider
', caseSensitive: false), '\n')
+ .replaceAll(RegExp(r'<[^>]*>'), '')
+ .trim();
+ }
+
Future resolve(BuildContext context, String text, CryptoCurrency currency) async {
final ticker = currency.title;
try {
@@ -348,7 +356,8 @@ class AddressResolver {
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
if (mastodonUser != null) {
- String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency, requireSurroundingWhitespaces: true);
+ final mastodonNote = _stripHtmlTags(mastodonUser.note);
+ String? addressFromBio = extractAddressByType(raw: mastodonNote, type: currency, requireSurroundingWhitespaces: true);
if (addressFromBio != null && addressFromBio.isNotEmpty) {
return ParsedAddress.fetchMastodonAddress(
@@ -361,7 +370,7 @@ class AddressResolver {
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
if (pinnedPosts.isNotEmpty) {
- final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
+ final userPinnedPostsText = pinnedPosts.map((item) => _stripHtmlTags(item.content)).join('\n');
String? addressFromPinnedPost =
extractAddressByType(raw: userPinnedPostsText, type: currency,
requireSurroundingWhitespaces: true);
From 6c1ee1651349cf02ee05c1edc26137118f59bbf9 Mon Sep 17 00:00:00 2001
From: malik1004x
Date: Mon, 1 Jun 2026 21:32:12 +0200
Subject: [PATCH 5/6] new-node-list-ui-on-dev (#3259)
* node list new ui
# Conflicts:
# cw_core/lib/node_list.dart
* increase touch target sizes
* fix scroll behavior
* fix concurrent modification
* Update lib/src/screens/settings/manage_nodes_page.dart [skip ci]
* Update lib/src/screens/nodes/node_create_or_edit_page.dart [skip ci]
* run speed test in parallel not sequential
---------
Co-authored-by: Omar Hatem
---
cw_core/lib/node.dart | 26 ++-
cw_core/lib/node_list.dart | 8 +-
lib/di.dart | 16 +-
.../action_row/coin_action_row.dart | 9 +-
lib/new-ui/widgets/modal_header.dart | 9 +-
.../nodes/node_create_or_edit_page.dart | 134 ++++++------
lib/src/screens/nodes/node_share_page.dart | 82 ++++++++
.../screens/nodes/widgets/node_list_row.dart | 170 +++++++--------
.../screens/settings/manage_nodes_page.dart | 197 +++++++++---------
.../node_create_or_edit_view_model.dart | 19 +-
.../node_list/node_list_view_model.dart | 94 ++++++++-
.../node_list/pow_node_list_view_model.dart | 83 --------
pubspec_base.yaml | 1 +
res/pictures/default_node.svg | 3 +
.../node_speed_badges/disconnected.svg | 4 +
res/pictures/node_speed_badges/fast.svg | 3 +
res/pictures/node_speed_badges/medium.svg | 4 +
res/pictures/node_speed_badges/slow.svg | 7 +
res/pictures/official_node.svg | 3 +
res/values/strings_en.arb | 4 +
20 files changed, 472 insertions(+), 404 deletions(-)
create mode 100644 lib/src/screens/nodes/node_share_page.dart
create mode 100644 res/pictures/default_node.svg
create mode 100644 res/pictures/node_speed_badges/disconnected.svg
create mode 100644 res/pictures/node_speed_badges/fast.svg
create mode 100644 res/pictures/node_speed_badges/medium.svg
create mode 100644 res/pictures/node_speed_badges/slow.svg
create mode 100644 res/pictures/official_node.svg
diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart
index 7550fc626..b7f2703b8 100644
--- a/cw_core/lib/node.dart
+++ b/cw_core/lib/node.dart
@@ -132,6 +132,26 @@ class Node {
};
}
+ Node.fromUri(Uri uri, WalletType type)
+ : id = 0,
+ uriRaw =
+ "${uri.host}${uri.hasPort ? ':${uri.port}' : (uri.queryParameters['port']?.isNotEmpty == true ? ':${uri.queryParameters['port']}' : '')}",
+ path = uri.path,
+ login = uri.userInfo.contains(':')
+ ? uri.userInfo.split(':')[0]
+ : uri.queryParameters['username'],
+ password = uri.userInfo.contains(':')
+ ? uri.userInfo.split(':')[1]
+ : uri.queryParameters['password'],
+ useSSL = uri.queryParameters['protocol'] == 'https',
+ trusted = uri.queryParameters['trusted'] == 'true',
+ isPow = false,
+ isEnabledForAutoSwitching = false,
+ isOfficial = false,
+ isBuiltin = false,
+ isDefault = false,
+ typeRaw = serializeToInt(type);
+
Future delete() async {
return await db!.delete(tableName, where: '${selfIdColumn} = ?', whereArgs: [id]);
}
@@ -378,14 +398,14 @@ class Node {
final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
try {
- final client = ProxyWrapper().getHttpIOClient();
final jsonBody = json.encode(body);
- final response = await client.post(
- rpcUri,
+ final response = await ProxyWrapper().post(
+ clearnetUri: rpcUri,
headers: {'Content-Type': 'application/json'},
body: jsonBody,
+ allowMitmMoneroBypassSSLCheck: true
);
// Check if we received a 401 Unauthorized response
if (response.statusCode == 401) {
diff --git a/cw_core/lib/node_list.dart b/cw_core/lib/node_list.dart
index e46f0eee4..2c5007bea 100644
--- a/cw_core/lib/node_list.dart
+++ b/cw_core/lib/node_list.dart
@@ -1,5 +1,4 @@
import 'package:flutter/services.dart';
-import 'package:hive/hive.dart';
import "package:yaml/yaml.dart";
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
@@ -78,6 +77,7 @@ Future> loadDefaultNodes(WalletType type) async {
map["isBuiltin"] = 1;
map["isOfficial"] = ((map["isOfficial"] as bool? ?? false) ? 1 : 0);
map["isDefault"] = ((map["isDefault"] as bool? ?? false) ? 1 : 0);
+
final node = Node.fromMap(Map.from(map));
node.type = type;
nodes.add(node);
@@ -94,7 +94,11 @@ Future> loadDefaultNanoPowNodes() async {
for (final raw in loadedPowNodes) {
if (raw is Map) {
- final node = Node.fromMap(Map.from(raw));
+ final map = Map.from(raw);
+ map["isBuiltin"] = 1;
+ map["isOfficial"] = ((map["isOfficial"] as bool? ?? false) ? 1 : 0);
+ map["isDefault"] = ((map["isDefault"] as bool? ?? false) ? 1 : 0);
+ final node = Node.fromMap(Map.from(map));
node.isPow = true;
node.type = WalletType.nano;
nodes.add(node);
diff --git a/lib/di.dart b/lib/di.dart
index aa42887d7..991e2f7ee 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -1118,9 +1118,9 @@ Future setup({
getIt.registerFactory(() => AddressListPage(getIt.get()));
- getIt.registerLazySingleton(() {
+ getIt.registerFactoryParam((isPow, _) {
final appStore = getIt.get();
- return NodeListViewModel(appStore);
+ return NodeListViewModel(appStore, isPow);
});
getIt.registerFactoryParam(
@@ -1131,11 +1131,6 @@ Future setup({
),
);
- getIt.registerLazySingleton(() {
- final appStore = getIt.get();
- return PowNodeListViewModel(appStore);
- });
-
getIt.registerFactory(() => ConnectionSyncViewModel(getIt.get(), getIt.get().wallet!));
getIt.registerFactory(() => ConnectionSyncPage(getIt.get()));
@@ -1178,8 +1173,6 @@ Future setup({
final Node? editingNode = args['editingNode'] as Node?;
return NodeCreateOrEditViewModel(
isPow,
- getIt.get(),
- getIt.get(),
type,
getIt.get(),
editingNode: editingNode
@@ -1700,10 +1693,7 @@ Future setup({
);
getIt.registerFactoryParam((bool isPow, _) {
- if (isPow) {
- return ManageNodesPage(isPow, powNodeListViewModel: getIt.get(), dashboardViewModel: getIt.get());
- }
- return ManageNodesPage(isPow, nodeListViewModel: getIt.get(), dashboardViewModel: getIt.get());
+ return ManageNodesPage(isPow, nodeListViewModel: getIt.get(param1: isPow), dashboardViewModel: getIt.get());
});
getIt.registerFactory(
diff --git a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart
index 10a411c2f..b700f4aab 100644
--- a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart
+++ b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart
@@ -17,10 +17,10 @@ import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/lnurl.dart';
+import 'package:cw_core/node.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_svg/svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import '../../../pages/receive_page.dart';
@@ -198,7 +198,12 @@ class CoinActionRow extends StatelessWidget {
Navigator.of(context)
.pushNamed(Routes.walletConnectConnectionsListing, arguments: Uri.parse(code));
return;
- } else {
+ } else if(["http", "https", "tcp"].contains(Uri.tryParse(code)?.scheme)) {
+ Navigator.of(context).pushNamed(Routes.newNode,
+ arguments: {"editingNode": Node.fromUri(Uri.parse(code), walletType)});
+ return;
+
+ }else {
final uri = Uri.tryParse(code);
if (uri == null) return;
req = PaymentRequest.fromUri(uri);
diff --git a/lib/new-ui/widgets/modal_header.dart b/lib/new-ui/widgets/modal_header.dart
index 46c292cea..6bef2bba5 100644
--- a/lib/new-ui/widgets/modal_header.dart
+++ b/lib/new-ui/widgets/modal_header.dart
@@ -4,11 +4,12 @@ import 'package:flutter_svg/svg.dart';
class ModalHeader extends StatelessWidget {
const ModalHeader(
- {super.key, required this.iconPath, required this.message, required this.title});
+ {super.key, required this.iconPath, required this.message, required this.title, this.bottomWidget});
final String iconPath;
final String title;
final String message;
+ final Widget? bottomWidget;
@override
Widget build(BuildContext context) {
@@ -41,7 +42,11 @@ class ModalHeader extends StatelessWidget {
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
- ))
+ )),
+ if(bottomWidget != null) ...[
+ Padding(padding: EdgeInsets.symmetric(horizontal: 18), child: Container(height:1, color: Theme.of(context).colorScheme.surfaceContainerHigh),),
+ bottomWidget!
+ ]
],
),
),
diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart
index 92bccb27a..0e45c51f4 100644
--- a/lib/src/screens/nodes/node_create_or_edit_page.dart
+++ b/lib/src/screens/nodes/node_create_or_edit_page.dart
@@ -1,20 +1,20 @@
import 'package:cake_wallet/core/execution_state.dart';
+import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:cake_wallet/src/screens/nodes/node_share_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.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/new_list_row/new_list_section.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
+import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/node.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';
-import 'package:flutter_svg/svg.dart';
import 'package:mobx/mobx.dart';
class NodeCreateOrEditPage extends StatefulWidget {
@@ -76,6 +76,14 @@ class _NodeCreateOrEditPageState extends State {
},
);
+
+ reaction((_)=>widget.nodeCreateOrEditViewModel.state, (state) async {
+ if(state is ExecutedSuccessfullyState) {
+ await Future.delayed(Duration(milliseconds: 100));
+ if (mounted) Navigator.of(context).pop(widget.nodeCreateOrEditViewModel.editingNode);
+ }
+
+ });
}
@override
@@ -90,86 +98,62 @@ class _NodeCreateOrEditPageState extends State {
title: widget.editingNode != null ? S.current.edit_node : S.current.node_new,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop,
- trailingIcon: CakeImageWidget(imageUrl:
- "assets/new-ui/scan.svg",width:24,height:24,
- colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
- ),
- onTrailingPressed: () => widget.nodeCreateOrEditViewModel.scanQRCodeForNewNode(context),
+ trailingIcon: Icon(Icons.check),
+ onTrailingPressed: () async {
+ if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) {
+ return;
+ }
+
+ await widget.nodeCreateOrEditViewModel
+ .save(saveAsCurrent: widget.isSelected ?? false);
+ },
),
Expanded(
child: KeyboardHideOverlay(
child: Container(
padding: const EdgeInsets.only(left: 18, right: 18),
- child: ScrollableWithBottomSection(
- contentPadding: const EdgeInsets.only(bottom: 24.0, top: 8),
- content: NodeForm(
+ child: Column(
+ spacing: 18,
+ children: [NodeForm(
key: _nodeFormKey,
nodeViewModel: widget.nodeCreateOrEditViewModel,
),
- bottomSectionPadding: const EdgeInsets.only(bottom: 24),
- bottomSection: Observer(
- builder: (_) => Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Flexible(
- child: Container(
- padding: const EdgeInsets.only(right: 8.0),
- child: LoadingPrimaryButton(
- onPressed: () async {
- final confirmed = await showPopUp(
- context: context,
- builder: (context) => AlertWithTwoActions(
- alertTitle: S.of(context).remove_node,
- alertContent: S.of(context).remove_node_message,
- rightButtonText: S.of(context).remove,
- leftButtonText: S.of(context).cancel,
- actionRightButton: () => Navigator.pop(context, true),
- actionLeftButton: () => Navigator.pop(context, false),
- ),
- ) ??
- false;
+ NewListSections(sections: {"":[
+ if(widget.editingNode != null)
- if (confirmed) {
- await widget.nodeCreateOrEditViewModel.delete(editingNode: widget.editingNode!);
- Navigator.of(context).pop();
- }
- },
- text: S.of(context).delete,
- isDisabled: widget.editingNode == null ||
- !widget.nodeCreateOrEditViewModel.isReady ||
- widget.editingNode!.isBuiltin ||
- (widget.isSelected ?? false),
- color: Theme.of(context).colorScheme.errorContainer,
- textColor: Theme.of(context).colorScheme.onErrorContainer,
- ),
- ),
- ),
- Flexible(
- child: Container(
- padding: const EdgeInsets.only(left: 8.0),
- child: PrimaryButton(
- onPressed: () async {
- if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) {
- return;
- }
+ ListItemRegularRow(keyValue: "share", label: S.of(context).share_this_node, foregroundColor: Theme.of(context).colorScheme.primary, showArrow: false, onTap: (){
- await widget.nodeCreateOrEditViewModel.save(
- saveAsCurrent: widget.isSelected ?? false);
- if (context.mounted) {
- Navigator.of(context).pop();
- }
- },
- text: S.of(context).save,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- isDisabled: (!widget.nodeCreateOrEditViewModel.isReady) ||
- (widget.nodeCreateOrEditViewModel.connectionState is IsExecutingState),
- ),
- ),
- ),
- ],
- ),
- ),
+ Navigator.of(context).push(CupertinoPageRoute(builder: (context)=>NodeSharePage(uri: widget.editingNode!.uri, currency: walletTypeToCryptoCurrency(widget.type!))));
+
+
+ }),
+ if(!(widget.editingNode == null ||
+ !widget.nodeCreateOrEditViewModel.isReady ||
+ widget.editingNode!.isBuiltin ||
+ (widget.isSelected ?? false)))
+ ListItemRegularRow(keyValue: "delete", label: S.of(context).delete_this_node, foregroundColor: Theme.of(context).colorScheme.errorContainer, showArrow: false, onTap: ()async{
+ final confirmed = await showPopUp(
+ context: context,
+ builder: (context) => AlertWithTwoActions(
+ alertTitle: S.of(context).remove_node,
+ alertContent: S.of(context).remove_node_message,
+ rightButtonText: S.of(context).remove,
+ leftButtonText: S.of(context).cancel,
+ actionRightButton: () => Navigator.pop(context, true),
+ actionLeftButton: () => Navigator.pop(context, false),
+ ),
+ ) ??
+ false;
+
+ if (confirmed) {
+ await widget.nodeCreateOrEditViewModel.delete(editingNode: widget.editingNode!);
+ Navigator.of(context).pop();
+ }
+
+ })
+ ]})
+
+ ]
),
),
),
diff --git a/lib/src/screens/nodes/node_share_page.dart b/lib/src/screens/nodes/node_share_page.dart
new file mode 100644
index 000000000..1319d8426
--- /dev/null
+++ b/lib/src/screens/nodes/node_share_page.dart
@@ -0,0 +1,82 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cw_core/crypto_currency.dart';
+import 'package:flutter/material.dart';
+
+class NodeSharePage extends StatelessWidget {
+ const NodeSharePage({super.key, required this.uri, required this.currency});
+
+ final Uri uri;
+ final CryptoCurrency currency;
+
+ @override
+ Widget build(BuildContext context) {
+ final qrSize = MediaQuery.of(context).size.width * 0.786;
+ final padding = 16.0;
+ return Container(
+ color: Theme.of(context).colorScheme.surface,
+ child: SafeArea(
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ ModalTopBar(
+ title: S.of(context).share,
+ leadingIcon: Icon(Icons.arrow_back_ios_new),
+ onLeadingPressed: Navigator.of(context).pop,
+ ),
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 40,
+ children: [
+ Stack(alignment: Alignment.center, children: [
+ SizedBox(
+ width: qrSize,
+ height: qrSize,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(18),
+ child: QrImage(
+ size: qrSize - padding * 2,
+ embeddedImagePath: "",
+ data: uri.toString())),
+ ),
+ SizedBox(
+ width: 60,
+ height: 60,
+ child: Stack(
+ children: [
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/settings_row_icons/nodes.svg",
+ width: 54,
+ height: 54,
+ ),
+ Align(
+ alignment: Alignment.bottomRight,
+ child: CakeImageWidget(
+ imageUrl: currency.iconPath,
+ width: 24,
+ height: 24,
+ ))
+ ],
+ ),
+ ),
+ ]),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 36.0),
+ child: Text(S.of(context).node_share_desc,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).colorScheme.onSurfaceVariant)),
+ )
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/src/screens/nodes/widgets/node_list_row.dart b/lib/src/screens/nodes/widgets/node_list_row.dart
index 2e43ad3ff..5be027481 100644
--- a/lib/src/screens/nodes/widgets/node_list_row.dart
+++ b/lib/src/screens/nodes/widgets/node_list_row.dart
@@ -1,115 +1,55 @@
import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/nodes/widgets/node_indicator.dart';
-import 'package:cake_wallet/src/widgets/standard_list.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
-class NodeListRow extends StandardListRow {
+class NodeListRow extends StatelessWidget {
NodeListRow(
- {required String title,
- String? subtitle,
- required this.node,
- required void Function(BuildContext context) onTap,
- required bool isSelected,
- required this.isPow})
- : super(title: title, onTap: onTap, isSelected: isSelected, subtitle: subtitle);
+ {required this.node,
+ required this.onTap,
+ required this.onEditComplete,
+ required this.isSelected,
+ required this.isPow, this.speed});
final Node node;
final bool isPow;
+ final VoidCallback onTap;
+ final Future Function(Object?) onEditComplete;
+ final bool isSelected;
+ final NodeSpeed? speed;
@override
Widget build(BuildContext context) {
final leading = buildLeading(context);
final trailing = buildTrailing(context);
- return Container(
- padding: EdgeInsets.only(left: 18, right: 18, top: 2, bottom: 2),
- margin: EdgeInsets.only(top: 2, bottom: 2),
- child: FilledButton(
- onPressed: () => onTap?.call(context),
- style: FilledButton.styleFrom(
- backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.all(Radius.circular(20)),
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- leading,
- buildCenter(context, hasLeftOffset: true),
- trailing,
- ],
- ),
- ),
- ),
- );
- }
- @override
- Widget buildLeading(BuildContext context) {
- return FutureBuilder(
- future: node.requestNode(),
- builder: (context, snapshot) {
- switch (snapshot.connectionState) {
- case ConnectionState.done:
- return NodeIndicator(isLive: snapshot.data ?? false);
- default:
- return NodeIndicator(isLive: false);
- }
- });
- }
-
- @override
- Widget buildTrailing(BuildContext context) {
+ final hasLabel = node.label != null && node.label!.isNotEmpty;
+ final uriText = "${node.uri.host}:${node.uri.port}";
return GestureDetector(
- onTap: () => Navigator.of(context).pushNamed(
- isPow ? Routes.newPowNode : Routes.newNode,
- arguments: {'editingNode': node, 'isSelected': isSelected},
- ),
- child: Container(
- padding: EdgeInsets.all(10),
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: Theme.of(context).colorScheme.surface,
- ),
- child: Icon(
- Icons.edit,
- size: 14,
- color: Theme.of(context).colorScheme.onSurfaceVariant,
- ),
- ),
- );
- }
-}
-
-class NodeHeaderListRow extends StandardListRow {
- NodeHeaderListRow({required String title, required void Function(BuildContext context) onTap})
- : super(title: title, onTap: onTap, isSelected: false);
-
- @override
- Widget build(BuildContext context) {
- final leading = buildLeading(context);
- final trailing = buildTrailing(context);
- return Container(
- height: 56,
- padding: EdgeInsets.only(left: 12, right: 12, top: 2, bottom: 2),
- child: TextButton(
- onPressed: () => onTap?.call(context),
- style: ButtonStyle(
- backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.surface),
- shape: WidgetStateProperty.all(
- RoundedRectangleBorder(
- borderRadius: BorderRadius.all(Radius.circular(10)),
- ),
- ),
- ),
+ behavior: HitTestBehavior.opaque,
+ onTap: onTap,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 12),
child: Row(
+ spacing: 12,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- if (leading != null) leading,
- buildCenter(context, hasLeftOffset: leading != null),
+ leading,
+ Expanded(
+ child: Column(crossAxisAlignment:CrossAxisAlignment.start,spacing:4,children: [
+ Row(
+ spacing: 4,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Text(hasLabel ? node.label! : uriText, style: TextStyle(fontSize: 12),),
+ if(badgePath != null)
+ CakeImageWidget(imageUrl: badgePath, colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary,BlendMode.srcIn),)
+ ],
+ ),
+ if(hasLabel) Text(uriText, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),),
+ ],),
+ ),
trailing,
],
),
@@ -117,12 +57,44 @@ class NodeHeaderListRow extends StandardListRow {
);
}
- @override
+ Widget buildLeading(BuildContext context) => isSelected
+ ? Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: Icon(
+ Icons.check,
+ size: 16,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ )
+ : SizedBox(width: 24, height: 24);
+
Widget buildTrailing(BuildContext context) {
- return SizedBox(
- width: 20,
- child: Icon(Icons.add,
- color: Theme.of(context).colorScheme.onSurfaceVariant, size: 24.0),
+ return Row(
+ children: [
+ if (speed != null) CakeImageWidget(imageUrl: speed!.iconPath, width: 24, height: 24),
+ GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: () async {
+ final res = await Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode,
+ arguments: {'editingNode': node, 'isSelected': isSelected});
+ await onEditComplete(res);
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: CakeImageWidget(
+ imageUrl: "assets/new-ui/3dots_vertical.svg",
+ colorFilter:
+ ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
+ ),
+ ),
+ )
+ ],
);
}
+
+ String? get badgePath {
+ if(node.isOfficial) return "assets/new-ui/official_node.svg";
+ if(node.isBuiltin) return "assets/new-ui/default_node.svg";
+ return null;
+ }
}
diff --git a/lib/src/screens/settings/manage_nodes_page.dart b/lib/src/screens/settings/manage_nodes_page.dart
index 02c2e4e6f..9f69c6587 100644
--- a/lib/src/screens/settings/manage_nodes_page.dart
+++ b/lib/src/screens/settings/manage_nodes_page.dart
@@ -1,42 +1,42 @@
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
-import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
-import 'package:cake_wallet/src/widgets/standard_list.dart';
-import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
-import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
+import 'package:cw_core/node.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
-class ManageNodesPage extends BasePage {
- ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel, this.dashboardViewModel})
- : assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null)),
- assert(powNodeListViewModel == null || nodeListViewModel == null);
+class ManageNodesPage extends StatefulWidget {
+ ManageNodesPage(this.isPow, {required this.nodeListViewModel, this.dashboardViewModel});
final DashboardViewModel? dashboardViewModel;
- final NodeListViewModel? nodeListViewModel;
- final PowNodeListViewModel? powNodeListViewModel;
+ final NodeListViewModel nodeListViewModel;
final bool isPow;
@override
- bool get hideAppBar => true;
+ State createState() => _ManageNodesPageState();
+}
+
+class _ManageNodesPageState extends State {
+ @override
+ void initState() {
+ super.initState();
+ Future.delayed(Duration(milliseconds: 300))
+ .then((_) => widget.nodeListViewModel.speedTestNodes());
+ }
@override
- String get title => S.current.manage_nodes;
-
- @override
- Widget body(BuildContext context) {
+ Widget build(BuildContext context) {
return ModalPageWrapper(
- horizontalPadding: 0,
topBar: ModalTopBar(
title: S.of(context).manage_nodes,
leadingIcon: Icon(Icons.arrow_back_ios_new),
@@ -44,87 +44,96 @@ class ManageNodesPage extends BasePage {
trailingWidget: Row(
spacing: 8,
children: [
- if (dashboardViewModel?.hasRescan ?? true)
- ModernButton(
+ Observer(
+ builder: (_) => ModernButton(
size: 36,
- icon: Icon(Icons.history),
- onPressed: () => Navigator.of(context).pushNamed(Routes.rescan)),
+ icon: widget.nodeListViewModel.isTestingNodeSpeed
+ ? CupertinoActivityIndicator()
+ : Icon(Icons.refresh),
+ onPressed: () => widget.nodeListViewModel.speedTestNodes()),
+ ),
ModernButton(
size: 36,
icon: Icon(Icons.add),
- onPressed: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode))
+ onPressed: ()async {
+ final res = await Navigator.of(context)
+ .pushNamed(widget.isPow ? Routes.newPowNode : Routes.newNode);
+ if(res != null && res is Node) {
+ widget.nodeListViewModel.nodes.add(res);
+ }
+ })
],
),
),
+ header: ModalHeader(
+ iconPath: "assets/new-ui/settings_row_icons/nodes.svg",
+ message: S.of(context).nodes_desc,
+ title: S.of(context).nodes,
+ bottomWidget: Observer(
+ builder: (_) => NodeListRow(
+ node: widget.nodeListViewModel.currentNode,
+ speed: widget.nodeListViewModel.nodeSpeedFor(widget.nodeListViewModel.currentNode),
+ onEditComplete: (res)async{
+ if(res != null && res is Node) {
+ widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id);
+ widget.nodeListViewModel.nodes.add(res);
+ }
+
+ },
+ onTap: () {},
+ isSelected: true,
+ isPow: widget.isPow)),
+ ),
content: Column(
children: [
- if (FeatureFlag.isAutomaticNodeSwitchingEnabled)
- Observer(
- builder: (_) => SettingsSwitcherCell(
- key: ValueKey('manage_nodes_page_enable_auto_node_switching_button_key'),
- title: S.current.enable_auto_node_switching,
- value: isPow
- ? powNodeListViewModel!.enableAutomaticNodeSwitching
- : nodeListViewModel!.enableAutomaticNodeSwitching,
- onValueChange: (BuildContext context, bool value) {
- if (isPow) {
- powNodeListViewModel!.setEnableAutomaticNodeSwitching(value);
- } else {
- nodeListViewModel!.setEnableAutomaticNodeSwitching(value);
- }
- },
- ),
- ),
- if (FeatureFlag.isAutomaticNodeSwitchingEnabled) SizedBox(height: 8),
- Observer(
- builder: (BuildContext context) {
- int itemsCount =
- nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
- return SectionStandardList(
- scrollController: ModalScrollController.of(context),
- sectionCount: 1,
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- dividerPadding: EdgeInsets.symmetric(horizontal: 24),
- itemCounter: (int sectionIndex) => itemsCount,
- itemBuilder: (_, index) {
- return Observer(
- builder: (context) {
- final node =
- nodeListViewModel?.nodes[index] ?? powNodeListViewModel!.nodes[index];
- late bool isSelected;
- if (isPow) {
- isSelected = node.id == powNodeListViewModel!.currentNode.id;
- } else {
- isSelected = node.id == nodeListViewModel!.currentNode.id;
- }
- final nodeListRow = NodeListRow(
- title: node.uriRaw,
- subtitle: node.label ?? '',
- node: node,
- isSelected: isSelected,
- isPow: isPow,
- onTap: (_) async {
- if (isSelected) {
- return;
- }
+ Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(18)),
+ child: Observer(
+ builder: (BuildContext context) {
+ int itemsCount = widget.nodeListViewModel.nonCurrentNodes.length;
+ return ListView.separated(
+ controller: ModalScrollController.of(context),
+ shrinkWrap: true,
+ physics: BouncingScrollPhysics(),
+ itemCount: itemsCount,
+ separatorBuilder: (context, index) => Padding(
+ padding: EdgeInsets.symmetric(horizontal: 12),
+ child: Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
+ ),
+ ),
+ itemBuilder: (_, index) {
+ return Observer(
+ builder: (context) {
+ final node = widget.nodeListViewModel.nonCurrentNodes[index];
+ final nodeListRow = NodeListRow(
+ node: node,
+ isSelected: false,
+ isPow: widget.isPow,
+ speed: widget.nodeListViewModel.nodeSpeedFor(node),
+ onEditComplete: (res) async {
+ if(res != null && res is Node) {
+ widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id);
+ widget.nodeListViewModel.nodes.add(res);
+ }
+ },
+ onTap: () async {
await showPopUp(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
- alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ??
- powNodeListViewModel!.getAlertContent(node.uriRaw),
+ alertContent:
+ widget.nodeListViewModel.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
- if (isPow) {
- await powNodeListViewModel!.setAsCurrent(node);
- } else {
- await nodeListViewModel!.setAsCurrent(node);
- }
+ await widget.nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
@@ -132,32 +141,16 @@ class ManageNodesPage extends BasePage {
);
},
);
- return nodeListRow;
- },
- );
- },
- );
- },
+ return nodeListRow;
+ },
+ );
+ },
+ );
+ },
+ ),
),
],
),
);
}
- Future _presentReconnectAlert(BuildContext context) async {
- await showPopUp(
- context: context,
- builder: (BuildContext context) {
- return AlertWithTwoActions(
- alertTitle: S.of(context).reconnection,
- alertContent: S.of(context).reconnect_alert_text,
- rightButtonText: S.of(context).ok,
- leftButtonText: S.of(context).cancel,
- actionRightButton: () async {
- Navigator.of(context).pop();
- await dashboardViewModel!.reconnect();
- },
- actionLeftButton: () => Navigator.of(context).pop());
- },
- );
- }
}
diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart
index 774136ac4..bef64ef50 100644
--- a/lib/view_model/node_list/node_create_or_edit_view_model.dart
+++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart
@@ -3,10 +3,7 @@ import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
-import 'package:collection/collection.dart';
import 'package:cw_core/node.dart';
-import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
-import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
@@ -20,7 +17,7 @@ class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
abstract class NodeCreateOrEditViewModelBase with Store {
NodeCreateOrEditViewModelBase(
- this.isPow, this.nodeListViewModel,this.powNodeListViewModel, this.walletType, this._settingsStore, {this.editingNode})
+ this.isPow, this.walletType, this._settingsStore, {this.editingNode})
: state = InitialExecutionState(),
connectionState = InitialExecutionState(),
label = editingNode?.label ?? '',
@@ -116,8 +113,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@observable
bool isPow;
- final NodeListViewModel? nodeListViewModel;
- final PowNodeListViewModel? powNodeListViewModel;
@computed
bool get isReady =>
@@ -257,12 +252,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@action
Future delete({required Node editingNode}) async {
await editingNode.delete();
- if(nodeListViewModel != null) {
- nodeListViewModel!.bindNodes();
- }
- if(powNodeListViewModel != null) {
- powNodeListViewModel!.bindNodes();
- }
}
@action
@@ -287,12 +276,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
}
state = ExecutedSuccessfullyState();
- if(nodeListViewModel != null) {
- nodeListViewModel!.bindNodes();
- }
- if(powNodeListViewModel != null) {
- powNodeListViewModel!.bindNodes();
- }
} catch (e) {
state = FailureState(e.toString());
diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart
index 55207f0df..cc91577af 100644
--- a/lib/view_model/node_list/node_list_view_model.dart
+++ b/lib/view_model/node_list/node_list_view_model.dart
@@ -1,12 +1,14 @@
+import 'dart:math';
+
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/node_list.dart';
+import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/node.dart';
-import 'package:cake_wallet/entities/node_list.dart';
import 'package:cake_wallet/entities/default_settings_migration.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/evm/evm.dart';
@@ -14,11 +16,34 @@ import 'package:cake_wallet/reactions/wallet_connect.dart';
part 'node_list_view_model.g.dart';
+
+class NodeSpeed {
+ final String iconPath;
+
+ // fraction of best node ping needed to be classified as this speed.
+ final double treshold;
+
+ const NodeSpeed._(this.iconPath, this.treshold);
+
+ static const disconnected = NodeSpeed._(
+ "assets/new-ui/node_speed_badges/disconnected.svg", 0);
+ static const slow = NodeSpeed._(
+ "assets/new-ui/node_speed_badges/slow.svg", 0);
+ static const medium = NodeSpeed._(
+ "assets/new-ui/node_speed_badges/medium.svg", 0.5);
+ static const fast = NodeSpeed._(
+ "assets/new-ui/node_speed_badges/fast.svg", 0.8);
+
+ static const all = [fast, medium, slow, disconnected];
+}
+
+
class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel;
abstract class NodeListViewModelBase with Store {
- NodeListViewModelBase(this._appStore)
+ NodeListViewModelBase(this._appStore, this.isPow)
: nodes = ObservableList(),
+ _nodeSpeeds = ObservableMap(),
settingsStore = _appStore.settingsStore {
bindNodes();
@@ -37,6 +62,7 @@ abstract class NodeListViewModelBase with Store {
bindNodes();
});
}
+
@computed
Node get currentNode {
@@ -52,7 +78,7 @@ abstract class NodeListViewModelBase with Store {
return settingsStore.getCurrentNode(walletType, chainId: chainId);
}
- final node = settingsStore.nodes[walletType];
+ final node = isPow ? settingsStore.powNodes[walletType] : settingsStore.nodes[walletType];
if (node == null) {
throw Exception('No node for wallet type: $walletType');
}
@@ -73,6 +99,59 @@ abstract class NodeListViewModelBase with Store {
final ObservableList nodes;
final SettingsStore settingsStore;
final AppStore _appStore;
+ final bool isPow;
+ final ObservableMap _nodeSpeeds;
+
+
+ @computed
+ List get nonCurrentNodes => nodes.where((item)=>item != currentNode).toList();
+
+ @observable
+ bool isTestingNodeSpeed = false;
+
+ Future speedTestNodes() async {
+ try {
+ isTestingNodeSpeed = true;
+ _nodeSpeeds.clear();
+ final nodes = this.nodes.toList();
+ await Future.wait(nodes.map((node) async {
+ final sw = Stopwatch()..start();
+ final res = await node
+ .requestNode()
+ .timeout(const Duration(seconds: 10), onTimeout: () => false);
+ sw.stop();
+ if (res) {
+ _nodeSpeeds[node.uriRaw] = sw.elapsedMilliseconds;
+ }
+ }));
+ } finally {
+ isTestingNodeSpeed = false;
+ }
+ }
+
+ NodeSpeed? nodeSpeedFor(Node node) {
+ final bestSpeed = _bestNodeSpeed;
+ final currentSpeed = _nodeSpeeds[node.uriRaw];
+ if (bestSpeed == null) {
+ return null;
+ }
+ if (currentSpeed == null) {
+ return NodeSpeed.disconnected;
+ }
+ for (final speed in NodeSpeed.all) {
+ if (currentSpeed < (bestSpeed / speed.treshold)) return speed;
+ }
+
+ return null;
+ }
+
+ int? get _bestNodeSpeed {
+ try {
+ printV(_nodeSpeeds);
+ return _nodeSpeeds.values.reduce(min);
+ } catch (e) {}
+ return null;
+ }
Future reset() async {
await resetToDefault();
@@ -125,7 +204,12 @@ abstract class NodeListViewModelBase with Store {
}
// For non-EVM wallets, use the wallet type directly
- settingsStore.nodes[walletType] = node;
+ if(isPow) {
+ settingsStore.powNodes[walletType] = node;
+ } else {
+ settingsStore.nodes[walletType] = node;
+ }
+
}
@action
@@ -150,7 +234,7 @@ abstract class NodeListViewModelBase with Store {
}
// For non-EVM wallets, use the wallet type directly
- nodes.addAll(await Node.getAllForWalletType(walletType));
+ nodes.addAll(isPow ? await Node.getAllForWalletTypePow(walletType) : await Node.getAllForWalletType(walletType));
}
}
diff --git a/lib/view_model/node_list/pow_node_list_view_model.dart b/lib/view_model/node_list/pow_node_list_view_model.dart
index b7e185e2b..e69de29bb 100644
--- a/lib/view_model/node_list/pow_node_list_view_model.dart
+++ b/lib/view_model/node_list/pow_node_list_view_model.dart
@@ -1,83 +0,0 @@
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/store/app_store.dart';
-import 'package:cake_wallet/utils/mobx.dart';
-import 'package:cw_core/node_list.dart';
-import 'package:cw_core/utils/proxy_wrapper.dart';
-import 'package:hive/hive.dart';
-import 'package:mobx/mobx.dart';
-import 'package:cw_core/wallet_base.dart';
-import 'package:cake_wallet/store/settings_store.dart';
-import 'package:cw_core/node.dart';
-import 'package:cake_wallet/entities/node_list.dart';
-import 'package:cake_wallet/entities/default_settings_migration.dart';
-import 'package:cw_core/wallet_type.dart';
-
-part 'pow_node_list_view_model.g.dart';
-
-class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewModel;
-
-abstract class PowNodeListViewModelBase with Store {
- PowNodeListViewModelBase(this._appStore)
- : nodes = ObservableList(),
- settingsStore = _appStore.settingsStore {
- bindNodes();
-
- reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
- bindNodes();
- });
- }
-
- @computed
- Node get currentNode {
- final node = settingsStore.powNodes[_appStore.wallet!.type];
-
- if (node == null) {
- throw Exception('No node for wallet type: ${_appStore.wallet!.type}');
- }
-
- return node;
- }
-
- String getAlertContent(String uri) =>
- S.current.change_current_node(uri) +
- '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance!.enabled ? '' : S.current.orbot_running_alert) : ''}';
-
- @computed
- bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching;
-
- @action
- void setEnableAutomaticNodeSwitching(bool value) =>
- settingsStore.enableAutomaticNodeSwitching = value;
-
- final ObservableList nodes;
- final SettingsStore settingsStore;
- final AppStore _appStore;
-
- Future reset() async {
- await resetPowToDefault();
-
- Node node;
-
- switch (_appStore.wallet!.type) {
- case WalletType.nano:
- node = (await getNanoDefaultPowNode())!;
- break;
- default:
- throw Exception('Unexpected wallet type: ${_appStore.wallet!.type} for pow node list');
- }
-
- await setAsCurrent(node);
- }
-
- @action
- Future delete(Node node) async => node.delete();
-
- Future setAsCurrent(Node node) async =>
- settingsStore.powNodes[_appStore.wallet!.type] = node;
-
- @action
- Future bindNodes() async {
- nodes.clear();
- nodes.addAll(await Node.getAllPow());
- }
-}
diff --git a/pubspec_base.yaml b/pubspec_base.yaml
index 416c6dea5..a04b093d6 100644
--- a/pubspec_base.yaml
+++ b/pubspec_base.yaml
@@ -290,6 +290,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/node_speed_badges/
fonts:
- family: Lato
diff --git a/res/pictures/default_node.svg b/res/pictures/default_node.svg
new file mode 100644
index 000000000..43d730b90
--- /dev/null
+++ b/res/pictures/default_node.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/pictures/node_speed_badges/disconnected.svg b/res/pictures/node_speed_badges/disconnected.svg
new file mode 100644
index 000000000..33b7d28ba
--- /dev/null
+++ b/res/pictures/node_speed_badges/disconnected.svg
@@ -0,0 +1,4 @@
+
diff --git a/res/pictures/node_speed_badges/fast.svg b/res/pictures/node_speed_badges/fast.svg
new file mode 100644
index 000000000..4205def6f
--- /dev/null
+++ b/res/pictures/node_speed_badges/fast.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/pictures/node_speed_badges/medium.svg b/res/pictures/node_speed_badges/medium.svg
new file mode 100644
index 000000000..398280251
--- /dev/null
+++ b/res/pictures/node_speed_badges/medium.svg
@@ -0,0 +1,4 @@
+
diff --git a/res/pictures/node_speed_badges/slow.svg b/res/pictures/node_speed_badges/slow.svg
new file mode 100644
index 000000000..fc4d0b200
--- /dev/null
+++ b/res/pictures/node_speed_badges/slow.svg
@@ -0,0 +1,7 @@
+
diff --git a/res/pictures/official_node.svg b/res/pictures/official_node.svg
new file mode 100644
index 000000000..75a09a0b9
--- /dev/null
+++ b/res/pictures/official_node.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index 70468bc30..f8828872e 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -300,6 +300,7 @@
"default_sell_provider": "Default Sell Provider",
"delete": "Delete",
"delete_account": "Delete Account",
+ "delete_this_node": "Delete this node",
"delete_wallet": "Delete wallet",
"delete_wallet_confirm_message": "Are you sure that you want to delete ${wallet_name} wallet?",
"deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to",
@@ -681,8 +682,10 @@
"node_new": "New Node",
"node_port": "Node port",
"node_reset_settings_title": "Reset settings",
+ "node_share_desc": "Scan this QR code from the Scan screen to import this node.",
"node_test": "Test",
"nodes": "Nodes",
+ "nodes_desc": "Manage existing nodes or add a custom one.",
"nodes_list_reset_to_default_message": "Are you sure that you want to reset settings to default?",
"none_of_selected_providers_can_exchange": "None of the selected providers can make this swap",
"noNFTYet": "No NFTs yet",
@@ -1022,6 +1025,7 @@
"share_address": "Share address",
"shared_seed_wallet_groups": "Shared Seed Wallet Groups",
"shared_seed_wallets": "Shared Seed Wallets",
+ "share_this_node": "Share this node",
"show": "Show",
"show_address_book_popup": "Show Address Book popup",
"show_balance": "Long Press to Show Balance",
From 5b2a8f67559046e89e64d34c841ab2ce0b87e0e5 Mon Sep 17 00:00:00 2001
From: Omar Hatem
Date: Tue, 2 Jun 2026 02:33:17 +0200
Subject: [PATCH 6/6] Cw 1379 add monero support to trezor rebase dev (#3273)
* refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services
* refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services
* fix: correctly check for Ledger hardware wallet type in `isHardwareWallet` logic
* refactor: unify `isConnected` method for hardware wallets, add Trezor-specific support, and update Monero sync logic
* refactor: add Trezor transaction signing support, streamline hardware wallet handling, and update Monero dependency
* refactor: improve Trezor transaction handling, adjust hardware wallet state logic, and update Monero dependency references
* fix: non-hww tx commit() in xmr
fix: prod monero_c
fix: keyImage import workaround for Trezor/Cupcake
chore: add kotlin's .salive to .gitignore
* fix: universal_ble builds on iOS
chore: remove prints
fix: proper CI prebuilt for linux
* new trezor connection ui
* fix page title
* typo
* fix page title anim
* refactor: enhance Trezor pairing flow, update state management, and simplify UI logic in hardware wallet integration
* fix: correct widget indentation in Trezor pairing failure state UI
* fix: extract Trezor pairing failure state UI to `_errorBox` widget and update Trezor dependency reference
* refactor: simplify hardware wallet pairing error UI, optimize `_errorBox` layout, and streamline widget logic
* Update cw_monero/lib/pending_monero_transaction.dart
Co-authored-by: Omar Hatem
* Update lib/buy/robinhood/robinhood_buy_provider.dart
Co-authored-by: Omar Hatem
* feat: add USB intent filters, device filter for hardware wallets, and update Trezor dependency reference
* Update cw_monero/pubspec.yaml [skip ci]
* Update cw_wownero/pubspec.yaml [skip ci]
* Update cw_zano/pubspec.yaml [skip ci]
* fix: use CryptoCurrency.title instead of amount-refactor-only .symbol getter
new_wallet_type_page used curr.symbol, which only exists on the
amount-refactor branch (String get symbol => title). On dev the
equivalent is curr.title. Adapts the Trezor feature to dev's API
without pulling in amount-refactor.
Co-Authored-By: Claude Opus 4.8 (1M context)
* Fix wallet list screen not scrollable
minor fixes
---------
Co-authored-by: Konstantin Ullrich
Co-authored-by: Czarek Nakamoto
Co-authored-by: Robert Malikowski
Co-authored-by: Konstantin Ullrich
Co-authored-by: Claude Opus 4.8 (1M context)
---
.github/workflows/pr_test_build_linux.yml | 2 +-
.gitignore | 2 +
android/app/build.gradle | 2 +-
android/app/src/main/AndroidManifestBase.xml | 8 +
.../app/src/main/res/xml/device_filter.xml | 11 +
.../device_ledger_nano_gen_5.svg | 3 -
.../hardware_wallet/device_trezor_safe_5.svg | 3 -
assets/images/hardware_wallet/ledger_flex.png | Bin 1777 -> 0 bytes
.../images/hardware_wallet/ledger_nano_s.png | Bin 2639 -> 0 bytes
assets/images/hardware_wallet/ledger_stax.png | Bin 1612 -> 0 bytes
.../lib/hardware/device_connection_type.dart | 2 +-
.../lib/hardware/hardware_wallet_service.dart | 3 +-
cw_core/lib/wallet_info.dart | 2 +-
cw_monero/lib/ledger.dart | 8 +
cw_monero/lib/monero_wallet.dart | 35 ++
cw_monero/lib/monero_wallet_service.dart | 38 +-
cw_monero/lib/pending_monero_transaction.dart | 41 ++-
cw_monero/lib/trezor.dart | 53 +++
cw_monero/pubspec.lock | 56 ++-
cw_monero/pubspec.yaml | 9 +-
cw_wownero/pubspec.lock | 6 +-
cw_wownero/pubspec.yaml | 4 +-
cw_zano/pubspec.lock | 6 +-
cw_zano/pubspec.yaml | 4 +-
.../components/common_test_flows.dart | 5 +-
.../robots/new_wallet_type_page_robot.dart | 6 +-
ios/Podfile.lock | 1 +
lib/buy/dfx/dfx_buy_provider.dart | 2 +-
lib/buy/robinhood/robinhood_buy_provider.dart | 2 +-
lib/di.dart | 6 +-
.../hardware_wallet_device.dart | 55 ++-
lib/monero/cw_monero.dart | 55 +--
lib/new-ui/pages/send_page.dart | 13 +-
.../widgets/coins_page/wallet_info.dart | 6 +-
lib/new-ui/widgets/digit_input.dart | 182 ++++++++++
.../proceed_on_device_message.dart | 60 ++++
.../proceed_on_device_sheet.dart | 338 ++++++++++++++++++
.../hardware_wallet/trezor_paring_sheet.dart | 106 ++++++
.../send_page/send_confirm_bottom_widget.dart | 61 +---
.../widgets/swap_page/swap_confirm_sheet.dart | 2 +-
lib/router.dart | 2 +-
.../connect_device/connect_device_page.dart | 172 +++++----
.../connect_device/widgets/device_tile.dart | 15 +-
.../pages/balance/crypto_balance_widget.dart | 6 +-
.../exchange_trade/exchange_trade_page.dart | 2 +-
.../integrations/deuro/savings_page.dart | 2 +-
.../new_wallet/new_wallet_type_page.dart | 224 ++++++------
lib/src/screens/send/send_page.dart | 2 +-
.../hardware_wallet/bitbox_view_model.dart | 2 +-
.../hardware_wallet_view_model.dart | 3 +-
.../hardware_wallet/ledger_view_model.dart | 8 +-
.../trezor_connect_view_model.dart | 258 +++++++++++++
.../hardware_wallet/trezor_view_model.dart | 79 ----
lib/view_model/send/send_view_model.dart | 8 +-
.../wallet_hardware_restore_view_model.dart | 2 +-
pubspec_base.yaml | 9 +-
res/pictures/bluetooth.svg | 3 +
res/pictures/cake_coin.svg | 60 ++++
res/pictures/hardware_wallet.svg | 191 ++++++++++
.../hardware_wallets}/device_bitbox.svg | 2 +-
.../hardware_wallets/device_ledger_flex.svg | 3 +
.../device_ledger_nano_gen_5.svg | 3 +
.../hardware_wallets/device_ledger_nano_s.svg | 3 +
.../device_ledger_nano_x.svg | 2 +-
.../hardware_wallets/device_ledger_stax.svg | 3 +
.../device_trezor_model_one.svg | 3 +
.../device_trezor_model_t.svg | 3 +
.../hardware_wallets/device_trezor_safe_3.svg | 3 +
.../hardware_wallets/device_trezor_safe_5.svg | 3 +
.../hardware_wallets/device_trezor_safe_7.svg | 3 +
res/values/strings_en.arb | 12 +-
scripts/prepare_moneroc.sh | 4 +-
tool/configure.dart | 9 +-
73 files changed, 1851 insertions(+), 451 deletions(-)
create mode 100644 android/app/src/main/res/xml/device_filter.xml
delete mode 100644 assets/images/hardware_wallet/device_ledger_nano_gen_5.svg
delete mode 100644 assets/images/hardware_wallet/device_trezor_safe_5.svg
delete mode 100644 assets/images/hardware_wallet/ledger_flex.png
delete mode 100644 assets/images/hardware_wallet/ledger_nano_s.png
delete mode 100644 assets/images/hardware_wallet/ledger_stax.png
create mode 100644 cw_monero/lib/trezor.dart
create mode 100644 lib/new-ui/widgets/digit_input.dart
create mode 100644 lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart
create mode 100644 lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart
create mode 100644 lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart
create mode 100644 lib/view_model/hardware_wallet/trezor_connect_view_model.dart
delete mode 100644 lib/view_model/hardware_wallet/trezor_view_model.dart
create mode 100644 res/pictures/bluetooth.svg
create mode 100644 res/pictures/cake_coin.svg
create mode 100644 res/pictures/hardware_wallet.svg
rename {assets/images/hardware_wallet => res/pictures/hardware_wallets}/device_bitbox.svg (87%)
create mode 100644 res/pictures/hardware_wallets/device_ledger_flex.svg
create mode 100644 res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg
create mode 100644 res/pictures/hardware_wallets/device_ledger_nano_s.svg
rename {assets/images/hardware_wallet => res/pictures/hardware_wallets}/device_ledger_nano_x.svg (94%)
create mode 100644 res/pictures/hardware_wallets/device_ledger_stax.svg
create mode 100644 res/pictures/hardware_wallets/device_trezor_model_one.svg
create mode 100644 res/pictures/hardware_wallets/device_trezor_model_t.svg
create mode 100644 res/pictures/hardware_wallets/device_trezor_safe_3.svg
create mode 100644 res/pictures/hardware_wallets/device_trezor_safe_5.svg
create mode 100644 res/pictures/hardware_wallets/device_trezor_safe_7.svg
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 36fb28a25..99a489bb1 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 9fce14fcf..268167bc4 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 6c1540222..26f5544c9 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 a7364d670..0f15c75bc 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -128,6 +128,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 9d2fc8bbd..000000000
--- a/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
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 14c0329be..000000000
--- a/assets/images/hardware_wallet/device_trezor_safe_5.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/assets/images/hardware_wallet/ledger_flex.png b/assets/images/hardware_wallet/ledger_flex.png
deleted file mode 100644
index fa39f241fccf109565a28f66c090bf57ca274fd0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1777
zcmVPx*t4TybRCr$Poo$FzRT#(r&s~*jgAA8~ie9AXgwQf<`
z8}8g$1C7X?NwLTX9r_R^>D=*~Nl*lS%A7l!(W{1qRG&;1F<6t<`^2uhIxqKS?mhR+
znVmiJbj;={
z0FtS#w>fa`137&Ls1M+))cSxX9Pnwdae6_I^ZhsbawprVr!5iC|J1fV_td!$
z4;|V(DNCs|0LfK_0cI|Nu~e3LS>x742H}|FAU#$#e~@8M3V>uLzm6I2%Mg5N%%&ic
z8dc|r@m!^Bej4|jVh1pgDZE0=@5U{(6UHOLno7ys9jBI90W43A_B%w?V1ZIsYq0s~
zE&esw=|55)`8oO!^*v()a8GLU)xC7ZF92@x6*ud9##b=#BQc+8YVQCr;+o>jI38m%
z(PB8b7laipT0CxB=3One4Qv)0fJC~m4dB-UES@C7rVA6l?;kr*oCs9c#s>GV-rBq9
z{A7keZvnW$w|r$Du9kCK6FTnTqFfMhzqlR-mn?|OCvfQ$3PQ2mWA
zj~$GS2kr8(=hH#AUgA}d-hwXq)gj~dvC{C=bL5tP;sCn53=NTl&)Sw*^9Fcp3^ahC
zTM>;5%%d9Rh%al21JGL#nE35#DfdQeYrejf1`reh0gogd#zv8E2gCu)=Nu5jy)F$P
zC;}ueFbzNhh?2V6*T=UE)mxB!5E_7dANyL+05kx956E62iv17S^V3z3NEKvI(cUNa
z=AQ;20-*D&AfsrPhP(IO(9Q?Y<*%h^0P<6`PQvnitgizU`&a{*Qvm8dLJ`k#x(w~^
zGE{tpsE&@yN!aCKupS`v1tk}mIfCeDo0oi+`fV5b;%ATJtLviehu5E4L%bzH@wAa@GtvOMo)f|Zc8gxC3oAd*jAow~7>m(esM%4b@eG8hoFQjvT
zd^mF?2h1GU)aZ{GR)~BT`&;OI00LjgJd`QiJ?UVDK~A3vM>}ks2JmIIoIUPk-|w)F
zIKBdvnuH0?u8K9{dT8JDzcwLyM}^}XZ8FokYB~FXo0i%LXgsc_GXO-0fS}WMLqiEZ
z#=-3>s_y^TTwuQA+j8ziW5YQe0r1EH1L?vh0&foOXsI1I~|c2hetY4)FPe$16k=
z>HJiFjsfi&zehxF2i*3XEezu$IA+vs3J0l0I
z$`|3x+=^)NunwsJ!XumG03syesl>+2Yn#I_;_{8?^xAHo+hv6Lg^?p=3%XRUc_iU5
z2jPX8j8I!It=;&Us!9N!M^9=1LhDz&^Be~77=im;JwQFxN@@5rm(uQH9~u8IEGmj1
zt#0Fb^a&$DI0u9=rrS1qKm$;`Dl`BMK(PlDo7WMb0Vwu>V)HrzGyugOP;CBx3ot01
ToY=F<00000NkvXXu0mjfR%KGE
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 02777aeb69b2e436d2b9bd96c3b5f39a773b7def..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 2639
zcmb7``9IT-1IORfFtag7h>siM@B-qbKl3vhvrDmx!%)c&eW(O=gg5KzGQNa
z7KOH4$q{|X5jk^K`1bu1zCXNPugB~82fQAyWJ_}+BwQ2@008oavB9lB>G40oIR5x7
zIYQ%4KEn3QjgRe
z7p;=o@(3R?lfp^2P&Ui5Z2EEm+F}Xb$!c!aP+0b^01Sm%=ee6IUCQQXOj7C63LlbD
z@h{T<8){H|PKEJKbe*Xe(Y{hW)~XzFd$B&;_bQGRdlOlyzXK1SH!vVp)Gx9frl
z0NWZ=3PhplaHvbn5=06FPr{n_Kb+79Ao8f-&0dQn=>Y>M}cUs*yT*M{du)o0M{rLq6(S<2PphkHx
zoXC|5xUk9hm!BY@Az2Jv_?O!a%pUqMTqzaL^onF-T$qhVzH!hRq50C2GL{|r@DPVK
zimzK&5ow;R`%&LYTr+EZSv8C#M#xn?g4dRj6p+a*#nJQ
zmWxFNR_a%bh5zAq2R^~H?2A2+!v6^INdTy9o5c;KMYU(cEyuEcGb*`O?swV1~w}w5gWLrtq&^z^P3WDXPei
z8aIh~Nx?M0T7+(y2)huse;q4=vJEgWc>?^zR=
zszv)dlEN8)SDOvd&>pcGzm(3tIr2*|0gM&jYO`6lI2z{+pr!<~gYL8Ti4^ynjT_oT
zT~3)zO|4e%W1Ou&J$jL*Tr*^IR7COc-=XAQPfJvq|dG@7nzuk
zgTH-1(fl^!QN}Ei(%`VuWZlcM7K4jGOkE3y?fTK{((_{9J3=M`Y>i4H
zlwV6h^>12K-Dve`@mv=H&$q$!l25bGJ&}B!nIEj;=Vcsu%c9CX^qQ$`lnN9r*zB=F
zQI{Viig3$B>DwmOW|ma%O$p5EIFl>f%@J|>tg{h)<2cNy5@1zxeeWuc;%Q)R9N7(%
zZ;Pu_jAG<1q|N)!tu7@zEDx@{m_?#X6t1
z0$}w;e9F?_v*AH|a<)&L{sxq;L5Mkm!6aTTS($NqW~Ec(@+~$HAgcX`vd;SHQ&fyK
zk`1P~h@2CMLvILzt}w=@$D7`&@jFcSS4WH-#hL??J&B6p^bpX;ljn}ERrE8%V0fSc
z42GcxN~+X^p*@wJQ+2zM6)kmg=>Oiyg(0+x6o{OKex|hjqjqxy9T+m~P>2NTV&IkRW=df^smR3ZYwwm8q7^tOF6a3@TtCd-a6}6Ilq40kIL^9-`chF@kXZ7=d`8~Knk`pZv2&{9NTAa
zr9gI1IdEnExw-qGvg-_~J`4d8-TTW(cE9v}mX&<4j5RH#Dyo>X>5BOL?U@A!)-&ha~8})l=voeJ=2z<^YKYcgE9xuAo46@DJh%!`5#B
zUKl^S$us5;?ATC+vc4lX`;{~~m7@!IAr3e+>mpn1WH;7Um*M
zVP42GTFhUIUs5#!20DmlG`(h#{>e#ZM&VZFOSnm+wMB%EmE1zN0ZWbTu%v*FGgQt0
zK2gB+lsh*NFlZI(i4KyTokI*fmm1jHN&89lFUBNiq2isz0mbE0qoJ}zW&o5!_=SCb
z20$R1(6o1_2Zb=|WP5xHCvPEUv@F@y%Eha#l*!W!YEn&)^IXTO1v@h3Bdb
zKWlbAZ5knRGgbgiq#XtP!z_6pJ3^_{qJfOR@d&cFQ&PEV}xEDwmAI7$-qA(2vz|BQ%DX_uQ_
zjsFMBQ~Rz`674hC_TFmQeO@{%?wgNe+HgwuGFtN>&oHH)i$1@$Qn!HL!F3nXM|dyu
zH1EXUF@-2hPyh0~pT5cymYX$@nC>5FdtFn4Bl*4UqO;){#k^RjEM{Hb2SISy^mXVK
roB(`%xy85){gPb0mGj{LDO<Px*21!IgRCr$Pon43I}_78*`QruT<7~(O#p^{uMC_A;~WhcKbIC#6~BG-ECNOo+54F2Fo0X_2W7dy
z!sG7R=Dz?=7#C^Z;?dMOVW(P}^8`R_JohLuj`=YFsXnYJuqH+o-ZC?#D#g^hQuf=~
z5eG0jHob#489xEo;mmnwvvpk{XI6V|Ww~YItBWunKV6*oqi1_<42T1WC1xH2aZ%c^
zC;(^7tCBJEnN8So*Vo0Dt@I@Rk=6&rug|k80B!l8!R7v6hMw7Ys7BcPw!Izk_t4~@;ilU1SkLxr*jvkH3GU2P4bfP0~nu~
z1M}WiqV#(eWceJo@gKlv2Ak={;>7n-(B;>F@!2`B8C3vP-5Hpz6w)aP2=V|zdMCW0
z40W`Z#o%Wuh0F)NLXZa#?G&_8@88RX^fd~gR|Oi&Q2RBYuJw??H-58_`K1>ZJdc2l
z4^kqq2-j)N2H##;$eiwL02`fC0L`d8i2!Ea+sd#3%q{luW?XX_YX3B?KO-QSn`dB8
zOS0kEjB`{4S+ES|*kUoW)L$uRK+4c6fD0U3Jf;A8nSf2#+HOTOwAKg^m43}w_yG*g
zjTriJ5w?3PA`E4yo&QY%$Hr~t;=ylw2}Joav|$mhd!tROpe9!G00trjt=%82GT(fv
zm^s_m5m4t`v?5@+Tu5*2MSSa8`3M-0`v78krBqD4(SHC2AO#UHT`Hs}dllcC2&e;4
zzn2n*D#-qkBp+uoCoB2M?CJq6%WFzMzBKw43k_Syp=Yu`ZpD^^+s4Nev0%?xrm!81LE2+#=dc1F9k9*IS`?GLwV8LDMyr-+D71n6d5C)U;f
z>muxU5hfUfFh7t9&s!0Gu3;JhO%aei4$vdb`J*b;;c_9h-%)OH*hn5g*uSEb%vnEO
zd~Iv}cz?N&vChR2`^W>RJ(LgOBHZcl31+QcQg*roJx1Yf|3()YQn|%OkS{ykMir
z%3tR%-*)!oODp{iz$OLti5?1$w-`e{k=Jt;$7{nkQPqp|-LtFBBs
zxX4^JZ&~_u;ztKfT{RJ314fhCDF%uD#=ff&dC-`MkCgJ6Cp|DNe4t70)Lkbkv^!(gnzhR2L!mmz%@Qx3$UX?7
zlb3{S8n+W62n^nMyr2kA$3_Sr51X5x^=jtV{19IQYKH$$MehT+%JHCGH*^BhrBTL7
z8lu;{I5Vu3C!PVYI@>k&=G7O5&D{q0dJxwgjI(~ytaaY3^;fVTlZ`(K^hvpR@Ham_
z@f3ic0|{$i3Ls!|3P1q_tU$osYXm5OfE5V1dyN1E5U>IPcmE$44V#X~Kndvp0000<
KMNUMnLSTZg0^5E7
diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart
index 39e0400c9..3b1dd9349 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 19125ba84..94cbdc443 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> getAvailableAccounts({int index = 0, int limit = 5});
+ Future> getAvailableAccounts({int index = 0, int limit = 5}) =>
+ throw UnimplementedError();
Future signTransaction({required String transaction}) => throw UnimplementedError();
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index 1ce397267..5877ad2da 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 7707acd48..7c1742ada 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 e315f24fa..30c550721 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 syncTrezor() async {
+ if (trezorService == null) throw Exception("Trezor not connected");
+
+ final ptr = Pointer.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 signTrezorTransaction(String json) async {
+ if (trezorService == null) throw Exception("Trezor not connected");
+ return Trezor(trezorService!).signTransaction(json);
+ }
+
@override
Future 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 _createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
final inputs = [];
final outputs = _credentials.outputs;
diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart
index 2a153c5e1..1b0e9e347 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 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 e1d191eac..c32a2cc9c 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 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.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);
+
+ final wptr = Pointer.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();
- rethrow;
+ 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 000000000..f2651d062
--- /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 getWatchCredentials() async {
+ final credentials = await TrezorMonero(service.client).getWatchCredentials();
+
+ return MoneroTrezorWatchCredentials(credentials.$1, credentials.$2);
+ }
+
+ Future keyImageSync(String tdis) async {
+ final tdisMap = jsonDecode(tdis) as Map;
+ final tdisList = tdisMap["tdis"] as List;
+
+ final txIds = [];
+ 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 signTransaction(String json) async {
+ return TrezorMonero(service.client).signTransaction(jsonDecode(json));
+ }
+}
diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock
index 6f6174d08..d00f2149e 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 7f38c0ea0..4c10e94d7 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 734af9338..55cfb8fdf 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 a23fc3955..ca9c70bea 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 662ccded6..6e2916d71 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 8eb5d3e14..16585b715 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 d885e3e14..888c2ce03 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 fc3e94f59..11785b67d 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 selectWalletType(WalletType type) async {
await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key');
}
-
- Future onNextButtonPressed() async {
- await commonTestCases.tapItemByKey('new_wallet_type_next_button_key');
- }
}
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index e661632c8..2d32cb833 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 f927b702d..582cccd5d 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 7cd992812..2c26fa4ca 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 991e2f7ee..cd7ee62d1 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 setup({
switch(type) {
case HardwareWalletType.bitbox: return getIt();
case HardwareWalletType.ledger: return getIt();
- case HardwareWalletType.trezor: return getIt();
+ case HardwareWalletType.trezor: return getIt();
case HardwareWalletType.cupcake:
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
@@ -429,7 +429,7 @@ Future setup({
getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
appName: "Cake Wallet"));
- getIt.registerLazySingleton(() => TrezorViewModel(getIt()));
+ getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt()));
getIt.registerFactory(() => KeyService(getIt.get()));
diff --git a/lib/entities/hardware_wallet/hardware_wallet_device.dart b/lib/entities/hardware_wallet/hardware_wallet_device.dart
index 646f5a2c2..757e68099 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 08143b3b3..0a378e219 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 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> debugCallLength() {
- return monero_wallet_api.debugCallLength();
- }
@override
- Map getWalletCacheDebug() {
- return monero_wallet_api.getWalletCacheDebug();
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
+ MoneroLedgerService(connection);
+
+ @override
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client) =>
+ MoneroTrezorService(client);
+
+ @override
+ Future syncTrezor(Object wallet) async {
+ final moneroWallet = wallet as MoneroWallet;
+
+ return moneroWallet.syncTrezor();
}
+
+ bool isViewOnly() => isViewOnlyBySpendKey(null);
+
+ @override
+ Map> debugCallLength() => monero_wallet_api.debugCallLength();
+
+ @override
+ Map getWalletCacheDebug() => monero_wallet_api.getWalletCacheDebug();
}
diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart
index c3f5da957..9292d1af2 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 {
}
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 {
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 21c83db44..4963513fc 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 000000000..d6889db67
--- /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 _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 createState() => _DigitInputState();
+}
+
+class _DigitInputState extends State 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 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 000000000..100325dbe
--- /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 000000000..2a06f37e6
--- /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 createState() =>
+ _HardwareWalletProceedOnDeviceSheetState();
+}
+
+class _HardwareWalletProceedOnDeviceSheetState extends State {
+ 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: [
+ ...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 000000000..9b429edc4
--- /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 createState() => _HardwareWalletTrezorParingSheetState();
+}
+
+class _HardwareWalletTrezorParingSheetState extends State {
+ 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 68f8c6889..671579914 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 9ccfb1453..3676cd6e4 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 {
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 a5af558eb..89365171a 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -371,7 +371,7 @@ Route createRoute(RouteSettings settings) {
(_) => getIt.get(
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 2122db44a..22e466cf3 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 {
var bleDevices = [];
var usbDevices = [];
+ List get allDevices => [...bleDevices, ...usbDevices];
+
late Timer? _usbRefreshTimer = null;
late Timer? _bleRefreshTimer = null;
late Timer? _bleStateTimer = null;
@@ -158,24 +163,38 @@ class ConnectDevicePageBodyState extends State {
Future _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 {
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 {
),
),
),
- 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 {
),
),
),
- 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 803a648bb..4014e5040 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: [
- 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 a6ea7b340..de05bccf4 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 d7fd3196e..6ebd2526f 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 {
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 4a5d62a02..a08437822 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 _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 fff206836..4b170dc03 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 {
static const aspectRatioImage = 1.22;
- final TextEditingController searchController = TextEditingController();
-
- WalletType? selected;
List types;
List filteredTypes = [];
@@ -87,21 +104,12 @@ class WalletTypeFormState extends State {
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 {
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,
- ),
- ),
+ const NewWalletTypePageHeader(),
+ Text(
+ S.of(context).choose_wallet_currency,
+ textAlign: TextAlign.center,
),
- Padding(
- padding: EdgeInsets.fromLTRB(24, 24, 24, 12),
- child: SearchBarWidget(searchController: searchController),
- ),
- 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: [
- ...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 onTypeSelected() async {
- if (selected == null) throw Exception('Wallet Type is not selected yet.');
-
- if (selected == WalletType.haven && widget.isCreate) {
- return await showPopUp(
- context: context,
- builder: (BuildContext context) => PopUpCancellableAlertDialog(
- contentText: S.of(context).pause_wallet_creation,
- actionButtonText: S.of(context).ok,
- buttonAction: () => Navigator.of(context).pop(),
- ),
- );
- }
-
+ Future 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 7e44e413d..db0d5e883 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 07a412cc2..b51815451 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 4c79eda71..de64079b5 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 updateBleState();
Stream scanForBleDevices();
diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart
index 793f0df84..c0746223a 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 000000000..3fd4514e6
--- /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 _initBLE() async {
+ if (isBleEnabled && !_bleIsInitialized) {
+ trezorBLE = sdk.TrezorInterface.ble(
+ onPermissionRequest: (_) async {
+ if (Platform.isMacOS) return true;
+
+ Map 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 updateBleState() async {
+ final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
+
+ final newState = bleState == sdk.AvailabilityState.poweredOn;
+
+ if (newState != isBleEnabled) isBleEnabled = newState;
+ }
+
+ @override
+ Stream scanForBleDevices() =>
+ trezorBLE.scan().map((d) => TrezorHardwareWalletDevice(d));
+
+ @override
+ Future> getAllUsbDevices() => trezorUSB.devices
+ .then((devices) => devices.map((d) => TrezorHardwareWalletDevice(d)).toList());
+
+ @override
+ Future stopScanning() async {
+ if (_bleIsInitialized) await trezorBLE.stopScanning();
+ if (!Platform.isIOS) await trezorUSB.stopScanning();
+ }
+
+ bool _isConnecting = false;
+ sdk.TrezorClient? _client;
+
+ Completer? _pinCompleter;
+
+ @observable
+ TrezorParingState paringState = TrezorParingState.initial;
+
+ void setParingPin(String pin) => _pinCompleter?.complete(pin);
+
+ @override
+ Future 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 onPinCode() async {
+ _pinCompleter = Completer();
+ 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 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 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 8a56fb1a4..000000000
--- 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 updateBleState() async {}
-
- @override
- Stream scanForBleDevices() => throw UnimplementedError();
-
- @override
- Future> getAllUsbDevices() async => [];
-
- @override
- Future stopScanning() async {}
-
- @override
- Future 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 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 35883f502..a51423ec5 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 ac89b2adc..1baabd1b3 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 a04b093d6..fb35f3582 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 000000000..ddee93cf7
--- /dev/null
+++ b/res/pictures/bluetooth.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/pictures/cake_coin.svg b/res/pictures/cake_coin.svg
new file mode 100644
index 000000000..03d468a3c
--- /dev/null
+++ b/res/pictures/cake_coin.svg
@@ -0,0 +1,60 @@
+
diff --git a/res/pictures/hardware_wallet.svg b/res/pictures/hardware_wallet.svg
new file mode 100644
index 000000000..56f7c8998
--- /dev/null
+++ b/res/pictures/hardware_wallet.svg
@@ -0,0 +1,191 @@
+
diff --git a/assets/images/hardware_wallet/device_bitbox.svg b/res/pictures/hardware_wallets/device_bitbox.svg
similarity index 87%
rename from assets/images/hardware_wallet/device_bitbox.svg
rename to res/pictures/hardware_wallets/device_bitbox.svg
index 52368aaec..fe41717d5 100644
--- a/assets/images/hardware_wallet/device_bitbox.svg
+++ b/res/pictures/hardware_wallets/device_bitbox.svg
@@ -1,3 +1,3 @@
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 000000000..9c8e9f7d4
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_flex.svg
@@ -0,0 +1,3 @@
+
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 000000000..fe1124e33
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg
@@ -0,0 +1,3 @@
+
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 000000000..a8032746a
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_nano_s.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/hardware_wallet/device_ledger_nano_x.svg b/res/pictures/hardware_wallets/device_ledger_nano_x.svg
similarity index 94%
rename from assets/images/hardware_wallet/device_ledger_nano_x.svg
rename to res/pictures/hardware_wallets/device_ledger_nano_x.svg
index c605249dc..538bb3fa3 100644
--- a/assets/images/hardware_wallet/device_ledger_nano_x.svg
+++ b/res/pictures/hardware_wallets/device_ledger_nano_x.svg
@@ -1,3 +1,3 @@
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 000000000..270457001
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_ledger_stax.svg
@@ -0,0 +1,3 @@
+
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 000000000..5d1c779c5
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_model_one.svg
@@ -0,0 +1,3 @@
+
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 000000000..fb437c9ea
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_model_t.svg
@@ -0,0 +1,3 @@
+
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 000000000..338c7275b
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_safe_3.svg
@@ -0,0 +1,3 @@
+
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 000000000..5ee539f39
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_safe_5.svg
@@ -0,0 +1,3 @@
+
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 000000000..8d442f4b6
--- /dev/null
+++ b/res/pictures/hardware_wallets/device_trezor_safe_7.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index f8828872e..f0b425652 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 2d7b06f0c..0a328f5f3 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 80856320d..6a5385f86 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 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 syncTrezor(Object wallet);
Map> debugCallLength();
Map getWalletCacheDebug();
}