mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Explore-Reown-For-WalletConnect (#2156)
* Reown WalletConnect Implementation - Setup for Solana Chain - Setup for EVM chain - Enable signing messages and transactions on Solana Chain - Fix issues related to signinig of transactions on Solana chain - Decode mesage to human readable format - WIP * Reown WalletConnect Implementation Continuation: - Add readable text for signedTypedDatav4 and sendTransaction event requests - Modify styling for bottomsheetModel - Switch minimum deployment target to 13.0 - Gracefully handle errors - Fix issues discovered while testing across dApps * chore: ReownWalletConnectImplementation (Cont.) - Remove unneeded code and files - Rename files to match classes and vice versa - Add maven jitpack repository for reown.com/yttrium repo - Add better folder structuring to entire feature - Add localization for all feature related texts - Add testnet and devnet access for Solana Chain Service - Add a better error message for Moralis NFT fetch * feat: - Add field to enter wc uri directly - Handle bottomsheet not coming up after initial display - Remove unneeded print statements - Remove double display of success popup for session proposal bottomsheets * fix: - Add visual feedback to WC RelayClient Errors - Adjust theme for bottomSheets * Testing a theroy * Temporarily add flags back * Remove added flags and update proguard rules * fix(android): update proguard-rules.pro and drop minify flags (#2199) Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * fix: Switch bottomsheet result from signOne to signAll --------- Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
cyan
parent
526796e2d7
commit
e6c9cf54fb
@@ -1,5 +0,0 @@
|
||||
abstract class ChainService {
|
||||
String getNamespace();
|
||||
String getChainId();
|
||||
List<String> getEvents();
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||
import 'package:eth_sig_util/util/utils.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
import '../chain_service.dart';
|
||||
import '../../wallet_connect_key_service.dart';
|
||||
|
||||
class EvmChainServiceImpl implements ChainService {
|
||||
final AppStore appStore;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final Web3Wallet wallet;
|
||||
final WalletConnectKeyService wcKeyService;
|
||||
|
||||
static const namespace = 'eip155';
|
||||
static const pSign = 'personal_sign';
|
||||
static const eSign = 'eth_sign';
|
||||
static const eSignTransaction = 'eth_signTransaction';
|
||||
static const eSignTypedData = 'eth_signTypedData_v4';
|
||||
static const eSendTransaction = 'eth_sendTransaction';
|
||||
|
||||
final EVMChainId reference;
|
||||
|
||||
final Web3Client ethClient;
|
||||
|
||||
EvmChainServiceImpl({
|
||||
required this.reference,
|
||||
required this.appStore,
|
||||
required this.wcKeyService,
|
||||
required this.bottomSheetService,
|
||||
required this.wallet,
|
||||
Web3Client? web3Client,
|
||||
}) : ethClient = web3Client ??
|
||||
Web3Client(
|
||||
appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
|
||||
http.Client(),
|
||||
) {
|
||||
for (final String event in getEvents()) {
|
||||
wallet.registerEventEmitter(chainId: getChainId(), event: event);
|
||||
}
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: pSign,
|
||||
handler: personalSign,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSign,
|
||||
handler: ethSign,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSignTransaction,
|
||||
handler: ethSignTransaction,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSendTransaction,
|
||||
handler: ethSignTransaction,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSignTypedData,
|
||||
handler: ethSignTypedData,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
@override
|
||||
String getChainId() {
|
||||
return reference.chain();
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getEvents() {
|
||||
return ['chainChanged', 'accountsChanged'];
|
||||
}
|
||||
|
||||
Future<String?> requestAuthorization(String? text) async {
|
||||
// Show the bottom sheet
|
||||
final bool? isApproved = await bottomSheetService.queueBottomSheet(
|
||||
widget: Web3RequestModal(
|
||||
child: ConnectionWidget(
|
||||
title: S.current.signTransaction,
|
||||
info: [
|
||||
ConnectionModel(
|
||||
text: text,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
) as bool?;
|
||||
|
||||
if (isApproved != null && isApproved == false) {
|
||||
return 'User rejected signature';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String> personalSign(String topic, dynamic parameters) async {
|
||||
log('received personal sign request: $parameters');
|
||||
|
||||
final String message;
|
||||
if (parameters[0] == null) {
|
||||
message = '';
|
||||
} else {
|
||||
message = parameters[0].toString().utf8Message;
|
||||
}
|
||||
|
||||
final String? authError = await requestAuthorization(message);
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the private key
|
||||
final List<ChainKeyModel> keys = wcKeyService
|
||||
.getKeysForChain(appStore.wallet!);
|
||||
|
||||
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
final String signature = hex.encode(
|
||||
credentials.signPersonalMessageToUint8List(Uint8List.fromList(utf8.encode(message))),
|
||||
);
|
||||
|
||||
return '0x$signature';
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: '${S.current.errorGettingCredentials} ${e.toString()}',
|
||||
),
|
||||
);
|
||||
return 'Failed: Error while getting credentials';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> ethSign(String topic, dynamic parameters) async {
|
||||
log('received eth sign request: $parameters');
|
||||
|
||||
final String message;
|
||||
if (parameters[1] == null) {
|
||||
message = '';
|
||||
} else {
|
||||
message = parameters[1].toString().utf8Message;
|
||||
}
|
||||
|
||||
final String? authError = await requestAuthorization(message);
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the private key
|
||||
final List<ChainKeyModel> keys = wcKeyService
|
||||
.getKeysForChain(appStore.wallet!);
|
||||
|
||||
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
final String signature = hex.encode(
|
||||
credentials.signPersonalMessageToUint8List(
|
||||
Uint8List.fromList(utf8.encode(message)),
|
||||
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
|
||||
),
|
||||
);
|
||||
log(signature);
|
||||
|
||||
return '0x$signature';
|
||||
} catch (e) {
|
||||
log('error: ${e.toString()}');
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: '${S.current.error}: ${e.toString()}'),
|
||||
);
|
||||
return 'Failed';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> ethSignTransaction(String topic, dynamic parameters) async {
|
||||
log('received eth sign transaction request: $parameters');
|
||||
|
||||
final paramsData = parameters[0] as Map<String, dynamic>;
|
||||
|
||||
final message = _convertToReadable(paramsData);
|
||||
|
||||
final String? authError = await requestAuthorization(message);
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
// Load the private key
|
||||
final List<ChainKeyModel> keys = wcKeyService
|
||||
.getKeysForChain(appStore.wallet!);
|
||||
|
||||
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
WCEthereumTransactionModel ethTransaction =
|
||||
WCEthereumTransactionModel.fromJson(parameters[0] as Map<String, dynamic>);
|
||||
|
||||
final transaction = Transaction(
|
||||
from: EthereumAddress.fromHex(ethTransaction.from),
|
||||
to: EthereumAddress.fromHex(ethTransaction.to),
|
||||
maxGas: ethTransaction.gasLimit != null ? int.tryParse(ethTransaction.gasLimit ?? "") : null,
|
||||
gasPrice: ethTransaction.gasPrice != null
|
||||
? EtherAmount.inWei(BigInt.parse(ethTransaction.gasPrice ?? ""))
|
||||
: null,
|
||||
value: EtherAmount.inWei(BigInt.parse(ethTransaction.value)),
|
||||
data: hexToBytes(ethTransaction.data ?? ""),
|
||||
nonce: ethTransaction.nonce != null ? int.tryParse(ethTransaction.nonce ?? "") : null,
|
||||
);
|
||||
|
||||
try {
|
||||
final result = await ethClient.sendTransaction(
|
||||
credentials,
|
||||
transaction,
|
||||
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
|
||||
);
|
||||
|
||||
log('Result: $result');
|
||||
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: S.current.awaitDAppProcessing,
|
||||
isError: false,
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
log('An error has occurred while signing transaction: ${e.toString()}');
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: '${S.current.errorSigningTransaction}: ${e.toString()}',
|
||||
),
|
||||
);
|
||||
return 'Failed';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> ethSignTypedData(String topic, dynamic parameters) async {
|
||||
log('received eth sign typed data request: $parameters');
|
||||
final String? data = parameters[1] as String?;
|
||||
|
||||
final String? authError = await requestAuthorization(data);
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
final List<ChainKeyModel> keys = wcKeyService
|
||||
.getKeysForChain(appStore.wallet!);
|
||||
|
||||
return EthSigUtil.signTypedData(
|
||||
privateKey: keys[0].privateKey,
|
||||
jsonData: data ?? '',
|
||||
version: TypedDataVersion.V4,
|
||||
);
|
||||
}
|
||||
|
||||
String _convertToReadable(Map<String, dynamic> data) {
|
||||
final tokenName = getTokenNameBasedOnWalletType(appStore.wallet!.type);
|
||||
String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString();
|
||||
String value = data['value'] != null
|
||||
? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() +
|
||||
' $tokenName'
|
||||
: '0 $tokenName';
|
||||
String from = data['from'] as String;
|
||||
String to = data['to'] as String;
|
||||
|
||||
return '''
|
||||
Gas: $gas\n
|
||||
Value: $value\n
|
||||
From: $from\n
|
||||
To: $to
|
||||
''';
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
class SolanaSignMessage {
|
||||
final String pubkey;
|
||||
final String message;
|
||||
|
||||
SolanaSignMessage({
|
||||
required this.pubkey,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
factory SolanaSignMessage.fromJson(Map<String, dynamic> json) {
|
||||
return SolanaSignMessage(
|
||||
pubkey: json['pubkey'] as String,
|
||||
message: json['message'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'pubkey': pubkey,
|
||||
'message': message,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SolanaSignMessage(pubkey: $pubkey, message: $message)';
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
class SolanaSignTransaction {
|
||||
final String? feePayer;
|
||||
final String? recentBlockhash;
|
||||
final String transaction;
|
||||
final List<SolanaInstruction>? instructions;
|
||||
|
||||
SolanaSignTransaction({
|
||||
required this.feePayer,
|
||||
required this.recentBlockhash,
|
||||
required this.instructions,
|
||||
required this.transaction,
|
||||
});
|
||||
|
||||
factory SolanaSignTransaction.fromJson(Map<String, dynamic> json) {
|
||||
return SolanaSignTransaction(
|
||||
feePayer:json['feePayer'] !=null ? json['feePayer'] as String: null,
|
||||
recentBlockhash: json['recentBlockhash']!=null? json['recentBlockhash'] as String: null,
|
||||
instructions:json['instructions']!=null? (json['instructions'] as List<dynamic>)
|
||||
.map((e) => SolanaInstruction.fromJson(e as Map<String, dynamic>))
|
||||
.toList(): null,
|
||||
transaction: json['transaction'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'feePayer': feePayer,
|
||||
'recentBlockhash': recentBlockhash,
|
||||
'instructions': instructions,
|
||||
'transaction': transaction,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SolanaSignTransaction(feePayer: $feePayer, recentBlockhash: $recentBlockhash, instructions: $instructions, transaction: $transaction)';
|
||||
}
|
||||
}
|
||||
|
||||
class SolanaInstruction {
|
||||
final String programId;
|
||||
final List<SolanaKeyMetadata> keys;
|
||||
final List<int> data;
|
||||
|
||||
SolanaInstruction({
|
||||
required this.programId,
|
||||
required this.keys,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory SolanaInstruction.fromJson(Map<String, dynamic> json) {
|
||||
return SolanaInstruction(
|
||||
programId: json['programId'] as String,
|
||||
keys: (json['keys'] as List<dynamic>)
|
||||
.map((e) => SolanaKeyMetadata.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
data: (json['data'] as List<dynamic>).map((e) => e as int).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'programId': programId,
|
||||
'keys': keys,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SolanaInstruction(programId: $programId, keys: $keys, data: $data)';
|
||||
}
|
||||
}
|
||||
|
||||
class SolanaKeyMetadata {
|
||||
final String pubkey;
|
||||
final bool isSigner;
|
||||
final bool isWritable;
|
||||
|
||||
SolanaKeyMetadata({
|
||||
required this.pubkey,
|
||||
required this.isSigner,
|
||||
required this.isWritable,
|
||||
});
|
||||
|
||||
factory SolanaKeyMetadata.fromJson(Map<String, dynamic> json) {
|
||||
return SolanaKeyMetadata(
|
||||
pubkey: json['pubkey'] as String,
|
||||
isSigner: json['isSigner'] as bool,
|
||||
isWritable: json['isWritable'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
'pubkey': pubkey,
|
||||
'isSigner': isSigner,
|
||||
'isWritable': isWritable,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SolanaKeyMetadata(pubkey: $pubkey, isSigner: $isSigner, isWritable: $isWritable)';
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'solana_chain_service.dart';
|
||||
|
||||
enum SolanaChainId {
|
||||
mainnet,
|
||||
// testnet,
|
||||
// devnet,
|
||||
}
|
||||
|
||||
extension SolanaChainIdX on SolanaChainId {
|
||||
String chain() {
|
||||
String name = '';
|
||||
|
||||
switch (this) {
|
||||
case SolanaChainId.mainnet:
|
||||
name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ';
|
||||
// solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
|
||||
break;
|
||||
// case SolanaChainId.devnet:
|
||||
// name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K';
|
||||
// // solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1
|
||||
// break;
|
||||
// case SolanaChainId.testnet:
|
||||
// name = '';
|
||||
// // solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z
|
||||
// break;
|
||||
}
|
||||
|
||||
return '${SolanaChainServiceImpl.namespace}:$name';
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/solana/entities/solana_sign_message.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/solana/solana_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||
import 'package:cw_core/solana_rpc_http_service.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:on_chain/solana/solana.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
import '../chain_service.dart';
|
||||
import '../../wallet_connect_key_service.dart';
|
||||
import 'entities/solana_sign_transaction.dart';
|
||||
|
||||
class SolanaChainServiceImpl implements ChainService {
|
||||
final BottomSheetService bottomSheetService;
|
||||
final Web3Wallet wallet;
|
||||
final WalletConnectKeyService wcKeyService;
|
||||
|
||||
static const namespace = 'solana';
|
||||
static const solSignTransaction = 'solana_signTransaction';
|
||||
static const solSignMessage = 'solana_signMessage';
|
||||
|
||||
final SolanaChainId reference;
|
||||
|
||||
final SolanaRPC solanaProvider;
|
||||
|
||||
final SolanaPrivateKey? ownerPrivateKey;
|
||||
|
||||
SolanaChainServiceImpl({
|
||||
required this.reference,
|
||||
required this.wcKeyService,
|
||||
required this.bottomSheetService,
|
||||
required this.wallet,
|
||||
required this.ownerPrivateKey,
|
||||
required String formattedRPCUrl,
|
||||
SolanaRPC? solanaProvider,
|
||||
}) : solanaProvider = solanaProvider ?? SolanaRPC(SolanaRPCHTTPService(url: formattedRPCUrl)) {
|
||||
for (final String event in getEvents()) {
|
||||
wallet.registerEventEmitter(chainId: getChainId(), event: event);
|
||||
}
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: solSignTransaction,
|
||||
handler: solanaSignTransaction,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: solSignMessage,
|
||||
handler: solanaSignMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
@override
|
||||
String getChainId() {
|
||||
return reference.chain();
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getEvents() {
|
||||
return ['chainChanged', 'accountsChanged'];
|
||||
}
|
||||
|
||||
Future<String?> requestAuthorization(String? text) async {
|
||||
// Show the bottom sheet
|
||||
final bool? isApproved = await bottomSheetService.queueBottomSheet(
|
||||
widget: Web3RequestModal(
|
||||
child: ConnectionWidget(
|
||||
title: S.current.signTransaction,
|
||||
info: [
|
||||
ConnectionModel(
|
||||
text: text,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
) as bool?;
|
||||
|
||||
if (isApproved != null && isApproved == false) {
|
||||
return 'User rejected signature';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String> solanaSignTransaction(String topic, dynamic parameters) async {
|
||||
log('received solana sign transaction request $parameters');
|
||||
|
||||
final solanaSignTx = SolanaSignTransaction.fromJson(parameters as Map<String, dynamic>);
|
||||
|
||||
final String? authError = await requestAuthorization('Confirm request to sign transaction?');
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert transaction string to bytes
|
||||
List<int> transactionBytes = base64Decode(solanaSignTx.transaction);
|
||||
|
||||
final message = SolanaTransactionUtils.deserializeMessageLegacy(transactionBytes);
|
||||
|
||||
final sign = ownerPrivateKey!.sign(message.serialize());
|
||||
|
||||
final signature = solanaProvider.request(
|
||||
SolanaRPCSendTransaction(
|
||||
encodedTransaction: Base58Encoder.encode(sign),
|
||||
commitment: Commitment.confirmed,
|
||||
),
|
||||
);
|
||||
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: S.current.awaitDAppProcessing,
|
||||
isError: false,
|
||||
),
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (e) {
|
||||
log('An error has occurred while signing transaction: ${e.toString()}');
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: '${S.current.errorSigningTransaction}: ${e.toString()}',
|
||||
),
|
||||
);
|
||||
return 'Failed';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> solanaSignMessage(String topic, dynamic parameters) async {
|
||||
log('received solana sign message request: $parameters');
|
||||
|
||||
final solanaSignMessage = SolanaSignMessage.fromJson(parameters as Map<String, dynamic>);
|
||||
|
||||
final String? authError = await requestAuthorization('Confirm request to sign message?');
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
List<int>? sign;
|
||||
|
||||
try {
|
||||
sign = ownerPrivateKey!.sign(Base58Decoder.decode(solanaSignMessage.message));
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
|
||||
if (sign == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
final signature = Base58Encoder.encode(sign);
|
||||
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
class WCEthereumTransactionModel {
|
||||
final String from;
|
||||
final String to;
|
||||
final String value;
|
||||
final String? nonce;
|
||||
final String? gasPrice;
|
||||
final String? maxFeePerGas;
|
||||
final String? maxPriorityFeePerGas;
|
||||
final String? gas;
|
||||
final String? gasLimit;
|
||||
final String? data;
|
||||
|
||||
WCEthereumTransactionModel({
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.value,
|
||||
this.nonce,
|
||||
this.gasPrice,
|
||||
this.maxFeePerGas,
|
||||
this.maxPriorityFeePerGas,
|
||||
this.gas,
|
||||
this.gasLimit,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory WCEthereumTransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
return WCEthereumTransactionModel(
|
||||
from: json['from'] as String,
|
||||
to: json['to'] as String,
|
||||
value: json['value'] as String,
|
||||
nonce: json['nonce'] as String?,
|
||||
gasPrice: json['gasPrice'] as String?,
|
||||
maxFeePerGas: json['maxFeePerGas'] as String?,
|
||||
maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?,
|
||||
gas: json['gas'] as String?,
|
||||
gasLimit: json['gasLimit'] as String?,
|
||||
data: json['data'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'from': from,
|
||||
'to': to,
|
||||
'value': value,
|
||||
'nonce': nonce,
|
||||
'gasPrice': gasPrice,
|
||||
'maxFeePerGas': maxFeePerGas,
|
||||
'maxPriorityFeePerGas': maxPriorityFeePerGas,
|
||||
'gas': gas,
|
||||
'gasLimit': gasLimit,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'EthereumTransactionModel(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)';
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
class AuthRequestModel {
|
||||
final String iss;
|
||||
final AuthRequest request;
|
||||
|
||||
AuthRequestModel({
|
||||
required this.iss,
|
||||
required this.request,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthRequestModel(iss: $iss, request: $request)';
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
class ChainKeyModel {
|
||||
final List<String> chains;
|
||||
final String privateKey;
|
||||
final String publicKey;
|
||||
|
||||
ChainKeyModel({
|
||||
required this.chains,
|
||||
required this.privateKey,
|
||||
required this.publicKey,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChainKeyModel(chains: $chains, privateKey: $privateKey, publicKey: $publicKey)';
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
class SessionRequestModel {
|
||||
final ProposalData request;
|
||||
|
||||
SessionRequestModel({
|
||||
required this.request,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SessionRequestModel(request: $request)';
|
||||
}
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:on_chain/solana/solana.dart' hide Store;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import 'chain_service/solana/solana_chain_id.dart';
|
||||
import 'chain_service/solana/solana_chain_service.dart';
|
||||
import 'wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
part 'web3wallet_service.g.dart';
|
||||
|
||||
class Web3WalletService = Web3WalletServiceBase with _$Web3WalletService;
|
||||
|
||||
abstract class Web3WalletServiceBase with Store {
|
||||
final AppStore appStore;
|
||||
final SharedPreferences sharedPreferences;
|
||||
final BottomSheetService _bottomSheetHandler;
|
||||
final WalletConnectKeyService walletKeyService;
|
||||
|
||||
late Web3Wallet _web3Wallet;
|
||||
|
||||
@observable
|
||||
bool isInitialized;
|
||||
|
||||
/// The list of requests from the dapp
|
||||
/// Potential types include, but aren't limited to:
|
||||
/// [SessionProposalEvent], [AuthRequest]
|
||||
@observable
|
||||
ObservableList<PairingInfo> pairings;
|
||||
|
||||
@observable
|
||||
ObservableList<SessionData> sessions;
|
||||
|
||||
@observable
|
||||
ObservableList<StoredCacao> auth;
|
||||
|
||||
Web3WalletServiceBase(
|
||||
this._bottomSheetHandler, this.walletKeyService, this.appStore, this.sharedPreferences)
|
||||
: pairings = ObservableList<PairingInfo>(),
|
||||
sessions = ObservableList<SessionData>(),
|
||||
auth = ObservableList<StoredCacao>(),
|
||||
isInitialized = false;
|
||||
|
||||
@action
|
||||
void create() {
|
||||
// Create the web3wallet client
|
||||
_web3Wallet = Web3Wallet(
|
||||
core: Core(projectId: secrets.walletConnectProjectId),
|
||||
metadata: const PairingMetadata(
|
||||
name: 'Cake Wallet',
|
||||
description: 'Cake Wallet',
|
||||
url: 'https://cakewallet.com',
|
||||
icons: ['https://cakewallet.com/assets/image/cake_logo.png'],
|
||||
),
|
||||
);
|
||||
|
||||
// Setup our accounts
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeys(appStore.wallet!);
|
||||
for (final chainKey in chainKeys) {
|
||||
for (final chainId in chainKey.chains) {
|
||||
_web3Wallet.registerAccount(
|
||||
chainId: chainId,
|
||||
accountAddress: chainKey.publicKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup our listeners
|
||||
log('Created instance of web3wallet');
|
||||
_web3Wallet.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid);
|
||||
_web3Wallet.core.pairing.onPairingCreate.subscribe(_onPairingCreate);
|
||||
_web3Wallet.core.pairing.onPairingDelete.subscribe(_onPairingDelete);
|
||||
_web3Wallet.core.pairing.onPairingExpire.subscribe(_onPairingDelete);
|
||||
_web3Wallet.pairings.onSync.subscribe(_onPairingsSync);
|
||||
_web3Wallet.onSessionProposal.subscribe(_onSessionProposal);
|
||||
_web3Wallet.onSessionProposalError.subscribe(_onSessionProposalError);
|
||||
_web3Wallet.onSessionConnect.subscribe(_onSessionConnect);
|
||||
_web3Wallet.onAuthRequest.subscribe(_onAuthRequest);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> init() async {
|
||||
// Await the initialization of the web3wallet
|
||||
log('Intializing web3wallet');
|
||||
if (!isInitialized) {
|
||||
try {
|
||||
await _web3Wallet.init();
|
||||
log('Initialized');
|
||||
isInitialized = true;
|
||||
} catch (e) {
|
||||
log('Experimentallllll: $e');
|
||||
isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
_refreshPairings();
|
||||
|
||||
final newSessions = _web3Wallet.sessions.getAll();
|
||||
sessions.addAll(newSessions);
|
||||
|
||||
final newAuthRequests = _web3Wallet.completeRequests.getAll();
|
||||
auth.addAll(newAuthRequests);
|
||||
|
||||
if (isEVMCompatibleChain(appStore.wallet!.type)) {
|
||||
for (final cId in EVMChainId.values) {
|
||||
EvmChainServiceImpl(
|
||||
reference: cId,
|
||||
appStore: appStore,
|
||||
wcKeyService: walletKeyService,
|
||||
bottomSheetService: _bottomSheetHandler,
|
||||
wallet: _web3Wallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (appStore.wallet!.type == WalletType.solana) {
|
||||
for (final cId in SolanaChainId.values) {
|
||||
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
|
||||
|
||||
String formattedUrl;
|
||||
String protocolUsed = node.isSSL ? "https" : "http";
|
||||
|
||||
if (node.uriRaw == 'rpc.ankr.com') {
|
||||
String ankrApiKey = secrets.ankrApiKey;
|
||||
|
||||
formattedUrl = '$protocolUsed://${node.uriRaw}/$ankrApiKey';
|
||||
} else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
|
||||
String chainStackApiKey = secrets.chainStackApiKey;
|
||||
|
||||
formattedUrl = '$protocolUsed://${node.uriRaw}/$chainStackApiKey';
|
||||
} else {
|
||||
formattedUrl = '$protocolUsed://${node.uriRaw}';
|
||||
}
|
||||
|
||||
SolanaChainServiceImpl(
|
||||
reference: cId,
|
||||
formattedRPCUrl: formattedUrl,
|
||||
wcKeyService: walletKeyService,
|
||||
bottomSheetService: _bottomSheetHandler,
|
||||
wallet: _web3Wallet,
|
||||
ownerPrivateKey: SolanaPrivateKey.fromSeedHex(solana!.getPrivateKey(appStore.wallet!)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
FutureOr<void> onDispose() {
|
||||
log('web3wallet dispose');
|
||||
_web3Wallet.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid);
|
||||
_web3Wallet.pairings.onSync.unsubscribe(_onPairingsSync);
|
||||
_web3Wallet.onSessionProposal.unsubscribe(_onSessionProposal);
|
||||
_web3Wallet.onSessionProposalError.unsubscribe(_onSessionProposalError);
|
||||
_web3Wallet.onSessionConnect.unsubscribe(_onSessionConnect);
|
||||
_web3Wallet.onAuthRequest.unsubscribe(_onAuthRequest);
|
||||
_web3Wallet.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete);
|
||||
_web3Wallet.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete);
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
Web3Wallet getWeb3Wallet() {
|
||||
return _web3Wallet;
|
||||
}
|
||||
|
||||
void _onPairingsSync(StoreSyncEvent? args) {
|
||||
if (args != null) {
|
||||
_refreshPairings();
|
||||
}
|
||||
}
|
||||
|
||||
void _onPairingDelete(PairingEvent? event) {
|
||||
_refreshPairings();
|
||||
}
|
||||
|
||||
Future<void> _onSessionProposalError(SessionProposalErrorEvent? args) async {
|
||||
log(args.toString());
|
||||
}
|
||||
|
||||
void _onSessionProposal(SessionProposalEvent? args) async {
|
||||
if (args != null) {
|
||||
final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
|
||||
final Widget modalWidget = Web3RequestModal(
|
||||
child: ConnectionRequestWidget(
|
||||
chaindIdNamespace: chaindIdNamespace,
|
||||
wallet: _web3Wallet,
|
||||
sessionProposal: SessionRequestModel(request: args.params),
|
||||
),
|
||||
);
|
||||
// show the bottom sheet
|
||||
final bool? isApproved = await _bottomSheetHandler.queueBottomSheet(
|
||||
widget: modalWidget,
|
||||
) as bool?;
|
||||
|
||||
if (isApproved != null && isApproved) {
|
||||
_web3Wallet.approveSession(
|
||||
id: args.id,
|
||||
namespaces: args.params.generatedNamespaces!,
|
||||
);
|
||||
} else {
|
||||
_web3Wallet.rejectSession(
|
||||
id: args.id,
|
||||
reason: Errors.getSdkError(
|
||||
Errors.USER_REJECTED,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _onPairingInvalid(PairingInvalidEvent? args) {
|
||||
log('Pairing Invalid Event: $args');
|
||||
_bottomSheetHandler.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: '${S.current.pairingInvalidEvent}: $args'),
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> pairWithUri(Uri uri) async {
|
||||
try {
|
||||
log('Pairing with URI: $uri');
|
||||
await _web3Wallet.pair(uri: uri);
|
||||
} on WalletConnectError catch (e) {
|
||||
_bottomSheetHandler.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: e.message),
|
||||
);
|
||||
} catch (e) {
|
||||
_bottomSheetHandler.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _refreshPairings() {
|
||||
printV('Refreshing pairings');
|
||||
pairings.clear();
|
||||
|
||||
final allPairings = _web3Wallet.pairings.getAll();
|
||||
|
||||
final keyForWallet = getKeyForStoringTopicsForWallet();
|
||||
|
||||
if (keyForWallet.isEmpty) return;
|
||||
|
||||
final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet);
|
||||
|
||||
final filteredPairings =
|
||||
allPairings.where((pairing) => currentTopicsForWallet.contains(pairing.topic)).toList();
|
||||
|
||||
pairings.addAll(filteredPairings);
|
||||
}
|
||||
|
||||
void _onPairingCreate(PairingEvent? args) {
|
||||
log('Pairing Create Event: $args');
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _onSessionConnect(SessionConnect? args) async {
|
||||
if (args != null) {
|
||||
log('Session Connected $args');
|
||||
|
||||
await savePairingTopicToLocalStorage(args.session.pairingTopic);
|
||||
|
||||
sessions.add(args.session);
|
||||
|
||||
_refreshPairings();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _onAuthRequest(AuthRequest? args) async {
|
||||
if (args != null) {
|
||||
final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
// Create the message to be signed
|
||||
final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}';
|
||||
final Widget modalWidget = Web3RequestModal(
|
||||
child: ConnectionRequestWidget(
|
||||
chaindIdNamespace: chaindIdNamespace,
|
||||
wallet: _web3Wallet,
|
||||
authRequest: AuthRequestModel(iss: iss, request: args),
|
||||
),
|
||||
);
|
||||
final bool? isAuthenticated = await _bottomSheetHandler.queueBottomSheet(
|
||||
widget: modalWidget,
|
||||
) as bool?;
|
||||
|
||||
if (isAuthenticated != null && isAuthenticated) {
|
||||
final String message = _web3Wallet.formatAuthMessage(
|
||||
iss: iss,
|
||||
cacaoPayload: CacaoRequestPayload.fromPayloadParams(
|
||||
args.payloadParams,
|
||||
),
|
||||
);
|
||||
|
||||
final String sig = EthSigUtil.signPersonalMessage(
|
||||
message: Uint8List.fromList(message.codeUnits),
|
||||
privateKey: chainKeys.first.privateKey,
|
||||
);
|
||||
|
||||
await _web3Wallet.respondAuthRequest(
|
||||
id: args.id,
|
||||
iss: iss,
|
||||
signature: CacaoSignature(
|
||||
t: CacaoSignature.EIP191,
|
||||
s: sig,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await _web3Wallet.respondAuthRequest(
|
||||
id: args.id,
|
||||
iss: iss,
|
||||
error: Errors.getSdkError(
|
||||
Errors.USER_REJECTED_AUTH,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> disconnectSession(String topic) async {
|
||||
final session = sessions.firstWhere((element) => element.pairingTopic == topic);
|
||||
|
||||
await _web3Wallet.core.pairing.disconnect(topic: topic);
|
||||
await _web3Wallet.disconnectSession(
|
||||
topic: session.topic, reason: Errors.getSdkError(Errors.USER_DISCONNECTED));
|
||||
}
|
||||
|
||||
@action
|
||||
List<SessionData> getSessionsForPairingInfo(PairingInfo pairing) {
|
||||
return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
|
||||
}
|
||||
|
||||
String getKeyForStoringTopicsForWallet() {
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
|
||||
if (chainKeys.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
final keyForPairingTopic =
|
||||
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
|
||||
|
||||
return keyForPairingTopic;
|
||||
}
|
||||
|
||||
List<String> 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<dynamic> jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
|
||||
// Cast each item to a string
|
||||
return jsonList.map((item) => item as String).toList();
|
||||
}
|
||||
|
||||
Future<void> 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);
|
||||
|
||||
printV(pairingTopicsForWallet);
|
||||
|
||||
bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic);
|
||||
printV('Is Pairing Topic Saved: $isPairingTopicAlreadySaved');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-9
@@ -20,9 +20,6 @@ import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/core/selectable_option.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
@@ -37,6 +34,9 @@ import 'package:cake_wallet/haven/cw_haven.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.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/services/walletkit_service.dart';
|
||||
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
@@ -636,11 +636,15 @@ Future<void> setup({
|
||||
|
||||
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl());
|
||||
|
||||
getIt.registerLazySingleton<Web3WalletService>(() {
|
||||
final Web3WalletService web3WalletService = Web3WalletService(getIt.get<BottomSheetService>(),
|
||||
getIt.get<WalletConnectKeyService>(), appStore, getIt.get<SharedPreferences>());
|
||||
web3WalletService.create();
|
||||
return web3WalletService;
|
||||
getIt.registerLazySingleton<WalletKitService>(() {
|
||||
final WalletKitService walletKitService = WalletKitService(
|
||||
getIt.get<BottomSheetService>(),
|
||||
getIt.get<WalletConnectKeyService>(),
|
||||
appStore,
|
||||
getIt.get<SharedPreferences>(),
|
||||
);
|
||||
walletKitService.create();
|
||||
return walletKitService;
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => BalancePage(
|
||||
@@ -1441,7 +1445,8 @@ Future<void> setup({
|
||||
});
|
||||
|
||||
getIt.registerFactory(
|
||||
() => WalletConnectConnectionsView(web3walletService: getIt.get<Web3WalletService>()));
|
||||
() => WalletConnectConnectionsView(walletKitService: getIt.get<WalletKitService>()),
|
||||
);
|
||||
|
||||
getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
|
||||
getIt.registerFactory<TorPage>(() => TorPage(getIt.get<AppStore>()));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
|
||||
import 'package:cake_wallet/entities/load_current_wallet.dart';
|
||||
@@ -8,6 +7,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
@@ -71,7 +71,7 @@ void startAuthenticationStateChange(
|
||||
buttonAction: () => Navigator.of(context).pop()),
|
||||
);
|
||||
await loadCurrentWallet();
|
||||
getIt.get<BottomSheetService>().resetCurrentSheet();
|
||||
getIt.get<BottomSheetService>().showNext();
|
||||
await navigatorKey.currentState!
|
||||
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/solana/solana_chain_id.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.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/chain_service/solana/solana_supported_methods.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
bool isEVMCompatibleChain(WalletType walletType) {
|
||||
@@ -47,6 +49,19 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getChainSupportedMethodsOnWalletType(WalletType walletType) {
|
||||
switch (walletType) {
|
||||
case WalletType.ethereum:
|
||||
return EVMSupportedMethods.values.map((e) => e.name).toList();
|
||||
case WalletType.polygon:
|
||||
return EVMSupportedMethods.values.map((e) => e.name).toList();
|
||||
case WalletType.solana:
|
||||
return SolanaSupportedMethods.values.map((e) => e.name).toList();
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
int getChainIdBasedOnWalletType(WalletType walletType) {
|
||||
switch (walletType) {
|
||||
case WalletType.polygon:
|
||||
|
||||
+2
-2
@@ -4,7 +4,6 @@ import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||
@@ -106,6 +105,7 @@ import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
@@ -777,7 +777,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.walletConnectConnectionsListing:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => WalletConnectConnectionsView(
|
||||
web3walletService: getIt.get<Web3WalletService>(),
|
||||
walletKitService: getIt.get<WalletKitService>(),
|
||||
launchUri: settings.arguments as Uri?,
|
||||
));
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/haven_wallet_removal_popup.dart';
|
||||
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
@@ -8,7 +7,8 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sideba
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||
|
||||
+2
@@ -6,11 +6,13 @@ class BottomSheetQueueItemModel {
|
||||
final Widget widget;
|
||||
final bool isModalDismissible;
|
||||
final Completer<dynamic> completer;
|
||||
final int closeAfter;
|
||||
|
||||
BottomSheetQueueItemModel({
|
||||
required this.widget,
|
||||
required this.completer,
|
||||
this.isModalDismissible = false,
|
||||
this.closeAfter = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
class ConnectionModel {
|
||||
class WCConnectionModel {
|
||||
final String? title;
|
||||
final String? text;
|
||||
final List<String>? elements;
|
||||
final Map<String, void Function()>? elementActions;
|
||||
|
||||
ConnectionModel({
|
||||
WCConnectionModel({
|
||||
this.title,
|
||||
this.text,
|
||||
this.elements,
|
||||
@@ -13,6 +13,6 @@ class ConnectionModel {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WalletConnectRequestModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)';
|
||||
return 'WCConnectionModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)';
|
||||
}
|
||||
}
|
||||
+23
-5
@@ -1,19 +1,24 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/bottom_sheet_queue_item_model.dart';
|
||||
import 'dart:collection';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/models/bottom_sheet_queue_item_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum WCBottomSheetResult { reject, one, all }
|
||||
|
||||
abstract class BottomSheetService {
|
||||
abstract final ValueNotifier<BottomSheetQueueItemModel?> currentSheet;
|
||||
|
||||
Future<dynamic> queueBottomSheet({
|
||||
required Widget widget,
|
||||
bool isModalDismissible = false,
|
||||
int closeAfter = 0,
|
||||
});
|
||||
|
||||
void resetCurrentSheet();
|
||||
void showNext();
|
||||
}
|
||||
|
||||
class BottomSheetServiceImpl implements BottomSheetService {
|
||||
Queue<BottomSheetQueueItemModel> queue = Queue<BottomSheetQueueItemModel>();
|
||||
|
||||
@override
|
||||
final ValueNotifier<BottomSheetQueueItemModel?> currentSheet = ValueNotifier(null);
|
||||
@@ -21,6 +26,7 @@ class BottomSheetServiceImpl implements BottomSheetService {
|
||||
@override
|
||||
Future<dynamic> queueBottomSheet({
|
||||
required Widget widget,
|
||||
int closeAfter = 0,
|
||||
bool isModalDismissible = false,
|
||||
}) async {
|
||||
// Create the bottom sheet queue item
|
||||
@@ -28,16 +34,28 @@ class BottomSheetServiceImpl implements BottomSheetService {
|
||||
final queueItem = BottomSheetQueueItemModel(
|
||||
widget: widget,
|
||||
completer: completer,
|
||||
closeAfter: closeAfter,
|
||||
isModalDismissible: isModalDismissible,
|
||||
);
|
||||
|
||||
currentSheet.value = queueItem;
|
||||
// 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;
|
||||
}
|
||||
|
||||
@override
|
||||
void resetCurrentSheet() {
|
||||
currentSheet.value = null;
|
||||
void showNext() {
|
||||
if (queue.isEmpty) {
|
||||
currentSheet.value = null;
|
||||
} else {
|
||||
currentSheet.value = queue.removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -1,5 +1,3 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart';
|
||||
|
||||
enum EVMChainId {
|
||||
ethereum,
|
||||
polygon,
|
||||
@@ -30,6 +28,6 @@ extension EVMChainIdX on EVMChainId {
|
||||
break;
|
||||
}
|
||||
|
||||
return '${EvmChainServiceImpl.namespace}:$name';
|
||||
return 'eip155:$name';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||
import 'package:eth_sig_util/util/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:reown_walletkit/reown_walletkit.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/eth/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.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/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/store/app_store.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
class EvmChainServiceImpl {
|
||||
Map<String, dynamic Function(String, dynamic)> get sessionRequestHandlers => {
|
||||
EVMSupportedMethods.ethSign.name: ethSign,
|
||||
EVMSupportedMethods.ethSignTransaction.name: ethSignTransaction,
|
||||
EVMSupportedMethods.ethSignTypedData.name: ethSignTypedData,
|
||||
EVMSupportedMethods.ethSignTypedDataV4.name: ethSignTypedDataV4,
|
||||
};
|
||||
|
||||
Map<String, dynamic Function(String, dynamic)> get methodRequestHandlers => {
|
||||
EVMSupportedMethods.personalSign.name: personalSign,
|
||||
EVMSupportedMethods.ethSendTransaction.name: ethSendTransaction,
|
||||
};
|
||||
|
||||
EvmChainServiceImpl({
|
||||
required this.reference,
|
||||
required this.appStore,
|
||||
required this.wcKeyService,
|
||||
required this.bottomSheetService,
|
||||
required this.walletKit,
|
||||
Web3Client? web3Client,
|
||||
}) : ethClient = web3Client ??
|
||||
Web3Client(
|
||||
appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
|
||||
http.Client(),
|
||||
) {
|
||||
for (final event in EventsConstants.allEvents) {
|
||||
walletKit.registerEventEmitter(
|
||||
chainId: getChainId(),
|
||||
event: event,
|
||||
);
|
||||
}
|
||||
|
||||
for (var handler in methodRequestHandlers.entries) {
|
||||
walletKit.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: handler.key,
|
||||
handler: handler.value,
|
||||
);
|
||||
}
|
||||
for (var handler in sessionRequestHandlers.entries) {
|
||||
walletKit.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: handler.key,
|
||||
handler: handler.value,
|
||||
);
|
||||
}
|
||||
|
||||
walletKit.onSessionRequest.subscribe(_onSessionRequest);
|
||||
}
|
||||
|
||||
final AppStore appStore;
|
||||
final EVMChainId reference;
|
||||
final Web3Client ethClient;
|
||||
final ReownWalletKit walletKit;
|
||||
final WalletConnectKeyService wcKeyService;
|
||||
final BottomSheetService bottomSheetService;
|
||||
|
||||
String getChainId() => reference.chain();
|
||||
|
||||
Future<void> personalSign(String topic, dynamic parameters) async {
|
||||
debugPrint('personalSign request: $parameters');
|
||||
|
||||
final pRequest = walletKit.pendingRequests.getAll().last;
|
||||
final address = EthUtils.getAddressFromSessionRequest(pRequest);
|
||||
final data = EthUtils.getDataFromSessionRequest(pRequest);
|
||||
final message = EthUtils.getUtf8Message(data.toString());
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
final isApproved = await MethodsUtils.requestApproval(
|
||||
message,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: address,
|
||||
transportType: pRequest.transportType.name,
|
||||
verifyContext: pRequest.verifyContext,
|
||||
);
|
||||
|
||||
if (isApproved) {
|
||||
try {
|
||||
// Load the private key
|
||||
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
|
||||
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
final signature = credentials.signPersonalMessageToUint8List(
|
||||
utf8.encode(message),
|
||||
);
|
||||
final signedTx = bytesToHex(signature, include0x: true);
|
||||
|
||||
isValidSignature(signedTx, message, credentials.address.hex);
|
||||
|
||||
response = response.copyWith(result: signedTx);
|
||||
} catch (e) {
|
||||
debugPrint('personalSign error $e');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
Future<void> ethSign(String topic, dynamic parameters) async {
|
||||
debugPrint('ethSign request: $parameters');
|
||||
|
||||
final pRequest = walletKit.pendingRequests.getAll().last;
|
||||
final address = EthUtils.getAddressFromSessionRequest(pRequest);
|
||||
final data = EthUtils.getDataFromSessionRequest(pRequest);
|
||||
final message = EthUtils.getUtf8Message(data.toString());
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
final isApproved = await MethodsUtils.requestApproval(
|
||||
message,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: address,
|
||||
transportType: pRequest.transportType.name,
|
||||
verifyContext: pRequest.verifyContext,
|
||||
);
|
||||
|
||||
if (isApproved) {
|
||||
try {
|
||||
// Load the private key
|
||||
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
|
||||
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
final signature = credentials.signPersonalMessageToUint8List(
|
||||
utf8.encode(message),
|
||||
);
|
||||
final signedTx = bytesToHex(signature, include0x: true);
|
||||
|
||||
isValidSignature(signedTx, message, credentials.address.hex);
|
||||
|
||||
response = response.copyWith(result: signedTx);
|
||||
} catch (e) {
|
||||
debugPrint('ethSign error $e');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError();
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
Future<void> ethSignTypedData(String topic, dynamic parameters) async {
|
||||
debugPrint('ethSignTypedData request: $parameters');
|
||||
|
||||
final pRequest = walletKit.pendingRequests.getAll().last;
|
||||
final address = EthUtils.getAddressFromSessionRequest(pRequest);
|
||||
final data = EthUtils.getDataFromSessionRequest(pRequest) as String;
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
final isApproved = await MethodsUtils.requestApproval(
|
||||
data,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: address,
|
||||
transportType: pRequest.transportType.name,
|
||||
verifyContext: pRequest.verifyContext,
|
||||
);
|
||||
|
||||
if (isApproved) {
|
||||
try {
|
||||
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
|
||||
|
||||
final signature = EthSigUtil.signTypedData(
|
||||
privateKey: keys[0].privateKey,
|
||||
jsonData: data,
|
||||
version: TypedDataVersion.V4,
|
||||
);
|
||||
|
||||
response = response.copyWith(result: signature);
|
||||
} catch (e) {
|
||||
debugPrint('ethSignTypedData error $e');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError();
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
Future<void> ethSignTypedDataV4(String topic, dynamic parameters) async {
|
||||
debugPrint('ethSignTypedDataV4 request: $parameters');
|
||||
|
||||
final permitRequestMessage = await extractPermitData(parameters);
|
||||
|
||||
final pRequest = walletKit.pendingRequests.getAll().last;
|
||||
final address = EthUtils.getAddressFromSessionRequest(pRequest);
|
||||
final data = EthUtils.getDataFromSessionRequest(pRequest) as String;
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
final isApproved = await MethodsUtils.requestApproval(
|
||||
permitRequestMessage,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: address,
|
||||
transportType: pRequest.transportType.name,
|
||||
verifyContext: pRequest.verifyContext,
|
||||
);
|
||||
|
||||
if (isApproved) {
|
||||
try {
|
||||
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
|
||||
|
||||
final signature = EthSigUtil.signTypedData(
|
||||
privateKey: keys[0].privateKey,
|
||||
jsonData: data,
|
||||
version: TypedDataVersion.V4,
|
||||
);
|
||||
|
||||
response = response.copyWith(result: signature);
|
||||
} catch (e) {
|
||||
debugPrint('ethSignTypedDataV4 error $e');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: 5002, message: S.current.user_rejected_method),
|
||||
);
|
||||
}
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
Future<void> ethSignTransaction(String topic, dynamic parameters) async {
|
||||
debugPrint('ethSignTransaction request: $parameters');
|
||||
|
||||
final SessionRequest pRequest = walletKit.pendingRequests.getAll().last;
|
||||
final data = EthUtils.getTransactionFromSessionRequest(pRequest);
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
final address = EthUtils.getAddressFromSessionRequest(pRequest);
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
final transaction = await _approveTransaction(
|
||||
data,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: address,
|
||||
transportType: pRequest.transportType.name,
|
||||
verifyContext: pRequest.verifyContext,
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
final signature = await ethClient.signTransaction(
|
||||
credentials,
|
||||
transaction,
|
||||
chainId: int.parse(chainId),
|
||||
);
|
||||
|
||||
// Sign the transaction
|
||||
final signedTx = bytesToHex(signature, include0x: true);
|
||||
response = response.copyWith(result: signedTx);
|
||||
} on RPCError catch (e) {
|
||||
debugPrint('ethSignTransaction error $e');
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: e.errorCode, message: e.message),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('ethSignTransaction error $e');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
response = response.copyWith(error: transaction as JsonRpcError);
|
||||
}
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
Future<void> ethSendTransaction(String topic, dynamic parameters) async {
|
||||
debugPrint('ethSendTransaction request: $parameters');
|
||||
final SessionRequest pRequest = walletKit.pendingRequests.getAll().last;
|
||||
|
||||
final data = EthUtils.getTransactionFromSessionRequest(pRequest);
|
||||
if (data == null) return;
|
||||
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
final transaction = await _approveTransaction(
|
||||
data,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
transportType: pRequest.transportType.name,
|
||||
verifyContext: pRequest.verifyContext,
|
||||
);
|
||||
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;
|
||||
|
||||
final signedTx = await ethClient.sendTransaction(
|
||||
credentials,
|
||||
transaction,
|
||||
chainId: int.parse(chainId),
|
||||
);
|
||||
|
||||
response = response.copyWith(result: signedTx);
|
||||
} on RPCError catch (e) {
|
||||
debugPrint('ethSendTransaction error $e');
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: e.errorCode, message: e.message),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('ethSendTransaction error $e');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
response = response.copyWith(error: transaction as JsonRpcError);
|
||||
}
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
void _handleResponseForTopic(String topic, JsonRpcResponse<dynamic> response) async {
|
||||
final session = walletKit.sessions.get(topic);
|
||||
|
||||
try {
|
||||
await walletKit.respondSessionRequest(
|
||||
topic: topic,
|
||||
response: response,
|
||||
);
|
||||
MethodsUtils.handleRedirect(
|
||||
topic,
|
||||
session!.peer.metadata.redirect,
|
||||
response.error?.message,
|
||||
response.error == null,
|
||||
);
|
||||
} on ReownSignError catch (error) {
|
||||
MethodsUtils.handleRedirect(
|
||||
topic,
|
||||
session!.peer.metadata.redirect,
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> _approveTransaction(
|
||||
Map<String, dynamic> transactionJson, {
|
||||
String? title,
|
||||
String? method,
|
||||
String? chainId,
|
||||
String? address,
|
||||
VerifyContext? verifyContext,
|
||||
required String transportType,
|
||||
}) async {
|
||||
Transaction transaction = transactionJson.toTransaction();
|
||||
|
||||
final gasPrice = await ethClient.getGasPrice();
|
||||
try {
|
||||
final gasLimit = await ethClient.estimateGas(
|
||||
sender: transaction.from,
|
||||
to: transaction.to,
|
||||
value: transaction.value,
|
||||
data: transaction.data,
|
||||
gasPrice: gasPrice,
|
||||
);
|
||||
|
||||
transaction = transaction.copyWith(
|
||||
gasPrice: gasPrice,
|
||||
maxGas: gasLimit.toInt(),
|
||||
);
|
||||
} on RPCError catch (e) {
|
||||
return JsonRpcError(code: e.errorCode, message: e.message);
|
||||
}
|
||||
|
||||
final gweiGasPrice = (transaction.gasPrice?.getInWei ?? BigInt.zero) / BigInt.from(1000000000);
|
||||
|
||||
final amount = (transaction.value?.getInWei ?? BigInt.zero) / BigInt.from(1e18);
|
||||
|
||||
final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} ETH\n'
|
||||
'${S.current.from}: ${transaction.from?.hex}\n'
|
||||
'${S.current.to}: ${transaction.to?.hex}';
|
||||
|
||||
if (await MethodsUtils.requestApproval(
|
||||
txMessageText,
|
||||
title: title,
|
||||
method: method,
|
||||
chainId: chainId,
|
||||
address: address,
|
||||
transportType: transportType,
|
||||
verifyContext: verifyContext,
|
||||
extraModels: [
|
||||
WCConnectionModel(
|
||||
title: S.current.gas_price,
|
||||
elements: ['${gweiGasPrice.toStringAsFixed(2)} GWEI'],
|
||||
),
|
||||
],
|
||||
)) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
return JsonRpcError(code: 5002, message: S.current.user_rejected_method);
|
||||
}
|
||||
|
||||
void _onSessionRequest(SessionRequestEvent? args) async {
|
||||
if (args != null && args.chainId == getChainId()) {
|
||||
debugPrint('_onSessionRequest ${args.toString()}');
|
||||
final handler = sessionRequestHandlers[args.method];
|
||||
if (handler != null) {
|
||||
await handler(args.topic, args.params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isValidSignature(String hexSignature, String message, String hexAddress) {
|
||||
try {
|
||||
debugPrint('isValidSignature: $hexSignature, $message, $hexAddress');
|
||||
final recoveredAddress = EthSigUtil.recoverPersonalSignature(
|
||||
signature: hexSignature,
|
||||
message: utf8.encode(message),
|
||||
);
|
||||
debugPrint('recoveredAddress: $recoveredAddress');
|
||||
|
||||
final recoveredAddress2 = EthSigUtil.recoverSignature(
|
||||
signature: hexSignature,
|
||||
message: utf8.encode(message),
|
||||
);
|
||||
debugPrint('recoveredAddress2: $recoveredAddress2');
|
||||
|
||||
final isValid = recoveredAddress == hexAddress;
|
||||
return isValid;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> extractPermitData(dynamic data) async {
|
||||
if (data is List && data.length >= 2) {
|
||||
final typedData = jsonDecode(data[1] as String) as Map<String, dynamic>;
|
||||
|
||||
// Extracting domain details.
|
||||
final domain = typedData['domain'] ?? {} as Map<String, dynamic>;
|
||||
final domainName = domain['name']?.toString() ?? '';
|
||||
final verifyingContract = domain['verifyingContract']?.toString() ?? '';
|
||||
|
||||
final chainId = domain['chainId']?.toString() ?? '';
|
||||
final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type);
|
||||
|
||||
// Get the primary type.
|
||||
final primaryType = typedData['primaryType']?.toString() ?? '';
|
||||
|
||||
// Extracting message details.
|
||||
final message = typedData['message'] ?? {} as Map<String, dynamic>;
|
||||
final details = message['details'] ?? {} as Map<String, dynamic>;
|
||||
final amount = details['amount']?.toString() ?? '';
|
||||
final expirationRaw = details['expiration']?.toString() ?? '';
|
||||
final nonce = details['nonce']?.toString() ?? '';
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
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 '';
|
||||
}
|
||||
|
||||
Future<String> getTokenDetails(String contractAddress, String chainName) async {
|
||||
final uri = Uri.https(
|
||||
'deep-index.moralis.io',
|
||||
'/api/v2.2/erc20/metadata',
|
||||
{
|
||||
"chain": chainName,
|
||||
"addresses": contractAddress,
|
||||
},
|
||||
);
|
||||
|
||||
final response = await http.get(
|
||||
uri,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-API-Key": secrets.moralisApiKey,
|
||||
},
|
||||
);
|
||||
|
||||
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
|
||||
|
||||
final symbol = (decodedResponse['symbol'] ?? '') as String;
|
||||
|
||||
final name = decodedResponse['name'] ?? '';
|
||||
return '$name ($symbol)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
enum EVMSupportedMethods {
|
||||
ethSign,
|
||||
ethSignTransaction,
|
||||
ethSignTypedData,
|
||||
ethSignTypedDataV4,
|
||||
switchChain,
|
||||
addChain,
|
||||
personalSign,
|
||||
ethSendTransaction;
|
||||
|
||||
String get name {
|
||||
switch (this) {
|
||||
case ethSign:
|
||||
return 'eth_sign';
|
||||
case ethSignTransaction:
|
||||
return 'eth_signTransaction';
|
||||
case ethSignTypedData:
|
||||
return 'eth_signTypedData';
|
||||
case ethSignTypedDataV4:
|
||||
return 'eth_signTypedData_v4';
|
||||
case switchChain:
|
||||
return 'wallet_switchEthereumChain';
|
||||
case addChain:
|
||||
return 'wallet_addEthereumChain';
|
||||
case personalSign:
|
||||
return 'personal_sign';
|
||||
case ethSendTransaction:
|
||||
return 'eth_sendTransaction';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
enum SolanaChainId { mainnet, devnet, testnet }
|
||||
|
||||
extension SolanaChainIdX on SolanaChainId {
|
||||
String chain() {
|
||||
String name = '';
|
||||
|
||||
switch (this) {
|
||||
case SolanaChainId.mainnet:
|
||||
name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ';
|
||||
// '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
|
||||
break;
|
||||
case SolanaChainId.devnet:
|
||||
name = 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1';
|
||||
break;
|
||||
case SolanaChainId.testnet:
|
||||
name = '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z';
|
||||
break;
|
||||
}
|
||||
|
||||
return 'solana:$name';
|
||||
}
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
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';
|
||||
import 'package:reown_walletkit/reown_walletkit.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/store/app_store.dart';
|
||||
|
||||
class SolanaChainService {
|
||||
Map<String, dynamic Function(String, dynamic)> get solanaRequestHandlers => {
|
||||
SolanaSupportedMethods.solSignMessage.name: solanaSignMessage,
|
||||
SolanaSupportedMethods.solSignTransaction.name: solanaSignTransaction,
|
||||
SolanaSupportedMethods.solSignAllTransaction.name: solanaSignAllTransaction,
|
||||
};
|
||||
|
||||
SolanaChainService({
|
||||
required this.appStore,
|
||||
required this.bottomSheetService,
|
||||
required this.walletKit,
|
||||
required this.wcKeyService,
|
||||
required this.reference,
|
||||
}) {
|
||||
for (var handler in solanaRequestHandlers.entries) {
|
||||
walletKit.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: handler.key,
|
||||
handler: handler.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final AppStore appStore;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final ReownWalletKit walletKit;
|
||||
final WalletConnectKeyService wcKeyService;
|
||||
final SolanaChainId reference;
|
||||
|
||||
String getChainId() => reference.chain();
|
||||
|
||||
Future<void> solanaSignMessage(String topic, dynamic parameters) async {
|
||||
debugPrint('solanaSignMessage request: $parameters');
|
||||
|
||||
final pRequest = walletKit.pendingRequests.getAll().last;
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
try {
|
||||
final params = parameters as Map<String, dynamic>;
|
||||
final message = params['message'].toString();
|
||||
|
||||
final privateKey = _getSolanaPrivateKey();
|
||||
|
||||
// it's sent as base58 encoded from the dapp
|
||||
final base58Decoded = base58.decode(message);
|
||||
final decodedMessage = utf8.decode(base58Decoded);
|
||||
|
||||
final isApproved = await MethodsUtils.requestApproval(
|
||||
decodedMessage,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: privateKey.publicKey().toAddress().address,
|
||||
transportType: pRequest.transportType.name,
|
||||
);
|
||||
|
||||
if (isApproved) {
|
||||
final signedBytes = await privateKey.sign(base58Decoded);
|
||||
|
||||
final signature = blockchain_utils.Base58Encoder.encode(signedBytes);
|
||||
|
||||
response = response.copyWith(result: {'signature': signature});
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
//
|
||||
} catch (e) {
|
||||
debugPrint('solanaSignMessage error $e');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
|
||||
await walletKit.respondSessionRequest(topic: topic, response: response);
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
Future<void> solanaSignTransaction(String topic, dynamic parameters) async {
|
||||
debugPrint('solanaSignTransaction: ${jsonEncode(parameters)}');
|
||||
|
||||
final pRequest = walletKit.pendingRequests.getAll().last;
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
try {
|
||||
final params = parameters as Map<String, dynamic>;
|
||||
final privateKey = _getSolanaPrivateKey();
|
||||
|
||||
final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params);
|
||||
|
||||
SolanaTransaction unSignedTransaction;
|
||||
if (params.containsKey('transaction')) {
|
||||
final transaction = params['transaction'] as String;
|
||||
final transactionBytes = base64.decode(transaction);
|
||||
unSignedTransaction = SolanaTransaction.deserialize(transactionBytes);
|
||||
} else {
|
||||
final feePayer = params['feePayer'].toString();
|
||||
final recentBlockHash = params['recentBlockhash'].toString();
|
||||
final instructionsList = params['instructions'] as List<dynamic>;
|
||||
|
||||
final instructions = instructionsList.map((json) {
|
||||
return (json as Map<String, dynamic>).toInstruction();
|
||||
}).toList();
|
||||
|
||||
unSignedTransaction = SolanaTransaction(
|
||||
payerKey: SolAddress(feePayer),
|
||||
instructions: instructions,
|
||||
recentBlockhash: SolAddress(recentBlockHash),
|
||||
);
|
||||
}
|
||||
|
||||
final isApproved = await MethodsUtils.requestApproval(
|
||||
beautifiedTrx,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: privateKey.publicKey().toAddress().address,
|
||||
transportType: pRequest.transportType.name,
|
||||
);
|
||||
|
||||
if (isApproved) {
|
||||
final signedTx = await privateKey.sign(unSignedTransaction.serializeMessage());
|
||||
|
||||
final signature = Base58Encoder.encode(signedTx.toList(growable: false));
|
||||
|
||||
response = response.copyWith(result: {'signature': signature});
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
debugPrint('solanaSignTransaction error $e, $s');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
|
||||
await walletKit.respondSessionRequest(topic: topic, response: response);
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
Future<void> solanaSignAllTransaction(String topic, dynamic parameters) async {
|
||||
debugPrint('solanaSignAllTransaction: ${jsonEncode(parameters)}');
|
||||
|
||||
final pRequest = walletKit.pendingRequests.getAll().last;
|
||||
var response = JsonRpcResponse(id: pRequest.id, jsonrpc: '2.0');
|
||||
|
||||
try {
|
||||
final params = parameters as Map<String, dynamic>;
|
||||
final beautifiedTrx = const JsonEncoder.withIndent(' ').convert(params);
|
||||
|
||||
final privateKey = _getSolanaPrivateKey();
|
||||
|
||||
final isApproved = await MethodsUtils.requestApproval(
|
||||
beautifiedTrx,
|
||||
method: pRequest.method,
|
||||
chainId: pRequest.chainId,
|
||||
address: privateKey.publicKey().toAddress().address,
|
||||
transportType: pRequest.transportType.name,
|
||||
);
|
||||
|
||||
if (isApproved) {
|
||||
if (params.containsKey('transactions')) {
|
||||
final transactions = params['transactions'] as List<String>;
|
||||
|
||||
List<String> signedTransactions = [];
|
||||
for (var transaction in transactions) {
|
||||
final transactionBytes = base64.decode(transaction);
|
||||
|
||||
final unsignedTx = SolanaTransaction.deserialize(transactionBytes);
|
||||
|
||||
final serializedTx = await privateKey.sign(unsignedTx.serializeMessage());
|
||||
|
||||
unsignedTx.addSignature(privateKey.publicKey().toAddress(), serializedTx);
|
||||
|
||||
final reEncodedTx = unsignedTx.serializeString(
|
||||
encoding: TransactionSerializeEncoding.base64,
|
||||
);
|
||||
|
||||
signedTransactions.add(reEncodedTx);
|
||||
}
|
||||
|
||||
response = response.copyWith(result: {'transactions': signedTransactions});
|
||||
}
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED);
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
debugPrint('solanaSignAllTransactions error $e, $s');
|
||||
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
|
||||
|
||||
response = response.copyWith(
|
||||
error: JsonRpcError(code: error.code, message: error.message),
|
||||
);
|
||||
}
|
||||
|
||||
await walletKit.respondSessionRequest(topic: topic, response: response);
|
||||
|
||||
_handleResponseForTopic(topic, response);
|
||||
}
|
||||
|
||||
SolanaPrivateKey _getSolanaPrivateKey() {
|
||||
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
|
||||
|
||||
return SolanaPrivateKey.fromSeedHex(keys[0].privateKey);
|
||||
}
|
||||
|
||||
void _handleResponseForTopic(String topic, JsonRpcResponse<dynamic> response) async {
|
||||
final session = walletKit.sessions.get(topic);
|
||||
|
||||
try {
|
||||
await walletKit.respondSessionRequest(topic: topic, response: response);
|
||||
|
||||
MethodsUtils.handleRedirect(
|
||||
topic,
|
||||
session!.peer.metadata.redirect,
|
||||
response.error?.message,
|
||||
);
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension on Map<String, dynamic> {
|
||||
TransactionInstruction toInstruction() {
|
||||
final programId = this['programId'] as String;
|
||||
|
||||
final data = (this['data'] as List).map((e) => e as int).toList();
|
||||
// final data58 = base58.encode(Uint8List.fromList(data));
|
||||
// final dataBytes = ByteArray.fromBase58(data58);
|
||||
|
||||
final keys = this['keys'] as List;
|
||||
return TransactionInstruction.fromBytes(
|
||||
programId: SolAddress(programId),
|
||||
instructionBytes: data,
|
||||
keys: keys.map((k) {
|
||||
final kParams = (k as Map<String, dynamic>);
|
||||
return AccountMeta(
|
||||
publicKey:
|
||||
SolanaPublicKey.fromBytes(base58.decode(kParams['pubkey'] as String)).toAddress(),
|
||||
isWritable: kParams['isWritable'] as bool,
|
||||
isSigner: kParams['isSigner'] as bool,
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
enum SolanaSupportedMethods {
|
||||
solSignMessage,
|
||||
solSignTransaction,
|
||||
solSignAllTransaction;
|
||||
|
||||
String get name {
|
||||
switch (this) {
|
||||
case solSignMessage:
|
||||
return 'solana_signMessage';
|
||||
case solSignTransaction:
|
||||
return 'solana_signTransaction';
|
||||
case solSignAllTransaction:
|
||||
return 'solana_signAllTransactions';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class ChainKeyModel {
|
||||
final List<String> chains;
|
||||
final String privateKey;
|
||||
final String publicKey;
|
||||
|
||||
ChainKeyModel({
|
||||
required this.chains,
|
||||
required this.privateKey,
|
||||
required this.publicKey,
|
||||
});
|
||||
|
||||
String get namespace {
|
||||
if (chains.isNotEmpty) {
|
||||
return chains.first.split(':').first;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'chains': chains,
|
||||
'privateKey': privateKey,
|
||||
'publicKey': privateKey,
|
||||
};
|
||||
|
||||
factory ChainKeyModel.fromJson(Map<String, dynamic> json) {
|
||||
return ChainKeyModel(
|
||||
chains: (json['chains'] as List).map((e) => '$e').toList(),
|
||||
privateKey: json['privateKey'] as String,
|
||||
publicKey: json['publicKey'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => jsonEncode(toJson());
|
||||
}
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chain_key_model.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
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';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chain_key_model.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/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/store/app_store.dart';
|
||||
|
||||
import 'bottom_sheet_service.dart';
|
||||
import 'chain_service/solana/solana_chain_id.dart';
|
||||
import 'chain_service/solana/solana_chain_service.dart';
|
||||
|
||||
part 'walletkit_service.g.dart';
|
||||
|
||||
class WalletKitService = WalletKitServiceBase with _$WalletKitService;
|
||||
|
||||
abstract class WalletKitServiceBase with Store {
|
||||
WalletKitServiceBase(
|
||||
this._bottomSheetHandler,
|
||||
this.walletKeyService,
|
||||
this.appStore,
|
||||
this.sharedPreferences,
|
||||
) : pairings = ObservableList<PairingInfo>(),
|
||||
sessions = ObservableList<SessionData>(),
|
||||
auth = ObservableList<PendingSessionAuthRequest>(),
|
||||
isInitialized = false;
|
||||
|
||||
final AppStore appStore;
|
||||
final SharedPreferences sharedPreferences;
|
||||
final BottomSheetService _bottomSheetHandler;
|
||||
final WalletConnectKeyService walletKeyService;
|
||||
|
||||
late ReownWalletKit _walletKit;
|
||||
|
||||
@observable
|
||||
bool isInitialized;
|
||||
|
||||
/// The list of requests from the dapp
|
||||
/// Potential types include, but aren't limited to:
|
||||
/// [SessionProposalEvent], [SessionAuthRequest]
|
||||
@observable
|
||||
ObservableList<PairingInfo> pairings;
|
||||
|
||||
@observable
|
||||
ObservableList<SessionData> sessions;
|
||||
|
||||
@observable
|
||||
ObservableList<PendingSessionAuthRequest> auth;
|
||||
|
||||
@action
|
||||
void create() {
|
||||
// Create the walletkit client
|
||||
_walletKit = ReownWalletKit(
|
||||
core: ReownCore(
|
||||
projectId: secrets.walletConnectProjectId,
|
||||
),
|
||||
metadata: const PairingMetadata(
|
||||
name: 'Cake Wallet',
|
||||
description: 'Cake Wallet',
|
||||
url: 'https://cakewallet.com',
|
||||
icons: ['https://cakewallet.com/assets/image/cake_logo.png'],
|
||||
),
|
||||
);
|
||||
|
||||
_walletKit.core.addLogListener(_logListener);
|
||||
|
||||
// Setup our listeners
|
||||
log('Created instance of walletKit');
|
||||
|
||||
_walletKit.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid);
|
||||
_walletKit.core.pairing.onPairingCreate.subscribe(_onPairingCreate);
|
||||
_walletKit.core.relayClient.onRelayClientError.subscribe(_onRelayClientError);
|
||||
_walletKit.core.relayClient.onRelayClientMessage.subscribe(_onRelayClientMessage);
|
||||
|
||||
_walletKit.onSessionProposal.subscribe(_onSessionProposal);
|
||||
_walletKit.onSessionProposalError.subscribe(_onSessionProposalError);
|
||||
_walletKit.onSessionConnect.subscribe(_onSessionConnect);
|
||||
_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!);
|
||||
for (final chainKey in chainKeys) {
|
||||
for (final chainId in chainKey.chains) {
|
||||
final chainNameSpace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
|
||||
if (chainNameSpace == chainId) {
|
||||
final account = '$chainId:${chainKey.publicKey}';
|
||||
debugPrint('registerAccount $account');
|
||||
_walletKit.registerAccount(
|
||||
chainId: chainId,
|
||||
accountAddress: chainKey.publicKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _logListener(String event) {
|
||||
debugPrint('[WalletKit] $event');
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> init() async {
|
||||
// Await the initialization of walletKit
|
||||
debugPrint('Intializing walletKit');
|
||||
if (!isInitialized) {
|
||||
try {
|
||||
await _walletKit.init();
|
||||
debugPrint('Initialized');
|
||||
isInitialized = true;
|
||||
} catch (e) {
|
||||
debugPrint('init Error: ${e.toString()}');
|
||||
isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
await _emitEvent();
|
||||
|
||||
_refreshPairings();
|
||||
|
||||
final newSessions = _walletKit.sessions.getAll();
|
||||
sessions.addAll(newSessions);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (appStore.wallet!.type == WalletType.solana) {
|
||||
for (final cId in SolanaChainId.values) {
|
||||
SolanaChainService(
|
||||
reference: cId,
|
||||
appStore: appStore,
|
||||
wcKeyService: walletKeyService,
|
||||
bottomSheetService: _bottomSheetHandler,
|
||||
walletKit: _walletKit,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _emitEvent() async {
|
||||
final isOnline = _walletKit.core.connectivity.isOnline.value;
|
||||
if (!isOnline) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
_emitEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
final sessions = _walletKit.sessions.getAll();
|
||||
for (var session in sessions) {
|
||||
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
for (var chain in chainKeys) {
|
||||
for (var chainID in chain.chains) {
|
||||
try {
|
||||
final events = NamespaceUtils.getNamespacesEventsForChain(
|
||||
chainId: chainID,
|
||||
namespaces: session.namespaces,
|
||||
);
|
||||
if (events.contains('accountsChanged')) {
|
||||
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
_walletKit.emitSessionEvent(
|
||||
topic: session.topic,
|
||||
chainId: chainID,
|
||||
event: SessionEventParams(
|
||||
name: 'accountsChanged',
|
||||
data: [chainKeys.first.publicKey],
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
FutureOr<void> onDispose() {
|
||||
log('walletKit dispose');
|
||||
_walletKit.core.removeLogListener(_logListener);
|
||||
|
||||
_walletKit.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid);
|
||||
_walletKit.core.pairing.onPairingCreate.unsubscribe(_onPairingCreate);
|
||||
_walletKit.core.relayClient.onRelayClientError.unsubscribe(_onRelayClientError);
|
||||
_walletKit.core.relayClient.onRelayClientMessage.unsubscribe(_onRelayClientMessage);
|
||||
|
||||
_walletKit.onSessionProposal.unsubscribe(_onSessionProposal);
|
||||
_walletKit.onSessionProposalError.unsubscribe(_onSessionProposalError);
|
||||
_walletKit.onSessionConnect.unsubscribe(_onSessionConnect);
|
||||
_walletKit.onSessionAuthRequest.unsubscribe(_onSessionAuthRequest);
|
||||
|
||||
_walletKit.pairings.onSync.unsubscribe(_onPairingsSync);
|
||||
_walletKit.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete);
|
||||
_walletKit.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete);
|
||||
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
ReownWalletKit get walletKit => _walletKit;
|
||||
|
||||
void _onRelayClientMessage(MessageEvent? event) async {
|
||||
if (event != null) {
|
||||
final jsonObject = await EthUtils.decodeMessageEvent(event);
|
||||
debugPrint('_onRelayClientMessage $jsonObject');
|
||||
|
||||
if (jsonObject is JsonRpcRequest) {
|
||||
debugPrint(jsonObject.id.toString());
|
||||
debugPrint(jsonObject.method);
|
||||
|
||||
if (jsonObject.method == 'wc_sessionDelete') {
|
||||
await disconnectSession(topic: event.topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onPairingsSync(StoreSyncEvent? args) {
|
||||
if (args != null) {
|
||||
_refreshPairings();
|
||||
}
|
||||
}
|
||||
|
||||
void _onPairingDelete(PairingEvent? event) {
|
||||
_refreshPairings();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _onSessionProposal(SessionProposalEvent? args) async {
|
||||
debugPrint('_onSessionProposal ${jsonEncode(args?.params)}');
|
||||
|
||||
if (args != null) {
|
||||
final proposer = args.params.proposer;
|
||||
final result = (await _bottomSheetHandler.queueBottomSheet(
|
||||
widget: WCRequestWidget(
|
||||
verifyContext: args.verifyContext,
|
||||
child: WCConnectionRequestWidget(
|
||||
proposalData: args.params,
|
||||
verifyContext: args.verifyContext,
|
||||
requester: proposer,
|
||||
walletKeyService: walletKeyService,
|
||||
walletKit: walletKit,
|
||||
appStore: appStore,
|
||||
),
|
||||
),
|
||||
)) ??
|
||||
WCBottomSheetResult.reject;
|
||||
|
||||
if (result != WCBottomSheetResult.reject) {
|
||||
try {
|
||||
await _walletKit.approveSession(
|
||||
id: args.id,
|
||||
namespaces: NamespaceUtils.regenerateNamespacesWithChains(
|
||||
args.params.generatedNamespaces!,
|
||||
),
|
||||
sessionProperties: args.params.sessionProperties,
|
||||
);
|
||||
} on ReownSignError catch (error) {
|
||||
MethodsUtils.handleRedirect(
|
||||
'',
|
||||
proposer.metadata.redirect,
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED).toSignError();
|
||||
await _walletKit.rejectSession(id: args.id, reason: error);
|
||||
await _walletKit.core.pairing.disconnect(topic: args.params.pairingTopic);
|
||||
MethodsUtils.handleRedirect(
|
||||
'',
|
||||
proposer.metadata.redirect,
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _onSessionProposalError(SessionProposalErrorEvent? args) async {
|
||||
debugPrint('_onSessionProposalError $args');
|
||||
|
||||
if (args != null) {
|
||||
String errorMessage = args.error.message;
|
||||
if (args.error.code == 5100) {
|
||||
errorMessage =
|
||||
errorMessage.replaceFirst('${S.current.requested}:', '\n\n${S.current.requested}:');
|
||||
errorMessage =
|
||||
errorMessage.replaceFirst('${S.current.supported}:', '\n\n${S.current.supported}:');
|
||||
}
|
||||
MethodsUtils.goBackModal(
|
||||
title: S.current.error,
|
||||
message: errorMessage,
|
||||
success: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _onSessionConnect(SessionConnect? args) async {
|
||||
if (args != null) {
|
||||
final session = jsonEncode(args.session.toJson());
|
||||
|
||||
debugPrint('_onSessionConnect $session');
|
||||
|
||||
await savePairingTopicToLocalStorage(args.session.pairingTopic);
|
||||
|
||||
sessions.add(args.session);
|
||||
|
||||
_refreshPairings();
|
||||
|
||||
MethodsUtils.handleRedirect(
|
||||
args.session.topic,
|
||||
args.session.peer.metadata.redirect,
|
||||
'',
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _onRelayClientError(ErrorEvent? args) {
|
||||
debugPrint('_onRelayClientError ${args?.error}');
|
||||
// _bottomSheetHandler.queueBottomSheet(
|
||||
// isModalDismissible: true,
|
||||
// widget: BottomSheetMessageDisplayWidget(
|
||||
// message: "WC RelayClient Error: ${args?.error}",
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
@action
|
||||
void _onPairingInvalid(PairingInvalidEvent? args) {
|
||||
debugPrint('_onPairingInvalid $args');
|
||||
_bottomSheetHandler.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: '${S.current.pairingInvalidEvent}: $args',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
void _onPairingCreate(PairingEvent? args) {
|
||||
debugPrint('_onPairingCreate $args');
|
||||
}
|
||||
|
||||
Future<void> _onSessionAuthRequest(SessionAuthRequest? args) async {
|
||||
if (args != null) {
|
||||
final SessionAuthPayload authPayload = args.authPayload;
|
||||
final jsonPyaload = jsonEncode(authPayload.toJson());
|
||||
|
||||
debugPrint('_onSessionAuthRequest $jsonPyaload');
|
||||
|
||||
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
final supportedChains = chainKeys.first.chains;
|
||||
final supportedMethods = getChainSupportedMethodsOnWalletType(appStore.wallet!.type);
|
||||
|
||||
final newAuthPayload = AuthSignature.populateAuthPayload(
|
||||
authPayload: authPayload,
|
||||
chains: supportedChains.toList(),
|
||||
methods: supportedMethods.toList(),
|
||||
);
|
||||
final cacaoRequestPayload = CacaoRequestPayload.fromSessionAuthPayload(
|
||||
newAuthPayload,
|
||||
);
|
||||
|
||||
final List<Map<String, dynamic>> formattedMessages = [];
|
||||
for (var chain in newAuthPayload.chains) {
|
||||
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
final iss = 'did:pkh:$chain:${chainKeys.first.publicKey}';
|
||||
|
||||
final message = _walletKit.formatAuthMessage(
|
||||
iss: iss,
|
||||
cacaoPayload: cacaoRequestPayload,
|
||||
);
|
||||
formattedMessages.add({iss: message});
|
||||
}
|
||||
|
||||
final WCBottomSheetResult result = (await _bottomSheetHandler.queueBottomSheet(
|
||||
widget: WCSessionAuthRequestWidget(
|
||||
child: WCConnectionRequestWidget(
|
||||
sessionAuthPayload: newAuthPayload,
|
||||
verifyContext: args.verifyContext,
|
||||
requester: args.requester,
|
||||
walletKeyService: walletKeyService,
|
||||
walletKit: _walletKit,
|
||||
appStore: appStore,
|
||||
),
|
||||
),
|
||||
) as WCBottomSheetResult?) ??
|
||||
WCBottomSheetResult.reject;
|
||||
|
||||
if (result != WCBottomSheetResult.reject) {
|
||||
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<Cacao> cacaos = [];
|
||||
for (var i = 0; i < count; i++) {
|
||||
final iss = formattedMessages[i].keys.first;
|
||||
final message = formattedMessages[i].values.first as String;
|
||||
|
||||
final signature = credentials.signPersonalMessageToUint8List(
|
||||
Uint8List.fromList(message.codeUnits),
|
||||
);
|
||||
final hexSignature = bytesToHex(signature, include0x: true);
|
||||
|
||||
cacaos.add(
|
||||
AuthSignature.buildAuthObject(
|
||||
requestPayload: cacaoRequestPayload,
|
||||
signature: CacaoSignature(t: CacaoSignature.EIP191, s: hexSignature),
|
||||
iss: iss,
|
||||
),
|
||||
);
|
||||
}
|
||||
//
|
||||
try {
|
||||
final session = await _walletKit.approveSessionAuthenticate(
|
||||
id: args.id,
|
||||
auths: cacaos,
|
||||
);
|
||||
|
||||
debugPrint('_onSessionAuthRequest - approveSessionAuthenticate $session');
|
||||
|
||||
MethodsUtils.handleRedirect(
|
||||
session.topic,
|
||||
session.session?.peer.metadata.redirect,
|
||||
'',
|
||||
true,
|
||||
);
|
||||
} on ReownSignError catch (error) {
|
||||
MethodsUtils.handleRedirect(
|
||||
args.topic,
|
||||
args.requester.metadata.redirect,
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final error = Errors.getSdkError(Errors.USER_REJECTED_AUTH);
|
||||
await _walletKit.rejectSessionAuthenticate(
|
||||
id: args.id,
|
||||
reason: error.toSignError(),
|
||||
);
|
||||
MethodsUtils.handleRedirect(
|
||||
args.topic,
|
||||
args.requester.metadata.redirect,
|
||||
error.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> deletePairing({required String topic}) async {
|
||||
final topicSessions = sessions.where((element) => element.pairingTopic == topic);
|
||||
|
||||
await _walletKit.core.pairing.disconnect(topic: topic);
|
||||
for (var session in topicSessions) {
|
||||
await _walletKit.disconnectSession(
|
||||
topic: session.topic,
|
||||
reason: Errors.getSdkError(Errors.USER_DISCONNECTED).toSignError(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> disconnectSession({required String topic}) async {
|
||||
await walletKit.disconnectSession(
|
||||
topic: topic,
|
||||
reason: Errors.getSdkError(Errors.USER_DISCONNECTED).toSignError(),
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> updateSession({
|
||||
required String topic,
|
||||
required Map<String, Namespace> namespaces,
|
||||
}) async {
|
||||
await walletKit.updateSession(topic: topic, namespaces: namespaces);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> extendSession({required String topic}) async {
|
||||
await walletKit.extendSession(topic: topic);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> pairWithUri(Uri uri) async {
|
||||
try {
|
||||
debugPrint('pairWithUri - Pairing with URI: $uri');
|
||||
await _walletKit.pair(uri: uri);
|
||||
} on ReownSignError catch (e) {
|
||||
_bottomSheetHandler.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: e.message),
|
||||
);
|
||||
} catch (e) {
|
||||
_bottomSheetHandler.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _refreshPairings() {
|
||||
debugPrint('_refreshPairings - Refreshing pairings');
|
||||
|
||||
pairings.clear();
|
||||
|
||||
final allPairings = _walletKit.pairings.getAll();
|
||||
|
||||
final keyForWallet = getKeyForStoringTopicsForWallet();
|
||||
|
||||
if (keyForWallet.isEmpty) return;
|
||||
|
||||
final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet);
|
||||
|
||||
final filteredPairings = allPairings.where(
|
||||
(pairing) {
|
||||
bool isInCurrentTopics = currentTopicsForWallet.contains(pairing.topic);
|
||||
bool isActive = pairing.active;
|
||||
|
||||
return isInCurrentTopics && isActive;
|
||||
},
|
||||
).toList();
|
||||
|
||||
pairings.addAll(filteredPairings);
|
||||
}
|
||||
|
||||
@action
|
||||
List<SessionData> getSessionsForPairingInfo(PairingInfo pairing) {
|
||||
return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
|
||||
}
|
||||
|
||||
String getKeyForStoringTopicsForWallet() {
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
|
||||
if (chainKeys.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
final keyForPairingTopic =
|
||||
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
|
||||
|
||||
return keyForPairingTopic;
|
||||
}
|
||||
|
||||
List<String> 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<dynamic> jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
|
||||
// Cast each item to a string
|
||||
return jsonList.map((item) => item as String).toList();
|
||||
}
|
||||
|
||||
Future<void> 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);
|
||||
debugPrint('Is Pairing Topic Saved: $isPairingTopicAlreadySaved');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
|
||||
class EthUtils {
|
||||
static String getUtf8Message(String maybeHex) {
|
||||
if (maybeHex.startsWith('0x')) {
|
||||
final List<int> decoded = hex.decode(
|
||||
maybeHex.substring(2),
|
||||
);
|
||||
return utf8.decode(decoded);
|
||||
}
|
||||
|
||||
return maybeHex;
|
||||
}
|
||||
|
||||
static String? getAddressFromSessionRequest(SessionRequest request) {
|
||||
try {
|
||||
final paramsList = List<String?>.from((request.params as List));
|
||||
if (request.method == 'personal_sign') {
|
||||
// for `personal_sign` first value in params has to be always the message
|
||||
paramsList.removeAt(0);
|
||||
}
|
||||
|
||||
return paramsList.firstWhere((p) {
|
||||
try {
|
||||
EthereumAddress.fromHex(p ?? '');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static dynamic getDataFromSessionRequest(SessionRequest request) {
|
||||
try {
|
||||
final paramsList = List.from((request.params as List));
|
||||
if (request.method == 'personal_sign') {
|
||||
return paramsList.first;
|
||||
}
|
||||
return paramsList.firstWhere((p) {
|
||||
final address = getAddressFromSessionRequest(request);
|
||||
return p != address;
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('getDataFromSessionRequest $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? getTransactionFromSessionRequest(
|
||||
SessionRequest request,
|
||||
) {
|
||||
try {
|
||||
final param = (request.params as List<dynamic>).first;
|
||||
return param as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
debugPrint('getTransactionFromSessionRequest $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<dynamic> decodeMessageEvent(MessageEvent event) async {
|
||||
final walletKit = getIt<WalletKitService>().walletKit;
|
||||
|
||||
final payloadString = await walletKit.core.crypto.decode(
|
||||
event.topic,
|
||||
event.message,
|
||||
);
|
||||
|
||||
if (payloadString == null) return null;
|
||||
|
||||
final data = jsonDecode(payloadString) as Map<String, dynamic>;
|
||||
if (data.containsKey('method')) {
|
||||
return JsonRpcRequest.fromJson(data);
|
||||
} else {
|
||||
return JsonRpcResponse.fromJson(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import 'package:cake_wallet/di.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/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:flutter/material.dart';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
|
||||
class MethodsUtils {
|
||||
static final walletKit = getIt.get<WalletKitService>().walletKit;
|
||||
static final bottomSheetService = getIt.get<BottomSheetService>();
|
||||
|
||||
static Future<bool> requestApproval(
|
||||
String text, {
|
||||
String? title,
|
||||
String? method,
|
||||
String? chainId,
|
||||
String? address,
|
||||
required String transportType,
|
||||
List<WCConnectionModel> extraModels = const [],
|
||||
VerifyContext? verifyContext,
|
||||
}) async {
|
||||
final WCBottomSheetResult result = (await bottomSheetService.queueBottomSheet(
|
||||
widget: WCRequestWidget(
|
||||
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,
|
||||
],
|
||||
),
|
||||
),
|
||||
) as WCBottomSheetResult?) ??
|
||||
WCBottomSheetResult.reject;
|
||||
|
||||
return result != WCBottomSheetResult.reject;
|
||||
}
|
||||
|
||||
static void handleRedirect(
|
||||
String topic,
|
||||
Redirect? redirect, [
|
||||
String? error,
|
||||
bool success = false,
|
||||
]) {
|
||||
debugPrint('handleRedirect topic: $topic, redirect: $redirect, error: $error');
|
||||
openApp(
|
||||
topic,
|
||||
redirect,
|
||||
onFail: (e) => goBackModal(
|
||||
title: success ? S.current.success : S.current.error,
|
||||
message: error,
|
||||
success: success,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void openApp(
|
||||
String topic,
|
||||
Redirect? redirect, {
|
||||
int delay = 100,
|
||||
Function(ReownSignError? error)? onFail,
|
||||
}) async {
|
||||
await Future.delayed(Duration(milliseconds: delay));
|
||||
try {
|
||||
await walletKit.redirectToDapp(
|
||||
topic: topic,
|
||||
redirect: redirect,
|
||||
);
|
||||
} on ReownSignError catch (e) {
|
||||
onFail?.call(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void goBackModal({
|
||||
String? title,
|
||||
String? message,
|
||||
bool success = true,
|
||||
}) async {
|
||||
await bottomSheetService.queueBottomSheet(
|
||||
closeAfter: success ? 3 : 0,
|
||||
widget: GoBackModalWidget(
|
||||
isSuccess: success,
|
||||
title: title,
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GoBackModalWidget extends StatelessWidget {
|
||||
const GoBackModalWidget({
|
||||
required this.isSuccess,
|
||||
this.message,
|
||||
this.title,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool isSuccess;
|
||||
final String? title;
|
||||
final String? message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
height: 280.0,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
isSuccess ? Icons.check_circle_sharp : Icons.error_outline_sharp,
|
||||
color: isSuccess ? Colors.green[100] : Colors.red[100],
|
||||
size: 80.0,
|
||||
),
|
||||
Text(
|
||||
title ?? S.current.connected,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.titleTextStyle!.color!,
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(message ?? S.current.youCanGoBackToYourDapp),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,76 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import '../../../../core/wallet_connect/models/connection_model.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<ConnectionWidget> buildFromRequiredNamespaces(
|
||||
Map<String, RequiredNamespace> requiredNamespaces,
|
||||
static List<WCConnectionWidget> buildFromRequiredNamespaces(
|
||||
Map<String, Namespace> generatedNamespaces,
|
||||
) {
|
||||
final List<ConnectionWidget> views = [];
|
||||
for (final key in requiredNamespaces.keys) {
|
||||
RequiredNamespace ns = requiredNamespaces[key]!;
|
||||
final List<ConnectionModel> models = [];
|
||||
// If the chains property is present, add the chain data to the models
|
||||
if (ns.chains != null) {
|
||||
models.add(ConnectionModel(title: S.current.chains, elements: ns.chains!));
|
||||
}
|
||||
models.add(ConnectionModel(title: S.current.methods, elements: ns.methods));
|
||||
models.add(ConnectionModel(title: S.current.events, elements: ns.events));
|
||||
final List<WCConnectionWidget> views = [];
|
||||
for (final key in generatedNamespaces.keys) {
|
||||
final namespaces = generatedNamespaces[key]!;
|
||||
final chains = NamespaceUtils.getChainsFromAccounts(namespaces.accounts);
|
||||
|
||||
views.add(ConnectionWidget(title: key, info: models));
|
||||
final List<WCConnectionModel> 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<ConnectionWidget> buildFromNamespaces(
|
||||
static List<WCConnectionWidget> buildFromNamespaces(
|
||||
String topic,
|
||||
Map<String, Namespace> namespaces,
|
||||
Web3Wallet web3wallet,
|
||||
BuildContext context,
|
||||
) {
|
||||
final List<ConnectionWidget> views = [];
|
||||
final List<WCConnectionWidget> views = [];
|
||||
for (final key in namespaces.keys) {
|
||||
final Namespace ns = namespaces[key]!;
|
||||
final List<ConnectionModel> models = [];
|
||||
final ns = namespaces[key]!;
|
||||
final List<WCConnectionModel> models = [];
|
||||
|
||||
// If the chains property is present, add the chain data to the models
|
||||
models.add(
|
||||
ConnectionModel(
|
||||
title: S.current.chains,
|
||||
elements: ns.accounts,
|
||||
),
|
||||
);
|
||||
models.add(ConnectionModel(
|
||||
title: S.current.methods,
|
||||
elements: ns.methods,
|
||||
));
|
||||
models.add(WCConnectionModel(title: S.current.accounts, elements: ns.accounts));
|
||||
models.add(WCConnectionModel(title: S.current.methods, elements: ns.methods));
|
||||
|
||||
Map<String, void Function()> actions = {};
|
||||
for (final String event in ns.events) {
|
||||
actions[event] = () async {
|
||||
final String chainId = NamespaceUtils.isValidChainId(key)
|
||||
? key
|
||||
: NamespaceUtils.getChainFromAccount(ns.accounts.first);
|
||||
await web3wallet.emitSessionEvent(
|
||||
topic: topic,
|
||||
chainId: chainId,
|
||||
event: SessionEventParams(name: event, data: '${S.current.event}: $event'),
|
||||
);
|
||||
};
|
||||
if (ns.events.isNotEmpty) {
|
||||
models.add(WCConnectionModel(title: S.current.events, elements: ns.events));
|
||||
}
|
||||
models.add(
|
||||
ConnectionModel(title: S.current.events, elements: ns.events, elementActions: actions),
|
||||
);
|
||||
|
||||
views.add(ConnectionWidget(title: key, info: models));
|
||||
views.add(WCConnectionWidget(title: key, info: models));
|
||||
}
|
||||
|
||||
return views;
|
||||
}
|
||||
|
||||
static Map<String, Namespace> updateNamespaces(
|
||||
Map<String, Namespace> currentNamespaces,
|
||||
String namespace,
|
||||
List<String> newChains,
|
||||
) {
|
||||
final updatedNamespaces = Map<String, Namespace>.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<String>.from(accounts)..addAll(newAccounts),
|
||||
);
|
||||
|
||||
updatedNamespaces[namespace] = newNamespaces;
|
||||
|
||||
return updatedNamespaces;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
|
||||
extension StringParsing on String {
|
||||
String get utf8Message {
|
||||
if (startsWith('0x')) {
|
||||
final List<int> decoded = hex.decode(
|
||||
substring(2),
|
||||
);
|
||||
return utf8.decode(decoded);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
String safeSubString(int start, int end) {
|
||||
return this.substring(0, min(this.toString().length, 12));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:developer';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.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/widgets/enter_wallet_connect_uri_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
@@ -9,19 +8,19 @@ import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.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 'widgets/pairing_item_widget.dart';
|
||||
import 'widgets/wc_pairing_item_widget.dart';
|
||||
import 'wc_pairing_detail_page.dart';
|
||||
|
||||
class WalletConnectConnectionsView extends StatelessWidget {
|
||||
final Web3WalletService web3walletService;
|
||||
final WalletKitService walletKitService;
|
||||
|
||||
WalletConnectConnectionsView({required this.web3walletService, Uri? launchUri, Key? key})
|
||||
WalletConnectConnectionsView({required this.walletKitService, Uri? launchUri, Key? key})
|
||||
: super(key: key) {
|
||||
_triggerPairingFromDeeplink(launchUri);
|
||||
}
|
||||
@@ -39,42 +38,38 @@ class WalletConnectConnectionsView extends StatelessWidget {
|
||||
|
||||
final uriData = Uri.parse(uri);
|
||||
|
||||
await web3walletService.pairWithUri(uriData);
|
||||
await walletKitService.pairWithUri(uriData);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WCPairingsWidget(web3walletService: web3walletService);
|
||||
return WCPairingsWidget(walletKitService: walletKitService);
|
||||
}
|
||||
}
|
||||
|
||||
class WCPairingsWidget extends BasePage {
|
||||
WCPairingsWidget({required this.web3walletService, Key? key})
|
||||
: web3wallet = web3walletService.getWeb3Wallet();
|
||||
WCPairingsWidget({required this.walletKitService, Key? key})
|
||||
: walletKit = walletKitService.walletKit;
|
||||
|
||||
final Web3Wallet web3wallet;
|
||||
final Web3WalletService web3walletService;
|
||||
final ReownWalletKit walletKit;
|
||||
final WalletKitService walletKitService;
|
||||
|
||||
@override
|
||||
String get title => S.current.walletConnect;
|
||||
|
||||
Future<void> _onScanQrCode(BuildContext context, Web3Wallet web3Wallet) async {
|
||||
Future<void> _onScanQrCode(BuildContext context, ReownWalletKit web3Wallet) async {
|
||||
final String? uri;
|
||||
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
bool isCameraPermissionGranted =
|
||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
||||
if (!isCameraPermissionGranted) return;
|
||||
uri = await presentQRScanner(context);
|
||||
} else {
|
||||
uri = await _showEnterWalletConnectURIPopUp(context);
|
||||
}
|
||||
|
||||
if (uri == null) return _invalidUriToast(context, S.current.nullURIError);
|
||||
|
||||
log('_onFoundUri: $uri');
|
||||
final Uri uriData = Uri.parse(uri);
|
||||
await web3walletService.pairWithUri(uriData);
|
||||
await _handleWalletConnectURI(uri, context);
|
||||
}
|
||||
|
||||
Future<String?> _showEnterWalletConnectURIPopUp(BuildContext context) async {
|
||||
@@ -87,6 +82,17 @@ class WCPairingsWidget extends BasePage {
|
||||
return walletConnectURI;
|
||||
}
|
||||
|
||||
Future<void> _handleWalletConnectURI(
|
||||
String? walletConnectURI,
|
||||
BuildContext context,
|
||||
) async {
|
||||
if (walletConnectURI == null) return _invalidUriToast(context, S.current.nullURIError);
|
||||
|
||||
log('_onFoundUri: $walletConnectURI');
|
||||
final Uri uriData = Uri.parse(walletConnectURI);
|
||||
await walletKitService.pairWithUri(uriData);
|
||||
}
|
||||
|
||||
Future<void> _invalidUriToast(BuildContext context, String message) async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
@@ -126,15 +132,30 @@ class WCPairingsWidget extends BasePage {
|
||||
text: S.current.newConnection,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
onPressed: () => _onScanQrCode(context, web3wallet),
|
||||
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: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Visibility(
|
||||
visible: web3walletService.pairings.isEmpty,
|
||||
visible: walletKitService.pairings.isEmpty,
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.current.activeConnectionsPrompt,
|
||||
@@ -147,10 +168,10 @@ class WCPairingsWidget extends BasePage {
|
||||
),
|
||||
),
|
||||
replacement: ListView.builder(
|
||||
itemCount: web3walletService.pairings.length,
|
||||
itemCount: walletKitService.pairings.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final pairing = web3walletService.pairings[index];
|
||||
return PairingItemWidget(
|
||||
final pairing = walletKitService.pairings[index];
|
||||
return WCPairingItemWidget(
|
||||
key: ValueKey(pairing.topic),
|
||||
pairing: pairing,
|
||||
onTap: () {
|
||||
@@ -159,7 +180,7 @@ class WCPairingsWidget extends BasePage {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WalletConnectPairingDetailsPage(
|
||||
pairing: pairing,
|
||||
web3walletService: web3walletService,
|
||||
walletKitService: walletKitService,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.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/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
|
||||
import 'utils/namespace_model_builder.dart';
|
||||
|
||||
class WalletConnectPairingDetailsPage extends StatefulWidget {
|
||||
final PairingInfo pairing;
|
||||
final Web3WalletService web3walletService;
|
||||
final WalletKitService walletKitService;
|
||||
|
||||
const WalletConnectPairingDetailsPage({
|
||||
required this.pairing,
|
||||
required this.web3walletService,
|
||||
required this.walletKitService,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -33,7 +31,9 @@ class WalletConnectPairingDetailsPageState extends State<WalletConnectPairingDet
|
||||
void initState() {
|
||||
super.initState();
|
||||
initDateTime();
|
||||
initSessions();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
initSessions();
|
||||
});
|
||||
}
|
||||
|
||||
void initDateTime() {
|
||||
@@ -46,13 +46,13 @@ class WalletConnectPairingDetailsPageState extends State<WalletConnectPairingDet
|
||||
}
|
||||
|
||||
void initSessions() {
|
||||
List<SessionData> sessions = widget.web3walletService.getSessionsForPairingInfo(widget.pairing);
|
||||
List<SessionData> sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing);
|
||||
|
||||
for (final SessionData session in sessions) {
|
||||
List<Widget> namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces(
|
||||
session.topic,
|
||||
session.namespaces,
|
||||
widget.web3walletService.getWeb3Wallet(),
|
||||
context,
|
||||
);
|
||||
// Loop through and add the namespace widgets, but put 20 pixels between each one
|
||||
for (int i = 0; i < namespaceWidget.length; i++) {
|
||||
@@ -61,6 +61,59 @@ class WalletConnectPairingDetailsPageState extends State<WalletConnectPairingDet
|
||||
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).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
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).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
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).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +123,7 @@ class WalletConnectPairingDetailsPageState extends State<WalletConnectPairingDet
|
||||
widget.pairing,
|
||||
expiryDate,
|
||||
sessionWidgets,
|
||||
widget.web3walletService,
|
||||
widget.walletKitService,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -80,13 +133,13 @@ class WCCDetailsWidget extends BasePage {
|
||||
this.pairing,
|
||||
this.expiryDate,
|
||||
this.sessionWidgets,
|
||||
this.web3walletService,
|
||||
this.walletKitService,
|
||||
);
|
||||
|
||||
final PairingInfo pairing;
|
||||
final String expiryDate;
|
||||
final List<Widget> sessionWidgets;
|
||||
final Web3WalletService web3walletService;
|
||||
final WalletKitService walletKitService;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
@@ -141,7 +194,7 @@ class WCCDetailsWidget extends BasePage {
|
||||
const SizedBox(height: 20.0),
|
||||
PrimaryButton(
|
||||
onPressed: () =>
|
||||
_onDeleteButtonPressed(context, pairing.peerMetadata!.name, web3walletService),
|
||||
_onDeleteButtonPressed(context, pairing.peerMetadata!.name, walletKitService),
|
||||
text: S.current.delete,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
@@ -154,7 +207,10 @@ class WCCDetailsWidget extends BasePage {
|
||||
}
|
||||
|
||||
Future<void> _onDeleteButtonPressed(
|
||||
BuildContext context, String dAppName, Web3WalletService web3walletService) async {
|
||||
BuildContext context,
|
||||
String dAppName,
|
||||
WalletKitService walletKitService,
|
||||
) async {
|
||||
bool confirmed = false;
|
||||
|
||||
await showPopUp<void>(
|
||||
@@ -175,11 +231,13 @@ class WCCDetailsWidget extends BasePage {
|
||||
);
|
||||
if (confirmed) {
|
||||
try {
|
||||
await web3walletService.disconnectSession(pairing.topic);
|
||||
await walletKitService.deletePairing(topic: pairing.topic);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/models/bottom_sheet_queue_item_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetListener extends StatefulWidget {
|
||||
final BottomSheetService bottomSheetService;
|
||||
final Widget child;
|
||||
|
||||
const BottomSheetListener({
|
||||
required this.bottomSheetService,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
BottomSheetListenerState createState() => BottomSheetListenerState();
|
||||
}
|
||||
|
||||
class BottomSheetListenerState extends State<BottomSheetListener> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.bottomSheetService.currentSheet.addListener(_showBottomSheet);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.bottomSheetService.currentSheet.removeListener(_showBottomSheet);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _showBottomSheet() async {
|
||||
if (widget.bottomSheetService.currentSheet.value != null) {
|
||||
BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!;
|
||||
final value = await showModalBottomSheet(
|
||||
context: context,
|
||||
isDismissible: item.isModalDismissible,
|
||||
backgroundColor: Color.fromARGB(0, 0, 0, 0),
|
||||
isScrollControlled: true,
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
|
||||
builder: (context) {
|
||||
if (item.closeAfter > 0) {
|
||||
Future.delayed(Duration(seconds: item.closeAfter), () {
|
||||
try {
|
||||
if (!mounted) return;
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} catch (e, s) {
|
||||
debugPrint('[$runtimeType] close $e $s');
|
||||
}
|
||||
});
|
||||
}
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom + 24,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
color: Theme.of(context).appBarTheme.titleTextStyle!.color!,
|
||||
padding: const EdgeInsets.all(0.0),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.close_sharp),
|
||||
),
|
||||
],
|
||||
),
|
||||
Flexible(child: item.widget),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (!item.completer.isCompleted) {
|
||||
item.completer.complete(value);
|
||||
}
|
||||
widget.bottomSheetService.showNext();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetMessageDisplayWidget extends StatelessWidget {
|
||||
@@ -18,7 +19,7 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
@@ -30,7 +31,7 @@ class BottomSheetMessageDisplayWidget extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1,180 +0,0 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
|
||||
import '../../../../core/wallet_connect/models/auth_request_model.dart';
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
import '../../../../core/wallet_connect/models/session_request_model.dart';
|
||||
import '../utils/namespace_model_builder.dart';
|
||||
import 'connection_widget.dart';
|
||||
|
||||
class ConnectionRequestWidget extends StatefulWidget {
|
||||
const ConnectionRequestWidget({
|
||||
required this.wallet,
|
||||
required this.chaindIdNamespace,
|
||||
this.authRequest,
|
||||
this.sessionProposal,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final Web3Wallet wallet;
|
||||
final String chaindIdNamespace;
|
||||
final AuthRequestModel? authRequest;
|
||||
final SessionRequestModel? sessionProposal;
|
||||
|
||||
@override
|
||||
State<ConnectionRequestWidget> createState() => _ConnectionRequestWidgetState();
|
||||
}
|
||||
|
||||
class _ConnectionRequestWidgetState extends State<ConnectionRequestWidget> {
|
||||
ConnectionMetadata? metadata;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Get the connection metadata
|
||||
metadata = widget.authRequest?.request.requester ?? widget.sessionProposal?.request.proposer;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (metadata == null) {
|
||||
return Text(
|
||||
S.current.error,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _ConnectionMetadataDisplayWidget(
|
||||
metadata: metadata,
|
||||
wallet: widget.wallet,
|
||||
authRequest: widget.authRequest,
|
||||
sessionProposal: widget.sessionProposal,
|
||||
chaindIdNamespace: widget.chaindIdNamespace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ConnectionMetadataDisplayWidget extends StatelessWidget {
|
||||
const _ConnectionMetadataDisplayWidget({
|
||||
required this.wallet,
|
||||
required this.metadata,
|
||||
required this.sessionProposal,
|
||||
required this.chaindIdNamespace,
|
||||
this.authRequest,
|
||||
});
|
||||
|
||||
final ConnectionMetadata? metadata;
|
||||
final Web3Wallet wallet;
|
||||
final String chaindIdNamespace;
|
||||
final AuthRequestModel? authRequest;
|
||||
final SessionRequestModel? sessionProposal;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromARGB(255, 18, 18, 19),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
metadata!.metadata.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
S.current.wouoldLikeToConnect,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
metadata!.metadata.url,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Visibility(
|
||||
visible: authRequest != null,
|
||||
child: _AuthRequestWidget(
|
||||
wallet: wallet,
|
||||
authRequest: authRequest,
|
||||
chaindIdNamespace: chaindIdNamespace,
|
||||
),
|
||||
|
||||
//If authRequest is null, sessionProposal is not null.
|
||||
replacement: _SessionProposalWidget(sessionProposal: sessionProposal!),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AuthRequestWidget extends StatelessWidget {
|
||||
const _AuthRequestWidget({
|
||||
required this.wallet,
|
||||
required this.chaindIdNamespace,
|
||||
this.authRequest,
|
||||
});
|
||||
|
||||
final Web3Wallet wallet;
|
||||
final String chaindIdNamespace;
|
||||
final AuthRequestModel? authRequest;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final model = ConnectionModel(
|
||||
text: wallet.formatAuthMessage(
|
||||
iss: 'did:pkh:$chaindIdNamespace:${authRequest!.iss}',
|
||||
cacaoPayload: CacaoRequestPayload.fromPayloadParams(
|
||||
authRequest!.request.payloadParams,
|
||||
),
|
||||
),
|
||||
);
|
||||
return ConnectionWidget(
|
||||
title: S.current.message,
|
||||
info: [model],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SessionProposalWidget extends StatelessWidget {
|
||||
const _SessionProposalWidget({required this.sessionProposal});
|
||||
|
||||
final SessionRequestModel sessionProposal;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Create the connection models using the required and optional namespaces provided by the proposal data
|
||||
// The key is the title and the list of values is the data
|
||||
final List<ConnectionWidget> views = ConnectionWidgetBuilder.buildFromRequiredNamespaces(
|
||||
sessionProposal.request.requiredNamespaces,
|
||||
);
|
||||
|
||||
return Column(children: views);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -29,14 +30,14 @@ class _EnterWallectConnectURIWrapperWidgetState extends State<EnterWalletConnect
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _EnterWalletConnectURIWidget(
|
||||
return EnterWalletConnectURIWidget(
|
||||
controller: controller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EnterWalletConnectURIWidget extends BaseAlertDialog {
|
||||
_EnterWalletConnectURIWidget({
|
||||
class EnterWalletConnectURIWidget extends BaseAlertDialog {
|
||||
EnterWalletConnectURIWidget({
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@@ -57,18 +58,25 @@ class _EnterWalletConnectURIWidget extends BaseAlertDialog {
|
||||
@override
|
||||
Widget content(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
elevation: 0.0,
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
S.current.copyWalletConnectLink,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: controller,
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: Container(
|
||||
width: 24,
|
||||
@@ -79,14 +87,13 @@ class _EnterWalletConnectURIWidget extends BaseAlertDialog {
|
||||
child: InkWell(
|
||||
onTap: () => _pasteWalletConnectURI(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/images/paste_ios.png',
|
||||
color:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -99,7 +106,7 @@ class _EnterWalletConnectURIWidget extends BaseAlertDialog {
|
||||
),
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../core/wallet_connect/models/bottom_sheet_queue_item_model.dart';
|
||||
|
||||
class BottomSheetListener extends StatefulWidget {
|
||||
final BottomSheetService bottomSheetService;
|
||||
final Widget child;
|
||||
|
||||
const BottomSheetListener({
|
||||
required this.child,
|
||||
required this.bottomSheetService,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
BottomSheetListenerState createState() => BottomSheetListenerState();
|
||||
}
|
||||
|
||||
class BottomSheetListenerState extends State<BottomSheetListener> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.bottomSheetService.currentSheet.addListener(_showBottomSheet);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.bottomSheetService.currentSheet.removeListener(_showBottomSheet);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _showBottomSheet() async {
|
||||
if (widget.bottomSheetService.currentSheet.value != null) {
|
||||
BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!;
|
||||
final value = await showModalBottomSheet(
|
||||
context: context,
|
||||
isDismissible: item.isModalDismissible,
|
||||
backgroundColor: Color.fromARGB(0, 0, 0, 0),
|
||||
isScrollControlled: true,
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
|
||||
builder: (context) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromARGB(255, 18, 18, 19),
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: item.widget,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (!item.completer.isCompleted) {
|
||||
item.completer.complete(value);
|
||||
}
|
||||
widget.bottomSheetService.resetCurrentSheet();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Web3RequestModal extends StatelessWidget {
|
||||
const Web3RequestModal({required this.child, this.onAccept, this.onReject, super.key});
|
||||
|
||||
final Widget child;
|
||||
final VoidCallback? onAccept;
|
||||
final VoidCallback? onReject;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
child,
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: onReject ?? () => Navigator.of(context).pop(false),
|
||||
text: S.current.reject,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
textColor: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: onAccept ?? () => Navigator.of(context).pop(true),
|
||||
text: S.current.approve,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
|
||||
class ConnectionItemWidget extends StatelessWidget {
|
||||
const ConnectionItemWidget({required this.model, Key? key}) : super(key: key);
|
||||
class WCConnectionItemWidget extends StatelessWidget {
|
||||
const WCConnectionItemWidget({required this.model, Key? key}) : super(key: key);
|
||||
|
||||
final ConnectionModel model;
|
||||
final WCConnectionModel model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -52,7 +52,7 @@ class ConnectionItemWidget extends StatelessWidget {
|
||||
class _NoModelElementWidget extends StatelessWidget {
|
||||
const _NoModelElementWidget({required this.model});
|
||||
|
||||
final ConnectionModel model;
|
||||
final WCConnectionModel model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -73,7 +73,7 @@ class _ModelElementWidget extends StatelessWidget {
|
||||
required this.modelElement,
|
||||
});
|
||||
|
||||
final ConnectionModel model;
|
||||
final WCConnectionModel model;
|
||||
final String modelElement;
|
||||
|
||||
@override
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:cake_wallet/generated/i18n.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/models/wc_connection_model.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/utils/namespace_model_builder.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
|
||||
class WCConnectionRequestWidget extends StatelessWidget {
|
||||
WCConnectionRequestWidget({
|
||||
this.sessionAuthPayload,
|
||||
this.proposalData,
|
||||
this.requester,
|
||||
this.verifyContext,
|
||||
required this.walletKeyService,
|
||||
required this.walletKit,
|
||||
required this.appStore,
|
||||
});
|
||||
|
||||
final SessionAuthPayload? sessionAuthPayload;
|
||||
final ProposalData? proposalData;
|
||||
final ConnectionMetadata? requester;
|
||||
final VerifyContext? verifyContext;
|
||||
final WalletConnectKeyService walletKeyService;
|
||||
final AppStore appStore;
|
||||
final ReownWalletKit walletKit;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (requester == null) {
|
||||
return Text(S.current.error.toUpperCase());
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${requester!.metadata.name} ${S.current.wouoldLikeToConnect}',
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
(sessionAuthPayload != null)
|
||||
? _buildSessionAuthRequestView()
|
||||
: _buildSessionProposalView(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSessionAuthRequestView() {
|
||||
final cacaoPayload = CacaoRequestPayload.fromSessionAuthPayload(
|
||||
sessionAuthPayload!,
|
||||
);
|
||||
|
||||
final List<WCConnectionModel> messagesModels = [];
|
||||
for (var chain in sessionAuthPayload!.chains) {
|
||||
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
final iss = 'did:pkh:$chain:${chainKeys.first.publicKey}';
|
||||
|
||||
final message = walletKit.formatAuthMessage(
|
||||
iss: iss,
|
||||
cacaoPayload: cacaoPayload,
|
||||
);
|
||||
|
||||
messagesModels.add(
|
||||
WCConnectionModel(
|
||||
title: '${S.current.message} ${messagesModels.length + 1}',
|
||||
elements: [message],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return WCConnectionWidget(
|
||||
title: '${messagesModels.length} ${S.current.messages}',
|
||||
info: messagesModels,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSessionProposalView(BuildContext context) {
|
||||
// Create the connection models using the required and optional namespaces provided by the proposal data
|
||||
|
||||
final views = ConnectionWidgetBuilder.buildFromRequiredNamespaces(
|
||||
proposalData!.generatedNamespaces ?? {},
|
||||
);
|
||||
|
||||
return Column(children: views);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -1,17 +1,17 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
import 'connection_item_widget.dart';
|
||||
import 'wc_connection_item_widget.dart';
|
||||
|
||||
class ConnectionWidget extends StatelessWidget {
|
||||
const ConnectionWidget({required this.title, required this.info, super.key});
|
||||
class WCConnectionWidget extends StatelessWidget {
|
||||
const WCConnectionWidget({required this.title, required this.info, super.key});
|
||||
|
||||
final String title;
|
||||
final List<ConnectionModel> info;
|
||||
final List<WCConnectionModel> info;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColorLight,
|
||||
@@ -23,7 +23,7 @@ class ConnectionWidget extends StatelessWidget {
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: Theme.of(context) .colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
@@ -32,12 +32,12 @@ class ConnectionWidget extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
color: Theme.of(context).appBarTheme.titleTextStyle!.color!
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...info.map((e) => ConnectionItemWidget(model: e)),
|
||||
...info.map((e) => WCConnectionItemWidget(model: e)),
|
||||
],
|
||||
),
|
||||
);
|
||||
+4
-3
@@ -3,10 +3,10 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:walletconnect_flutter_v2/apis/core/pairing/utils/pairing_models.dart';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
|
||||
class PairingItemWidget extends StatelessWidget {
|
||||
const PairingItemWidget({required this.pairing, required this.onTap, super.key});
|
||||
class WCPairingItemWidget extends StatelessWidget {
|
||||
const WCPairingItemWidget({required this.pairing, required this.onTap, super.key});
|
||||
|
||||
final PairingInfo pairing;
|
||||
final void Function() onTap;
|
||||
@@ -14,6 +14,7 @@ class PairingItemWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
PairingMetadata? metadata = pairing.peerMetadata;
|
||||
|
||||
if (metadata == null) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:cake_wallet/di.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/widgets/wc_verify_context_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
|
||||
class WCRequestWidget extends StatelessWidget {
|
||||
WCRequestWidget({
|
||||
required this.child,
|
||||
this.verifyContext,
|
||||
this.onAccept,
|
||||
this.onReject,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final VerifyContext? verifyContext;
|
||||
final VoidCallback? onAccept;
|
||||
final VoidCallback? onReject;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentTheme = getIt.get<SettingsStore>().currentTheme;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
WCVerifyContextWidget(
|
||||
currentTheme: currentTheme,
|
||||
verifyContext: verifyContext,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(child: child),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: onReject ??
|
||||
() {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop(WCBottomSheetResult.reject);
|
||||
}
|
||||
},
|
||||
text: S.current.reject,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
textColor: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: onAccept ??
|
||||
() {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop(WCBottomSheetResult.one);
|
||||
}
|
||||
},
|
||||
text: S.current.approve,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: currentTheme.type == ThemeType.dark
|
||||
? Theme.of(context).extension<DashboardPageTheme>()!.textColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
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/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WCSessionAuthRequestWidget extends StatelessWidget {
|
||||
const WCSessionAuthRequestWidget({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(child: child),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
PrimaryButton(
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop(WCBottomSheetResult.reject);
|
||||
}
|
||||
},
|
||||
text: S.current.cancel,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
textColor: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PrimaryButton(
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop(WCBottomSheetResult.one);
|
||||
}
|
||||
},
|
||||
text: S.current.sign_one,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PrimaryButton(
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop(WCBottomSheetResult.all);
|
||||
}
|
||||
},
|
||||
text: S.current.sign_all,
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:reown_walletkit/reown_walletkit.dart';
|
||||
|
||||
class WCVerifyContextWidget extends StatelessWidget {
|
||||
const WCVerifyContextWidget({
|
||||
super.key,
|
||||
required this.verifyContext,
|
||||
required this.currentTheme,
|
||||
});
|
||||
|
||||
final VerifyContext? verifyContext;
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (verifyContext == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (verifyContext!.validation.scam) {
|
||||
return VerifyBanner(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
origin: verifyContext!.origin,
|
||||
title: S.current.security_risk,
|
||||
text: S.current.security_risk_description,
|
||||
);
|
||||
}
|
||||
if (verifyContext!.validation.invalid) {
|
||||
return VerifyBanner(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
origin: verifyContext!.origin,
|
||||
title: S.current.domain_mismatch,
|
||||
text: S.current.domain_mismatch_description,
|
||||
);
|
||||
}
|
||||
if (verifyContext!.validation.valid) {
|
||||
return VerifyHeader(
|
||||
iconColor: currentTheme.type == ThemeType.dark
|
||||
? Theme.of(context).extension<DashboardPageTheme>()!.textColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
title: verifyContext!.origin,
|
||||
);
|
||||
}
|
||||
return VerifyBanner(
|
||||
color: Colors.orange,
|
||||
origin: verifyContext!.origin,
|
||||
title: S.current.cannot_verify,
|
||||
text: S.current.cannot_verify_description,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyHeader extends StatelessWidget {
|
||||
const VerifyHeader({
|
||||
super.key,
|
||||
required this.iconColor,
|
||||
required this.title,
|
||||
});
|
||||
final Color iconColor;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.shield_outlined,
|
||||
color: iconColor,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: iconColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyBanner extends StatelessWidget {
|
||||
const VerifyBanner({
|
||||
super.key,
|
||||
required this.origin,
|
||||
required this.title,
|
||||
required this.text,
|
||||
required this.color,
|
||||
});
|
||||
final String origin, title, text;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
origin,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox.square(dimension: 8.0),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
VerifyHeader(
|
||||
iconColor: color,
|
||||
title: title,
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
|
||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
@@ -46,9 +47,9 @@ abstract class AppStoreBase with Store {
|
||||
this.wallet!.setExceptionHandler(ExceptionHandler.onError);
|
||||
|
||||
if (isWalletConnectCompatibleChain(wallet.type)) {
|
||||
await getIt.get<Web3WalletService>().onDispose();
|
||||
getIt.get<Web3WalletService>().create();
|
||||
await getIt.get<Web3WalletService>().init();
|
||||
await getIt.get<WalletKitService>().onDispose();
|
||||
getIt.get<WalletKitService>().create();
|
||||
await getIt.get<WalletKitService>().init();
|
||||
}
|
||||
getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
|
||||
getIt
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/entities/solana_nft_asset_model.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -121,7 +122,7 @@ abstract class NFTViewModelBase with Store {
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: e.toString(),
|
||||
message: S.current.moralis_nft_error,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user