From 65bb917bfb805ef5066055bb7a057a21a6105e8d Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Tue, 24 Jun 2025 03:48:27 +0100 Subject: [PATCH] CW-1094-WalletConnect-Issues (#2318) * feat(walletconnect): Minor update to WalletConnect tile UI to fix expanded image issue * feat(walletconnect): Minor update to WalletConnect tile UI to fix expanded image issue * feat(walletconnect): Enhance WalletConnect EVM chain service. This change: - Improves signTypedDataV4 method handing and data parsing in extractPermitData. - Adjusts UI for One Click Auth requests * feat(walletconnect): Add redirect to PairingMetadata in WalletKit setup * fix(walletconnect): Ensure session null checks before handling redirects in EvmChainService * fix(walletconnect): Add null safety checks for permitData properties in EvmChainService * refactor(walletconnect): Update WCPairingItemWidget layout and improve error handling for image loading * fix(walletconnect): Handle break in connection flow triggered by global exception handler when SVGParser fails on invalid SvgData and triggers FlutterError. * refactor(solana): Remove redundant session request responses and simplify error handling in SolanaChainService --------- Co-authored-by: Omar Hatem --- .../chain_service/eth/evm_chain_service.dart | 123 ++++++++++-------- .../solana/solana_chain_service.dart | 26 +--- .../services/walletkit_service.dart | 1 + .../widgets/wc_connection_item_widget.dart | 2 +- .../widgets/wc_pairing_item_widget.dart | 40 +++--- .../wc_session_auth_request_widget.dart | 10 +- lib/src/widgets/cake_image_widget.dart | 4 +- lib/src/widgets/provider_optoin_tile.dart | 13 ++ lib/utils/exception_handler.dart | 6 + lib/utils/image_utill.dart | 13 ++ 10 files changed, 141 insertions(+), 97 deletions(-) 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 426c1580f..e70930b48 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 @@ -379,16 +379,21 @@ class EvmChainServiceImpl { topic: topic, response: response, ); + + if (session == null) return; + MethodsUtils.handleRedirect( topic, - session!.peer.metadata.redirect, + session.peer.metadata.redirect, response.error?.message, response.error == null, ); } on ReownSignError catch (error) { + if (session == null) return; + MethodsUtils.handleRedirect( topic, - session!.peer.metadata.redirect, + session.peer.metadata.redirect, error.message, ); } @@ -489,68 +494,76 @@ class EvmChainServiceImpl { final typedData = jsonDecode(data[1] as String) as Map; // Extracting domain details. - final domain = typedData['domain'] ?? {} as Map; + final domain = typedData['domain'] as Map? ?? {}; final domainName = domain['name']?.toString() ?? ''; + final version = domain['version']?.toString() ?? ''; + final chainId = domain['chainId']?.toString() ?? ''; final verifyingContract = domain['verifyingContract']?.toString() ?? ''; - final chainId = domain['chainId']?.toString() ?? ''; - final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type); - - // Get the primary type. + // Get the primary type and types final primaryType = typedData['primaryType']?.toString() ?? ''; + final types = typedData['types'] as Map? ?? {}; + final message = typedData['message'] as Map? ?? {}; - // Extracting message details. - final message = typedData['message'] ?? {} as Map; - final details = message['details'] ?? {} as Map; - final amount = details['amount']?.toString() ?? ''; - final expirationRaw = details['expiration']?.toString() ?? ''; - final nonce = details['nonce']?.toString() ?? ''; + // Build a readable message based on the primary type and its structure + String messageDetails = ''; - final tokenAddress = details['token']?.toString() ?? ''; - final token = await getTokenDetails(tokenAddress, chainName); - - final spender = message['spender']?.toString() ?? ''; - final sigDeadlineRaw = message['sigDeadline']?.toString() ?? ''; - - // Converting expiration and sigDeadline from Unix time (seconds) to DateTime. - DateTime? expirationDate; - DateTime? sigDeadlineDate; - try { - if (expirationRaw.isNotEmpty) { - final int expirationInt = int.parse(expirationRaw); - expirationDate = DateTime.fromMillisecondsSinceEpoch(expirationInt * 1000); - } - if (sigDeadlineRaw.isNotEmpty) { - final int sigDeadlineInt = int.parse(sigDeadlineRaw); - sigDeadlineDate = DateTime.fromMillisecondsSinceEpoch(sigDeadlineInt * 1000); - } - } catch (e) { - // Parsing failed; we leave dates as null. + if (types.containsKey(primaryType)) { + final typeFields = types[primaryType] as List; + messageDetails = _formatMessageFields(message, typeFields, types); + } else { + // For unknown types, show the raw message + messageDetails = message.toString(); } - final permitData = { - 'domainName': domainName, - 'chainId': chainId, - 'verifyingContract': verifyingContract, - 'primaryType': primaryType, - 'token': token, - 'amount': amount, - 'expiration': expirationDate, - 'nonce': nonce, - 'spender': spender, - 'sigDeadline': sigDeadlineDate, - }; - - return 'Domain: ${permitData['domainName']}' - 'Chain ID: ${permitData['chainId']}' - 'Verifying Contract: ${permitData['verifyingContract']}' - 'Primary Type: ${permitData['primaryType']}' - 'Token: ${permitData['token']}' - 'Expiration: ${permitData['expiration'] != null ? permitData['expiration'] : 'N/A'}' - 'Spender: ${permitData['spender']}' - 'Signature Deadline: ${permitData['sigDeadline'] != null ? permitData['sigDeadline'] : 'N/A'}'; + return '''Domain Name: $domainName +Version: $version +Chain ID: $chainId +Verifying Contract: $verifyingContract +Primary Type: $primaryType\n +Message: +$messageDetails'''; } - return ''; + return 'Invalid typed data format'; + } + + String _formatMessageFields( + Map message, List fields, Map types) { + final buffer = StringBuffer(); + + for (var field in fields) { + final fieldName = _toCamelCase(field['name'] as String); + final fieldType = field['type'] as String; + final value = message[field['name'] as String]; + + 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 + final name = value['name'] as String; + final wallet = value['wallet'] as String; + buffer.writeln('$fieldName: $name ($wallet)'); + } else { + // For other nested types, format each field + final formattedValue = + _formatMessageFields(value as Map, nestedFields, types); + buffer.writeln('$fieldName: $formattedValue'); + } + } else { + // Handle primitive types + buffer.writeln('$fieldName: $value'); + } + } + + return buffer.toString(); + } + + String _toCamelCase(String input) { + if (input.isEmpty) return input; + return input[0].toUpperCase() + input.substring(1).toLowerCase(); } Future getTokenDetails(String contractAddress, String chainName) async { 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 076c63228..f7a57b51d 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 @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:blockchain_utils/base58/base58.dart'; import 'package:blockchain_utils/blockchain_utils.dart' as blockchain_utils; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart'; import 'package:flutter/material.dart'; import 'package:on_chain/solana/solana.dart'; @@ -91,8 +90,6 @@ class SolanaChainService { ); } - await walletKit.respondSessionRequest(topic: topic, response: response); - _handleResponseForTopic(topic, response); } @@ -158,8 +155,6 @@ class SolanaChainService { ); } - await walletKit.respondSessionRequest(topic: topic, response: response); - _handleResponseForTopic(topic, response); } @@ -221,8 +216,6 @@ class SolanaChainService { ); } - await walletKit.respondSessionRequest(topic: topic, response: response); - _handleResponseForTopic(topic, response); } @@ -242,21 +235,14 @@ class SolanaChainService { topic, session!.peer.metadata.redirect, response.error?.message, + response.error == null, ); } on ReownSignError catch (error) { - if (error.message.contains('No matching key')) { - MethodsUtils.handleRedirect( - topic, - session!.peer.metadata.redirect, - '${S.current.error_while_processing} ${S.current.youCanGoBackToYourDapp}', - ); - } else { - MethodsUtils.handleRedirect( - topic, - session!.peer.metadata.redirect, - error.message, - ); - } + MethodsUtils.handleRedirect( + topic, + session!.peer.metadata.redirect, + error.message, + ); } } } diff --git a/lib/src/screens/wallet_connect/services/walletkit_service.dart b/lib/src/screens/wallet_connect/services/walletkit_service.dart index 9b48cd233..4c25032a4 100644 --- a/lib/src/screens/wallet_connect/services/walletkit_service.dart +++ b/lib/src/screens/wallet_connect/services/walletkit_service.dart @@ -78,6 +78,7 @@ abstract class WalletKitServiceBase with Store { description: 'Cake Wallet', url: 'https://cakewallet.com', icons: ['https://cakewallet.com/assets/image/cake_logo.png'], + redirect: Redirect(native: 'cakewallet://'), ), ); diff --git a/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart index d97cc96b6..b701bdc62 100644 --- a/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart @@ -85,7 +85,7 @@ class _ModelElementWidget extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontWeight: FontWeight.w600, ), - maxLines: 10, + maxLines: 50, overflow: TextOverflow.ellipsis, ), ), diff --git a/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart index 93e2e32f0..3d0ce8dc0 100644 --- a/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart @@ -26,10 +26,18 @@ class WCPairingItemWidget extends StatelessWidget { '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; return ListTile( - leading: CakeImageWidget( - imageUrl: metadata.icons.isNotEmpty ? metadata.icons[0] : null, - errorWidget: CircleAvatar( - backgroundImage: AssetImage('assets/images/walletconnect_logo.png'), + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + leading: SizedBox( + width: 60, + height: 60, + child: CakeImageWidget( + borderRadius: 8, + width: 60, + height: 60, + imageUrl: metadata.icons.isNotEmpty ? metadata.icons[0] : null, + errorWidget: CircleAvatar( + backgroundImage: AssetImage('assets/images/walletconnect_logo.png'), + ), ), ), title: Text( @@ -59,18 +67,20 @@ class WCPairingItemWidget extends StatelessWidget { ), ], ), - trailing: Container( - height: 40, + trailing: SizedBox( width: 44, - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.primary, - ), - child: Icon( - Icons.edit, - size: 14, - color: Theme.of(context).colorScheme.onPrimary, + height: 40, + child: Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).colorScheme.primary, + ), + child: Icon( + Icons.edit, + size: 14, + color: Theme.of(context).colorScheme.onPrimary, + ), ), ), onTap: onTap, diff --git a/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart index ad4c6b8e1..7e10c0469 100644 --- a/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart @@ -17,7 +17,7 @@ class WCSessionAuthRequestWidget extends StatelessWidget { child: SingleChildScrollView(child: child), ), const SizedBox(height: 16), - Row( + Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ PrimaryButton( @@ -30,7 +30,7 @@ class WCSessionAuthRequestWidget extends StatelessWidget { color: Theme.of(context).colorScheme.error, textColor: Theme.of(context).colorScheme.onError, ), - const SizedBox(width: 8), + const SizedBox(height: 8), PrimaryButton( onPressed: () { if (Navigator.canPop(context)) { @@ -41,7 +41,7 @@ class WCSessionAuthRequestWidget extends StatelessWidget { color: Theme.of(context).colorScheme.primary, textColor: Theme.of(context).colorScheme.onPrimary, ), - const SizedBox(width: 8), + const SizedBox(height: 8), PrimaryButton( onPressed: () { if (Navigator.canPop(context)) { @@ -49,8 +49,8 @@ class WCSessionAuthRequestWidget extends StatelessWidget { } }, text: S.current.sign_all, - color: Theme.of(context).secondaryHeaderColor, - textColor: Theme.of(context).colorScheme.onPrimary, + color: Theme.of(context).colorScheme.secondaryContainer, + textColor: Theme.of(context).colorScheme.onSecondaryContainer, ), ], ), diff --git a/lib/src/widgets/cake_image_widget.dart b/lib/src/widgets/cake_image_widget.dart index b24c7f3d3..04d298e2e 100644 --- a/lib/src/widgets/cake_image_widget.dart +++ b/lib/src/widgets/cake_image_widget.dart @@ -11,6 +11,7 @@ class CakeImageWidget extends StatelessWidget { this.loadingWidget, this.errorWidget, this.color, + this.borderRadius = 24.0, }); final String? imageUrl; @@ -20,6 +21,7 @@ class CakeImageWidget extends StatelessWidget { final Widget? loadingWidget; final Widget? errorWidget; final Color? color; + final double borderRadius; @override Widget build(BuildContext context) { @@ -80,7 +82,7 @@ class CakeImageWidget extends StatelessWidget { height: height, width: width, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24.0), + borderRadius: BorderRadius.circular(borderRadius), color: Theme.of(context).colorScheme.surfaceContainerHighest, ), child: Center( diff --git a/lib/src/widgets/provider_optoin_tile.dart b/lib/src/widgets/provider_optoin_tile.dart index 337e50c9e..be6873975 100644 --- a/lib/src/widgets/provider_optoin_tile.dart +++ b/lib/src/widgets/provider_optoin_tile.dart @@ -289,6 +289,15 @@ Widget getImage(String imagePath, {double? height, double? width, Color? imageCo child: CircularProgressIndicator(), ), ), + errorBuilder: (_, __, ___) { + return Container( + height: imageHeight, + width: imageWidth, + child: Center( + child: Icon(Icons.error_outline, color: Colors.grey), + ), + ); + }, ) : Image.network( imagePath, @@ -315,6 +324,9 @@ Widget getImage(String imagePath, {double? height, double? width, Color? imageCo return Container( height: imageHeight, width: imageWidth, + child: Center( + child: Icon(Icons.error_outline, color: Colors.grey), + ), ); }, ); @@ -325,6 +337,7 @@ Widget getImage(String imagePath, {double? height, double? width, Color? imageCo height: imageHeight, width: imageWidth, colorFilter: imageColor != null ? ColorFilter.mode(imageColor, BlendMode.srcIn) : null, + errorBuilder: (_, __, ___) => Icon(Icons.error, color: Colors.grey), ) : Image.asset(imagePath, height: imageHeight, width: imageWidth); } diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index a6d3774ce..0f80fa737 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -274,6 +274,12 @@ class ExceptionHandler { "NetworkImage._loadAsync", "SSLV3_ALERT_BAD_RECORD_MAC", "PlatformException(already_active, File picker is already active", + // SVG-related errors + "SvgParser", + "SVG parsing error", + "Invalid SVG", + "SVG format error", + "SvgPicture", // Temporary ignored, More context: Flutter secure storage reads the values as null some times // probably when the device was locked and then opened on Cake // this is solved by a restart of the app diff --git a/lib/utils/image_utill.dart b/lib/utils/image_utill.dart index 994a3957b..40dde7020 100644 --- a/lib/utils/image_utill.dart +++ b/lib/utils/image_utill.dart @@ -26,6 +26,15 @@ class ImageUtil { child: CircularProgressIndicator(), ), ), + errorBuilder: (_, __, ___) { + return Container( + height: _height, + width: _width, + child: Center( + child: Icon(Icons.error_outline, color: Colors.grey), + ), + ); + }, ) : Image.network( key: ValueKey(imagePath), @@ -54,6 +63,9 @@ class ImageUtil { return Container( height: _height, width: _width, + child: Center( + child: Icon(Icons.error_outline, color: Colors.grey), + ), ); }, ); @@ -64,6 +76,7 @@ class ImageUtil { height: _height, width: _width, placeholderBuilder: (_) => Icon(Icons.error), + errorBuilder: (_, __, ___) => Icon(Icons.error), key: ValueKey(imagePath), ) : Image.asset(