| 1 | import 'package:cake_wallet/di.dart'; |
| 2 | import 'package:cake_wallet/generated/i18n.dart'; |
| 3 | import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; |
| 4 | import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; |
| 5 | import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; |
| 6 | import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_message_card.dart'; |
| 7 | import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart'; |
| 8 | import 'package:cake_wallet/store/app_store.dart'; |
| 9 | import 'package:cake_wallet/themes/core/custom_theme_colors.dart'; |
| 10 | import 'package:flutter/material.dart'; |
| 11 | import 'package:reown_walletkit/reown_walletkit.dart'; |
| 12 | |
| 13 | class MethodsUtils { |
| 14 | static ReownWalletKit get walletKit => getIt.get<WalletKitService>().walletKit; |
| 15 | static final bottomSheetService = getIt.get<BottomSheetService>(); |
| 16 | |
| 17 | static bool isSessionOwnedByWallet(SessionData? session, String walletPublicKey) { |
| 18 | if (session == null || walletPublicKey.isEmpty) { |
| 19 | return false; |
| 20 | } |
| 21 | |
| 22 | final accounts = session.namespaces.values.expand((namespace) => namespace.accounts); |
| 23 | |
| 24 | return accounts.any((account) => isSameAccount(account.split(":").last, walletPublicKey)); |
| 25 | } |
| 26 | |
| 27 | static bool isSameAccount(String a, String b) { |
| 28 | if (a.startsWith("0x") && b.startsWith("0x")) { |
| 29 | return a.toLowerCase() == b.toLowerCase(); |
| 30 | } |
| 31 | |
| 32 | return a == b; |
| 33 | } |
| 34 | |
| 35 | static const _transactionMethods = { |
| 36 | 'eth_sendTransaction', |
| 37 | 'eth_signTransaction', |
| 38 | 'solana_signTransaction', |
| 39 | 'solana_signAllTransactions', |
| 40 | 'solana_signAndSendTransaction', |
| 41 | }; |
| 42 | |
| 43 | static Future<bool> requestApproval( |
| 44 | String text, { |
| 45 | String? title, |
| 46 | String? method, |
| 47 | String? chainId, |
| 48 | String? address, |
| 49 | String? topic, |
| 50 | required String transportType, |
| 51 | List<WCConnectionModel> extraModels = const [], |
| 52 | VerifyContext? verifyContext, |
| 53 | }) async { |
| 54 | final appStore = getIt.get<AppStore>(); |
| 55 | SessionData? session; |
| 56 | if (topic != null) { |
| 57 | session = walletKit.sessions.get(topic); |
| 58 | } else { |
| 59 | final pending = walletKit.pendingRequests.getAll(); |
| 60 | session = pending.isNotEmpty ? walletKit.sessions.get(pending.last.topic) : null; |
| 61 | } |
| 62 | final dAppMetadata = session?.peer.metadata; |
| 63 | |
| 64 | final isTransaction = method != null && _transactionMethods.contains(method); |
| 65 | final resolvedTitle = title ?? |
| 66 | (isTransaction ? S.current.wc_approve_request_title : S.current.wc_signing_request_title); |
| 67 | final swipeLabel = isTransaction ? S.current.wc_swipe_to_approve : S.current.wc_swipe_to_sign; |
| 68 | |
| 69 | final extraRows = <WCMessageRow>[]; |
| 70 | if (method != null && method.isNotEmpty) { |
| 71 | extraRows.add(WCMessageRow(label: S.current.method, value: method)); |
| 72 | } |
| 73 | if (chainId != null && chainId.isNotEmpty) { |
| 74 | extraRows.add(WCMessageRow(label: S.current.chain_id, value: chainId)); |
| 75 | } |
| 76 | if (transportType.isNotEmpty) { |
| 77 | extraRows.add(WCMessageRow( |
| 78 | label: S.current.transport_type, |
| 79 | value: transportType.toUpperCase(), |
| 80 | )); |
| 81 | } |
| 82 | for (final model in extraModels) { |
| 83 | if (model.title == null) continue; |
| 84 | final value = model.elements?.join(', ') ?? model.text ?? ''; |
| 85 | extraRows.add(WCMessageRow(label: model.title!, value: value)); |
| 86 | } |
| 87 | |
| 88 | final WCBottomSheetResult result = (await bottomSheetService.queueBottomSheet( |
| 89 | widget: WCSigningRequestSheet( |
| 90 | title: resolvedTitle, |
| 91 | swipeLabel: swipeLabel, |
| 92 | dappName: dAppMetadata?.name ?? '', |
| 93 | dappIconUrl: |
| 94 | (dAppMetadata?.icons.isNotEmpty ?? false) ? dAppMetadata!.icons.first : null, |
| 95 | dappSubtitle: method ?? dAppMetadata?.url ?? '', |
| 96 | message: text, |
| 97 | walletName: appStore.wallet?.name ?? '', |
| 98 | address: address ?? '', |
| 99 | verifyContext: verifyContext, |
| 100 | extraRows: extraRows, |
| 101 | ), |
| 102 | ) as WCBottomSheetResult?) ?? |
| 103 | WCBottomSheetResult.reject; |
| 104 | |
| 105 | return result != WCBottomSheetResult.reject; |
| 106 | } |
| 107 | |
| 108 | static void handleRedirect( |
| 109 | String topic, |
| 110 | Redirect? redirect, [ |
| 111 | String? error, |
| 112 | bool success = false, |
| 113 | ]) { |
| 114 | debugPrint('handleRedirect topic: $topic, redirect: $redirect, error: $error'); |
| 115 | openApp( |
| 116 | topic, |
| 117 | redirect, |
| 118 | onFail: (e) => goBackModal( |
| 119 | title: success ? S.current.success : S.current.error, |
| 120 | message: error, |
| 121 | success: success, |
| 122 | ), |
| 123 | ); |
| 124 | } |
| 125 | |
| 126 | static void openApp( |
| 127 | String topic, |
| 128 | Redirect? redirect, { |
| 129 | int delay = 100, |
| 130 | Function(ReownSignError? error)? onFail, |
| 131 | }) async { |
| 132 | await Future.delayed(Duration(milliseconds: delay)); |
| 133 | try { |
| 134 | await walletKit.redirectToDapp( |
| 135 | topic: topic, |
| 136 | redirect: redirect, |
| 137 | ); |
| 138 | } on ReownSignError catch (e) { |
| 139 | onFail?.call(e); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | static void goBackModal({ |
| 144 | String? title, |
| 145 | String? message, |
| 146 | bool success = true, |
| 147 | }) async { |
| 148 | await bottomSheetService.queueBottomSheet( |
| 149 | closeAfter: success ? 3 : 0, |
| 150 | widget: GoBackModalWidget( |
| 151 | isSuccess: success, |
| 152 | title: title, |
| 153 | message: message, |
| 154 | ), |
| 155 | ); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | class GoBackModalWidget extends StatelessWidget { |
| 160 | const GoBackModalWidget({ |
| 161 | required this.isSuccess, |
| 162 | this.message, |
| 163 | this.title, |
| 164 | super.key, |
| 165 | }); |
| 166 | |
| 167 | final bool isSuccess; |
| 168 | final String? title; |
| 169 | final String? message; |
| 170 | |
| 171 | @override |
| 172 | Widget build(BuildContext context) { |
| 173 | return Container( |
| 174 | color: Theme.of(context).colorScheme.surface, |
| 175 | height: 280.0, |
| 176 | width: double.infinity, |
| 177 | padding: const EdgeInsets.all(20.0), |
| 178 | child: Column( |
| 179 | children: [ |
| 180 | Icon( |
| 181 | isSuccess ? Icons.check_circle_sharp : Icons.error_outline_sharp, |
| 182 | color: isSuccess |
| 183 | ? CustomThemeColors.syncGreen |
| 184 | : Theme.of(context).colorScheme.errorContainer, |
| 185 | size: 80.0, |
| 186 | ), |
| 187 | Text( |
| 188 | title ?? S.current.connected, |
| 189 | style: Theme.of(context).textTheme.titleLarge?.copyWith( |
| 190 | fontSize: 18.0, |
| 191 | fontWeight: FontWeight.w600, |
| 192 | ), |
| 193 | ), |
| 194 | Text( |
| 195 | message ?? S.current.youCanGoBackToYourDapp, |
| 196 | style: Theme.of(context).textTheme.bodyMedium?.copyWith( |
| 197 | fontSize: 16.0, |
| 198 | ), |
| 199 | ), |
| 200 | ], |
| 201 | ), |
| 202 | ); |
| 203 | } |
| 204 | } |