mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-21 06:18:42 +02:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-477-add-ens
Conflicts: pubspec_base.yaml res/values/strings_ar.arb
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
abstract class ChainService {
|
||||
String getNamespace();
|
||||
String getChainId();
|
||||
List<String> getEvents();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart';
|
||||
|
||||
enum EVMChainId {
|
||||
ethereum,
|
||||
polygon,
|
||||
goerli,
|
||||
mumbai,
|
||||
arbitrum,
|
||||
}
|
||||
|
||||
extension EVMChainIdX on EVMChainId {
|
||||
String chain() {
|
||||
String name = '';
|
||||
|
||||
switch (this) {
|
||||
case EVMChainId.ethereum:
|
||||
name = '1';
|
||||
break;
|
||||
case EVMChainId.polygon:
|
||||
name = '137';
|
||||
break;
|
||||
case EVMChainId.goerli:
|
||||
name = '5';
|
||||
break;
|
||||
case EVMChainId.arbitrum:
|
||||
name = '42161';
|
||||
break;
|
||||
case EVMChainId.mumbai:
|
||||
name = '80001';
|
||||
break;
|
||||
}
|
||||
|
||||
return '${EvmChainServiceImpl.namespace}:$name';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
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/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/src/screens/wallet_connect/widgets/error_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:cw_core/wallet_type.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? ethClient,
|
||||
}) : ethClient = ethClient ??
|
||||
Web3Client(
|
||||
appStore.settingsStore.getCurrentNode(WalletType.ethereum).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(getChainId());
|
||||
|
||||
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(getChainId());
|
||||
|
||||
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
final String signature = hex.encode(
|
||||
credentials.signPersonalMessageToUint8List(
|
||||
Uint8List.fromList(utf8.encode(message)),
|
||||
),
|
||||
);
|
||||
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(getChainId());
|
||||
|
||||
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);
|
||||
|
||||
log('Result: $result');
|
||||
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: S.current.awaitDAppProcessing,
|
||||
isError: false,
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
log('An error has occured 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(getChainId());
|
||||
|
||||
return EthSigUtil.signTypedData(
|
||||
privateKey: keys[0].privateKey,
|
||||
jsonData: data ?? '',
|
||||
version: TypedDataVersion.V4,
|
||||
);
|
||||
}
|
||||
|
||||
String _convertToReadable(Map<String, dynamic> data) {
|
||||
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() + ' ETH'
|
||||
: '0 ETH';
|
||||
String from = data['from'] as String;
|
||||
String to = data['to'] as String;
|
||||
|
||||
return '''
|
||||
Gas: $gas\n
|
||||
Value: $value\n
|
||||
From: $from\n
|
||||
To: $to
|
||||
''';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class BottomSheetQueueItemModel {
|
||||
final Widget widget;
|
||||
final bool isModalDismissible;
|
||||
final Completer<dynamic> completer;
|
||||
|
||||
BottomSheetQueueItemModel({
|
||||
required this.widget,
|
||||
required this.completer,
|
||||
this.isModalDismissible = false,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BottomSheetQueueItemModel(widget: $widget, completer: $completer)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
class ConnectionModel {
|
||||
final String? title;
|
||||
final String? text;
|
||||
final List<String>? elements;
|
||||
final Map<String, void Function()>? elementActions;
|
||||
|
||||
ConnectionModel({
|
||||
this.title,
|
||||
this.text,
|
||||
this.elements,
|
||||
this.elementActions,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WalletConnectRequestModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
abstract class WalletConnectKeyService {
|
||||
/// Returns a list of all the keys.
|
||||
List<ChainKeyModel> getKeys();
|
||||
|
||||
/// Returns a list of all the chain ids.
|
||||
List<String> getChains();
|
||||
|
||||
/// Returns a list of all the keys for a given chain id.
|
||||
/// If the chain is not found, returns an empty list.
|
||||
/// - [chain]: The chain to get the keys for.
|
||||
List<ChainKeyModel> getKeysForChain(String chain);
|
||||
|
||||
/// Returns a list of all the accounts in namespace:chainId:address format.
|
||||
List<String> getAllAccounts();
|
||||
}
|
||||
|
||||
class KeyServiceImpl implements WalletConnectKeyService {
|
||||
KeyServiceImpl(this.wallet)
|
||||
: _keys = [
|
||||
ChainKeyModel(
|
||||
chains: [
|
||||
'eip155:1',
|
||||
'eip155:5',
|
||||
'eip155:137',
|
||||
'eip155:42161',
|
||||
'eip155:80001',
|
||||
],
|
||||
privateKey: ethereum!.getPrivateKey(wallet),
|
||||
publicKey: ethereum!.getPublicKey(wallet),
|
||||
),
|
||||
|
||||
];
|
||||
|
||||
late final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
late final List<ChainKeyModel> _keys;
|
||||
|
||||
@override
|
||||
List<String> getChains() {
|
||||
final List<String> chainIds = [];
|
||||
for (final ChainKeyModel key in _keys) {
|
||||
chainIds.addAll(key.chains);
|
||||
}
|
||||
return chainIds;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ChainKeyModel> getKeys() => _keys;
|
||||
|
||||
@override
|
||||
List<ChainKeyModel> getKeysForChain(String chain) {
|
||||
return _keys.where((e) => e.chains.contains(chain)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getAllAccounts() {
|
||||
final List<String> accounts = [];
|
||||
for (final ChainKeyModel key in _keys) {
|
||||
for (final String chain in key.chains) {
|
||||
accounts.add('$chain:${key.publicKey}');
|
||||
}
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/bottom_sheet_queue_item_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class BottomSheetService {
|
||||
abstract final ValueNotifier<BottomSheetQueueItemModel?> currentSheet;
|
||||
|
||||
Future<dynamic> queueBottomSheet({
|
||||
required Widget widget,
|
||||
bool isModalDismissible = false,
|
||||
});
|
||||
|
||||
void resetCurrentSheet();
|
||||
}
|
||||
|
||||
class BottomSheetServiceImpl implements BottomSheetService {
|
||||
|
||||
@override
|
||||
final ValueNotifier<BottomSheetQueueItemModel?> currentSheet = ValueNotifier(null);
|
||||
|
||||
@override
|
||||
Future<dynamic> queueBottomSheet({
|
||||
required Widget widget,
|
||||
bool isModalDismissible = false,
|
||||
}) async {
|
||||
// Create the bottom sheet queue item
|
||||
final completer = Completer<dynamic>();
|
||||
final queueItem = BottomSheetQueueItemModel(
|
||||
widget: widget,
|
||||
completer: completer,
|
||||
isModalDismissible: isModalDismissible,
|
||||
);
|
||||
|
||||
currentSheet.value = queueItem;
|
||||
|
||||
return await completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
void resetCurrentSheet() {
|
||||
currentSheet.value = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.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/src/screens/wallet_connect/widgets/connection_request_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/error_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:eth_sig_util/eth_sig_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.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 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)
|
||||
: 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();
|
||||
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);
|
||||
|
||||
for (final cId in EVMChainId.values) {
|
||||
EvmChainServiceImpl(
|
||||
reference: cId,
|
||||
appStore: appStore,
|
||||
wcKeyService: walletKeyService,
|
||||
bottomSheetService: _bottomSheetHandler,
|
||||
wallet: _web3Wallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
Web3Wallet getWeb3Wallet() {
|
||||
return _web3Wallet;
|
||||
}
|
||||
|
||||
void _onPairingsSync(StoreSyncEvent? args) {
|
||||
if (args != null) {
|
||||
_refreshPairings();
|
||||
}
|
||||
}
|
||||
|
||||
void _onPairingDelete(PairingEvent? event) {
|
||||
_refreshPairings();
|
||||
}
|
||||
|
||||
@action
|
||||
void _refreshPairings() {
|
||||
pairings.clear();
|
||||
final allPairings = _web3Wallet.pairings.getAll();
|
||||
pairings.addAll(allPairings);
|
||||
}
|
||||
|
||||
Future<void> _onSessionProposalError(SessionProposalErrorEvent? args) async {
|
||||
log(args.toString());
|
||||
}
|
||||
|
||||
void _onSessionProposal(SessionProposalEvent? args) async {
|
||||
if (args != null) {
|
||||
final Widget modalWidget = Web3RequestModal(
|
||||
child: ConnectionRequestWidget(
|
||||
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'),
|
||||
);
|
||||
}
|
||||
|
||||
void _onPairingCreate(PairingEvent? args) {
|
||||
log('Pairing Create Event: $args');
|
||||
}
|
||||
|
||||
@action
|
||||
void _onSessionConnect(SessionConnect? args) {
|
||||
if (args != null) {
|
||||
sessions.add(args.session);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _onAuthRequest(AuthRequest? args) async {
|
||||
if (args != null) {
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain('eip155:1');
|
||||
// Create the message to be signed
|
||||
final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}';
|
||||
|
||||
final Widget modalWidget = Web3RequestModal(
|
||||
child: ConnectionRequestWidget(
|
||||
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();
|
||||
}
|
||||
}
|
||||
+34
-5
@@ -3,10 +3,12 @@ import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
||||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.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/buy/robinhood/robinhood_buy_provider.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||
@@ -399,6 +401,10 @@ Future<void> setup({
|
||||
}
|
||||
if (appStore.wallet != null) {
|
||||
authStore.allowed();
|
||||
|
||||
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||
getIt.get<Web3WalletService>().init();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -419,6 +425,10 @@ Future<void> setup({
|
||||
} else {
|
||||
if (appStore.wallet != null) {
|
||||
authStore.allowed();
|
||||
|
||||
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||
getIt.get<Web3WalletService>().init();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -438,11 +448,28 @@ Future<void> setup({
|
||||
}, closable: false);
|
||||
}, instanceName: 'login');
|
||||
|
||||
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
||||
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl(appStore.wallet!));
|
||||
|
||||
getIt.registerLazySingleton<Web3WalletService>(() {
|
||||
final Web3WalletService web3WalletService = Web3WalletService(
|
||||
getIt.get<BottomSheetService>(),
|
||||
getIt.get<WalletConnectKeyService>(),
|
||||
appStore,
|
||||
);
|
||||
web3WalletService.create();
|
||||
return web3WalletService;
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => BalancePage(
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory<DashboardPage>(() => DashboardPage(
|
||||
bottomSheetService: getIt.get<BottomSheetService>(),
|
||||
balancePage: getIt.get<BalancePage>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
@@ -459,6 +486,7 @@ Future<void> setup({
|
||||
});
|
||||
getIt.registerFactoryParam<DesktopDashboardPage, GlobalKey<NavigatorState>, void>(
|
||||
(desktopKey, _) => DesktopDashboardPage(
|
||||
bottomSheetService: getIt.get<BottomSheetService>(),
|
||||
balancePage: getIt.get<BalancePage>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
@@ -668,7 +696,9 @@ Future<void> setup({
|
||||
return NodeListViewModel(_nodeSource, appStore);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
|
||||
getIt.registerFactory(
|
||||
() => ConnectionSyncPage(getIt.get<DashboardViewModel>(), getIt.get<Web3WalletService>()),
|
||||
);
|
||||
|
||||
getIt.registerFactory(
|
||||
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
|
||||
@@ -851,9 +881,8 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
SupportChatPage(
|
||||
getIt.get<SupportViewModel>(), secureStorage: getIt.get<FlutterSecureStorage>()));
|
||||
getIt.registerFactory(() => SupportChatPage(getIt.get<SupportViewModel>(),
|
||||
secureStorage: getIt.get<FlutterSecureStorage>()));
|
||||
|
||||
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ class PreferencesKey {
|
||||
static const disableSellKey = 'disable_sell';
|
||||
static const defaultBuyProvider = 'default_buy_provider';
|
||||
static const currentFiatApiModeKey = 'current_fiat_api_mode';
|
||||
static const allowBiometricalAuthenticationKey =
|
||||
'allow_biometrical_authentication';
|
||||
static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication';
|
||||
static const useTOTP2FA = 'use_totp_2fa';
|
||||
static const failedTotpTokenTrials = 'failed_token_trials';
|
||||
static const disableExchangeKey = 'disable_exchange';
|
||||
@@ -54,8 +53,7 @@ class PreferencesKey {
|
||||
static const clearnetDonationLink = 'clearnet_donation_link';
|
||||
static const onionDonationLink = 'onion_donation_link';
|
||||
static const lastSeenAppVersion = 'last_seen_app_version';
|
||||
static const shouldShowMarketPlaceInDashboard =
|
||||
'should_show_marketplace_in_dashboard';
|
||||
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
|
||||
static const isNewInstall = 'is_new_install';
|
||||
static const shouldRequireTOTP2FAForAccessingWallet =
|
||||
'should_require_totp_2fa_for_accessing_wallets';
|
||||
|
||||
@@ -33,6 +33,20 @@ class CWEthereum extends Ethereum {
|
||||
@override
|
||||
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
|
||||
|
||||
@override
|
||||
String getPrivateKey(WalletBase wallet) {
|
||||
final privateKeyHolder = (wallet as EthereumWallet).ethPrivateKey;
|
||||
String stringKey = bytesToHex(privateKeyHolder.privateKey);
|
||||
return stringKey;
|
||||
}
|
||||
|
||||
@override
|
||||
String getPublicKey(WalletBase wallet) {
|
||||
final privateKeyInUnitInt = (wallet as EthereumWallet).ethPrivateKey;
|
||||
final publicKey = privateKeyInUnitInt.address.hex;
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
|
||||
|
||||
|
||||
+10
-11
@@ -39,7 +39,6 @@ import 'package:cake_wallet/src/screens/root/root.dart';
|
||||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
@@ -155,7 +154,7 @@ Future<void> initializeAppConfigs() async {
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 21);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initialSetup(
|
||||
{required SharedPreferences sharedPreferences,
|
||||
@@ -308,26 +307,26 @@ class _Home extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomeState extends State<_Home> {
|
||||
@override
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
if(!ResponsiveLayoutUtil.instance.isMobile){
|
||||
_setOrientation(context);
|
||||
if (!ResponsiveLayoutUtil.instance.isMobile) {
|
||||
_setOrientation(context);
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
|
||||
void _setOrientation(BuildContext context){
|
||||
void _setOrientation(BuildContext context) {
|
||||
final orientation = MediaQuery.of(context).orientation;
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
final height = MediaQuery.of(context).size.height;
|
||||
if (orientation == Orientation.portrait && width < height) {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
} else if (orientation == Orientation.landscape && width > height) {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'dart:async';
|
||||
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/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/main_actions.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
@@ -35,12 +38,14 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||
|
||||
class DashboardPage extends StatelessWidget {
|
||||
DashboardPage({
|
||||
required this.bottomSheetService,
|
||||
required this.balancePage,
|
||||
required this.dashboardViewModel,
|
||||
required this.addressListViewModel,
|
||||
});
|
||||
|
||||
final BalancePage balancePage;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
|
||||
@@ -55,12 +60,14 @@ class DashboardPage extends StatelessWidget {
|
||||
} else {
|
||||
return _DashboardPageView(
|
||||
balancePage: balancePage,
|
||||
bottomSheetService: bottomSheetService,
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
addressListViewModel: addressListViewModel,
|
||||
);
|
||||
}
|
||||
} else if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
|
||||
return _DashboardPageView(
|
||||
bottomSheetService: bottomSheetService,
|
||||
balancePage: balancePage,
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
addressListViewModel: addressListViewModel,
|
||||
@@ -76,6 +83,7 @@ class DashboardPage extends StatelessWidget {
|
||||
|
||||
class _DashboardPageView extends BasePage {
|
||||
_DashboardPageView({
|
||||
required this.bottomSheetService,
|
||||
required this.balancePage,
|
||||
required this.dashboardViewModel,
|
||||
required this.addressListViewModel,
|
||||
@@ -126,6 +134,7 @@ class _DashboardPageView extends BasePage {
|
||||
}
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
|
||||
int get initialPage => dashboardViewModel.shouldShowMarketPlaceInDashboard ? 1 : 0;
|
||||
@@ -158,102 +167,106 @@ class _DashboardPageView extends BasePage {
|
||||
|
||||
return SafeArea(
|
||||
minimum: EdgeInsets.only(bottom: 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return PageView.builder(
|
||||
controller: controller,
|
||||
itemCount: pages.length,
|
||||
itemBuilder: (context, index) => pages[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 24, top: 10),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return ExcludeSemantics(
|
||||
child: SmoothPageIndicator(
|
||||
child: BottomSheetListener(
|
||||
bottomSheetService: bottomSheetService,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return PageView.builder(
|
||||
controller: controller,
|
||||
count: pages.length,
|
||||
effect: ColorTransitionEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context).indicatorColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.indicatorDotTheme
|
||||
.activeIndicatorColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: pages.length,
|
||||
itemBuilder: (context, index) => pages[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return ClipRect(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(50.0),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
width: 1,
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 24, top: 10),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return ExcludeSemantics(
|
||||
child: SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: pages.length,
|
||||
effect: ColorTransitionEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context).indicatorColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.indicatorDotTheme
|
||||
.activeIndicatorColor,
|
||||
),
|
||||
color:
|
||||
Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return ClipRect(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 32, right: 32),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: MainActions.all
|
||||
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
|
||||
.map(
|
||||
(action) => Semantics(
|
||||
button: true,
|
||||
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
|
||||
child: ActionButton(
|
||||
image: Image.asset(
|
||||
action.image,
|
||||
height: 24,
|
||||
width: 24,
|
||||
color: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.mainActionsIconColor
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(50.0),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
width: 1,
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.extension<SyncIndicatorTheme>()!
|
||||
.syncedBackgroundColor,
|
||||
),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 32, right: 32),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: MainActions.all
|
||||
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
|
||||
.map(
|
||||
(action) => Semantics(
|
||||
button: true,
|
||||
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
|
||||
child: ActionButton(
|
||||
image: Image.asset(
|
||||
action.image,
|
||||
height: 24,
|
||||
width: 24,
|
||||
color: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.mainActionsIconColor
|
||||
: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
),
|
||||
title: action.name(context),
|
||||
onClick: () async =>
|
||||
await action.onTap(context, dashboardViewModel),
|
||||
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? null
|
||||
: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
),
|
||||
title: action.name(context),
|
||||
onClick: () async =>
|
||||
await action.onTap(context, dashboardViewModel),
|
||||
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? null
|
||||
: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +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/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
@@ -19,12 +21,14 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
class DesktopDashboardPage extends StatelessWidget {
|
||||
DesktopDashboardPage({
|
||||
required this.balancePage,
|
||||
required this.bottomSheetService,
|
||||
required this.dashboardViewModel,
|
||||
required this.addressListViewModel,
|
||||
required this.desktopKey,
|
||||
});
|
||||
|
||||
final BalancePage balancePage;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final GlobalKey<NavigatorState> desktopKey;
|
||||
@@ -36,31 +40,34 @@ class DesktopDashboardPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 400,
|
||||
child: balancePage,
|
||||
),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: Navigator(
|
||||
key: desktopKey,
|
||||
initialRoute: Routes.desktop_actions,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
return BottomSheetListener(
|
||||
bottomSheetService: bottomSheetService,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 400,
|
||||
child: balancePage,
|
||||
),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: Navigator(
|
||||
key: desktopKey,
|
||||
initialRoute: Routes.desktop_actions,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -97,8 +98,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isInactive &&
|
||||
widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||
if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||
setState(() => _setInactive(true));
|
||||
}
|
||||
|
||||
@@ -125,16 +125,15 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||
return;
|
||||
} else {
|
||||
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
|
||||
final shouldUseTotp2FAToAccessWallets = widget.appStore
|
||||
.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
final shouldUseTotp2FAToAccessWallets =
|
||||
widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||
_reset();
|
||||
auth.close(
|
||||
route: Routes.totpAuthCodePage,
|
||||
arguments: TotpAuthArgumentsModel(
|
||||
onTotpAuthenticationFinished:
|
||||
(bool isAuthenticatedSuccessfully,
|
||||
TotpAuthCodePageState totpAuth) {
|
||||
(bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
@@ -169,7 +168,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||
launchUri = null;
|
||||
}
|
||||
|
||||
return WillPopScope(onWillPop: () async => false, child: widget.child);
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/wallet_connect_button.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
@@ -15,11 +18,12 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class ConnectionSyncPage extends BasePage {
|
||||
ConnectionSyncPage(this.dashboardViewModel);
|
||||
ConnectionSyncPage(this.dashboardViewModel, this.web3walletService);
|
||||
|
||||
@override
|
||||
String get title => S.current.connection_sync;
|
||||
|
||||
final Web3WalletService web3walletService;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
@@ -66,6 +70,20 @@ class ConnectionSyncPage extends BasePage {
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
|
||||
),
|
||||
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||
if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
|
||||
WalletConnectTile(
|
||||
onTap: () async {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return WalletConnectConnectionsView(web3walletService: web3walletService);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WalletConnectTile extends StatelessWidget {
|
||||
const WalletConnectTile({required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
'assets/images/walletconnect_logo.png',
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.current.walletConnect,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/select_arrow.png',
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
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';
|
||||
|
||||
class ConnectionWidgetBuilder {
|
||||
static List<ConnectionWidget> buildFromRequiredNamespaces(
|
||||
Map<String, RequiredNamespace> requiredNamespaces,
|
||||
) {
|
||||
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));
|
||||
|
||||
views.add(ConnectionWidget(title: key, info: models));
|
||||
}
|
||||
|
||||
return views;
|
||||
}
|
||||
|
||||
static List<ConnectionWidget> buildFromNamespaces(
|
||||
String topic,
|
||||
Map<String, Namespace> namespaces,
|
||||
Web3Wallet web3wallet,
|
||||
) {
|
||||
final List<ConnectionWidget> views = [];
|
||||
for (final key in namespaces.keys) {
|
||||
final Namespace ns = namespaces[key]!;
|
||||
final List<ConnectionModel> 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,
|
||||
));
|
||||
|
||||
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'),
|
||||
);
|
||||
};
|
||||
}
|
||||
models.add(
|
||||
ConnectionModel(title: S.current.events, elements: ns.events, elementActions: actions),
|
||||
);
|
||||
|
||||
views.add(ConnectionWidget(title: key, info: models));
|
||||
}
|
||||
|
||||
return views;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'dart:convert';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
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/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.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 'widgets/pairing_item_widget.dart';
|
||||
import 'wc_pairing_detail_page.dart';
|
||||
|
||||
class WalletConnectConnectionsView extends StatelessWidget {
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
WalletConnectConnectionsView({required this.web3walletService, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WCPairingsWidget(web3walletService: web3walletService);
|
||||
}
|
||||
}
|
||||
|
||||
class WCPairingsWidget extends BasePage {
|
||||
WCPairingsWidget({required this.web3walletService, Key? key})
|
||||
: web3wallet = web3walletService.getWeb3Wallet();
|
||||
|
||||
final Web3Wallet web3wallet;
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
@override
|
||||
String get title => S.current.walletConnect;
|
||||
|
||||
Future<void> _onScanQrCode(BuildContext context, Web3Wallet web3Wallet) async {
|
||||
final String? uri = await presentQRScanner();
|
||||
|
||||
if (uri == null) return _invalidUriToast(context, S.current.nullURIError);
|
||||
|
||||
try {
|
||||
log('_onFoundUri: $uri');
|
||||
final Uri uriData = Uri.parse(uri);
|
||||
await web3Wallet.pair(uri: uriData);
|
||||
} on WalletConnectError catch (e) {
|
||||
await _invalidUriToast(context, e.message);
|
||||
} catch (e) {
|
||||
await _invalidUriToast(context, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _invalidUriToast(BuildContext context, String message) async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: message,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: Navigator.of(context).pop,
|
||||
alertBarrierDismissible: false,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
Text(
|
||||
S.current.connectWalletPrompt,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
PrimaryButton(
|
||||
text: S.current.newConnection,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
onPressed: () => _onScanQrCode(context, web3wallet),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
Expanded(
|
||||
child: Visibility(
|
||||
visible: web3walletService.pairings.isEmpty,
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.current.activeConnectionsPrompt,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
replacement: ListView.builder(
|
||||
itemCount: web3walletService.pairings.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final pairing = web3walletService.pairings[index];
|
||||
return PairingItemWidget(
|
||||
key: ValueKey(pairing.topic),
|
||||
pairing: pairing,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WalletConnectPairingDetailsPage(
|
||||
pairing: pairing,
|
||||
web3walletService: web3walletService,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
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/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 'utils/namespace_model_builder.dart';
|
||||
|
||||
class WalletConnectPairingDetailsPage extends StatefulWidget {
|
||||
final PairingInfo pairing;
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
const WalletConnectPairingDetailsPage({
|
||||
required this.pairing,
|
||||
required this.web3walletService,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
WalletConnectPairingDetailsPageState createState() => WalletConnectPairingDetailsPageState();
|
||||
}
|
||||
|
||||
class WalletConnectPairingDetailsPageState extends State<WalletConnectPairingDetailsPage> {
|
||||
List<Widget> sessionWidgets = [];
|
||||
late String expiryDate;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initDateTime();
|
||||
initSessions();
|
||||
}
|
||||
|
||||
void initDateTime() {
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000);
|
||||
int year = dateTime.year;
|
||||
int month = dateTime.month;
|
||||
int day = dateTime.day;
|
||||
|
||||
expiryDate = '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
void initSessions() {
|
||||
List<SessionData> sessions = widget.web3walletService.getSessionsForPairingInfo(widget.pairing);
|
||||
|
||||
for (final SessionData session in sessions) {
|
||||
List<Widget> namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces(
|
||||
session.topic,
|
||||
session.namespaces,
|
||||
widget.web3walletService.getWeb3Wallet(),
|
||||
);
|
||||
// Loop through and add the namespace widgets, but put 20 pixels between each one
|
||||
for (int i = 0; i < namespaceWidget.length; i++) {
|
||||
sessionWidgets.add(namespaceWidget[i]);
|
||||
if (i != namespaceWidget.length - 1) {
|
||||
sessionWidgets.add(const SizedBox(height: 20.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WCCDetailsWidget(
|
||||
widget.pairing,
|
||||
expiryDate,
|
||||
sessionWidgets,
|
||||
widget.web3walletService,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WCCDetailsWidget extends BasePage {
|
||||
WCCDetailsWidget(
|
||||
this.pairing,
|
||||
this.expiryDate,
|
||||
this.sessionWidgets,
|
||||
this.web3walletService,
|
||||
);
|
||||
|
||||
final PairingInfo pairing;
|
||||
final String expiryDate;
|
||||
final List<Widget> sessionWidgets;
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: CircleAvatar(
|
||||
backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty
|
||||
? NetworkImage(pairing.peerMetadata!.icons[0])
|
||||
: const AssetImage('assets/images/default_icon.png'))
|
||||
as ImageProvider<Object>,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Text(
|
||||
pairing.peerMetadata!.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Text(
|
||||
pairing.peerMetadata!.url,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Text(
|
||||
'${S.current.expiresOn}: $expiryDate',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: sessionWidgets,
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
PrimaryButton(
|
||||
onPressed: () =>
|
||||
_onDeleteButtonPressed(context, pairing.peerMetadata!.name, web3walletService),
|
||||
text: S.current.delete,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDeleteButtonPressed(
|
||||
BuildContext context, String dAppName, Web3WalletService web3walletService) async {
|
||||
bool confirmed = false;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).delete,
|
||||
alertContent: '${S.current.deleteConnectionConfirmationPrompt} $dAppName?',
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).delete,
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(),
|
||||
actionRightButton: () {
|
||||
confirmed = true;
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (confirmed) {
|
||||
try {
|
||||
await web3walletService.disconnectSession(pairing.topic);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
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);
|
||||
|
||||
final ConnectionModel model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
margin: const EdgeInsetsDirectional.only(top: 8),
|
||||
child: Visibility(
|
||||
visible: model.elements != null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
model.title ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (model.elements != null)
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
direction: Axis.horizontal,
|
||||
children: model.elements!
|
||||
.map((e) => _ModelElementWidget(model: model, modelElement: e))
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
replacement: _NoModelElementWidget(model: model),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NoModelElementWidget extends StatelessWidget {
|
||||
const _NoModelElementWidget({required this.model});
|
||||
|
||||
final ConnectionModel model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
model.text!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ModelElementWidget extends StatelessWidget {
|
||||
const _ModelElementWidget({
|
||||
required this.model,
|
||||
required this.modelElement,
|
||||
});
|
||||
|
||||
final ConnectionModel model;
|
||||
final String modelElement;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: model.elementActions != null ? model.elementActions![modelElement] : null,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
modelElement,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 10,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// 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,
|
||||
this.authRequest,
|
||||
this.sessionProposal,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final Web3Wallet wallet;
|
||||
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,
|
||||
authRequest: widget.authRequest,
|
||||
sessionProposal: widget.sessionProposal,
|
||||
wallet: widget.wallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ConnectionMetadataDisplayWidget extends StatelessWidget {
|
||||
const _ConnectionMetadataDisplayWidget({
|
||||
required this.metadata,
|
||||
required this.wallet,
|
||||
this.authRequest,
|
||||
required this.sessionProposal,
|
||||
});
|
||||
|
||||
final ConnectionMetadata? metadata;
|
||||
final Web3Wallet wallet;
|
||||
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),
|
||||
|
||||
//If authRequest is null, sessionProposal is not null.
|
||||
replacement: _SessionProposalWidget(sessionProposal: sessionProposal!),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AuthRequestWidget extends StatelessWidget {
|
||||
const _AuthRequestWidget({required this.wallet, this.authRequest});
|
||||
|
||||
final Web3Wallet wallet;
|
||||
final AuthRequestModel? authRequest;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final model = ConnectionModel(
|
||||
text: wallet.formatAuthMessage(
|
||||
iss: 'did:pkh:eip155:1:${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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
import 'connection_item_widget.dart';
|
||||
|
||||
class ConnectionWidget extends StatelessWidget {
|
||||
const ConnectionWidget({required this.title, required this.info, super.key});
|
||||
|
||||
final String title;
|
||||
final List<ConnectionModel> info;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColorLight,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...info.map((e) => ConnectionItemWidget(model: e)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetMessageDisplayWidget extends StatelessWidget {
|
||||
final String message;
|
||||
final bool isError;
|
||||
|
||||
const BottomSheetMessageDisplayWidget({super.key, required this.message, this.isError = true});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isError ? S.current.error : S.current.successful,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
item.completer.complete(value);
|
||||
widget.bottomSheetService.resetCurrentSheet();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'package:cake_wallet/generated/i18n.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';
|
||||
|
||||
class PairingItemWidget extends StatelessWidget {
|
||||
const PairingItemWidget({required this.pairing, required this.onTap, super.key});
|
||||
|
||||
final PairingInfo pairing;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
PairingMetadata? metadata = pairing.peerMetadata;
|
||||
if (metadata == null) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(pairing.expiry * 1000);
|
||||
int year = dateTime.year;
|
||||
int month = dateTime.month;
|
||||
int day = dateTime.day;
|
||||
|
||||
String expiryDate =
|
||||
'$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||||
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: (metadata.icons.isNotEmpty
|
||||
? NetworkImage(metadata.icons[0])
|
||||
: const AssetImage(
|
||||
'assets/images/default_icon.png',
|
||||
)) as ImageProvider<Object>,
|
||||
),
|
||||
title: Text(
|
||||
metadata.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
metadata.url,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${S.current.expiresOn}: $expiryDate',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Container(
|
||||
height: 40,
|
||||
width: 44,
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsBackgroundColor,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -595,8 +595,7 @@ abstract class SettingsStoreBase with Store {
|
||||
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
|
||||
final pinNativeTokenAtTop =
|
||||
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||
final useEtherscan =
|
||||
sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||
final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||
|
||||
// If no value
|
||||
if (pinLength == null || pinLength == 0) {
|
||||
|
||||
@@ -47,70 +47,70 @@ abstract class DashboardViewModelBase with Store {
|
||||
required this.yatStore,
|
||||
required this.ordersStore,
|
||||
required this.anonpayTransactionsStore})
|
||||
: isOutdatedElectrumWallet = false,
|
||||
hasSellAction = false,
|
||||
hasBuyAction = false,
|
||||
hasExchangeAction = false,
|
||||
isShowFirstYatIntroduction = false,
|
||||
isShowSecondYatIntroduction = false,
|
||||
isShowThirdYatIntroduction = false,
|
||||
filterItems = {
|
||||
S.current.transactions: [
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayAll,
|
||||
caption: S.current.all_transactions,
|
||||
onChanged: transactionFilterStore.toggleAll),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayIncoming,
|
||||
caption: S.current.incoming,
|
||||
onChanged:transactionFilterStore.toggleIncoming),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayOutgoing,
|
||||
caption: S.current.outgoing,
|
||||
onChanged: transactionFilterStore.toggleOutgoing),
|
||||
// FilterItem(
|
||||
// value: () => false,
|
||||
// caption: S.current.transactions_by_date,
|
||||
// onChanged: null),
|
||||
],
|
||||
S.current.trades: [
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayAllTrades,
|
||||
caption: S.current.all_trades,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.all)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayChangeNow,
|
||||
caption: ExchangeProviderDescription.changeNow.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySideShift,
|
||||
caption: ExchangeProviderDescription.sideShift.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySimpleSwap,
|
||||
caption: ExchangeProviderDescription.simpleSwap.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayTrocador,
|
||||
caption: ExchangeProviderDescription.trocador.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayExolix,
|
||||
caption: ExchangeProviderDescription.exolix.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||
]
|
||||
},
|
||||
subname = '',
|
||||
name = appStore.wallet!.name,
|
||||
type = appStore.wallet!.type,
|
||||
transactions = ObservableList<TransactionListItem>(),
|
||||
wallet = appStore.wallet! {
|
||||
: isOutdatedElectrumWallet = false,
|
||||
hasSellAction = false,
|
||||
hasBuyAction = false,
|
||||
hasExchangeAction = false,
|
||||
isShowFirstYatIntroduction = false,
|
||||
isShowSecondYatIntroduction = false,
|
||||
isShowThirdYatIntroduction = false,
|
||||
filterItems = {
|
||||
S.current.transactions: [
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayAll,
|
||||
caption: S.current.all_transactions,
|
||||
onChanged: transactionFilterStore.toggleAll),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayIncoming,
|
||||
caption: S.current.incoming,
|
||||
onChanged: transactionFilterStore.toggleIncoming),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayOutgoing,
|
||||
caption: S.current.outgoing,
|
||||
onChanged: transactionFilterStore.toggleOutgoing),
|
||||
// FilterItem(
|
||||
// value: () => false,
|
||||
// caption: S.current.transactions_by_date,
|
||||
// onChanged: null),
|
||||
],
|
||||
S.current.trades: [
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayAllTrades,
|
||||
caption: S.current.all_trades,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayChangeNow,
|
||||
caption: ExchangeProviderDescription.changeNow.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySideShift,
|
||||
caption: ExchangeProviderDescription.sideShift.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySimpleSwap,
|
||||
caption: ExchangeProviderDescription.simpleSwap.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayTrocador,
|
||||
caption: ExchangeProviderDescription.trocador.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayExolix,
|
||||
caption: ExchangeProviderDescription.exolix.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||
]
|
||||
},
|
||||
subname = '',
|
||||
name = appStore.wallet!.name,
|
||||
type = appStore.wallet!.type,
|
||||
transactions = ObservableList<TransactionListItem>(),
|
||||
wallet = appStore.wallet! {
|
||||
name = wallet.name;
|
||||
type = wallet.type;
|
||||
isOutdatedElectrumWallet =
|
||||
@@ -125,15 +125,17 @@ abstract class DashboardViewModelBase with Store {
|
||||
if (_wallet.type == WalletType.monero) {
|
||||
subname = monero!.getCurrentAccount(_wallet).label;
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet)
|
||||
.account, (Account account) => _onMoneroAccountChange(_wallet));
|
||||
_onMoneroAccountChangeReaction = reaction(
|
||||
(_) => monero!.getMoneroWalletDetails(wallet).account,
|
||||
(Account account) => _onMoneroAccountChange(_wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet).balance,
|
||||
_onMoneroBalanceChangeReaction = reaction(
|
||||
(_) => monero!.getMoneroWalletDetails(wallet).balance,
|
||||
(MoneroBalance balance) => _onMoneroTransactionsUpdate(_wallet));
|
||||
|
||||
final _accountTransactions = _wallet
|
||||
.transactionHistory.transactions.values
|
||||
.where((tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
|
||||
final _accountTransactions = _wallet.transactionHistory.transactions.values
|
||||
.where((tx) =>
|
||||
monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
|
||||
.toList();
|
||||
|
||||
transactions = ObservableList.of(_accountTransactions.map((transaction) =>
|
||||
@@ -142,34 +144,33 @@ abstract class DashboardViewModelBase with Store {
|
||||
balanceViewModel: balanceViewModel,
|
||||
settingsStore: appStore.settingsStore)));
|
||||
} else {
|
||||
transactions = ObservableList.of(wallet
|
||||
.transactionHistory.transactions.values
|
||||
.map((transaction) => TransactionListItem(
|
||||
transactions = ObservableList.of(wallet.transactionHistory.transactions.values.map(
|
||||
(transaction) => TransactionListItem(
|
||||
transaction: transaction,
|
||||
balanceViewModel: balanceViewModel,
|
||||
settingsStore: appStore.settingsStore)));
|
||||
}
|
||||
|
||||
reaction((_) => appStore.wallet, _onWalletChange);
|
||||
|
||||
|
||||
connectMapToListWithTransform(
|
||||
appStore.wallet!.transactionHistory.transactions,
|
||||
transactions,
|
||||
(TransactionInfo? transaction) => TransactionListItem(
|
||||
transaction: transaction!,
|
||||
balanceViewModel: balanceViewModel,
|
||||
settingsStore: appStore.settingsStore),
|
||||
filter: (TransactionInfo? transaction) {
|
||||
if (transaction == null) {
|
||||
return false;
|
||||
}
|
||||
settingsStore: appStore.settingsStore), filter: (TransactionInfo? transaction) {
|
||||
if (transaction == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final wallet = _wallet;
|
||||
if (wallet.type == WalletType.monero) {
|
||||
return monero!.getTransactionInfoAccountId(transaction) == monero!.getCurrentAccount(wallet).id;
|
||||
}
|
||||
final wallet = _wallet;
|
||||
if (wallet.type == WalletType.monero) {
|
||||
return monero!.getTransactionInfoAccountId(transaction) ==
|
||||
monero!.getCurrentAccount(wallet).id;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -216,24 +217,21 @@ abstract class DashboardViewModelBase with Store {
|
||||
}
|
||||
|
||||
@computed
|
||||
BalanceDisplayMode get balanceDisplayMode =>
|
||||
appStore.settingsStore.balanceDisplayMode;
|
||||
|
||||
BalanceDisplayMode get balanceDisplayMode => appStore.settingsStore.balanceDisplayMode;
|
||||
|
||||
@computed
|
||||
bool get shouldShowMarketPlaceInDashboard {
|
||||
return appStore.settingsStore.shouldShowMarketPlaceInDashboard;
|
||||
}
|
||||
|
||||
@computed
|
||||
List<TradeListItem> get trades => tradesStore.trades
|
||||
.where((trade) => trade.trade.walletId == wallet.id)
|
||||
.toList();
|
||||
List<TradeListItem> get trades =>
|
||||
tradesStore.trades.where((trade) => trade.trade.walletId == wallet.id).toList();
|
||||
|
||||
@computed
|
||||
List<OrderListItem> get orders => ordersStore.orders
|
||||
.where((item) => item.order.walletId == wallet.id)
|
||||
.toList();
|
||||
|
||||
List<OrderListItem> get orders =>
|
||||
ordersStore.orders.where((item) => item.order.walletId == wallet.id).toList();
|
||||
|
||||
@computed
|
||||
List<AnonpayTransactionListItem> get anonpayTransactons => anonpayTransactionsStore.transactions
|
||||
.where((item) => item.transaction.walletId == wallet.id)
|
||||
@@ -250,7 +248,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
List<ActionListItem> get items {
|
||||
final _items = <ActionListItem>[];
|
||||
|
||||
_items.addAll(transactionFilterStore.filtered(transactions: [...transactions, ...anonpayTransactons]));
|
||||
_items.addAll(
|
||||
transactionFilterStore.filtered(transactions: [...transactions, ...anonpayTransactons]));
|
||||
_items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
|
||||
_items.addAll(orders);
|
||||
|
||||
@@ -258,8 +257,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
}
|
||||
|
||||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||
wallet;
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
|
||||
|
||||
@@ -283,7 +281,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
Map<String, List<FilterItem>> filterItems;
|
||||
|
||||
|
||||
BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider;
|
||||
|
||||
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
||||
@@ -291,8 +288,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup;
|
||||
|
||||
@action
|
||||
void furtherShowYatPopup(bool shouldShow) =>
|
||||
settingsStore.shouldShowYatPopup = shouldShow;
|
||||
void furtherShowYatPopup(bool shouldShow) => settingsStore.shouldShowYatPopup = shouldShow;
|
||||
|
||||
@computed
|
||||
bool get isEnabledExchangeAction => settingsStore.exchangeStatus != ExchangeApiMode.disabled;
|
||||
@@ -301,8 +297,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
bool hasExchangeAction;
|
||||
|
||||
@computed
|
||||
bool get isEnabledBuyAction =>
|
||||
!settingsStore.disableBuy && wallet.type != WalletType.haven;
|
||||
bool get isEnabledBuyAction => !settingsStore.disableBuy && wallet.type != WalletType.haven;
|
||||
|
||||
@observable
|
||||
bool hasBuyAction;
|
||||
@@ -330,9 +325,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
@action
|
||||
void _onWalletChange(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||
TransactionInfo>?
|
||||
wallet) {
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
|
||||
if (wallet == null) {
|
||||
return;
|
||||
}
|
||||
@@ -350,10 +343,12 @@ abstract class DashboardViewModelBase with Store {
|
||||
_onMoneroAccountChangeReaction?.reaction.dispose();
|
||||
_onMoneroBalanceChangeReaction?.reaction.dispose();
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet)
|
||||
.account, (Account account) => _onMoneroAccountChange(wallet));
|
||||
_onMoneroAccountChangeReaction = reaction(
|
||||
(_) => monero!.getMoneroWalletDetails(wallet).account,
|
||||
(Account account) => _onMoneroAccountChange(wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet).balance,
|
||||
_onMoneroBalanceChangeReaction = reaction(
|
||||
(_) => monero!.getMoneroWalletDetails(wallet).balance,
|
||||
(MoneroBalance balance) => _onMoneroTransactionsUpdate(wallet));
|
||||
|
||||
_onMoneroTransactionsUpdate(wallet);
|
||||
@@ -364,8 +359,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
transactions.clear();
|
||||
|
||||
transactions.addAll(wallet.transactionHistory.transactions.values.map(
|
||||
(transaction) => TransactionListItem(
|
||||
transactions.addAll(wallet.transactionHistory.transactions.values.map((transaction) =>
|
||||
TransactionListItem(
|
||||
transaction: transaction,
|
||||
balanceViewModel: balanceViewModel,
|
||||
settingsStore: appStore.settingsStore)));
|
||||
@@ -374,21 +369,19 @@ abstract class DashboardViewModelBase with Store {
|
||||
connectMapToListWithTransform(
|
||||
appStore.wallet!.transactionHistory.transactions,
|
||||
transactions,
|
||||
(TransactionInfo? transaction)
|
||||
=> TransactionListItem(
|
||||
(TransactionInfo? transaction) => TransactionListItem(
|
||||
transaction: transaction!,
|
||||
balanceViewModel: balanceViewModel,
|
||||
settingsStore: appStore.settingsStore),
|
||||
filter: (TransactionInfo? tx) {
|
||||
if (tx == null) {
|
||||
return false;
|
||||
}
|
||||
settingsStore: appStore.settingsStore), filter: (TransactionInfo? tx) {
|
||||
if (tx == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.monero) {
|
||||
return monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id;
|
||||
}
|
||||
if (wallet.type == WalletType.monero) {
|
||||
return monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -402,15 +395,18 @@ abstract class DashboardViewModelBase with Store {
|
||||
void _onMoneroTransactionsUpdate(WalletBase wallet) {
|
||||
transactions.clear();
|
||||
|
||||
final _accountTransactions = monero!.getTransactionHistory(wallet).transactions.values
|
||||
.where((tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
|
||||
final _accountTransactions = monero!
|
||||
.getTransactionHistory(wallet)
|
||||
.transactions
|
||||
.values
|
||||
.where(
|
||||
(tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
|
||||
.toList();
|
||||
|
||||
transactions.addAll(_accountTransactions.map((transaction) =>
|
||||
TransactionListItem(
|
||||
transaction: transaction,
|
||||
balanceViewModel: balanceViewModel,
|
||||
settingsStore: appStore.settingsStore)));
|
||||
transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem(
|
||||
transaction: transaction,
|
||||
balanceViewModel: balanceViewModel,
|
||||
settingsStore: appStore.settingsStore)));
|
||||
}
|
||||
|
||||
void updateActions() {
|
||||
|
||||
@@ -417,7 +417,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
String translateErrorMessage(String error, WalletType walletType, CryptoCurrency currency,) {
|
||||
if (walletType == WalletType.ethereum || walletType == WalletType.haven) {
|
||||
if (error.contains('gas required exceeds allowance (0)') || error.contains('insufficient funds for gas')) {
|
||||
if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for gas')) {
|
||||
return S.current.do_not_have_enough_gas_asset(currency.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user