diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index d01d4222e..0f2dfc25b 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -2,11 +2,10 @@ name: PR Test Build on: pull_request: - branches: [ main ] + branches: [main] jobs: PR_test_build: - runs-on: ubuntu-20.04 env: STORE_PASS: test@cake_wallet @@ -23,12 +22,12 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '8.x' + java-version: "8.x" - name: Flutter action uses: subosito/flutter-action@v1 with: - flutter-version: '3.10.x' + flutter-version: "3.10.x" channel: stable - name: Install package dependencies @@ -131,6 +130,7 @@ jobs: echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart + echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - name: Rename app run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties @@ -140,18 +140,18 @@ jobs: cd /opt/android/cake_wallet flutter build apk --release -# - name: Push to App Center -# run: | -# echo 'Installing App Center CLI tools' -# npm install -g appcenter-cli -# echo "Publishing test to App Center" -# appcenter distribute release \ -# --group "Testers" \ -# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ -# --release-notes ${GITHUB_HEAD_REF} \ -# --app Cake-Labs/Cake-Wallet \ -# --token ${{ secrets.APP_CENTER_TOKEN }} \ -# --quiet + # - name: Push to App Center + # run: | + # echo 'Installing App Center CLI tools' + # npm install -g appcenter-cli + # echo "Publishing test to App Center" + # appcenter distribute release \ + # --group "Testers" \ + # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ + # --release-notes ${GITHUB_HEAD_REF} \ + # --app Cake-Labs/Cake-Wallet \ + # --token ${{ secrets.APP_CENTER_TOKEN }} \ + # --quiet - name: Rename apk file run: | @@ -171,6 +171,6 @@ jobs: token: ${{ secrets.SLACK_APP_TOKEN }} path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk channel: ${{ secrets.SLACK_APK_CHANNEL }} - title: '${{github.head_ref}}.apk' + title: "${{github.head_ref}}.apk" filename: ${{github.head_ref}}.apk initial_comment: ${{ github.event.head_commit.message }} diff --git a/assets/images/walletconnect_logo.png b/assets/images/walletconnect_logo.png new file mode 100644 index 000000000..9024b972c Binary files /dev/null and b/assets/images/walletconnect_logo.png differ diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index cbe201cf8..9393f7768 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,3 @@ -Enhance Monero coin control -Add Filipino localization -Bug Fixes \ No newline at end of file +Fix 2FA code issue +Bug fixes +Minor enhancements \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 263e7ccfe..1fd86c9ca 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,5 +1,4 @@ -New Buy Provider Robinhood -Fix sending Ethereum issue -Enhance Monero coin control -Add Filipino localization -Bug Fixes \ No newline at end of file +Ethereum enhancements and bug fixes +Fix 2FA code issue +Bug fixes +Minor enhancements \ No newline at end of file diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 50b30c5ae..21bde1233 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -77,6 +77,8 @@ abstract class EthereumWalletBase late final EthPrivateKey _ethPrivateKey; + EthPrivateKey get ethPrivateKey => _ethPrivateKey; + late EthereumClient _client; int? _gasPrice; diff --git a/lib/core/wallet_connect/chain_service.dart b/lib/core/wallet_connect/chain_service.dart new file mode 100644 index 000000000..1e3ce3efd --- /dev/null +++ b/lib/core/wallet_connect/chain_service.dart @@ -0,0 +1,5 @@ +abstract class ChainService { + String getNamespace(); + String getChainId(); + List getEvents(); +} diff --git a/lib/core/wallet_connect/eth_transaction_model.dart b/lib/core/wallet_connect/eth_transaction_model.dart new file mode 100644 index 000000000..deb33586f --- /dev/null +++ b/lib/core/wallet_connect/eth_transaction_model.dart @@ -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 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 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)'; + } +} diff --git a/lib/core/wallet_connect/evm_chain_id.dart b/lib/core/wallet_connect/evm_chain_id.dart new file mode 100644 index 000000000..b71fb562e --- /dev/null +++ b/lib/core/wallet_connect/evm_chain_id.dart @@ -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'; + } +} diff --git a/lib/core/wallet_connect/evm_chain_service.dart b/lib/core/wallet_connect/evm_chain_service.dart new file mode 100644 index 000000000..bcc6622fa --- /dev/null +++ b/lib/core/wallet_connect/evm_chain_service.dart @@ -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 getEvents() { + return ['chainChanged', 'accountsChanged']; + } + + Future 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 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 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 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 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 ethSignTransaction(String topic, dynamic parameters) async { + log('received eth sign transaction request: $parameters'); + + final paramsData = parameters[0] as Map; + + final message = _convertToReadable(paramsData); + + final String? authError = await requestAuthorization(message); + + if (authError != null) { + return authError; + } + + // Load the private key + final List keys = wcKeyService.getKeysForChain(getChainId()); + + final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); + + WCEthereumTransactionModel ethTransaction = + WCEthereumTransactionModel.fromJson(parameters[0] as Map); + + 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 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 keys = wcKeyService.getKeysForChain(getChainId()); + + return EthSigUtil.signTypedData( + privateKey: keys[0].privateKey, + jsonData: data ?? '', + version: TypedDataVersion.V4, + ); + } + + String _convertToReadable(Map 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 + '''; + } +} diff --git a/lib/core/wallet_connect/models/auth_request_model.dart b/lib/core/wallet_connect/models/auth_request_model.dart new file mode 100644 index 000000000..f7fd984c8 --- /dev/null +++ b/lib/core/wallet_connect/models/auth_request_model.dart @@ -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)'; + } +} diff --git a/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart b/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart new file mode 100644 index 000000000..49eecac0f --- /dev/null +++ b/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart @@ -0,0 +1,20 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; + +class BottomSheetQueueItemModel { + final Widget widget; + final bool isModalDismissible; + final Completer completer; + + BottomSheetQueueItemModel({ + required this.widget, + required this.completer, + this.isModalDismissible = false, + }); + + @override + String toString() { + return 'BottomSheetQueueItemModel(widget: $widget, completer: $completer)'; + } +} diff --git a/lib/core/wallet_connect/models/chain_key_model.dart b/lib/core/wallet_connect/models/chain_key_model.dart new file mode 100644 index 000000000..5cd2764da --- /dev/null +++ b/lib/core/wallet_connect/models/chain_key_model.dart @@ -0,0 +1,16 @@ +class ChainKeyModel { + final List 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)'; + } +} diff --git a/lib/core/wallet_connect/models/connection_model.dart b/lib/core/wallet_connect/models/connection_model.dart new file mode 100644 index 000000000..63cc8260f --- /dev/null +++ b/lib/core/wallet_connect/models/connection_model.dart @@ -0,0 +1,18 @@ +class ConnectionModel { + final String? title; + final String? text; + final List? elements; + final Map? elementActions; + + ConnectionModel({ + this.title, + this.text, + this.elements, + this.elementActions, + }); + + @override + String toString() { + return 'WalletConnectRequestModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)'; + } +} diff --git a/lib/core/wallet_connect/models/session_request_model.dart b/lib/core/wallet_connect/models/session_request_model.dart new file mode 100644 index 000000000..0c7a5d876 --- /dev/null +++ b/lib/core/wallet_connect/models/session_request_model.dart @@ -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)'; + } +} diff --git a/lib/core/wallet_connect/wallet_connect_key_service.dart b/lib/core/wallet_connect/wallet_connect_key_service.dart new file mode 100644 index 000000000..2e61ebb99 --- /dev/null +++ b/lib/core/wallet_connect/wallet_connect_key_service.dart @@ -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 getKeys(); + + /// Returns a list of all the chain ids. + List 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 getKeysForChain(String chain); + + /// Returns a list of all the accounts in namespace:chainId:address format. + List 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, TransactionInfo> wallet; + + late final List _keys; + + @override + List getChains() { + final List chainIds = []; + for (final ChainKeyModel key in _keys) { + chainIds.addAll(key.chains); + } + return chainIds; + } + + @override + List getKeys() => _keys; + + @override + List getKeysForChain(String chain) { + return _keys.where((e) => e.chains.contains(chain)).toList(); + } + + @override + List getAllAccounts() { + final List accounts = []; + for (final ChainKeyModel key in _keys) { + for (final String chain in key.chains) { + accounts.add('$chain:${key.publicKey}'); + } + } + return accounts; + } +} diff --git a/lib/core/wallet_connect/wc_bottom_sheet_service.dart b/lib/core/wallet_connect/wc_bottom_sheet_service.dart new file mode 100644 index 000000000..3da8660f0 --- /dev/null +++ b/lib/core/wallet_connect/wc_bottom_sheet_service.dart @@ -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 currentSheet; + + Future queueBottomSheet({ + required Widget widget, + bool isModalDismissible = false, + }); + + void resetCurrentSheet(); +} + +class BottomSheetServiceImpl implements BottomSheetService { + + @override + final ValueNotifier currentSheet = ValueNotifier(null); + + @override + Future queueBottomSheet({ + required Widget widget, + bool isModalDismissible = false, + }) async { + // Create the bottom sheet queue item + final completer = Completer(); + final queueItem = BottomSheetQueueItemModel( + widget: widget, + completer: completer, + isModalDismissible: isModalDismissible, + ); + + currentSheet.value = queueItem; + + return await completer.future; + } + + @override + void resetCurrentSheet() { + currentSheet.value = null; + } +} diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart new file mode 100644 index 000000000..0a7716b71 --- /dev/null +++ b/lib/core/wallet_connect/web3wallet_service.dart @@ -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 pairings; + + @observable + ObservableList sessions; + + @observable + ObservableList auth; + + Web3WalletServiceBase(this._bottomSheetHandler, this.walletKeyService, this.appStore) + : pairings = ObservableList(), + sessions = ObservableList(), + auth = ObservableList(), + 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 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 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 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 _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 _onAuthRequest(AuthRequest? args) async { + if (args != null) { + List 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 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 getSessionsForPairingInfo(PairingInfo pairing) { + return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); + } +} diff --git a/lib/di.dart b/lib/di.dart index e069a966b..678a8a325 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -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 setup({ } if (appStore.wallet != null) { authStore.allowed(); + + if (appStore.wallet!.type == WalletType.ethereum) { + getIt.get().init(); + } return; } @@ -419,6 +425,10 @@ Future setup({ } else { if (appStore.wallet != null) { authStore.allowed(); + + if (appStore.wallet!.type == WalletType.ethereum) { + getIt.get().init(); + } return; } @@ -438,11 +448,28 @@ Future setup({ }, closable: false); }, instanceName: 'login'); + getIt.registerSingleton(BottomSheetServiceImpl()); + + final appStore = getIt.get(); + + getIt.registerLazySingleton(() => KeyServiceImpl(appStore.wallet!)); + + getIt.registerLazySingleton(() { + final Web3WalletService web3WalletService = Web3WalletService( + getIt.get(), + getIt.get(), + appStore, + ); + web3WalletService.create(); + return web3WalletService; + }); + getIt.registerFactory(() => BalancePage( dashboardViewModel: getIt.get(), settingsStore: getIt.get())); getIt.registerFactory(() => DashboardPage( + bottomSheetService: getIt.get(), balancePage: getIt.get(), dashboardViewModel: getIt.get(), addressListViewModel: getIt.get(), @@ -459,6 +486,7 @@ Future setup({ }); getIt.registerFactoryParam, void>( (desktopKey, _) => DesktopDashboardPage( + bottomSheetService: getIt.get(), balancePage: getIt.get(), dashboardViewModel: getIt.get(), addressListViewModel: getIt.get(), @@ -668,7 +696,9 @@ Future setup({ return NodeListViewModel(_nodeSource, appStore); }); - getIt.registerFactory(() => ConnectionSyncPage(getIt.get())); + getIt.registerFactory( + () => ConnectionSyncPage(getIt.get(), getIt.get()), + ); getIt.registerFactory( () => SecurityBackupPage(getIt.get(), getIt.get())); @@ -851,9 +881,8 @@ Future setup({ getIt.registerFactory(() => SupportPage(getIt.get())); - getIt.registerFactory(() => - SupportChatPage( - getIt.get(), secureStorage: getIt.get())); + getIt.registerFactory(() => SupportChatPage(getIt.get(), + secureStorage: getIt.get())); getIt.registerFactory(() => SupportOtherLinksPage(getIt.get())); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index d32dcbca4..65f97be94 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -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'; diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 74a298bf9..602db2d49 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -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; diff --git a/lib/main.dart b/lib/main.dart index 40d7468f2..bd84d7a05 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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(); @@ -155,7 +154,7 @@ Future initializeAppConfigs() async { secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, initialMigrationVersion: 21); - } +} Future 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) { diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 68f5ca9aa..a0cf6cd28 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -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: [ - 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: [ + 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()! - .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()!.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()! + .indicatorDotTheme + .activeIndicatorColor, ), - color: - Theme.of(context).extension()!.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()! - .mainActionsIconColor + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.0), + border: Border.all( + color: Theme.of(context).extension()!.cardBorderColor, + width: 1, + ), + color: Theme.of(context) + .extension()! + .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()! + .mainActionsIconColor + : Theme.of(context) + .extension()! + .labelTextColor, + ), + title: action.name(context), + onClick: () async => + await action.onTap(context, dashboardViewModel), + textColor: action.isEnabled?.call(dashboardViewModel) ?? true + ? null : Theme.of(context) .extension()! .labelTextColor, ), - title: action.name(context), - onClick: () async => - await action.onTap(context, dashboardViewModel), - textColor: action.isEnabled?.call(dashboardViewModel) ?? true - ? null - : Theme.of(context) - .extension()! - .labelTextColor, ), - ), - ) - .toList(), + ) + .toList(), + ), ), ), ), - ), - ); - }, - ), - ], + ); + }, + ), + ], + ), ), ); } diff --git a/lib/src/screens/dashboard/desktop_dashboard_page.dart b/lib/src/screens/dashboard/desktop_dashboard_page.dart index 2332f4db6..41d41dd4f 100644 --- a/lib/src/screens/dashboard/desktop_dashboard_page.dart +++ b/lib/src/screens/dashboard/desktop_dashboard_page.dart @@ -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 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))! + ]; + }, + ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index af775705b..7a903bb69 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -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 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 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 with WidgetsBindingObserver { launchUri = null; } - return WillPopScope(onWillPop: () async => false, child: widget.child); + return WillPopScope( + onWillPop: () async => false, + child: widget.child, + ); } void _reset() { diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index c59e71be4..d5a26a0fe 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -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)), + ] ], ), ); diff --git a/lib/src/screens/settings/widgets/wallet_connect_button.dart b/lib/src/screens/settings/widgets/wallet_connect_button.dart new file mode 100644 index 000000000..d02462619 --- /dev/null +++ b/lib/src/screens/settings/widgets/wallet_connect_button.dart @@ -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: [ + 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()!.titleColor, + ), + ), + ), + Image.asset( + 'assets/images/select_arrow.png', + color: Theme.of(context).extension()!.detailsTitlesColor, + ) + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart new file mode 100644 index 000000000..936df93d3 --- /dev/null +++ b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart @@ -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 buildFromRequiredNamespaces( + Map requiredNamespaces, + ) { + final List views = []; + for (final key in requiredNamespaces.keys) { + RequiredNamespace ns = requiredNamespaces[key]!; + final List 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 buildFromNamespaces( + String topic, + Map namespaces, + Web3Wallet web3wallet, + ) { + final List views = []; + for (final key in namespaces.keys) { + final Namespace ns = namespaces[key]!; + final List 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 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; + } +} diff --git a/lib/src/screens/wallet_connect/utils/string_parsing.dart b/lib/src/screens/wallet_connect/utils/string_parsing.dart new file mode 100644 index 000000000..b9fdca7b2 --- /dev/null +++ b/lib/src/screens/wallet_connect/utils/string_parsing.dart @@ -0,0 +1,16 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; + +extension StringParsing on String { + String get utf8Message { + if (startsWith('0x')) { + final List decoded = hex.decode( + substring(2), + ); + return utf8.decode(decoded); + } + + return this; + } +} diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart new file mode 100644 index 000000000..ead50db07 --- /dev/null +++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart @@ -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 _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 _invalidUriToast(BuildContext context, String message) async { + await showPopUp( + 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()!.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()!.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), + ], + ); + }, + ); + } +} diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart new file mode 100644 index 000000000..f99eb9cdb --- /dev/null +++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart @@ -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 { + List 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 sessions = widget.web3walletService.getSessionsForPairingInfo(widget.pairing); + + for (final SessionData session in sessions) { + List 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 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, + ), + ), + const SizedBox(height: 20.0), + Text( + pairing.peerMetadata!.name, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.titleColor, + ), + ), + const SizedBox(height: 16.0), + Text( + pairing.peerMetadata!.url, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + const SizedBox(height: 8.0), + Text( + '${S.current.expiresOn}: $expiryDate', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.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 _onDeleteButtonPressed( + BuildContext context, String dAppName, Web3WalletService web3walletService) async { + bool confirmed = false; + + await showPopUp( + 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()); + } + } + } +} diff --git a/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart new file mode 100644 index 000000000..77c30417a --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart @@ -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()!.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()!.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()!.titleColor, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + maxLines: 10, + overflow: TextOverflow.ellipsis, + ), + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart new file mode 100644 index 000000000..c73c4bfa8 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart @@ -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 createState() => _ConnectionRequestWidgetState(); +} + +class _ConnectionRequestWidgetState extends State { + 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()!.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()!.secondaryTextColor, + ), + textAlign: TextAlign.center, + ), + Text( + S.current.wouoldLikeToConnect, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.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()!.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 views = ConnectionWidgetBuilder.buildFromRequiredNamespaces( + sessionProposal.request.requiredNamespaces, + ); + + return Column(children: views); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/connection_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_widget.dart new file mode 100644 index 000000000..921d8ea5c --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/connection_widget.dart @@ -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 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()!.titleColor, + ), + ), + ), + const SizedBox(height: 8), + ...info.map((e) => ConnectionItemWidget(model: e)), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/error_display_widget.dart b/lib/src/screens/wallet_connect/widgets/error_display_widget.dart new file mode 100644 index 000000000..0fbbda5c7 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/error_display_widget.dart @@ -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, + ), + ), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart b/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart new file mode 100644 index 000000000..30b6af7e0 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart @@ -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 { + + @override + void initState() { + super.initState(); + widget.bottomSheetService.currentSheet.addListener(_showBottomSheet); + } + + @override + void dispose() { + widget.bottomSheetService.currentSheet.removeListener(_showBottomSheet); + super.dispose(); + } + + Future _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; +} diff --git a/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart b/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart new file mode 100644 index 000000000..f16dcc0f8 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart @@ -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()!.titleColor, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart b/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart new file mode 100644 index 000000000..063de8ec3 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart @@ -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, + ), + title: Text( + metadata.name, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Theme.of(context).extension()!.titleColor, + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + metadata.url, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w700, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + ), + Text( + '${S.current.expiresOn}: $expiryDate', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w700, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + ), + ], + ), + trailing: Container( + height: 40, + width: 44, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).extension()!.iconsBackgroundColor, + ), + child: Icon( + Icons.edit, + size: 14, + color: Theme.of(context).extension()!.iconsColor, + ), + ), + onTap: onTap, + ); + } +} diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 54607815d..6bd3686b5 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -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) { diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 777db8f8d..c8a3a907c 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -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(), - 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(), + 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 get trades => tradesStore.trades - .where((trade) => trade.trade.walletId == wallet.id) - .toList(); + List get trades => + tradesStore.trades.where((trade) => trade.trade.walletId == wallet.id).toList(); @computed - List get orders => ordersStore.orders - .where((item) => item.order.walletId == wallet.id) - .toList(); - + List get orders => + ordersStore.orders.where((item) => item.order.walletId == wallet.id).toList(); + @computed List get anonpayTransactons => anonpayTransactionsStore.transactions .where((item) => item.transaction.walletId == wallet.id) @@ -250,7 +248,8 @@ abstract class DashboardViewModelBase with Store { List get items { final _items = []; - _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, TransactionInfo> - wallet; + WalletBase, TransactionInfo> wallet; bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; @@ -283,7 +281,6 @@ abstract class DashboardViewModelBase with Store { Map> 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, - TransactionInfo>? - wallet) { + WalletBase, 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() { diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 72ce9fdb2..0c322bfe6 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -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()); } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index c95a7b5bc..041bac19d 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -82,6 +82,8 @@ dependencies: shared_preferences_android: 2.0.17 url_launcher_android: 6.0.24 sensitive_clipboard: ^1.0.0 + walletconnect_flutter_v2: ^2.1.4 + eth_sig_util: ^0.0.9 ens_dart: git: url: https://github.com/cake-tech/ens_dart.git diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 46a454758..f7bb19c45 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -689,7 +689,27 @@ "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", "buy_provider_unavailable": "مزود حاليا غير متوفر.", + "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", + "errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ", + "errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ", + "pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ", + "chains": "ﻞﺳﻼﺴﻟﺍ", + "methods": " ﻕﺮﻃُ", + "events": "ﺙﺍﺪﺣﻷﺍ", + "reject": "ﺾﻓﺮﻳ", + "approve": "ﺪﻤﺘﻌﻳ", + "expiresOn": "ﻲﻓ ﻪﺘﻴﺣﻼﺻ ﻲﻬﺘﻨﺗ", + "walletConnect": "WalletConnect", + "nullURIError": "ﻍﺭﺎﻓ (URI) ﻢﻈﺘﻨﻤﻟﺍ ﺩﺭﺍﻮﻤﻟﺍ ﻑﺮﻌﻣ", + "connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ", + "newConnection": "ﺪﻳﺪﺟ ﻝﺎﺼﺗﺍ", + "activeConnectionsPrompt": "ﺎﻨﻫ ﺔﻄﺸﻨﻟﺍ ﺕﻻﺎﺼﺗﻻﺍ ﺮﻬﻈﺘﺳ", + "deleteConnectionConfirmationPrompt": "ـﺑ ﻝﺎﺼﺗﻻﺍ ﻑﺬﺣ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ", + "event": "ﺙﺪﺣ", + "successful": "ﺢﺟﺎﻧ", + "wouoldLikeToConnect": "ﻝﺎﺼﺗﻻﺍ ﻲﻓ ﺐﻏﺮﺗ", + "message": "ﺔﻟﺎﺳﺭ", "do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.", - "totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ" + "totp_auth_url": "TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ", + "awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ" } - diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 39ce6196b..644e18d33 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -685,6 +685,27 @@ "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", + "signTransaction": "Подпишете транзакция", + "errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни", + "errorSigningTransaction": "Възникна грешка при подписване на транзакция", + "pairingInvalidEvent": "Невалидно събитие при сдвояване", + "chains": "Вериги", + "methods": "Методи", + "events": "събития", + "reject": "Отхвърляне", + "approve": "Одобряване", + "expiresOn": "Изтича на", + "walletConnect": "WalletConnect", + "nullURIError": "URI е нула", + "connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции", + "newConnection": "Нова връзка", + "activeConnectionsPrompt": "Тук ще се появят активни връзки", + "deleteConnectionConfirmationPrompt": "Сигурни ли сте, че искате да изтриете връзката към", + "event": "Събитие", + "successful": "Успешен", + "wouoldLikeToConnect": "иска да се свърже", + "message": "Съобщение", "do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката." } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 0c063f0c9..41d428d7b 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -685,6 +685,27 @@ "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", + "signTransaction": "Podepsat transakci", + "errorGettingCredentials": "Selhalo: Chyba při získávání přihlašovacích údajů", + "errorSigningTransaction": "Při podepisování transakce došlo k chybě", + "pairingInvalidEvent": "Neplatná událost párování", + "chains": "Řetězy", + "methods": "Metody", + "events": "Události", + "reject": "Odmítnout", + "approve": "Schvalovat", + "expiresOn": "Vyprší dne", + "walletConnect": "WalletConnect", + "nullURIError": "URI je nulové", + "connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce", + "newConnection": "Nové připojení", + "activeConnectionsPrompt": "Zde se zobrazí aktivní připojení", + "deleteConnectionConfirmationPrompt": "Jste si jisti, že chcete smazat připojení k?", + "event": "událost", + "successful": "Úspěšný", + "wouoldLikeToConnect": "by se chtělo připojit", + "message": "Zpráva", "do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.", - "totp_auth_url": "URL AUTH TOTP" + "totp_auth_url": "URL AUTH TOTP", + "awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování." } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 4b2cba6d4..4fcf08363 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", + "signTransaction": "Transaktion unterzeichnen", + "errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen", + "errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten", + "pairingInvalidEvent": "Paarung ungültiges Ereignis", + "chains": "Ketten", + "methods": "Methoden", + "events": "Veranstaltungen", + "reject": "Ablehnen", + "approve": "Genehmigen", + "expiresOn": "Läuft aus am", + "walletConnect": "WalletConnect", + "nullURIError": "URI ist null", + "connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen", + "newConnection": "Neue Verbindung", + "activeConnectionsPrompt": "Hier werden aktive Verbindungen angezeigt", + "deleteConnectionConfirmationPrompt": "Sind Sie sicher, dass Sie die Verbindung zu löschen möchten?", + "event": "Ereignis", + "successful": "Erfolgreich", + "wouoldLikeToConnect": "möchte mich gerne vernetzen", + "message": "Nachricht", "do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.", - "totp_auth_url": "TOTP-Auth-URL" + "totp_auth_url": "TOTP-Auth-URL", + "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat." } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 7e3b1b55d..d1d356f29 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -694,6 +694,27 @@ "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", "buy_provider_unavailable": "Provider currently unavailable.", + "signTransaction": "Sign Transaction", + "errorGettingCredentials": "Failed: Error while getting credentials", + "errorSigningTransaction": "An error has occured while signing transaction", + "pairingInvalidEvent": "Pairing Invalid Event", + "chains": "Chains", + "methods": "Methods", + "events": "Events", + "reject": "Reject", + "approve": "Approve", + "expiresOn": "Expires on", + "walletConnect": "WalletConnect", + "nullURIError": "URI is null", + "connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions", + "newConnection": "New Connection", + "activeConnectionsPrompt": "Active connections will appear here", + "deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to", + "event": "Event", + "successful": "Successful", + "wouoldLikeToConnect": "would like to connect", + "message": "Message", "do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Kindly wait for the dApp to finish processing." } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index f2b0729f3..8dea97b87 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", "buy_provider_unavailable": "Proveedor actualmente no disponible.", + "signTransaction": "Firmar transacción", + "errorGettingCredentials": "Error: error al obtener las credenciales", + "errorSigningTransaction": "Se ha producido un error al firmar la transacción.", + "pairingInvalidEvent": "Evento de emparejamiento no válido", + "chains": "Cadenas", + "methods": "Métodos", + "events": "Eventos", + "reject": "Rechazar", + "approve": "Aprobar", + "expiresOn": "Expira el", + "walletConnect": "MonederoConectar", + "nullURIError": "URI es nula", + "connectWalletPrompt": "Conecte su billetera con WalletConnect para realizar transacciones", + "newConnection": "Nueva conexión", + "activeConnectionsPrompt": "Las conexiones activas aparecerán aquí", + "deleteConnectionConfirmationPrompt": "¿Está seguro de que desea eliminar la conexión a", + "event": "Evento", + "successful": "Exitoso", + "wouoldLikeToConnect": "quisiera conectar", + "message": "Mensaje", "do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.", - "totp_auth_url": "URL de autenticación TOTP" + "totp_auth_url": "URL de autenticación TOTP", + "awaitDAppProcessing": "Espere a que la dApp termine de procesarse." } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index a081727fb..bdc92e40f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", "buy_provider_unavailable": "Fournisseur actuellement indisponible.", + "signTransaction": "Signer une transaction", + "errorGettingCredentials": "Échec : erreur lors de l'obtention des informations d'identification", + "errorSigningTransaction": "Une erreur s'est produite lors de la signature de la transaction", + "pairingInvalidEvent": "Événement de couplage non valide", + "chains": "Chaînes", + "methods": "Méthodes", + "events": "Événements", + "reject": "Rejeter", + "approve": "Approuver", + "expiresOn": "Expire le", + "walletConnect": "PortefeuilleConnect", + "nullURIError": "L'URI est nul", + "connectWalletPrompt": "Connectez votre portefeuille avec WalletConnect pour effectuer des transactions", + "newConnection": "Nouvelle connexion", + "activeConnectionsPrompt": "Les connexions actives apparaîtront ici", + "deleteConnectionConfirmationPrompt": "Êtes-vous sûr de vouloir supprimer la connexion à", + "event": "Événement", + "successful": "Réussi", + "wouoldLikeToConnect": "je voudrais me connecter", + "message": "Message", "do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.", - "totp_auth_url": "URL D'AUTORISATION TOTP" + "totp_auth_url": "URL D'AUTORISATION TOTP", + "awaitDAppProcessing": "Veuillez attendre que le dApp termine le traitement." } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 68051b5d9..18b65fb28 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -671,6 +671,27 @@ "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", + "signTransaction": "Sa hannu Ma'amala", + "errorGettingCredentials": "Ba a yi nasara ba: Kuskure yayin samun takaddun shaida", + "errorSigningTransaction": "An sami kuskure yayin sanya hannu kan ciniki", + "pairingInvalidEvent": "Haɗa Lamarin mara inganci", + "chains": "Sarkoki", + "methods": "Hanyoyin", + "events": "Abubuwan da suka faru", + "reject": "Ƙi", + "approve": "Amincewa", + "expiresOn": "Yana ƙarewa", + "walletConnect": "WalletConnect", + "nullURIError": "URI banza ne", + "connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala", + "newConnection": "Sabuwar Haɗi", + "activeConnectionsPrompt": "Haɗin kai mai aiki zai bayyana a nan", + "deleteConnectionConfirmationPrompt": "Shin kun tabbata cewa kuna son share haɗin zuwa", + "event": "Lamarin", + "successful": "Nasara", + "wouoldLikeToConnect": "ina son haɗi", + "message": "Sako", "do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki." } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 9f531bb29..1a2edd9ad 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -693,6 +693,27 @@ "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", + "signTransaction": "लेन-देन पर हस्ताक्षर करें", + "errorGettingCredentials": "विफल: क्रेडेंशियल प्राप्त करते समय त्रुटि", + "errorSigningTransaction": "लेन-देन पर हस्ताक्षर करते समय एक त्रुटि उत्पन्न हुई है", + "pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना", + "chains": "चेन", + "methods": "तरीकों", + "events": "आयोजन", + "reject": "अस्वीकार करना", + "approve": "मंज़ूरी देना", + "expiresOn": "पर समय सीमा समाप्त", + "walletConnect": "वॉलेटकनेक्ट", + "nullURIError": "यूआरआई शून्य है", + "connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें", + "newConnection": "नया कनेक्शन", + "activeConnectionsPrompt": "सक्रिय कनेक्शन यहां दिखाई देंगे", + "deleteConnectionConfirmationPrompt": "क्या आप वाकई कनेक्शन हटाना चाहते हैं?", + "event": "आयोजन", + "successful": "सफल", + "wouoldLikeToConnect": "जुड़ना चाहेंगे", + "message": "संदेश", "do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।", - "totp_auth_url": "TOTP प्रामाणिक यूआरएल" + "totp_auth_url": "TOTP प्रामाणिक यूआरएल", + "awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 4b56ef344..d47890556 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", + "signTransaction": "Potpišite transakciju", + "errorGettingCredentials": "Neuspješno: Pogreška prilikom dobivanja vjerodajnica", + "errorSigningTransaction": "Došlo je do pogreške prilikom potpisivanja transakcije", + "pairingInvalidEvent": "Nevažeći događaj uparivanja", + "chains": "Lanci", + "methods": "Metode", + "events": "Događaji", + "reject": "Odbiti", + "approve": "Odobriti", + "expiresOn": "Istječe", + "walletConnect": "WalletConnect", + "nullURIError": "URI je nula", + "connectWalletPrompt": "Povežite svoj novčanik s WalletConnectom za obavljanje transakcija", + "newConnection": "Nova veza", + "activeConnectionsPrompt": "Ovdje će se pojaviti aktivne veze", + "deleteConnectionConfirmationPrompt": "Jeste li sigurni da želite izbrisati vezu s", + "event": "Događaj", + "successful": "Uspješno", + "wouoldLikeToConnect": "želio bi se povezati", + "message": "Poruka", "do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.", - "totp_auth_url": "TOTP AUTH URL" -} \ No newline at end of file + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Molimo pričekajte da dApp završi obradu." +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 6200559e3..6adbe2a1f 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -681,6 +681,27 @@ "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", + "signTransaction": "Tandatangani Transaksi", + "errorGettingCredentials": "Gagal: Terjadi kesalahan saat mendapatkan kredensial", + "errorSigningTransaction": "Terjadi kesalahan saat menandatangani transaksi", + "pairingInvalidEvent": "Menyandingkan Acara Tidak Valid", + "chains": "Rantai", + "methods": "Metode", + "events": "Acara", + "reject": "Menolak", + "approve": "Menyetujui", + "expiresOn": "Kadaluarsa pada", + "walletConnect": "DompetConnect", + "nullURIError": "URI adalah nol", + "connectWalletPrompt": "Hubungkan dompet Anda dengan WalletConnect untuk melakukan transaksi", + "newConnection": "Koneksi Baru", + "activeConnectionsPrompt": "Koneksi aktif akan muncul di sini", + "deleteConnectionConfirmationPrompt": "Apakah Anda yakin ingin menghapus koneksi ke", + "event": "Peristiwa", + "successful": "Berhasil", + "wouoldLikeToConnect": "ingin terhubung", + "message": "Pesan", "do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.", - "totp_auth_url": "URL Otentikasi TOTP" -} \ No newline at end of file + "totp_auth_url": "URL Otentikasi TOTP", + "awaitDAppProcessing": "Mohon tunggu hingga dApp menyelesaikan pemrosesan." +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index e3fd1b94b..db687ffe7 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", "buy_provider_unavailable": "Provider attualmente non disponibile.", + "signTransaction": "Firma la transazione", + "errorGettingCredentials": "Non riuscito: errore durante il recupero delle credenziali", + "errorSigningTransaction": "Si è verificato un errore durante la firma della transazione", + "pairingInvalidEvent": "Associazione evento non valido", + "chains": "Catene", + "methods": "Metodi", + "events": "Eventi", + "reject": "Rifiutare", + "approve": "Approvare", + "expiresOn": "Scade il", + "walletConnect": "PortafoglioConnetti", + "nullURIError": "L'URI è nullo", + "connectWalletPrompt": "Collega il tuo portafoglio con WalletConnect per effettuare transazioni", + "newConnection": "Nuova connessione", + "activeConnectionsPrompt": "Le connessioni attive verranno visualizzate qui", + "deleteConnectionConfirmationPrompt": "Sei sicuro di voler eliminare la connessione a", + "event": "Evento", + "successful": "Riuscito", + "wouoldLikeToConnect": "vorrei connettermi", + "message": "Messaggio", "do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.", - "totp_auth_url": "URL DI AUT. TOTP" + "totp_auth_url": "URL DI AUT. TOTP", + "awaitDAppProcessing": "Attendi gentilmente che la dApp termini l'elaborazione." } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 8dbf429c5..32dd157a4 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -693,6 +693,27 @@ "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", "buy_provider_unavailable": "現在、プロバイダーは利用できません。", + "signTransaction": "トランザクションに署名する", + "errorGettingCredentials": "失敗: 認証情報の取得中にエラーが発生しました", + "errorSigningTransaction": "トランザクションの署名中にエラーが発生しました", + "pairingInvalidEvent": "ペアリング無効イベント", + "chains": "チェーン", + "methods": "メソッド", + "events": "イベント", + "reject": "拒否する", + "approve": "承認する", + "expiresOn": "有効期限は次のとおりです", + "walletConnect": "ウォレットコネクト", + "nullURIError": "URIがnullです", + "connectWalletPrompt": "ウォレットを WalletConnect に接続して取引を行う", + "newConnection": "新しい接続", + "activeConnectionsPrompt": "アクティブな接続がここに表示されます", + "deleteConnectionConfirmationPrompt": "への接続を削除してもよろしいですか?", + "event": "イベント", + "successful": "成功", + "wouoldLikeToConnect": "接続したいです", + "message": "メッセージ", "do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。", - "totp_auth_url": "TOTP認証URL" + "totp_auth_url": "TOTP認証URL", + "awaitDAppProcessing": "dAppの処理が完了するまでお待ちください。" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index da9b99c2b..2ac9ed4c6 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -693,6 +693,27 @@ "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", + "signTransaction": "거래 서명", + "errorGettingCredentials": "실패: 자격 증명을 가져오는 중 오류가 발생했습니다.", + "errorSigningTransaction": "거래에 서명하는 동안 오류가 발생했습니다.", + "pairingInvalidEvent": "잘못된 이벤트 페어링", + "chains": "쇠사슬", + "methods": "행동 양식", + "events": "이벤트", + "reject": "거부하다", + "approve": "승인하다", + "expiresOn": "만료 날짜", + "walletConnect": "월렛커넥트", + "nullURIError": "URI가 null입니다.", + "connectWalletPrompt": "거래를 하려면 WalletConnect에 지갑을 연결하세요.", + "newConnection": "새로운 연결", + "activeConnectionsPrompt": "활성 연결이 여기에 표시됩니다", + "deleteConnectionConfirmationPrompt": "다음 연결을 삭제하시겠습니까?", + "event": "이벤트", + "successful": "성공적인", + "wouoldLikeToConnect": "연결하고 싶습니다", + "message": "메시지", "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", - "totp_auth_url": "TOTP 인증 URL" + "totp_auth_url": "TOTP 인증 URL", + "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요." } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 043484feb..80f8d6206 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -691,6 +691,27 @@ "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", + "signTransaction": "ငွေလွှဲဝင်ပါ။", + "errorGettingCredentials": "မအောင်မြင်ပါ- အထောက်အထားများ ရယူနေစဉ် အမှားအယွင်း", + "errorSigningTransaction": "ငွေပေးငွေယူ လက်မှတ်ထိုးစဉ် အမှားအယွင်းတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။", + "pairingInvalidEvent": "မမှန်ကန်သောဖြစ်ရပ်ကို တွဲချိတ်ခြင်း။", + "chains": "ဆွဲကြိုး", + "methods": "နည်းလမ်းများ", + "events": "အဲ့ဒါနဲ့", + "reject": "ငြင်းပယ်ပါ။", + "approve": "လက်မခံပါ။", + "expiresOn": "သက်တမ်းကုန်သည်။", + "walletConnect": "Wallet ချိတ်ဆက်မှု", + "nullURIError": "URI သည် null ဖြစ်သည်။", + "connectWalletPrompt": "အရောင်းအဝယ်ပြုလုပ်ရန် သင့်ပိုက်ဆံအိတ်ကို WalletConnect နှင့် ချိတ်ဆက်ပါ။", + "newConnection": "ချိတ်ဆက်မှုအသစ်", + "activeConnectionsPrompt": "လက်ရှိချိတ်ဆက်မှုများ ဤနေရာတွင် ပေါ်လာပါမည်။", + "deleteConnectionConfirmationPrompt": "ချိတ်ဆက်မှုကို ဖျက်လိုသည်မှာ သေချာပါသလား။", + "event": "ပွဲ", + "successful": "အောင်မြင်တယ်။", + "wouoldLikeToConnect": "ချိတ်ဆက်ချင်ပါတယ်။", + "message": "မက်ဆေ့ချ်", "do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "ကျေးဇူးပြု၍ dApp ကို စီမံလုပ်ဆောင်ခြင်း အပြီးသတ်ရန် စောင့်ပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 2d9f3655e..45cab5d09 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", + "signTransaction": "Transactie ondertekenen", + "errorGettingCredentials": "Mislukt: fout bij het ophalen van inloggegevens", + "errorSigningTransaction": "Er is een fout opgetreden tijdens het ondertekenen van de transactie", + "pairingInvalidEvent": "Koppelen Ongeldige gebeurtenis", + "chains": "Ketens", + "methods": "Methoden", + "events": "Evenementen", + "reject": "Afwijzen", + "approve": "Goedkeuren", + "expiresOn": "Verloopt op", + "walletConnect": "WalletConnect", + "nullURIError": "URI is nul", + "connectWalletPrompt": "Verbind uw portemonnee met WalletConnect om transacties uit te voeren", + "newConnection": "Nieuwe verbinding", + "activeConnectionsPrompt": "Actieve verbindingen worden hier weergegeven", + "deleteConnectionConfirmationPrompt": "Weet u zeker dat u de verbinding met", + "event": "Evenement", + "successful": "Succesvol", + "wouoldLikeToConnect": "wil graag verbinden", + "message": "Bericht", "do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.", - "totp_auth_url": "TOTP AUTH-URL" + "totp_auth_url": "TOTP AUTH-URL", + "awaitDAppProcessing": "Wacht tot de dApp klaar is met verwerken." } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 6f3a41d0b..0d9c22c79 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", "buy_provider_unavailable": "Dostawca obecnie niedostępny.", + "signTransaction": "Podpisz transakcję", + "errorGettingCredentials": "Niepowodzenie: Błąd podczas uzyskiwania poświadczeń", + "errorSigningTransaction": "Wystąpił błąd podczas podpisywania transakcji", + "pairingInvalidEvent": "Nieprawidłowe zdarzenie parowania", + "chains": "Więzy", + "methods": "Metody", + "events": "Wydarzenia", + "reject": "Odrzucić", + "approve": "Zatwierdzić", + "expiresOn": "Upływa w dniu", + "walletConnect": "PortfelPołącz", + "nullURIError": "URI ma wartość zerową", + "connectWalletPrompt": "Połącz swój portfel z WalletConnect, aby dokonywać transakcji", + "newConnection": "Nowe połączenie", + "activeConnectionsPrompt": "Tutaj pojawią się aktywne połączenia", + "deleteConnectionConfirmationPrompt": "Czy na pewno chcesz usunąć połączenie z", + "event": "Wydarzenie", + "successful": "Udany", + "wouoldLikeToConnect": "chciałbym się połączyć", + "message": "Wiadomość", "do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.", - "totp_auth_url": "Adres URL TOTP AUTH" + "totp_auth_url": "Adres URL TOTP AUTH", + "awaitDAppProcessing": "Poczekaj, aż dApp zakończy przetwarzanie." } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index ea442420f..302955780 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -692,6 +692,27 @@ "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", "buy_provider_unavailable": "Provedor atualmente indisponível.", + "signTransaction": "Assinar transação", + "errorGettingCredentials": "Falha: Erro ao obter credenciais", + "errorSigningTransaction": "Ocorreu um erro ao assinar a transação", + "pairingInvalidEvent": "Emparelhamento de evento inválido", + "chains": "Correntes", + "methods": "Métodos", + "events": "Eventos", + "reject": "Rejeitar", + "approve": "Aprovar", + "expiresOn": "Expira em", + "walletConnect": "CarteiraConectada", + "nullURIError": "URI é nulo", + "connectWalletPrompt": "Conecte sua carteira ao WalletConnect para fazer transações", + "newConnection": "Nova conexão", + "activeConnectionsPrompt": "Conexões ativas aparecerão aqui", + "deleteConnectionConfirmationPrompt": "Tem certeza de que deseja excluir a conexão com", + "event": "Evento", + "successful": "Bem-sucedido", + "wouoldLikeToConnect": "gostaria de me conectar", + "message": "Mensagem", "do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.", - "totp_auth_url": "URL de autenticação TOTP" + "totp_auth_url": "URL de autenticação TOTP", + "awaitDAppProcessing": "Aguarde até que o dApp termine o processamento." } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 5e70bc544..c1af0eb74 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -693,6 +693,27 @@ "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", + "signTransaction": "Подписать транзакцию", + "errorGettingCredentials": "Не удалось: ошибка при получении учетных данных.", + "errorSigningTransaction": "Произошла ошибка при подписании транзакции", + "pairingInvalidEvent": "Недействительное событие сопряжения", + "chains": "Цепи", + "methods": "Методы", + "events": "События", + "reject": "Отклонять", + "approve": "Утвердить", + "expiresOn": "Годен до", + "walletConnect": "КошелекПодключиться", + "nullURIError": "URI имеет значение null", + "connectWalletPrompt": "Подключите свой кошелек к WalletConnect для совершения транзакций.", + "newConnection": "Новое соединение", + "activeConnectionsPrompt": "Здесь появятся активные подключения", + "deleteConnectionConfirmationPrompt": "Вы уверены, что хотите удалить подключение к", + "event": "Событие", + "successful": "Успешный", + "wouoldLikeToConnect": "хотел бы подключиться", + "message": "Сообщение", "do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.", - "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ" + "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ", + "awaitDAppProcessing": "Пожалуйста, подождите, пока dApp завершит обработку." } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 8778802a9..eb4b1ac31 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -691,6 +691,27 @@ "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", + "signTransaction": "ลงนามในการทำธุรกรรม", + "errorGettingCredentials": "ล้มเหลว: เกิดข้อผิดพลาดขณะรับข้อมูลรับรอง", + "errorSigningTransaction": "เกิดข้อผิดพลาดขณะลงนามธุรกรรม", + "pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง", + "chains": "ห่วงโซ่", + "methods": "วิธีการ", + "events": "กิจกรรม", + "reject": "ปฏิเสธ", + "approve": "อนุมัติ", + "expiresOn": "หมดอายุวันที่", + "walletConnect": "WalletConnect", + "nullURIError": "URI เป็นโมฆะ", + "connectWalletPrompt": "เชื่อมต่อกระเป๋าเงินของคุณด้วย WalletConnect เพื่อทำธุรกรรม", + "newConnection": "การเชื่อมต่อใหม่", + "activeConnectionsPrompt": "การเชื่อมต่อที่ใช้งานอยู่จะปรากฏที่นี่", + "deleteConnectionConfirmationPrompt": "คุณแน่ใจหรือไม่ว่าต้องการลบการเชื่อมต่อไปยัง", + "event": "เหตุการณ์", + "successful": "ประสบความสำเร็จ", + "wouoldLikeToConnect": "ต้องการเชื่อมต่อ", + "message": "ข้อความ", "do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม", - "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP" + "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP", + "awaitDAppProcessing": "โปรดรอให้ dApp ประมวลผลเสร็จสิ้น" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 18c52aa83..8072eee61 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -688,6 +688,33 @@ "support_description_other_links": "Sumali sa aming mga komunidad o maabot sa amin ang aming mga kasosyo sa pamamagitan ng iba pang mga pamamaraan", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup file.", "save_to_downloads": "I -save sa mga pag -download", + "select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.", + "onramper_option_description": "Mabilis na bumili ng crypto na may maraming paraan ng pagbabayad. Available sa karamihan ng mga bansa. Iba-iba ang mga spread at bayarin.", + "default_buy_provider": "Default na Provider ng Pagbili", + "ask_each_time": "Magtanong sa bawat oras", + "robinhood_option_description": "Bumili at ilipat kaagad gamit ang iyong debit card, bank account, o balanse ng Robinhood. USA lang.", + "buy_provider_unavailable": "Kasalukuyang hindi available ang provider.", + "signTransaction": "Mag-sign Transaksyon", + "errorGettingCredentials": "Nabigo: Error habang kumukuha ng mga kredensyal", + "errorSigningTransaction": "May naganap na error habang pinipirmahan ang transaksyon", + "pairingInvalidEvent": "Pagpares ng Di-wastong Kaganapan", + "chains": "Mga tanikala", + "methods": "Paraan", + "events": "Mga kaganapan", + "reject": "Tanggihan", + "approve": "Aprubahan", + "expiresOn": "Mag-e-expire sa", + "walletConnect": "WalletConnect", + "nullURIError": "Ang URI ay null", + "connectWalletPrompt": "Ikonekta ang iyong wallet sa WalletConnect upang gumawa ng mga transaksyon", + "newConnection": "Bagong Koneksyon", + "activeConnectionsPrompt": "Lalabas dito ang mga aktibong koneksyon", + "deleteConnectionConfirmationPrompt": "Sigurado ka bang gusto mong tanggalin ang koneksyon sa", + "event": "Kaganapan", + "successful": "Matagumpay", + "wouoldLikeToConnect": "gustong kumonekta", + "message": "Mensahe", "do_not_have_enough_gas_asset": "Wala kang sapat na ${currency} para gumawa ng transaksyon sa kasalukuyang kundisyon ng network ng blockchain. Kailangan mo ng higit pang ${currency} upang magbayad ng mga bayarin sa network ng blockchain, kahit na nagpapadala ka ng ibang asset.", - "totp_auth_url": "TOTP AUTH URL" -} \ No newline at end of file + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Pakihintay na matapos ang pagproseso ng dApp." +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index fa4568e93..d0afc5825 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -691,6 +691,27 @@ "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", + "signTransaction": "İşlem İmzala", + "errorGettingCredentials": "Başarısız: Kimlik bilgileri alınırken hata oluştu", + "errorSigningTransaction": "İşlem imzalanırken bir hata oluştu", + "pairingInvalidEvent": "Geçersiz Etkinliği Eşleştirme", + "chains": "Zincirler", + "methods": "Yöntemler", + "events": "Olaylar", + "reject": "Reddetmek", + "approve": "Onaylamak", + "expiresOn": "Tarihinde sona eriyor", + "walletConnect": "WalletConnect", + "nullURIError": "URI boş", + "connectWalletPrompt": "İşlem yapmak için cüzdanınızı WalletConnect'e bağlayın", + "newConnection": "Yeni bağlantı", + "activeConnectionsPrompt": "Aktif bağlantılar burada görünecek", + "deleteConnectionConfirmationPrompt": "Bağlantıyı silmek istediğinizden emin misiniz?", + "event": "Etkinlik", + "successful": "Başarılı", + "wouoldLikeToConnect": "bağlanmak istiyorum", + "message": "İleti", "do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.", - "totp_auth_url": "TOTP YETKİ URL'si" + "totp_auth_url": "TOTP YETKİ URL'si", + "awaitDAppProcessing": "Lütfen dApp'in işlemeyi bitirmesini bekleyin." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 7b739e201..99720ca51 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", "buy_provider_unavailable": "В даний час постачальник недоступний.", + "signTransaction": "Підписати транзакцію", + "errorGettingCredentials": "Помилка: помилка під час отримання облікових даних", + "errorSigningTransaction": "Під час підписання транзакції сталася помилка", + "pairingInvalidEvent": "Недійсна подія сполучення", + "chains": "Ланцюги", + "methods": "методи", + "events": "Події", + "reject": "Відхиляти", + "approve": "Затвердити", + "expiresOn": "Термін дії закінчується", + "walletConnect": "WalletConnect", + "nullURIError": "URI нульовий", + "connectWalletPrompt": "Підключіть свій гаманець до WalletConnect, щоб здійснювати транзакції", + "newConnection": "Нове підключення", + "activeConnectionsPrompt": "Тут з’являться активні підключення", + "deleteConnectionConfirmationPrompt": "Ви впевнені, що хочете видалити з’єднання з", + "event": "Подія", + "successful": "Успішний", + "wouoldLikeToConnect": "хотів би підключитися", + "message": "повідомлення", "do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Зачекайте, доки dApp завершить обробку." } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index e53784923..21c247fd1 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -685,6 +685,27 @@ "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", + "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", + "errorGettingCredentials": "۔ﯽﺑﺍﺮﺧ ﮟﯿﻣ ﮯﻧﺮﮐ ﻞﺻﺎﺣ ﺩﺎﻨﺳﺍ :ﻡﺎﮐﺎﻧ", + "errorSigningTransaction": "۔ﮯﮨ ﯽﺌﮔﺁ ﺶﯿﭘ ﯽﺑﺍﺮﺧ ﮏﯾﺍ ﺖﻗﻭ ﮯﺗﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", + "pairingInvalidEvent": "ﭧﻧﻮﯾﺍ ﻂﻠﻏ ﺎﻧﺎﻨﺑ ﺍﮌﻮﺟ", + "chains": "ﮟﯾﺮﯿﺠﻧﺯ", + "methods": "ﮯﻘﯾﺮﻃ", + "events": "ﺕﺎﺒﯾﺮﻘﺗ", + "reject": "ﺎﻧﺮﮐ ﺩﺭ", + "approve": "ﻭﺮﮐ ﺭﻮﻈﻨﻣ", + "expiresOn": "ﺩﺎﻌﯿﻣ ﯽﻣﺎﺘﺘﺧﺍ", + "walletConnect": "WalletConnect", + "nullURIError": "URI ۔ﮯﮨ ﻡﺪﻌﻟﺎﮐ", + "connectWalletPrompt": "۔ﮟﯾﮌﻮﺟ ﮫﺗﺎﺳ ﮯﮐ WalletConnect ﻮﮐ ﮮﻮﭩﺑ ﮯﻨﭘﺍ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻦﯾﺩ ﻦﯿﻟ", + "newConnection": "ﻦﺸﮑﻨﮐ ﺎﯿﻧ", + "activeConnectionsPrompt": "۔ﮯﮔ ﮞﻮﮨ ﺮﮨﺎﻇ ﮞﺎﮩﯾ ﺰﻨﺸﮑﻨﮐ ﻝﺎﻌﻓ", + "deleteConnectionConfirmationPrompt": "۔ﮟﯿﮨ ﮯﺘﮨﺎﭼ ﺎﻧﺮﮐ ﻑﺬﺣ ﻮﮐ ﻦﺸﮑﻨﮐ ﭖﺁ ﮧﮐ ﮯﮨ ﻦﯿﻘﯾ ﻮﮐ ﭖﺁ ﺎﯿﮐ", + "event": "ﺐﯾﺮﻘﺗ", + "successful": "ﺏﺎﯿﻣﺎﮐ", + "wouoldLikeToConnect": "؟ﮯﮔ ﮟﯿﮨﺎﭼ ﺎﻧﮍﺟ", + "message": "ﻡﺎﻐﯿﭘ", "do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﮓﻨﺴﯿﺳﻭﺮﭘ ﮯﮐ dApp ﻡﺮﮐ ﮦﺍﺮﺑ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 0532ae2eb..e362df16d 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -687,6 +687,27 @@ "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", + "signTransaction": "Wole Idunadura", + "errorGettingCredentials": "Kuna: Aṣiṣe lakoko gbigba awọn iwe-ẹri", + "errorSigningTransaction": "Aṣiṣe kan ti waye lakoko ti o fowo si iṣowo", + "pairingInvalidEvent": "Pipọpọ Iṣẹlẹ Ti ko tọ", + "chains": "Awọn ẹwọn", + "methods": "Awọn ọna", + "events": "Awọn iṣẹlẹ", + "reject": "Kọ", + "approve": "Fi ọwọ si", + "expiresOn": "Ipari lori", + "walletConnect": "Asopọmọra apamọwọ", + "nullURIError": "URI jẹ asan", + "connectWalletPrompt": "So apamọwọ rẹ pọ pẹlu WalletConnect lati ṣe awọn iṣowo", + "newConnection": "Tuntun Asopọ", + "activeConnectionsPrompt": "Awọn asopọ ti nṣiṣe lọwọ yoo han nibi", + "deleteConnectionConfirmationPrompt": "Ṣe o da ọ loju pe o fẹ paarẹ asopọ si", + "event": "Iṣẹlẹ", + "successful": "Aseyori", + "wouoldLikeToConnect": "yoo fẹ lati sopọ", + "message": "Ifiranṣẹ", "do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Fi inurere duro fun dApp lati pari sisẹ." } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 7c947847e..19928f5e2 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -692,6 +692,27 @@ "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", "buy_provider_unavailable": "提供者目前不可用。", + "signTransaction": "签署交易", + "errorGettingCredentials": "失败:获取凭据时出错", + "errorSigningTransaction": "签署交易时发生错误", + "pairingInvalidEvent": "配对无效事件", + "chains": "链条", + "methods": "方法", + "events": "活动", + "reject": "拒绝", + "approve": "批准", + "expiresOn": "到期", + "walletConnect": "钱包连接", + "nullURIError": "URI 为空", + "connectWalletPrompt": "将您的钱包与 WalletConnect 连接以进行交易", + "newConnection": "新连接", + "activeConnectionsPrompt": "活动连接将出现在这里", + "deleteConnectionConfirmationPrompt": "您确定要删除与", + "event": "事件", + "successful": "成功的", + "wouoldLikeToConnect": "想要连接", + "message": "信息", "do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。", - "totp_auth_url": "TOTP 授权 URL" + "totp_auth_url": "TOTP 授权 URL", + "awaitDAppProcessing": "请等待 dApp 处理完成。" } diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index dd8c83948..92c8a0559 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.1" -MONERO_COM_BUILD_NUMBER=57 +MONERO_COM_VERSION="1.6.2" +MONERO_COM_BUILD_NUMBER=58 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.9.1" -CAKEWALLET_BUILD_NUMBER=170 +CAKEWALLET_VERSION="4.9.2" +CAKEWALLET_BUILD_NUMBER=171 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 714441167..078688918 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.1" -MONERO_COM_BUILD_NUMBER=55 +MONERO_COM_VERSION="1.6.2" +MONERO_COM_BUILD_NUMBER=56 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.9.1" -CAKEWALLET_BUILD_NUMBER=183 +CAKEWALLET_VERSION="4.9.2" +CAKEWALLET_BUILD_NUMBER=185 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index bb2aa2ebc..5103d42b2 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -15,8 +15,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.2.1" -CAKEWALLET_BUILD_NUMBER=32 +CAKEWALLET_VERSION="1.2.2" +CAKEWALLET_BUILD_NUMBER=33 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/tool/configure.dart b/tool/configure.dart index e3a2efe35..196230929 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -476,6 +476,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; +import 'package:eth_sig_util/util/utils.dart'; import 'package:hive/hive.dart'; import 'package:web3dart/web3dart.dart'; """; @@ -498,6 +499,8 @@ abstract class Ethereum { WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); WalletCredentials createEthereumRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); String getAddress(WalletBase wallet); + String getPrivateKey(WalletBase wallet); + String getPublicKey(WalletBase wallet); TransactionPriority getDefaultTransactionPriority(); List getTransactionPriorities(); TransactionPriority deserializeEthereumTransactionPriority(int raw); diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 5e959b99b..a8c6a6166 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -35,6 +35,7 @@ class SecretKey { SecretKey('exolixApiKey', () => ''), SecretKey('robinhoodApplicationId', () => ''), SecretKey('robinhoodCIdApiSecret', () => ''), + SecretKey('walletConnectProjectId', () => ''), ]; static final ethereumSecrets = [