Merge branch 'refs/heads/dev' into cw-1440-new-buy-sell-flow

This commit is contained in:
Robert Malikowski
2026-08-27 12:28:17 +02:00
110 changed files with 2159 additions and 797 deletions
@@ -328,6 +328,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
amount: S.of(bottomSheetContext).send_amount,
amountValue: sendVM.amountParsingProxy
.getDisplayCryptoAmount(amountValue, sendVM.selectedCryptoCurrency),
explanation: sendVM.pendingTransactionAdditionalCostNotice,
fiatAmountValue: fiatAmountValue,
fee: isEVMCompatibleChain(sendVM.walletType)
? S.of(bottomSheetContext).send_estimated_fee
@@ -11,6 +11,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:file_picker/file_picker.dart';
import "package:flutter/services.dart";
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
@@ -180,8 +181,11 @@ class RestoreFromBackupPage extends BasePage {
return;
}
FocusManager.instance.primaryFocus?.unfocus();
try {
await restoreFromBackupViewModel.import(textEditingController.text);
await SystemChannels.textInput.invokeMethod<void>("TextInput.hide");
} on IncompatibleBackupAppException catch (e) {
final isConfirmed = await showPopUp<bool>(
context: context,
@@ -630,7 +630,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
}
if ((walletRestoreViewModel.type == WalletType.decred) &&
seedWords.length != WalletRestoreViewModelBase.decredSeedMnemonicLength) {
![12, 15, 24].contains(seedWords.length)) {
return false;
}
@@ -143,6 +143,7 @@ class OtherSettingsPage extends BasePage {
},
}),
),
if(_otherSettingsViewModel.hasSignVerify)
ListItemRegularRow(
keyValue: "security_backup_page_sign_and_verify",
label: S.of(context).sign_verify_title,
@@ -152,10 +153,12 @@ class OtherSettingsPage extends BasePage {
],
if (_otherSettingsViewModel.walletType == WalletType.bitcoin)
"btc_logging": [
if(_otherSettingsViewModel.hasLightning)
ListItemRegularRow(
keyValue: "export_lightning_logs",
label: S.of(context).export_lightning_logs,
onTap: () => onExportLNLog(context)),
if(_otherSettingsViewModel.hasPayjoin)
ListItemRegularRow(
keyValue: "export_payjoin_logs",
label: S.of(context).export_payjoin_logs,
+1 -1
View File
@@ -76,7 +76,7 @@ class PrivacyPage extends BasePage {
}),
],
"": [
if (_privacySettingsViewModel.isBitcoin)
if (_privacySettingsViewModel.hasSilentPaymentsScanning)
ListItemRegularRow(
iconPath: "assets/new-ui/settings_row_icons/silent-payments.svg",
keyValue: "silent_payments",
@@ -1,3 +1,4 @@
import "dart:async";
import 'dart:convert';
import 'package:cake_wallet/di.dart';
@@ -5,6 +6,7 @@ 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/print_verbose.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';
@@ -18,6 +20,7 @@ import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wall
import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.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/bottom_sheet/bottom_sheet_message_display_widget.dart";
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/evm/evm.dart';
@@ -82,7 +85,9 @@ class EvmChainServiceImpl {
if (appStore.wallet != null && isEVMCompatibleChain(appStore.wallet!.type)) {
final walletClient = evm?.getWeb3Client(appStore.wallet!);
if (walletClient != null) return walletClient;
if (walletClient != null) {
return walletClient;
}
}
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
@@ -93,8 +98,18 @@ class EvmChainServiceImpl {
Future<void> personalSign(String topic, dynamic parameters) async {
debugPrint('personalSign request: $parameters');
final pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, EVMSupportedMethods.personalSign.name);
if (pRequest == null) {
return;
}
final address = EthUtils.getAddressFromSessionRequest(pRequest);
if (!_isRequestAuthorized(topic, requestAddress: address)) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
final data = EthUtils.getDataFromSessionRequest(pRequest);
final message = EthUtils.getUtf8Message(data.toString());
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
@@ -104,6 +119,7 @@ class EvmChainServiceImpl {
method: pRequest.method,
chainId: pRequest.chainId,
address: address,
topic: topic,
transportType: pRequest.transportType.name,
verifyContext: pRequest.verifyContext,
);
@@ -141,8 +157,18 @@ class EvmChainServiceImpl {
Future<void> ethSign(String topic, dynamic parameters) async {
debugPrint('ethSign request: $parameters');
final pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, EVMSupportedMethods.ethSign.name);
if (pRequest == null) {
return;
}
final address = EthUtils.getAddressFromSessionRequest(pRequest);
if (!_isRequestAuthorized(topic, requestAddress: address)) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
final data = EthUtils.getDataFromSessionRequest(pRequest);
final message = EthUtils.getUtf8Message(data.toString());
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
@@ -152,6 +178,7 @@ class EvmChainServiceImpl {
method: pRequest.method,
chainId: pRequest.chainId,
address: address,
topic: topic,
transportType: pRequest.transportType.name,
verifyContext: pRequest.verifyContext,
);
@@ -189,8 +216,18 @@ class EvmChainServiceImpl {
Future<void> ethSignTypedData(String topic, dynamic parameters) async {
debugPrint('ethSignTypedData request: $parameters');
final pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, EVMSupportedMethods.ethSignTypedData.name);
if (pRequest == null) {
return;
}
final address = EthUtils.getAddressFromSessionRequest(pRequest);
if (!_isRequestAuthorized(topic, requestAddress: address)) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
final data = EthUtils.getDataFromSessionRequest(pRequest) as String;
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
@@ -199,6 +236,7 @@ class EvmChainServiceImpl {
method: pRequest.method,
chainId: pRequest.chainId,
address: address,
topic: topic,
transportType: pRequest.transportType.name,
verifyContext: pRequest.verifyContext,
);
@@ -236,8 +274,18 @@ class EvmChainServiceImpl {
final permitRequestMessage = await extractPermitData(parameters);
final pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, EVMSupportedMethods.ethSignTypedDataV4.name);
if (pRequest == null) {
return;
}
final address = EthUtils.getAddressFromSessionRequest(pRequest);
if (!_isRequestAuthorized(topic, requestAddress: address)) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
final data = EthUtils.getDataFromSessionRequest(pRequest) as String;
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
@@ -246,6 +294,7 @@ class EvmChainServiceImpl {
method: pRequest.method,
chainId: pRequest.chainId,
address: address,
topic: topic,
transportType: pRequest.transportType.name,
verifyContext: pRequest.verifyContext,
);
@@ -280,10 +329,22 @@ class EvmChainServiceImpl {
Future<void> ethSignTransaction(String topic, dynamic parameters) async {
debugPrint('ethSignTransaction request: $parameters');
final SessionRequest pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, EVMSupportedMethods.ethSignTransaction.name);
if (pRequest == null) {
return;
}
final data = EthUtils.getTransactionFromSessionRequest(pRequest);
if (data == null) return;
if (data == null) {
_respondMalformedRequest(topic, pRequest.id);
return;
}
if (!_isRequestAuthorized(topic, requestAddress: data['from']?.toString())) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
final address = EthUtils.getAddressFromSessionRequest(pRequest);
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
@@ -293,6 +354,7 @@ class EvmChainServiceImpl {
method: pRequest.method,
chainId: pRequest.chainId,
address: address,
topic: topic,
transportType: pRequest.transportType.name,
verifyContext: pRequest.verifyContext,
);
@@ -333,10 +395,21 @@ class EvmChainServiceImpl {
Future<void> ethSendTransaction(String topic, dynamic parameters) async {
debugPrint('ethSendTransaction request: $parameters');
final SessionRequest pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, EVMSupportedMethods.ethSendTransaction.name);
if (pRequest == null) {
return;
}
final data = EthUtils.getTransactionFromSessionRequest(pRequest);
if (data == null) return;
if (data == null) {
_respondMalformedRequest(topic, pRequest.id);
return;
}
if (!_isRequestAuthorized(topic, requestAddress: data['from']?.toString())) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
@@ -344,6 +417,7 @@ class EvmChainServiceImpl {
data,
method: pRequest.method,
chainId: pRequest.chainId,
topic: topic,
transportType: pRequest.transportType.name,
verifyContext: pRequest.verifyContext,
);
@@ -379,6 +453,77 @@ class EvmChainServiceImpl {
_handleResponseForTopic(topic, response);
}
void _respondMalformedRequest(String topic, int requestId) {
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
_handleResponseForTopic(
topic,
JsonRpcResponse(
id: requestId,
jsonrpc: "2.0",
error: JsonRpcError(code: error.code, message: error.message),
),
);
}
SessionRequest? _pendingRequest(String topic, String method) {
final matches = walletKit.pendingRequests
.getAll()
.where((request) => request.topic == topic && request.method == method);
return matches.isEmpty ? null : matches.last;
}
bool _isRequestAuthorized(String topic, {String? requestAddress}) {
final wallet = appStore.wallet;
if (wallet == null) {
return false;
}
final keys = wcKeyService.getKeysForChain(wallet);
if (keys.isEmpty) {
return false;
}
final walletAddress = keys.first.publicKey;
if (!MethodsUtils.isSessionOwnedByWallet(walletKit.sessions.get(topic), walletAddress)) {
return false;
}
if (requestAddress != null && !MethodsUtils.isSameAccount(requestAddress, walletAddress)) {
return false;
}
return true;
}
Future<void> _rejectUnauthorizedRequest(String topic, int requestId) async {
unawaited(
bottomSheetService.queueBottomSheet(
isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget(
message: S.current.wc_request_for_different_wallet,
),
),
);
try {
await walletKit.respondSessionRequest(
topic: topic,
response: JsonRpcResponse(
id: requestId,
jsonrpc: "2.0",
error: const JsonRpcError(
code: 4100,
message: "The requested account has not been authorized by the user.",
),
),
);
} catch (e) {
printV("rejectUnauthorizedRequest: $e");
}
}
void _handleResponseForTopic(String topic, JsonRpcResponse<dynamic> response) async {
final session = walletKit.sessions.get(topic);
@@ -388,7 +533,9 @@ class EvmChainServiceImpl {
response: response,
);
if (session == null) return;
if (session == null) {
return;
}
MethodsUtils.handleRedirect(
topic,
@@ -397,7 +544,9 @@ class EvmChainServiceImpl {
response.error == null,
);
} on ReownSignError catch (error) {
if (session == null) return;
if (session == null) {
return;
}
MethodsUtils.handleRedirect(
topic,
@@ -413,6 +562,7 @@ class EvmChainServiceImpl {
String? method,
String? chainId,
String? address,
String? topic,
VerifyContext? verifyContext,
required String transportType,
}) async {
@@ -456,6 +606,7 @@ class EvmChainServiceImpl {
method: method,
chainId: chainId,
address: address ?? transaction.from?.hex ?? '',
topic: topic,
transportType: transportType,
verifyContext: verifyContext,
extraModels: feeRows,
@@ -472,13 +623,17 @@ class EvmChainServiceImpl {
String nativeSymbol,
) {
final gasLimit = transaction.maxGas;
if (gasLimit == null || gasLimit <= 0) return const [];
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 [];
if (perGasWei == null || perGasWei <= BigInt.zero) {
return const [];
}
final feeWei = gasLimitBig * perGasWei;
final feeNative = feeWei.toDouble() / 1e18;
@@ -500,19 +655,25 @@ class EvmChainServiceImpl {
) {
final cryptoPart = '${_formatNativeAmount(feeNative)} $nativeSymbol';
if (nativeCurrency == null) return cryptoPart;
if (nativeCurrency == null) {
return cryptoPart;
}
try {
final fiatStore = getIt.get<FiatConversionStore>();
final price = fiatStore.prices[nativeCurrency];
if (price == null || price <= 0) return cryptoPart;
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;
if (fiatValue.isEmpty || fiatValue == '0.00') {
return cryptoPart;
}
return '$cryptoPart (~ $fiatValue $fiatSymbol)';
} catch (_) {
@@ -521,14 +682,20 @@ class EvmChainServiceImpl {
}
String _formatNativeAmount(double value) {
if (value == 0) return '0';
if (value >= 0.0001) return value.toStringAsFixed(6);
if (value == 0) {
return '0';
}
if (value >= 0.0001) {
return value.toStringAsFixed(6);
}
return value.toStringAsExponential(4);
}
Future<Transaction> _ensureWCTransactionHasGasLimit(Transaction transaction) async {
final hasGasLimit = transaction.maxGas != null && transaction.maxGas! > 0;
if (hasGasLimit) return transaction;
if (hasGasLimit) {
return transaction;
}
final hint = transaction.gasPrice ?? transaction.maxFeePerGas ?? await ethClient.getGasPrice();
@@ -689,7 +856,9 @@ $messageDetails''';
final fieldType = field['type'] as String;
final value = message[field['name'] as String];
if (value == null) continue;
if (value == null) {
continue;
}
if (types.containsKey(fieldType)) {
final nestedFields = types[fieldType] as List<dynamic>;
@@ -714,7 +883,9 @@ $messageDetails''';
}
String _toCamelCase(String input) {
if (input.isEmpty) return input;
if (input.isEmpty) {
return input;
}
return input[0].toUpperCase() + input.substring(1).toLowerCase();
}
@@ -1,4 +1,4 @@
enum SolanaChainId { mainnet, devnet, testnet }
enum SolanaChainId { mainnet, mainnetLegacy, devnet, testnet }
extension SolanaChainIdX on SolanaChainId {
String chain() {
@@ -6,8 +6,10 @@ extension SolanaChainIdX on SolanaChainId {
switch (this) {
case SolanaChainId.mainnet:
name = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
break;
case SolanaChainId.mainnetLegacy:
name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ';
// '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
break;
case SolanaChainId.devnet:
name = 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1';
@@ -1,16 +1,20 @@
import "dart:async";
import 'dart:convert';
import 'package:blockchain_utils/base58/base58.dart';
import 'package:blockchain_utils/blockchain_utils.dart' as blockchain_utils;
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart';
import "package:cw_core/utils/print_verbose.dart";
import 'package:flutter/material.dart';
import 'package:on_chain/solana/solana.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
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/services/chain_service/solana/solana_chain_id.dart';
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/method_utils.dart';
import "package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart";
import 'package:cake_wallet/store/app_store.dart';
class SolanaChainService {
@@ -47,7 +51,16 @@ class SolanaChainService {
Future<void> solanaSignMessage(String topic, dynamic parameters) async {
debugPrint('solanaSignMessage request: $parameters');
final pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, SolanaSupportedMethods.solSignMessage.name);
if (pRequest == null) {
return;
}
if (!_isRequestAuthorized(topic)) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
try {
@@ -65,6 +78,7 @@ class SolanaChainService {
method: pRequest.method,
chainId: pRequest.chainId,
address: privateKey.publicKey().toAddress().address,
topic: topic,
transportType: pRequest.transportType.name,
);
@@ -95,7 +109,16 @@ class SolanaChainService {
Future<void> solanaSignTransaction(String topic, dynamic parameters) async {
debugPrint('solanaSignTransaction: ${jsonEncode(parameters)}');
final pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, SolanaSupportedMethods.solSignTransaction.name);
if (pRequest == null) {
return;
}
if (!_isRequestAuthorized(topic)) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
try {
@@ -130,6 +153,7 @@ class SolanaChainService {
method: pRequest.method,
chainId: pRequest.chainId,
address: privateKey.publicKey().toAddress().address,
topic: topic,
transportType: pRequest.transportType.name,
);
@@ -160,7 +184,16 @@ class SolanaChainService {
Future<void> solanaSignAllTransaction(String topic, dynamic parameters) async {
debugPrint('solanaSignAllTransaction: ${jsonEncode(parameters)}');
final pRequest = walletKit.pendingRequests.getAll().last;
final pRequest = _pendingRequest(topic, SolanaSupportedMethods.solSignAllTransaction.name);
if (pRequest == null) {
return;
}
if (!_isRequestAuthorized(topic)) {
await _rejectUnauthorizedRequest(topic, pRequest.id);
return;
}
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
try {
@@ -174,6 +207,7 @@ class SolanaChainService {
method: pRequest.method,
chainId: pRequest.chainId,
address: privateKey.publicKey().toAddress().address,
topic: topic,
transportType: pRequest.transportType.name,
);
@@ -224,6 +258,55 @@ class SolanaChainService {
return SolanaPrivateKey.fromSeedHex(keys[0].privateKey);
}
SessionRequest? _pendingRequest(String topic, String method) {
final matches = walletKit.pendingRequests
.getAll()
.where((request) => request.topic == topic && request.method == method);
return matches.isEmpty ? null : matches.last;
}
bool _isRequestAuthorized(String topic) {
final wallet = appStore.wallet;
if (wallet == null) {
return false;
}
final keys = wcKeyService.getKeysForChain(wallet);
if (keys.isEmpty) {
return false;
}
return MethodsUtils.isSessionOwnedByWallet(walletKit.sessions.get(topic), keys.first.publicKey);
}
Future<void> _rejectUnauthorizedRequest(String topic, int requestId) async {
unawaited(
bottomSheetService.queueBottomSheet(
isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget(
message: S.current.wc_request_for_different_wallet,
),
),
);
try {
await walletKit.respondSessionRequest(
topic: topic,
response: JsonRpcResponse(
id: requestId,
jsonrpc: "2.0",
error: const JsonRpcError(
code: 4100,
message: "The requested account has not been authorized by the user.",
),
),
);
} catch (e) {
printV("rejectUnauthorizedRequest: $e");
}
}
void _handleResponseForTopic(String topic, JsonRpcResponse<dynamic> response) async {
final session = walletKit.sessions.get(topic);
@@ -43,30 +43,36 @@ class KeyServiceImpl implements WalletConnectKeyService {
@override
List<ChainKeyModel> getKeys(WalletBase wallet) {
final keys = [
ChainKeyModel(
chains: [
'eip155:1',
'eip155:5',
'eip155:56',
'eip155:137',
'eip155:8453',
'eip155:42161',
'eip155:80001',
],
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
ChainKeyModel(
chains: [
'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', // main-net
'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K', // test-net
],
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
];
return keys;
if (isEVMCompatibleChain(wallet.type)) {
return [
ChainKeyModel(
chains: [
'eip155:1',
'eip155:56',
'eip155:137',
'eip155:8453',
'eip155:42161',
],
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
];
}
if (wallet.type == WalletType.solana) {
return [
ChainKeyModel(
chains: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // main-net
'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', // legacy main-net id older dapps still use
],
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
];
}
return [];
}
@override
@@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
@@ -42,7 +41,8 @@ abstract class WalletKitServiceBase with Store {
) : pairings = ObservableList<PairingInfo>(),
sessions = ObservableList<SessionData>(),
auth = ObservableList<PendingSessionAuthRequest>(),
isInitialized = false;
isInitialized = false,
isLoadingConnections = true;
final AppStore appStore;
final SharedPreferences sharedPreferences;
@@ -54,6 +54,9 @@ abstract class WalletKitServiceBase with Store {
@observable
bool isInitialized;
@observable
bool isLoadingConnections;
/// The list of requests from the dapp
@observable
ObservableList<PairingInfo> pairings;
@@ -91,33 +94,23 @@ abstract class WalletKitServiceBase with Store {
_walletKit.onSessionProposal.subscribe(_onSessionProposal);
_walletKit.onSessionProposalError.subscribe(_onSessionProposalError);
_walletKit.onSessionConnect.subscribe(_onSessionConnect);
_walletKit.onSessionAuthRequest.subscribe(_onSessionAuthRequest);
if (isEVMCompatibleChain(appStore.wallet!.type)) {
_walletKit.onSessionAuthRequest.subscribe(_onSessionAuthRequest);
}
_walletKit.pairings.onSync.subscribe(_onPairingsSync);
_walletKit.core.pairing.onPairingDelete.subscribe(_onPairingDelete);
_walletKit.core.pairing.onPairingExpire.subscribe(_onPairingDelete);
// Setup our accounts
List<ChainKeyModel> chainKeys = walletKeyService.getKeys(appStore.wallet!);
final chainKeys = walletKeyService.getKeys(appStore.wallet!);
for (final chainKey in chainKeys) {
for (final chainId in chainKey.chains) {
if (isEVMCompatibleChain(appStore.wallet!.type)) {
// Register account for all EVM chains (chainId is already in eip155:format)
_walletKit.registerAccount(
chainId: chainId,
accountAddress: chainKey.publicKey,
);
} else {
final chainNameSpace = getChainNameSpaceAndIdBasedOnWalletType(
appStore.wallet!.type,
);
if (chainNameSpace == chainId) {
_walletKit.registerAccount(
chainId: chainId,
accountAddress: chainKey.publicKey,
);
}
}
_walletKit.registerAccount(
chainId: chainId,
accountAddress: chainKey.publicKey,
);
}
}
}
@@ -144,42 +137,43 @@ abstract class WalletKitServiceBase with Store {
}
}
await _emitEvent();
_refreshPairings();
sessions.clear();
auth.clear();
_reloadSessionsForCurrentWallet();
final newSessions = _walletKit.sessions.getAll();
sessions.addAll(newSessions);
auth.clear();
final newAuthRequests = _walletKit.sessionAuthRequests.getAll();
auth.addAll(newAuthRequests);
if (isEVMCompatibleChain(appStore.wallet!.type)) {
for (final cId in EVMChainId.values) {
EvmChainServiceImpl(
reference: cId,
appStore: appStore,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,
walletKit: _walletKit,
);
}
isLoadingConnections = false;
for (final cId in EVMChainId.values) {
EvmChainServiceImpl(
reference: cId,
appStore: appStore,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,
walletKit: _walletKit,
);
}
if (appStore.wallet!.type == WalletType.solana) {
for (final cId in SolanaChainId.values) {
SolanaChainService(
reference: cId,
appStore: appStore,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,
walletKit: _walletKit,
);
}
for (final cId in SolanaChainId.values) {
SolanaChainService(
reference: cId,
appStore: appStore,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,
walletKit: _walletKit,
);
}
unawaited(() async {
try {
await _emitEvent();
} catch (e) {
printV("emitEvent failed: $e");
}
}());
}
@action
@@ -195,6 +189,10 @@ abstract class WalletKitServiceBase with Store {
for (var session in engineSessions) {
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
for (var chain in chainKeys) {
if (!MethodsUtils.isSessionOwnedByWallet(session, chain.publicKey)) {
continue;
}
for (var chainID in chain.chains) {
try {
final events = NamespaceUtils.getNamespacesEventsForChain(
@@ -228,8 +226,21 @@ abstract class WalletKitServiceBase with Store {
}
@action
FutureOr<void> onDispose() {
log('walletKit dispose');
void resetConnectionsState() {
sessions.clear();
auth.clear();
pairings.clear();
isInitialized = false;
isLoadingConnections = true;
}
@action
Future<void> onDispose() async {
log("walletKit dispose");
resetConnectionsState();
_walletKit.core.removeLogListener(_logListener);
_walletKit.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid);
@@ -246,11 +257,11 @@ abstract class WalletKitServiceBase with Store {
_walletKit.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete);
_walletKit.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete);
sessions.clear();
auth.clear();
pairings.clear();
isInitialized = false;
try {
await _walletKit.core.relayClient.disconnect().timeout(const Duration(seconds: 3));
} catch (e) {
printV("walletKit relay disconnect: $e");
}
}
ReownWalletKit get walletKit => _walletKit;
@@ -534,8 +545,7 @@ abstract class WalletKitServiceBase with Store {
}
await _removePairingTopicFromLocalStorage(topic);
sessions.clear();
sessions.addAll(_walletKit.sessions.getAll());
_reloadSessionsForCurrentWallet();
_refreshPairings();
}
@@ -559,8 +569,7 @@ abstract class WalletKitServiceBase with Store {
printV('disconnectSession: $e');
}
sessions.clear();
sessions.addAll(_walletKit.sessions.getAll());
_reloadSessionsForCurrentWallet();
if (pairingTopic != null &&
!_walletKit.sessions.getAll().any((s) => s.pairingTopic == pairingTopic)) {
@@ -619,6 +628,24 @@ abstract class WalletKitServiceBase with Store {
pairings.addAll(filteredPairings);
}
@action
void _reloadSessionsForCurrentWallet() {
sessions.clear();
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
if (chainKeys.isEmpty) {
return;
}
final walletPublicKey = chainKeys.first.publicKey;
sessions.addAll(
_walletKit.sessions
.getAll()
.where((session) => MethodsUtils.isSessionOwnedByWallet(session, walletPublicKey)),
);
}
@action
List<SessionData> getSessionsForPairingInfo(PairingInfo pairing) {
return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
@@ -11,9 +11,27 @@ import 'package:flutter/material.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
class MethodsUtils {
static final walletKit = getIt.get<WalletKitService>().walletKit;
static ReownWalletKit get walletKit => getIt.get<WalletKitService>().walletKit;
static final bottomSheetService = getIt.get<BottomSheetService>();
static bool isSessionOwnedByWallet(SessionData? session, String walletPublicKey) {
if (session == null || walletPublicKey.isEmpty) {
return false;
}
final accounts = session.namespaces.values.expand((namespace) => namespace.accounts);
return accounts.any((account) => isSameAccount(account.split(":").last, walletPublicKey));
}
static bool isSameAccount(String a, String b) {
if (a.startsWith("0x") && b.startsWith("0x")) {
return a.toLowerCase() == b.toLowerCase();
}
return a == b;
}
static const _transactionMethods = {
'eth_sendTransaction',
'eth_signTransaction',
@@ -28,13 +46,19 @@ class MethodsUtils {
String? method,
String? chainId,
String? address,
String? topic,
required String transportType,
List<WCConnectionModel> extraModels = const [],
VerifyContext? verifyContext,
}) async {
final appStore = getIt.get<AppStore>();
final pending = walletKit.pendingRequests.getAll();
final session = pending.isNotEmpty ? walletKit.sessions.get(pending.last.topic) : null;
SessionData? session;
if (topic != null) {
session = walletKit.sessions.get(topic);
} else {
final pending = walletKit.pendingRequests.getAll();
session = pending.isNotEmpty ? walletKit.sessions.get(pending.last.topic) : null;
}
final dAppMetadata = session?.peer.metadata;
final isTransaction = method != null && _transactionMethods.contains(method);
@@ -12,6 +12,7 @@ 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/cupertino.dart";
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -145,6 +146,26 @@ class WalletConnectConnectionsView extends StatelessWidget {
const SizedBox(height: 24),
Observer(
builder: (_) {
if (walletKitService.isLoadingConnections) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Center(
child: Column(
spacing: 12,
children: [
const CupertinoActivityIndicator(),
Text(
"${S.of(context).loading}...",
style: theme.textTheme.bodyMedium?.copyWith(
color: colors.onSurfaceVariant,
),
),
],
),
),
);
}
if (walletKitService.pairings.isEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
@@ -20,7 +20,9 @@ class NewSimpleCheckbox extends StatelessWidget {
decoration: BoxDecoration(
color: value
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surfaceContainerHighest,
: Theme.of(context).brightness == Brightness.light
? Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(80)
: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: const BorderRadius.all(Radius.circular(50)),
),
height: 24,