diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 88cdc6f6c..6e5f9e171 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -105,21 +105,19 @@ jobs: run: | cd /opt/android/cake_wallet cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. - cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. - cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs - name: Add secrets run: | cd /opt/android/cake_wallet touch lib/.secrets.g.dart - touch cw_ethereum/lib/.secrets.g.dart - touch cw_polygon/lib/.secrets.g.dart + touch cw_evm/lib/.secrets.g.dart echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart @@ -146,14 +144,14 @@ jobs: echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart 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 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - name: Rename app run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties diff --git a/.gitignore b/.gitignore index 2ffb86d76..f084f8d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -90,9 +90,10 @@ android/key.properties **/tool/.secrets-prod.json **/tool/.secrets-test.json **/tool/.secrets-config.json +**/tool/.evm-secrets-config.json **/tool/.ethereum-secrets-config.json **/lib/.secrets.g.dart -**/cw_ethereum/lib/.secrets.g.dart +**/cw_evm/lib/.secrets.g.dart vendor/ diff --git a/configure_cake_wallet.sh b/configure_cake_wallet.sh index df96c70f6..a4b6f2654 100755 --- a/configure_cake_wallet.sh +++ b/configure_cake_wallet.sh @@ -24,11 +24,10 @@ source ./app_env.sh cakewallet cd ../.. && flutter pub get flutter packages pub run tool/generate_localization.dart cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/cw_ethereum/lib/default_ethereum_erc20_tokens.dart b/cw_ethereum/lib/default_ethereum_erc20_tokens.dart index a8f82d181..c26ee1efc 100644 --- a/cw_ethereum/lib/default_ethereum_erc20_tokens.dart +++ b/cw_ethereum/lib/default_ethereum_erc20_tokens.dart @@ -1,7 +1,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; -class DefaultErc20Tokens { +class DefaultEthereumErc20Tokens { final List _defaultTokens = [ Erc20Token( name: "USD Coin", diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index 1b3e4bb44..929dadf2f 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -1,229 +1,22 @@ -import 'dart:async'; import 'dart:convert'; +import 'dart:developer'; +import 'dart:typed_data'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_ethereum/erc20_balance.dart'; -import 'package:cw_core/erc20_token.dart'; -import 'package:cw_ethereum/ethereum_transaction_model.dart'; -import 'package:cw_ethereum/pending_ethereum_transaction.dart'; -import 'package:flutter/services.dart'; -import 'package:http/http.dart'; +import 'package:cw_evm/evm_chain_client.dart'; +import 'package:cw_evm/.secrets.g.dart' as secrets; +import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:web3dart/web3dart.dart'; -import 'package:erc20/erc20.dart'; -import 'package:cw_core/node.dart'; -import 'package:cw_ethereum/ethereum_transaction_priority.dart'; -import 'package:cw_ethereum/.secrets.g.dart' as secrets; - -class EthereumClient { - final httpClient = Client(); - Web3Client? _client; - - bool connect(Node node) { - try { - _client = Web3Client(node.uri.toString(), httpClient); - - return true; - } catch (e) { - return false; - } - } - - void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async { - // _client?.pendingTransactions().listen((transactionHash) async { - // final transaction = await _client!.getTransactionByHash(transactionHash); - // - // if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) { - // onNewTransaction(); - // } - // }); - } - - Future getBalance(EthereumAddress address) async { - try { - return await _client!.getBalance(address); - } catch (_) { - return EtherAmount.zero(); - } - } - - Future getGasUnitPrice() async { - try { - final gasPrice = await _client!.getGasPrice(); - return gasPrice.getInWei.toInt(); - } catch (_) { - return 0; - } - } - - Future getEstimatedGas() async { - try { - final estimatedGas = await _client!.estimateGas(); - return estimatedGas.toInt(); - } catch (_) { - return 0; - } - } - - Future signTransaction({ - required EthPrivateKey privateKey, - required String toAddress, - required String amount, - required int gas, - required EthereumTransactionPriority priority, - required CryptoCurrency currency, - required int exponent, - String? contractAddress, - }) async { - assert(currency == CryptoCurrency.eth || - currency == CryptoCurrency.maticpoly || - contractAddress != null); - - bool _isEVMCompatibleChain = - currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly; - - final price = _client!.getGasPrice(); - - final Transaction transaction = createTransaction( - from: privateKey.address, - to: EthereumAddress.fromHex(toAddress), - maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), - amount: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), - ); - - final signedTransaction = - await _client!.signTransaction(privateKey, transaction, chainId: chainId); - - final Function _sendTransaction; - - if (_isEVMCompatibleChain) { - _sendTransaction = () async => await sendTransaction(signedTransaction); - } else { - final erc20 = ERC20( - client: _client!, - address: EthereumAddress.fromHex(contractAddress!), - chainId: chainId, - ); - - _sendTransaction = () async { - await erc20.transfer( - EthereumAddress.fromHex(toAddress), - BigInt.parse(amount), - credentials: privateKey, - transaction: transaction, - ); - }; - } - - return PendingEthereumTransaction( - signedTransaction: signedTransaction, - amount: amount, - fee: BigInt.from(gas) * (await price).getInWei, - sendTransaction: _sendTransaction, - exponent: exponent, - ); - } +class EthereumClient extends EVMChainClient { + @override int get chainId => 1; - Transaction createTransaction({ - required EthereumAddress from, - required EthereumAddress to, - required EtherAmount amount, - EtherAmount? maxPriorityFeePerGas, - }) { - return Transaction( - from: from, - to: to, - maxPriorityFeePerGas: maxPriorityFeePerGas, - value: amount, - ); - } - - Future sendTransaction(Uint8List signedTransaction) async => - await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction)); - + @override Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => prependTransactionType(0x02, signedTransaction); - Future getTransactionDetails(String transactionHash) async { - // Wait for the transaction receipt to become available - TransactionReceipt? receipt; - while (receipt == null) { - receipt = await _client!.getTransactionReceipt(transactionHash); - await Future.delayed(Duration(seconds: 1)); - } - - // Print the receipt information - print('Transaction Hash: ${receipt.transactionHash}'); - print('Block Hash: ${receipt.blockHash}'); - print('Block Number: ${receipt.blockNumber}'); - print('Gas Used: ${receipt.gasUsed}'); - - /* - Transaction Hash: [112, 244, 4, 238, 89, 199, 171, 191, 210, 236, 110, 42, 185, 202, 220, 21, 27, 132, 123, 221, 137, 90, 77, 13, 23, 43, 12, 230, 93, 63, 221, 116] -I/flutter ( 4474): Block Hash: [149, 44, 250, 119, 111, 104, 82, 98, 17, 89, 30, 190, 25, 44, 218, 118, 127, 189, 241, 35, 213, 106, 25, 95, 195, 37, 55, 131, 185, 180, 246, 200] -I/flutter ( 4474): Block Number: 17120242 -I/flutter ( 4474): Gas Used: 21000 - */ - - // Wait for the transaction receipt to become available - TransactionInformation? transactionInformation; - while (transactionInformation == null) { - print("********************************"); - transactionInformation = await _client!.getTransactionByHash(transactionHash); - await Future.delayed(Duration(seconds: 1)); - } - // Print the receipt information - print('Transaction Hash: ${transactionInformation.hash}'); - print('Block Hash: ${transactionInformation.blockHash}'); - print('Block Number: ${transactionInformation.blockNumber}'); - print('Gas Used: ${transactionInformation.gas}'); - - /* - Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74 -I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8 -I/flutter ( 4474): Block Number: 17120242 -I/flutter ( 4474): Gas Used: 53000 - */ - } - - Future fetchERC20Balances( - EthereumAddress userAddress, String contractAddress) async { - final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); - try { - final balance = await erc20.balanceOf(userAddress); - - int exponent = (await erc20.decimals()).toInt(); - - return ERC20Balance(balance, exponent: exponent); - } catch (_) { - return ERC20Balance(BigInt.zero); - } - } - - Future getErc20Token(String contractAddress) async { - try { - final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); - final name = await erc20.name(); - final symbol = await erc20.symbol(); - final decimal = await erc20.decimals(); - - return Erc20Token( - name: name, - symbol: symbol, - contractAddress: contractAddress, - decimal: decimal.toInt(), - ); - } catch (e) { - return null; - } - } - - void stop() { - _client?.dispose(); - } - - Future> fetchTransactions(String address, + @override + Future> fetchTransactions(String address, {String? contractAddress}) async { try { final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", { @@ -234,41 +27,18 @@ I/flutter ( 4474): Gas Used: 53000 "apikey": secrets.etherScanApiKey, })); - final _jsonResponse = json.decode(response.body) as Map; + final jsonResponse = json.decode(response.body) as Map; - if (response.statusCode >= 200 && response.statusCode < 300 && _jsonResponse['status'] != 0) { - return (_jsonResponse['result'] as List) - .map((e) => EthereumTransactionModel.fromJson(e as Map)) + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map((e) => EVMChainTransactionModel.fromJson(e as Map, 'ETH')) .toList(); } return []; } catch (e) { - print(e); + log(e.toString()); return []; } } - - Web3Client? getWeb3Client() { - return _client; - } - -// Future _getDecimalPlacesForContract(DeployedContract contract) async { -// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json"); -// final contractAbi = ContractAbi.fromJson(abi, "ERC20"); -// -// final contract = DeployedContract( -// contractAbi, -// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!), -// ); -// final decimalsFunction = contract.function('decimals'); -// final decimals = await _client!.call( -// contract: contract, -// function: decimalsFunction, -// params: [], -// ); -// -// int exponent = int.parse(decimals.first.toString()); -// return exponent; -// } } diff --git a/cw_ethereum/lib/ethereum_exceptions.dart b/cw_ethereum/lib/ethereum_exceptions.dart deleted file mode 100644 index 518f46275..000000000 --- a/cw_ethereum/lib/ethereum_exceptions.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:cw_core/crypto_currency.dart'; - -class EthereumTransactionCreationException implements Exception { - final String exceptionMessage; - - EthereumTransactionCreationException(CryptoCurrency currency) : - this.exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.'; - - @override - String toString() => exceptionMessage; -} diff --git a/cw_ethereum/lib/ethereum_formatter.dart b/cw_ethereum/lib/ethereum_formatter.dart deleted file mode 100644 index 468c536f8..000000000 --- a/cw_ethereum/lib/ethereum_formatter.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:intl/intl.dart'; - -const ethereumAmountLength = 12; -const ethereumAmountDivider = 1000000000000; -final ethereumAmountFormat = NumberFormat() - ..maximumFractionDigits = ethereumAmountLength - ..minimumFractionDigits = 1; - -class EthereumFormatter { - static int parseEthereumAmount(String amount) { - try { - return (double.parse(amount) * ethereumAmountDivider).round(); - } catch (_) { - return 0; - } - } - - static double parseEthereumAmountToDouble(int amount) { - try { - return amount / ethereumAmountDivider; - } catch (_) { - return 0; - } - } -} diff --git a/cw_ethereum/lib/ethereum_mnemonics_exception.dart b/cw_ethereum/lib/ethereum_mnemonics_exception.dart new file mode 100644 index 000000000..b91a15c94 --- /dev/null +++ b/cw_ethereum/lib/ethereum_mnemonics_exception.dart @@ -0,0 +1,5 @@ +class EthereumMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Ethereum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_ethereum/lib/ethereum_transaction_history.dart b/cw_ethereum/lib/ethereum_transaction_history.dart index 4511f4436..f774ae905 100644 --- a/cw_ethereum/lib/ethereum_transaction_history.dart +++ b/cw_ethereum/lib/ethereum_transaction_history.dart @@ -1,77 +1,18 @@ -import 'dart:convert'; import 'dart:core'; -import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_ethereum/file.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cw_core/transaction_history.dart'; import 'package:cw_ethereum/ethereum_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; -part 'ethereum_transaction_history.g.dart'; - -const transactionsHistoryFileName = 'transactions.json'; - -class EthereumTransactionHistory = EthereumTransactionHistoryBase with _$EthereumTransactionHistory; - -abstract class EthereumTransactionHistoryBase - extends TransactionHistoryBase with Store { - EthereumTransactionHistoryBase({required this.walletInfo, required String password}) - : _password = password { - transactions = ObservableMap(); - } - - final WalletInfo walletInfo; - String _password; - - Future init() async => await _load(); +class EthereumTransactionHistory extends EVMChainTransactionHistory { + EthereumTransactionHistory({ + required super.walletInfo, + required super.password, + }); @override - Future save() async { - try { - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); - final path = '$dirPath/$transactionsHistoryFileName'; - final data = json.encode({'transactions': transactions}); - await writeData(path: path, password: _password, data: data); - } catch (e, s) { - print('Error while save ethereum transaction history: ${e.toString()}'); - print(s); - } - } + String getTransactionHistoryFileName() => 'transactions.json'; @override - void addOne(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction; - - @override - void addMany(Map transactions) => - this.transactions.addAll(transactions); - - Future> _read() async { - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); - final path = '$dirPath/$transactionsHistoryFileName'; - final content = await read(path: path, password: _password); - if (content.isEmpty) { - return {}; - } - return json.decode(content) as Map; - } - - Future _load() async { - try { - final content = await _read(); - final txs = content['transactions'] as Map? ?? {}; - - txs.entries.forEach((entry) { - final val = entry.value; - - if (val is Map) { - final tx = EthereumTransactionInfo.fromJson(val); - _update(tx); - } - }); - } catch (e) { - print(e); - } - } - - void _update(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction; + EVMChainTransactionInfo getTransactionInfo(Map val) => + EthereumTransactionInfo.fromJson(val); } diff --git a/cw_ethereum/lib/ethereum_transaction_info.dart b/cw_ethereum/lib/ethereum_transaction_info.dart index f0deae931..d5d3fea8d 100644 --- a/cw_ethereum/lib/ethereum_transaction_info.dart +++ b/cw_ethereum/lib/ethereum_transaction_info.dart @@ -1,57 +1,21 @@ -import 'dart:math'; - -import 'package:cw_core/format_amount.dart'; import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_core/transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; -class EthereumTransactionInfo extends TransactionInfo { +class EthereumTransactionInfo extends EVMChainTransactionInfo { EthereumTransactionInfo({ - required this.id, - required this.height, - required this.ethAmount, - required this.ethFee, - this.tokenSymbol = "ETH", - this.exponent = 18, - required this.direction, - required this.isPending, - required this.date, - required this.confirmations, - required this.to, - }) : this.amount = ethAmount.toInt(), - this.fee = ethFee.toInt(); - - final String id; - final int height; - final int amount; - final BigInt ethAmount; - final int exponent; - final TransactionDirection direction; - final DateTime date; - final bool isPending; - final int fee; - final BigInt ethFee; - final int confirmations; - final String tokenSymbol; - String? _fiatAmount; - final String? to; - - @override - String amountFormatted() { - final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString()); - return '${amount.substring(0, min(10, amount.length))} $tokenSymbol'; - } - - @override - String fiatAmount() => _fiatAmount ?? ''; - - @override - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - - @override - String feeFormatted() { - final amount = (ethFee / BigInt.from(10).pow(18)).toString(); - return '${amount.substring(0, min(10, amount.length))} ETH'; - } + required super.id, + required super.height, + required super.ethAmount, + required super.ethFee, + required super.tokenSymbol, + required super.direction, + required super.isPending, + required super.date, + required super.confirmations, + required super.to, + required super.from, + super.exponent, + }); factory EthereumTransactionInfo.fromJson(Map data) { return EthereumTransactionInfo( @@ -66,20 +30,10 @@ class EthereumTransactionInfo extends TransactionInfo { confirmations: data['confirmations'] as int, tokenSymbol: data['tokenSymbol'] as String, to: data['to'], + from: data['from'], ); } - Map toJson() => { - 'id': id, - 'height': height, - 'amount': ethAmount.toString(), - 'exponent': exponent, - 'fee': ethFee.toString(), - 'direction': direction.index, - 'date': date.millisecondsSinceEpoch, - 'isPending': isPending, - 'confirmations': confirmations, - 'tokenSymbol': tokenSymbol, - 'to': to, - }; + @override + String get feeCurrency => 'ETH'; } diff --git a/cw_ethereum/lib/ethereum_transaction_priority.dart b/cw_ethereum/lib/ethereum_transaction_priority.dart deleted file mode 100644 index ff5668397..000000000 --- a/cw_ethereum/lib/ethereum_transaction_priority.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:cw_core/transaction_priority.dart'; - -class EthereumTransactionPriority extends TransactionPriority { - final int tip; - - const EthereumTransactionPriority({required String title, required int raw, required this.tip}) - : super(title: title, raw: raw); - - static const List all = [fast, medium, slow]; - static const EthereumTransactionPriority slow = - EthereumTransactionPriority(title: 'slow', raw: 0, tip: 1); - static const EthereumTransactionPriority medium = - EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 2); - static const EthereumTransactionPriority fast = - EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 4); - - static EthereumTransactionPriority deserialize({required int raw}) { - switch (raw) { - case 0: - return slow; - case 1: - return medium; - case 2: - return fast; - default: - throw Exception('Unexpected token: $raw for EthereumTransactionPriority deserialize'); - } - } - - String get units => 'gas'; - - @override - String toString() { - var label = ''; - - switch (this) { - case EthereumTransactionPriority.slow: - label = 'Slow'; - break; - case EthereumTransactionPriority.medium: - label = 'Medium'; - break; - case EthereumTransactionPriority.fast: - label = 'Fast'; - break; - default: - break; - } - - return label; - } -} diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index cd4bd84cc..4604db662 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -1,126 +1,58 @@ -import 'dart:async'; import 'dart:convert'; -import 'dart:io'; -import 'dart:math'; -import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/cake_hive.dart'; -import 'package:cw_core/node.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/wallet_addresses.dart'; -import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart'; -import 'package:cw_ethereum/erc20_balance.dart'; import 'package:cw_ethereum/ethereum_client.dart'; -import 'package:cw_ethereum/ethereum_exceptions.dart'; -import 'package:cw_ethereum/ethereum_formatter.dart'; -import 'package:cw_ethereum/ethereum_transaction_credentials.dart'; import 'package:cw_ethereum/ethereum_transaction_history.dart'; import 'package:cw_ethereum/ethereum_transaction_info.dart'; -import 'package:cw_ethereum/ethereum_transaction_model.dart'; -import 'package:cw_ethereum/ethereum_transaction_priority.dart'; -import 'package:cw_ethereum/ethereum_wallet_addresses.dart'; -import 'package:cw_ethereum/file.dart'; -import 'package:cw_core/erc20_token.dart'; -import 'package:hive/hive.dart'; -import 'package:hex/hex.dart'; -import 'package:mobx/mobx.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:web3dart/crypto.dart'; -import 'package:web3dart/web3dart.dart'; -import 'package:bip39/bip39.dart' as bip39; -import 'package:bip32/bip32.dart' as bip32; +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/evm_erc20_balance.dart'; +import 'package:cw_evm/file.dart'; -part 'ethereum_wallet.g.dart'; +class EthereumWallet extends EVMChainWallet { + EthereumWallet({ + required super.client, + required super.password, + required super.walletInfo, + super.mnemonic, + super.initialBalance, + super.privateKey, + }) : super(nativeCurrency: CryptoCurrency.eth); -class EthereumWallet = EthereumWalletBase with _$EthereumWallet; + @override + void addInitialTokens() { + final initialErc20Tokens = DefaultEthereumErc20Tokens().initialErc20Tokens; -abstract class EthereumWalletBase - extends WalletBase - with Store { - EthereumWalletBase({ - required WalletInfo walletInfo, - String? mnemonic, - String? privateKey, - required String password, - ERC20Balance? initialBalance, - }) : syncStatus = NotConnectedSyncStatus(), - _password = password, - _mnemonic = mnemonic, - _hexPrivateKey = privateKey, - _isTransactionUpdating = false, - _client = EthereumClient(), - walletAddresses = EthereumWalletAddresses(walletInfo), - balance = ObservableMap.of( - {CryptoCurrency.eth: initialBalance ?? ERC20Balance(BigInt.zero)}), - super(walletInfo) { - this.walletInfo = walletInfo; - transactionHistory = EthereumTransactionHistory(walletInfo: walletInfo, password: password); - - if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) { - CakeHive.registerAdapter(Erc20TokenAdapter()); + for (var token in initialErc20Tokens) { + evmChainErc20TokensBox.put(token.contractAddress, token); } - - _sharedPrefs.complete(SharedPreferences.getInstance()); } - final String? _mnemonic; - final String? _hexPrivateKey; - final String _password; - - late final Box erc20TokensBox; - - late final Box ethereumErc20TokensBox; - - late final EthPrivateKey _ethPrivateKey; - - EthPrivateKey get ethPrivateKey => _ethPrivateKey; - - late EthereumClient _client; - - int? _gasPrice; - int? _estimatedGas; - bool _isTransactionUpdating; - - // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter - Timer? _transactionsUpdateTimer; + @override + Future checkIfScanProviderIsEnabled() async { + bool isEtherscanEnabled = (await sharedPrefs.future).getBool("use_etherscan") ?? true; + return isEtherscanEnabled; + } @override - WalletAddresses walletAddresses; - - @override - @observable - SyncStatus syncStatus; - - @override - @observable - late ObservableMap balance; - - Completer _sharedPrefs = Completer(); - - Future init() async { + Future initErc20TokensBox() async { + // This is for ethereum wallets, + // Other wallets would override and initialize their respective boxes with their boxNames. await movePreviousErc20BoxConfigsToNewBox(); - - await walletAddresses.init(); - await transactionHistory.init(); - _ethPrivateKey = await getPrivateKey( - mnemonic: _mnemonic, - privateKey: _hexPrivateKey, - password: _password, - ); - walletAddresses.address = _ethPrivateKey.address.toString(); - await save(); } /// Majorly for backward compatibility for previous configs that have been set. Future movePreviousErc20BoxConfigsToNewBox() async { // Opens a box specific to this wallet - ethereumErc20TokensBox = await CakeHive.openBox( + evmChainErc20TokensBox = await CakeHive.openBox( "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}"); //Open the previous token configs box @@ -130,7 +62,7 @@ abstract class EthereumWalletBase if (erc20TokensBox.isEmpty) { // If it's empty, but the new wallet specific box is also empty, // we load the initial tokens to the new box. - if (ethereumErc20TokensBox.isEmpty) addInitialTokens(); + if (evmChainErc20TokensBox.isEmpty) addInitialTokens(); return; } @@ -141,319 +73,37 @@ abstract class EthereumWalletBase await erc20TokensBox.deleteFromDisk(); // Add all the previous tokens with configs to the new box - ethereumErc20TokensBox.addAll(allValues); + evmChainErc20TokensBox.addAll(allValues); } @override - int calculateEstimatedFee(TransactionPriority priority, int? amount) { - try { - if (priority is EthereumTransactionPriority) { - final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); - return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); - } - - return 0; - } catch (e) { - return 0; - } - } - - @override - Future changePassword(String password) { - throw UnimplementedError("changePassword"); - } - - @override - void close() { - _client.stop(); - _transactionsUpdateTimer?.cancel(); - } - - @action - @override - Future connectToNode({required Node node}) async { - try { - syncStatus = ConnectingSyncStatus(); - - final isConnected = _client.connect(node); - - if (!isConnected) { - throw Exception("Ethereum Node connection failed"); - } - - _client.setListeners(_ethPrivateKey.address, _onNewTransaction); - - _setTransactionUpdateTimer(); - - syncStatus = ConnectedSyncStatus(); - } catch (e) { - syncStatus = FailedSyncStatus(); - } - } - - @override - Future createTransaction(Object credentials) async { - final _credentials = credentials as EthereumTransactionCredentials; - final outputs = _credentials.outputs; - final hasMultiDestination = outputs.length > 1; - - final CryptoCurrency transactionCurrency = - balance.keys.firstWhere((element) => element.title == _credentials.currency.title); - - final _erc20Balance = balance[transactionCurrency]!; - BigInt totalAmount = BigInt.zero; - int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; - num amountToEthereumMultiplier = pow(10, exponent); - - // so far this can not be made with Ethereum as Ethereum does not support multiple recipients - if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { - throw EthereumTransactionCreationException(transactionCurrency); - } - - final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble( - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); - totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier); - - if (_erc20Balance.balance < totalAmount) { - throw EthereumTransactionCreationException(transactionCurrency); - } - } else { - final output = outputs.first; - // since the fees are taken from Ethereum - // then no need to subtract the fees from the amount if send all - final BigInt allAmount; - if (transactionCurrency is Erc20Token) { - allAmount = _erc20Balance.balance; - } else { - allAmount = _erc20Balance.balance - - BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); - } - final totalOriginalAmount = - EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0); - totalAmount = output.sendAll - ? allAmount - : BigInt.from(totalOriginalAmount * amountToEthereumMultiplier); - - if (_erc20Balance.balance < totalAmount) { - throw EthereumTransactionCreationException(transactionCurrency); - } - } - - final pendingEthereumTransaction = await _client.signTransaction( - privateKey: _ethPrivateKey, - toAddress: _credentials.outputs.first.isParsedAddress - ? _credentials.outputs.first.extractedAddress! - : _credentials.outputs.first.address, - amount: totalAmount.toString(), - gas: _estimatedGas!, - priority: _credentials.priority!, - currency: transactionCurrency, - exponent: exponent, - contractAddress: - transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, + EVMChainTransactionInfo getTransactionInfo( + EVMChainTransactionModel transactionModel, String address) { + final model = EthereumTransactionInfo( + id: transactionModel.hash, + height: transactionModel.blockNumber, + ethAmount: transactionModel.amount, + direction: transactionModel.from == address + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + isPending: false, + date: transactionModel.date, + confirmations: transactionModel.confirmations, + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, + exponent: transactionModel.tokenDecimal ?? 18, + tokenSymbol: transactionModel.tokenSymbol ?? "ETH", + to: transactionModel.to, + from: transactionModel.from, ); - - return pendingEthereumTransaction; - } - - Future _updateTransactions() async { - try { - if (_isTransactionUpdating) { - return; - } - bool isEtherscanEnabled = (await _sharedPrefs.future).getBool("use_etherscan") ?? true; - if (!isEtherscanEnabled) { - return; - } - - _isTransactionUpdating = true; - final transactions = await fetchTransactions(); - transactionHistory.addMany(transactions); - await transactionHistory.save(); - _isTransactionUpdating = false; - } catch (_) { - _isTransactionUpdating = false; - } + return model; } @override - Future> fetchTransactions() async { - final address = _ethPrivateKey.address.hex; - final transactions = await _client.fetchTransactions(address); - - final List>> erc20TokensTransactions = []; - - for (var token in balance.keys) { - if (token is Erc20Token) { - erc20TokensTransactions.add(_client.fetchTransactions( - address, - contractAddress: token.contractAddress, - )); - } - } - - final tokensTransaction = await Future.wait(erc20TokensTransactions); - transactions.addAll(tokensTransaction.expand((element) => element)); - - final Map result = {}; - - for (var transactionModel in transactions) { - if (transactionModel.isError) { - continue; - } - - result[transactionModel.hash] = EthereumTransactionInfo( - id: transactionModel.hash, - height: transactionModel.blockNumber, - ethAmount: transactionModel.amount, - direction: transactionModel.from == address - ? TransactionDirection.outgoing - : TransactionDirection.incoming, - isPending: false, - date: transactionModel.date, - confirmations: transactionModel.confirmations, - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, - exponent: transactionModel.tokenDecimal ?? 18, - tokenSymbol: transactionModel.tokenSymbol ?? "ETH", - to: transactionModel.to, - ); - } - - return result; - } + String getTransactionHistoryFileName() => 'transactions.json'; @override - Object get keys => throw UnimplementedError("keys"); - - @override - Future rescan({required int height}) { - throw UnimplementedError("rescan"); - } - - @override - Future save() async { - await walletAddresses.updateAddressesInBox(); - final path = await makePath(); - await write(path: path, password: _password, data: toJSON()); - await transactionHistory.save(); - } - - @override - String? get seed => _mnemonic; - - @override - String get privateKey => HEX.encode(_ethPrivateKey.privateKey); - - @action - @override - Future startSync() async { - try { - syncStatus = AttemptingSyncStatus(); - await _updateBalance(); - await _updateTransactions(); - _gasPrice = await _client.getGasUnitPrice(); - _estimatedGas = await _client.getEstimatedGas(); - - Timer.periodic( - const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice()); - Timer.periodic(const Duration(seconds: 10), - (timer) async => _estimatedGas = await _client.getEstimatedGas()); - - syncStatus = SyncedSyncStatus(); - } catch (e) { - syncStatus = FailedSyncStatus(); - } - } - - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - - String toJSON() => json.encode({ - 'mnemonic': _mnemonic, - 'private_key': privateKey, - 'balance': balance[currency]!.toJSON(), - }); - - static Future open({ - required String name, - required String password, - required WalletInfo walletInfo, - }) async { - final path = await pathForWallet(name: name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final privateKey = data['private_key'] as String?; - final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero); - - return EthereumWallet( - walletInfo: walletInfo, - password: password, - mnemonic: mnemonic, - privateKey: privateKey, - initialBalance: balance, - ); - } - - Future _updateBalance() async { - balance[currency] = await _fetchEthBalance(); - - await _fetchErc20Balances(); - await save(); - } - - Future _fetchEthBalance() async { - final balance = await _client.getBalance(_ethPrivateKey.address); - return ERC20Balance(balance.getInWei); - } - - Future _fetchErc20Balances() async { - for (var token in ethereumErc20TokensBox.values) { - try { - if (token.enabled) { - balance[token] = await _client.fetchERC20Balances( - _ethPrivateKey.address, - token.contractAddress, - ); - } else { - balance.remove(token); - } - } catch (_) {} - } - } - - Future getPrivateKey( - {String? mnemonic, String? privateKey, required String password}) async { - assert(mnemonic != null || privateKey != null); - - if (privateKey != null) { - return EthPrivateKey.fromHex(privateKey); - } - - final seed = bip39.mnemonicToSeed(mnemonic!); - - final root = bip32.BIP32.fromSeed(seed); - - const _hdPathEthereum = "m/44'/60'/0'/0"; - const index = 0; - final addressAtIndex = root.derivePath("$_hdPathEthereum/$index"); - - return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List)); - } - - Future? updateBalance() async => await _updateBalance(); - - List get erc20Currencies => ethereumErc20TokensBox.values.toList(); - - Future addErc20Token(Erc20Token token) async { - String? iconPath; - try { - iconPath = CryptoCurrency.all - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) - .iconPath; - } catch (_) {} - - final _token = Erc20Token( + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) { + return Erc20Token( name: token.name, symbol: token.symbol, contractAddress: token.contractAddress, @@ -462,85 +112,30 @@ abstract class EthereumWalletBase tag: token.tag ?? "ETH", iconPath: iconPath, ); - - await ethereumErc20TokensBox.put(_token.contractAddress, _token); - - if (_token.enabled) { - balance[_token] = await _client.fetchERC20Balances( - _ethPrivateKey.address, - _token.contractAddress, - ); - } else { - balance.remove(_token); - } - } - - Future deleteErc20Token(Erc20Token token) async { - await token.delete(); - - balance.remove(token); - _updateBalance(); - } - - Future getErc20Token(String contractAddress) async => - await _client.getErc20Token(contractAddress); - - void _onNewTransaction() { - _updateBalance(); - _updateTransactions(); - } - - void addInitialTokens() { - final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens; - - initialErc20Tokens.forEach((token) => ethereumErc20TokensBox.put(token.contractAddress, token)); } @override - Future renameWalletFiles(String newWalletName) async { - final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); - final currentWalletFile = File(currentWalletPath); - - final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type); - final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName'); - - // Copies current wallet files into new wallet name's dir and files - if (currentWalletFile.existsSync()) { - final newWalletPath = await pathForWallet(name: newWalletName, type: type); - await currentWalletFile.copy(newWalletPath); - } - if (currentTransactionsFile.existsSync()) { - final newDirPath = await pathForWalletDir(name: newWalletName, type: type); - await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName'); - } - - // Delete old name's dir and files - await Directory(currentDirPath).delete(recursive: true); + EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password) { + return EthereumTransactionHistory(walletInfo: walletInfo, password: password); } - void _setTransactionUpdateTimer() { - if (_transactionsUpdateTimer?.isActive ?? false) { - _transactionsUpdateTimer!.cancel(); - } + static Future open( + {required String name, required String password, required WalletInfo walletInfo}) async { + final path = await pathForWallet(name: name, type: walletInfo.type); + final jsonSource = await read(path: path, password: password); + final data = json.decode(jsonSource) as Map; + final mnemonic = data['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ?? + EVMChainERC20Balance(BigInt.zero); - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) { - _updateTransactions(); - _updateBalance(); - }); + return EthereumWallet( + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + privateKey: privateKey, + initialBalance: balance, + client: EthereumClient(), + ); } - - void updateEtherscanUsageState(bool isEnabled) { - if (isEnabled) { - _updateTransactions(); - _setTransactionUpdateTimer(); - } else { - _transactionsUpdateTimer?.cancel(); - } - } - - @override - String signMessage(String message, {String? address}) => - bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); - - Web3Client? getWeb3Client() => _client.getWeb3Client(); } diff --git a/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart b/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart deleted file mode 100644 index 6546f2fae..000000000 --- a/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_info.dart'; - -class EthereumNewWalletCredentials extends WalletCredentials { - EthereumNewWalletCredentials({required String name, WalletInfo? walletInfo}) - : super(name: name, walletInfo: walletInfo); -} - -class EthereumRestoreWalletFromSeedCredentials extends WalletCredentials { - EthereumRestoreWalletFromSeedCredentials( - {required String name, - required String password, - required this.mnemonic, - WalletInfo? walletInfo}) - : super(name: name, password: password, walletInfo: walletInfo); - - final String mnemonic; -} - -class EthereumRestoreWalletFromPrivateKey extends WalletCredentials { - EthereumRestoreWalletFromPrivateKey( - {required String name, - required String password, - required this.privateKey, - WalletInfo? walletInfo}) - : super(name: name, password: password, walletInfo: walletInfo); - - final String privateKey; -} diff --git a/cw_ethereum/lib/ethereum_wallet_service.dart b/cw_ethereum/lib/ethereum_wallet_service.dart index 0acc90bac..1cd776867 100644 --- a/cw_ethereum/lib/ethereum_wallet_service.dart +++ b/cw_ethereum/lib/ethereum_wallet_service.dart @@ -1,32 +1,31 @@ -import 'dart:io'; - -import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_ethereum/ethereum_mnemonics.dart'; +import 'package:cw_ethereum/ethereum_client.dart'; +import 'package:cw_ethereum/ethereum_mnemonics_exception.dart'; import 'package:cw_ethereum/ethereum_wallet.dart'; -import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart'; -import 'package:hive/hive.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; +import 'package:cw_evm/evm_chain_wallet_service.dart'; import 'package:bip39/bip39.dart' as bip39; -import 'package:collection/collection.dart'; -class EthereumWalletService extends WalletService { - EthereumWalletService(this.walletInfoSource); +class EthereumWalletService extends EVMChainWalletService { + EthereumWalletService(super.walletInfoSource, {required this.client}); - final Box walletInfoSource; + late EthereumClient client; @override - Future create(EthereumNewWalletCredentials credentials) async { + WalletType getType() => WalletType.ethereum; + + @override + Future create(EVMChainNewWalletCredentials credentials) async { final strength = credentials.seedPhraseLength == 24 ? 256 : 128; final mnemonic = bip39.generateMnemonic(strength: strength); + final wallet = EthereumWallet( walletInfo: credentials.walletInfo!, mnemonic: mnemonic, password: credentials.password!, + client: client, ); await wallet.init(); @@ -36,18 +35,11 @@ class EthereumWalletService extends WalletService WalletType.ethereum; - - @override - Future isWalletExit(String name) async => - File(await pathForWallet(name: name, type: getType())).existsSync(); - @override Future openWallet(String name, String password) async { final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); - final wallet = await EthereumWalletBase.open( + final wallet = await EthereumWallet.open( name: name, password: password, walletInfo: walletInfo, @@ -60,19 +52,28 @@ class EthereumWalletService extends WalletService remove(String wallet) async { - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWallet = await EthereumWallet.open( + password: password, name: currentName, walletInfo: currentWalletInfo); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); } @override - Future restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async { + Future restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async { final wallet = EthereumWallet( password: credentials.password!, privateKey: credentials.privateKey, walletInfo: credentials.walletInfo!, + client: client, ); await wallet.init(); @@ -84,7 +85,7 @@ class EthereumWalletService extends WalletService restoreFromSeed( - EthereumRestoreWalletFromSeedCredentials credentials) async { + EVMChainRestoreWalletFromSeedCredentials credentials) async { if (!bip39.validateMnemonic(credentials.mnemonic)) { throw EthereumMnemonicIsIncorrectException(); } @@ -93,6 +94,7 @@ class EthereumWalletService extends WalletService rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); - final currentWallet = await EthereumWalletBase.open( - password: password, name: currentName, walletInfo: currentWalletInfo); - - await currentWallet.renameWalletFiles(newName); - - final newWalletInfo = currentWalletInfo; - newWalletInfo.id = WalletBase.idFor(newName, getType()); - newWalletInfo.name = newName; - - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); - } } diff --git a/cw_ethereum/pubspec.yaml b/cw_ethereum/pubspec.yaml index 6946a43a1..649ec574b 100644 --- a/cw_ethereum/pubspec.yaml +++ b/cw_ethereum/pubspec.yaml @@ -13,56 +13,23 @@ dependencies: flutter: sdk: flutter web3dart: ^2.7.1 - erc20: ^1.0.1 - mobx: ^2.0.7+4 - bip39: ^1.0.6 - bip32: ^2.0.0 - hex: ^0.2.0 - http: ^1.1.0 - shared_preferences: ^2.0.15 cw_core: path: ../cw_core + cw_evm: + path: ../cw_evm + hive: ^2.2.3 dev_dependencies: flutter_test: sdk: flutter build_runner: ^2.1.11 - mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/cw_evm/.gitignore b/cw_evm/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/cw_evm/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/cw_evm/.metadata b/cw_evm/.metadata new file mode 100644 index 000000000..fa347fc6a --- /dev/null +++ b/cw_evm/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + channel: stable + +project_type: package diff --git a/cw_evm/CHANGELOG.md b/cw_evm/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_evm/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_evm/LICENSE b/cw_evm/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_evm/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_evm/README.md b/cw_evm/README.md new file mode 100644 index 000000000..02fe8ecab --- /dev/null +++ b/cw_evm/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/cw_evm/analysis_options.yaml b/cw_evm/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_evm/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_evm/lib/cw_evm.dart b/cw_evm/lib/cw_evm.dart new file mode 100644 index 000000000..40f2bcaba --- /dev/null +++ b/cw_evm/lib/cw_evm.dart @@ -0,0 +1,7 @@ +library cw_evm; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart new file mode 100644 index 000000000..de5b3874a --- /dev/null +++ b/cw_evm/lib/evm_chain_client.dart @@ -0,0 +1,251 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:cw_core/node.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/crypto_currency.dart'; + +import 'package:cw_evm/evm_erc20_balance.dart'; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/pending_evm_chain_transaction.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:flutter/services.dart'; + +import 'package:http/http.dart'; +import 'package:erc20/erc20.dart'; +import 'package:web3dart/web3dart.dart'; + +abstract class EVMChainClient { + final httpClient = Client(); + Web3Client? _client; + + //! To be overridden by all child classes + + int get chainId; + + Future> fetchTransactions(String address, + {String? contractAddress}); + + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction); + + //! Common methods across all child classes + + bool connect(Node node) { + try { + _client = Web3Client(node.uri.toString(), httpClient); + + return true; + } catch (e) { + return false; + } + } + + void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async { + // _client?.pendingTransactions().listen((transactionHash) async { + // final transaction = await _client!.getTransactionByHash(transactionHash); + // + // if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) { + // onNewTransaction(); + // } + // }); + } + + Future getBalance(EthereumAddress address) async { + try { + return await _client!.getBalance(address); + } catch (_) { + return EtherAmount.zero(); + } + } + + Future getGasUnitPrice() async { + try { + final gasPrice = await _client!.getGasPrice(); + return gasPrice.getInWei.toInt(); + } catch (_) { + return 0; + } + } + + Future getEstimatedGas() async { + try { + final estimatedGas = await _client!.estimateGas(); + return estimatedGas.toInt(); + } catch (_) { + return 0; + } + } + + Future signTransaction({ + required EthPrivateKey privateKey, + required String toAddress, + required String amount, + required int gas, + required EVMChainTransactionPriority priority, + required CryptoCurrency currency, + required int exponent, + String? contractAddress, + }) async { + assert(currency == CryptoCurrency.eth || + currency == CryptoCurrency.maticpoly || + contractAddress != null); + + bool isEVMCompatibleChain = + currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly; + + final price = _client!.getGasPrice(); + + final Transaction transaction = createTransaction( + from: privateKey.address, + to: EthereumAddress.fromHex(toAddress), + maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), + amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), + ); + + final signedTransaction = + await _client!.signTransaction(privateKey, transaction, chainId: chainId); + + final Function _sendTransaction; + + if (isEVMCompatibleChain) { + _sendTransaction = () async => await sendTransaction(signedTransaction); + } else { + final erc20 = ERC20( + client: _client!, + address: EthereumAddress.fromHex(contractAddress!), + chainId: chainId, + ); + + _sendTransaction = () async { + await erc20.transfer( + EthereumAddress.fromHex(toAddress), + BigInt.parse(amount), + credentials: privateKey, + transaction: transaction, + ); + }; + } + + return PendingEVMChainTransaction( + signedTransaction: signedTransaction, + amount: amount, + fee: BigInt.from(gas) * (await price).getInWei, + sendTransaction: _sendTransaction, + exponent: exponent, + ); + } + + Transaction createTransaction({ + required EthereumAddress from, + required EthereumAddress to, + required EtherAmount amount, + EtherAmount? maxPriorityFeePerGas, + }) { + return Transaction( + from: from, + to: to, + maxPriorityFeePerGas: maxPriorityFeePerGas, + value: amount, + ); + } + + Future sendTransaction(Uint8List signedTransaction) async => + await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction)); + + Future getTransactionDetails(String transactionHash) async { + // Wait for the transaction receipt to become available + TransactionReceipt? receipt; + while (receipt == null) { + receipt = await _client!.getTransactionReceipt(transactionHash); + await Future.delayed(const Duration(seconds: 1)); + } + + // Print the receipt information + log('Transaction Hash: ${receipt.transactionHash}'); + log('Block Hash: ${receipt.blockHash}'); + log('Block Number: ${receipt.blockNumber}'); + log('Gas Used: ${receipt.gasUsed}'); + + /* + Transaction Hash: [112, 244, 4, 238, 89, 199, 171, 191, 210, 236, 110, 42, 185, 202, 220, 21, 27, 132, 123, 221, 137, 90, 77, 13, 23, 43, 12, 230, 93, 63, 221, 116] + I/flutter ( 4474): Block Hash: [149, 44, 250, 119, 111, 104, 82, 98, 17, 89, 30, 190, 25, 44, 218, 118, 127, 189, 241, 35, 213, 106, 25, 95, 195, 37, 55, 131, 185, 180, 246, 200] + I/flutter ( 4474): Block Number: 17120242 + I/flutter ( 4474): Gas Used: 21000 + */ + + // Wait for the transaction receipt to become available + TransactionInformation? transactionInformation; + while (transactionInformation == null) { + log("********************************"); + transactionInformation = await _client!.getTransactionByHash(transactionHash); + await Future.delayed(const Duration(seconds: 1)); + } + // Print the receipt information + log('Transaction Hash: ${transactionInformation.hash}'); + log('Block Hash: ${transactionInformation.blockHash}'); + log('Block Number: ${transactionInformation.blockNumber}'); + log('Gas Used: ${transactionInformation.gas}'); + + /* + Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74 + I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8 + I/flutter ( 4474): Block Number: 17120242 + I/flutter ( 4474): Gas Used: 53000 + */ + } + + Future fetchERC20Balances( + EthereumAddress userAddress, String contractAddress) async { + final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); + final balance = await erc20.balanceOf(userAddress); + + int exponent = (await erc20.decimals()).toInt(); + + return EVMChainERC20Balance(balance, exponent: exponent); + } + + Future getErc20Token(String contractAddress) async { + try { + final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); + final name = await erc20.name(); + final symbol = await erc20.symbol(); + final decimal = await erc20.decimals(); + + return Erc20Token( + name: name, + symbol: symbol, + contractAddress: contractAddress, + decimal: decimal.toInt(), + ); + } catch (e) { + return null; + } + } + + void stop() { + _client?.dispose(); + } + + Web3Client? getWeb3Client() { + return _client; + } + +// Future _getDecimalPlacesForContract(DeployedContract contract) async { +// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json"); +// final contractAbi = ContractAbi.fromJson(abi, "ERC20"); +// +// final contract = DeployedContract( +// contractAbi, +// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!), +// ); +// final decimalsFunction = contract.function('decimals'); +// final decimals = await _client!.call( +// contract: contract, +// function: decimalsFunction, +// params: [], +// ); +// +// int exponent = int.parse(decimals.first.toString()); +// return exponent; +// } +} diff --git a/cw_evm/lib/evm_chain_exceptions.dart b/cw_evm/lib/evm_chain_exceptions.dart new file mode 100644 index 000000000..1c09ecf6d --- /dev/null +++ b/cw_evm/lib/evm_chain_exceptions.dart @@ -0,0 +1,11 @@ +import 'package:cw_core/crypto_currency.dart'; + +class EVMChainTransactionCreationException implements Exception { + final String exceptionMessage; + + EVMChainTransactionCreationException(CryptoCurrency currency) + : exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.'; + + @override + String toString() => exceptionMessage; +} diff --git a/cw_evm/lib/evm_chain_formatter.dart b/cw_evm/lib/evm_chain_formatter.dart new file mode 100644 index 000000000..cb9b7346c --- /dev/null +++ b/cw_evm/lib/evm_chain_formatter.dart @@ -0,0 +1,25 @@ +import 'package:intl/intl.dart'; + +const evmChainAmountLength = 12; +const evmChainAmountDivider = 1000000000000; +final evmChainAmountFormat = NumberFormat() + ..maximumFractionDigits = evmChainAmountLength + ..minimumFractionDigits = 1; + +class EVMChainFormatter { + static int parseEVMChainAmount(String amount) { + try { + return (double.parse(amount) * evmChainAmountDivider).round(); + } catch (_) { + return 0; + } + } + + static double parseEVMChainAmountToDouble(int amount) { + try { + return amount / evmChainAmountDivider; + } catch (_) { + return 0; + } + } +} diff --git a/cw_ethereum/lib/ethereum_mnemonics.dart b/cw_evm/lib/evm_chain_mnemonics.dart similarity index 99% rename from cw_ethereum/lib/ethereum_mnemonics.dart rename to cw_evm/lib/evm_chain_mnemonics.dart index 8af7b10f3..55fa4c3a8 100644 --- a/cw_ethereum/lib/ethereum_mnemonics.dart +++ b/cw_evm/lib/evm_chain_mnemonics.dart @@ -1,10 +1,4 @@ -class EthereumMnemonicIsIncorrectException implements Exception { - @override - String toString() => - 'Ethereum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; -} - -class EthereumMnemonics { +class EVMChainMnemonics { static const englishWordlist = [ 'abandon', 'ability', diff --git a/cw_ethereum/lib/ethereum_transaction_credentials.dart b/cw_evm/lib/evm_chain_transaction_credentials.dart similarity index 60% rename from cw_ethereum/lib/ethereum_transaction_credentials.dart rename to cw_evm/lib/evm_chain_transaction_credentials.dart index b015b7141..5b5bdf170 100644 --- a/cw_ethereum/lib/ethereum_transaction_credentials.dart +++ b/cw_evm/lib/evm_chain_transaction_credentials.dart @@ -1,9 +1,9 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/output_info.dart'; -import 'package:cw_ethereum/ethereum_transaction_priority.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; -class EthereumTransactionCredentials { - EthereumTransactionCredentials( +class EVMChainTransactionCredentials { + EVMChainTransactionCredentials( this.outputs, { required this.priority, required this.currency, @@ -11,7 +11,7 @@ class EthereumTransactionCredentials { }); final List outputs; - final EthereumTransactionPriority? priority; + final EVMChainTransactionPriority? priority; final int? feeRate; final CryptoCurrency currency; } diff --git a/cw_evm/lib/evm_chain_transaction_history.dart b/cw_evm/lib/evm_chain_transaction_history.dart new file mode 100644 index 000000000..2f5c31e82 --- /dev/null +++ b/cw_evm/lib/evm_chain_transaction_history.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; +import 'dart:core'; +import 'dart:developer'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/file.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_history.dart'; + +part 'evm_chain_transaction_history.g.dart'; + +abstract class EVMChainTransactionHistory = EVMChainTransactionHistoryBase + with _$EVMChainTransactionHistory; + +abstract class EVMChainTransactionHistoryBase + extends TransactionHistoryBase with Store { + EVMChainTransactionHistoryBase({required this.walletInfo, required String password}) + : _password = password { + transactions = ObservableMap(); + } + + String _password; + + final WalletInfo walletInfo; + + //! Method to be overridden by all child classes + + String getTransactionHistoryFileName(); + + EVMChainTransactionInfo getTransactionInfo(Map val); + + //! Common methods across all child classes + + Future init() async => await _load(); + + @override + Future save() async { + final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName(); + try { + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + String path = '$dirPath/$transactionsHistoryFileNameForWallet'; + final data = json.encode({'transactions': transactions}); + await writeData(path: path, password: _password, data: data); + } catch (e, s) { + log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}'); + log(s.toString()); + } + } + + @override + void addOne(EVMChainTransactionInfo transaction) => transactions[transaction.id] = transaction; + + @override + void addMany(Map transactions) => + this.transactions.addAll(transactions); + + Future> _read() async { + final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName(); + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + String path = '$dirPath/$transactionsHistoryFileNameForWallet'; + final content = await read(path: path, password: _password); + if (content.isEmpty) { + return {}; + } + return json.decode(content) as Map; + } + + Future _load() async { + try { + final content = await _read(); + final txs = content['transactions'] as Map? ?? {}; + + for (var entry in txs.entries) { + final val = entry.value; + + if (val is Map) { + final tx = getTransactionInfo(val); + _update(tx); + } + } + } catch (e) { + log(e.toString()); + } + } + + void _update(EVMChainTransactionInfo transaction) => transactions[transaction.id] = transaction; +} diff --git a/cw_evm/lib/evm_chain_transaction_info.dart b/cw_evm/lib/evm_chain_transaction_info.dart new file mode 100644 index 000000000..329061db2 --- /dev/null +++ b/cw_evm/lib/evm_chain_transaction_info.dart @@ -0,0 +1,77 @@ +// ignore_for_file: overridden_fields, annotate_overrides + +import 'dart:math'; + +import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_info.dart'; + +abstract class EVMChainTransactionInfo extends TransactionInfo { + EVMChainTransactionInfo({ + required this.id, + required this.height, + required this.ethAmount, + required this.ethFee, + required this.tokenSymbol, + this.exponent = 18, + required this.direction, + required this.isPending, + required this.date, + required this.confirmations, + required this.to, + required this.from, + }) : amount = ethAmount.toInt(), + fee = ethFee.toInt(); + + final String id; + final int height; + final int amount; + final BigInt ethAmount; + final int exponent; + final TransactionDirection direction; + final DateTime date; + final bool isPending; + final int fee; + final BigInt ethFee; + final int confirmations; + final String tokenSymbol; + String? _fiatAmount; + final String? to; + final String? from; + + //! Getter to be overridden in child classes + String get feeCurrency; + + @override + String amountFormatted() { + final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString()); + return '${amount.substring(0, min(10, amount.length))} $tokenSymbol'; + } + + @override + String fiatAmount() => _fiatAmount ?? ''; + + @override + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + + @override + String feeFormatted() { + final amount = (ethFee / BigInt.from(10).pow(18)).toString(); + return '${amount.substring(0, min(10, amount.length))} $feeCurrency'; + } + + Map toJson() => { + 'id': id, + 'height': height, + 'amount': ethAmount.toString(), + 'exponent': exponent, + 'fee': ethFee.toString(), + 'direction': direction.index, + 'date': date.millisecondsSinceEpoch, + 'isPending': isPending, + 'confirmations': confirmations, + 'tokenSymbol': tokenSymbol, + 'to': to, + 'from': from, + }; +} diff --git a/cw_ethereum/lib/ethereum_transaction_model.dart b/cw_evm/lib/evm_chain_transaction_model.dart similarity index 81% rename from cw_ethereum/lib/ethereum_transaction_model.dart rename to cw_evm/lib/evm_chain_transaction_model.dart index 3b5f724fc..a328a2d6d 100644 --- a/cw_ethereum/lib/ethereum_transaction_model.dart +++ b/cw_evm/lib/evm_chain_transaction_model.dart @@ -1,5 +1,4 @@ -//! Model used for in parsing transactions fetched using etherscan -class EthereumTransactionModel { +class EVMChainTransactionModel { final DateTime date; final String hash; final String from; @@ -14,7 +13,7 @@ class EthereumTransactionModel { final int? tokenDecimal; final bool isError; - EthereumTransactionModel({ + EVMChainTransactionModel({ required this.date, required this.hash, required this.from, @@ -30,7 +29,8 @@ class EthereumTransactionModel { required this.isError, }); - factory EthereumTransactionModel.fromJson(Map json) => EthereumTransactionModel( + factory EVMChainTransactionModel.fromJson(Map json, String defaultSymbol) => + EVMChainTransactionModel( date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000), hash: json["hash"], from: json["from"], @@ -41,7 +41,7 @@ class EthereumTransactionModel { contractAddress: json["contractAddress"], confirmations: int.parse(json["confirmations"]), blockNumber: int.parse(json["blockNumber"]), - tokenSymbol: json["tokenSymbol"] ?? "ETH", + tokenSymbol: json["tokenSymbol"] ?? defaultSymbol, tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""), isError: json["isError"] == "1", ); diff --git a/cw_evm/lib/evm_chain_transaction_priority.dart b/cw_evm/lib/evm_chain_transaction_priority.dart new file mode 100644 index 000000000..b4ce55490 --- /dev/null +++ b/cw_evm/lib/evm_chain_transaction_priority.dart @@ -0,0 +1,52 @@ +import 'package:cw_core/transaction_priority.dart'; + +class EVMChainTransactionPriority extends TransactionPriority { + final int tip; + + const EVMChainTransactionPriority({required String title, required int raw, required this.tip}) + : super(title: title, raw: raw); + + static const List all = [fast, medium, slow]; + static const EVMChainTransactionPriority slow = + EVMChainTransactionPriority(title: 'slow', raw: 0, tip: 1); + static const EVMChainTransactionPriority medium = + EVMChainTransactionPriority(title: 'Medium', raw: 1, tip: 2); + static const EVMChainTransactionPriority fast = + EVMChainTransactionPriority(title: 'Fast', raw: 2, tip: 4); + + static EVMChainTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + throw Exception('Unexpected token: $raw for EVMChainTransactionPriority deserialize'); + } + } + + String get units => 'gas'; + + @override + String toString() { + var label = ''; + + switch (this) { + case EVMChainTransactionPriority.slow: + label = 'Slow'; + break; + case EVMChainTransactionPriority.medium: + label = 'Medium'; + break; + case EVMChainTransactionPriority.fast: + label = 'Fast'; + break; + default: + break; + } + + return label; + } +} diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart new file mode 100644 index 000000000..ea19a8557 --- /dev/null +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -0,0 +1,512 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip39; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_evm/evm_chain_client.dart'; +import 'package:cw_evm/evm_chain_exceptions.dart'; +import 'package:cw_evm/evm_chain_formatter.dart'; +import 'package:cw_evm/evm_chain_transaction_credentials.dart'; +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:cw_evm/evm_chain_wallet_addresses.dart'; +import 'package:cw_evm/file.dart'; +import 'package:hex/hex.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:web3dart/crypto.dart'; +import 'package:web3dart/web3dart.dart'; + +import 'evm_chain_transaction_info.dart'; +import 'evm_erc20_balance.dart'; + +part 'evm_chain_wallet.g.dart'; + +abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet; + +abstract class EVMChainWalletBase + extends WalletBase + with Store { + EVMChainWalletBase({ + required WalletInfo walletInfo, + required EVMChainClient client, + required CryptoCurrency nativeCurrency, + String? mnemonic, + String? privateKey, + required String password, + EVMChainERC20Balance? initialBalance, + }) : syncStatus = const NotConnectedSyncStatus(), + _password = password, + _mnemonic = mnemonic, + _hexPrivateKey = privateKey, + _isTransactionUpdating = false, + _client = client, + walletAddresses = EVMChainWalletAddresses(walletInfo), + balance = ObservableMap.of( + { + // Not sure of this yet, will it work? will it not? + nativeCurrency: initialBalance ?? EVMChainERC20Balance(BigInt.zero), + }, + ), + super(walletInfo) { + this.walletInfo = walletInfo; + transactionHistory = setUpTransactionHistory(walletInfo, password); + + if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) { + CakeHive.registerAdapter(Erc20TokenAdapter()); + } + + sharedPrefs.complete(SharedPreferences.getInstance()); + } + + final String? _mnemonic; + final String? _hexPrivateKey; + final String _password; + + late final Box erc20TokensBox; + + late final Box evmChainErc20TokensBox; + + late final EthPrivateKey _evmChainPrivateKey; + + EthPrivateKey get evmChainPrivateKey => _evmChainPrivateKey; + + late EVMChainClient _client; + + int? _gasPrice; + int? _estimatedGas; + bool _isTransactionUpdating; + + // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter + Timer? _transactionsUpdateTimer; + + @override + WalletAddresses walletAddresses; + + @override + @observable + SyncStatus syncStatus; + + @override + @observable + late ObservableMap balance; + + Completer sharedPrefs = Completer(); + + //! Methods to be overridden by every child + + void addInitialTokens(); + + // Future open({ + // required String name, + // required String password, + // required WalletInfo walletInfo, + // }); + + Future initErc20TokensBox(); + + String getTransactionHistoryFileName(); + + Future checkIfScanProviderIsEnabled(); + + EVMChainTransactionInfo getTransactionInfo( + EVMChainTransactionModel transactionModel, String address); + + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath); + + EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password); + + //! Common Methods across child classes + + String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name'; + + Future init() async { + await initErc20TokensBox(); + + await walletAddresses.init(); + await transactionHistory.init(); + _evmChainPrivateKey = await getPrivateKey( + mnemonic: _mnemonic, + privateKey: _hexPrivateKey, + password: _password, + ); + walletAddresses.address = _evmChainPrivateKey.address.toString(); + await save(); + } + + @override + int calculateEstimatedFee(TransactionPriority priority, int? amount) { + try { + if (priority is EVMChainTransactionPriority) { + final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); + } + + return 0; + } catch (e) { + return 0; + } + } + + @override + Future changePassword(String password) { + throw UnimplementedError("changePassword"); + } + + @override + void close() { + _client.stop(); + _transactionsUpdateTimer?.cancel(); + } + + @action + @override + Future connectToNode({required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + + final isConnected = _client.connect(node); + + if (!isConnected) { + throw Exception("${walletInfo.type.name.toUpperCase()} Node connection failed"); + } + + _client.setListeners(_evmChainPrivateKey.address, _onNewTransaction); + + _setTransactionUpdateTimer(); + + syncStatus = ConnectedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } + } + + @action + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + await _updateBalance(); + await _updateTransactions(); + _gasPrice = await _client.getGasUnitPrice(); + _estimatedGas = await _client.getEstimatedGas(); + + Timer.periodic( + const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice()); + Timer.periodic(const Duration(seconds: 10), + (timer) async => _estimatedGas = await _client.getEstimatedGas()); + + syncStatus = SyncedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } + } + + @override + Future createTransaction(Object credentials) async { + final _credentials = credentials as EVMChainTransactionCredentials; + final outputs = _credentials.outputs; + final hasMultiDestination = outputs.length > 1; + + final CryptoCurrency transactionCurrency = + balance.keys.firstWhere((element) => element.title == _credentials.currency.title); + + final _erc20Balance = balance[transactionCurrency]!; + BigInt totalAmount = BigInt.zero; + int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; + num amountToEVMChainMultiplier = pow(10, exponent); + + // so far this can not be made with Ethereum as Ethereum does not support multiple recipients + if (hasMultiDestination) { + if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { + throw EVMChainTransactionCreationException(transactionCurrency); + } + + final totalOriginalAmount = EVMChainFormatter.parseEVMChainAmountToDouble( + outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); + totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); + + if (_erc20Balance.balance < totalAmount) { + throw EVMChainTransactionCreationException(transactionCurrency); + } + } else { + final output = outputs.first; + // since the fees are taken from Ethereum + // then no need to subtract the fees from the amount if send all + final BigInt allAmount; + if (transactionCurrency is Erc20Token) { + allAmount = _erc20Balance.balance; + } else { + allAmount = _erc20Balance.balance - + BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); + } + final totalOriginalAmount = + EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0); + totalAmount = output.sendAll + ? allAmount + : BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); + + if (_erc20Balance.balance < totalAmount) { + throw EVMChainTransactionCreationException(transactionCurrency); + } + } + + final pendingEVMChainTransaction = await _client.signTransaction( + privateKey: _evmChainPrivateKey, + toAddress: _credentials.outputs.first.isParsedAddress + ? _credentials.outputs.first.extractedAddress! + : _credentials.outputs.first.address, + amount: totalAmount.toString(), + gas: _estimatedGas!, + priority: _credentials.priority!, + currency: transactionCurrency, + exponent: exponent, + contractAddress: + transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, + ); + + return pendingEVMChainTransaction; + } + + Future _updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + + final isProviderEnabled = await checkIfScanProviderIsEnabled(); + + if (!isProviderEnabled) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (_) { + _isTransactionUpdating = false; + } + } + + @override + Future> fetchTransactions() async { + final address = _evmChainPrivateKey.address.hex; + final transactions = await _client.fetchTransactions(address); + + final List>> erc20TokensTransactions = []; + + for (var token in balance.keys) { + if (token is Erc20Token) { + erc20TokensTransactions.add(_client.fetchTransactions( + address, + contractAddress: token.contractAddress, + )); + } + } + + final tokensTransaction = await Future.wait(erc20TokensTransactions); + transactions.addAll(tokensTransaction.expand((element) => element)); + + final Map result = {}; + + for (var transactionModel in transactions) { + if (transactionModel.isError) { + continue; + } + + result[transactionModel.hash] = getTransactionInfo(transactionModel, address); + } + + return result; + } + + @override + Object get keys => throw UnimplementedError("keys"); + + @override + Future rescan({required int height}) { + throw UnimplementedError("rescan"); + } + + @override + Future save() async { + await walletAddresses.updateAddressesInBox(); + final path = await makePath(); + await write(path: path, password: _password, data: toJSON()); + await transactionHistory.save(); + } + + @override + String? get seed => _mnemonic; + + @override + String get privateKey => HEX.encode(_evmChainPrivateKey.privateKey); + + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + + String toJSON() => json.encode({ + 'mnemonic': _mnemonic, + 'private_key': privateKey, + 'balance': balance[currency]!.toJSON(), + }); + + Future _updateBalance() async { + balance[currency] = await _fetchEVMChainBalance(); + + await _fetchErc20Balances(); + await save(); + } + + Future _fetchEVMChainBalance() async { + final balance = await _client.getBalance(_evmChainPrivateKey.address); + return EVMChainERC20Balance(balance.getInWei); + } + + Future _fetchErc20Balances() async { + for (var token in evmChainErc20TokensBox.values) { + try { + if (token.enabled) { + balance[token] = await _client.fetchERC20Balances( + _evmChainPrivateKey.address, + token.contractAddress, + ); + } else { + balance.remove(token); + } + } catch (_) {} + } + } + + Future getPrivateKey( + {String? mnemonic, String? privateKey, required String password}) async { + assert(mnemonic != null || privateKey != null); + + if (privateKey != null) { + return EthPrivateKey.fromHex(privateKey); + } + + final seed = bip39.mnemonicToSeed(mnemonic!); + + final root = bip32.BIP32.fromSeed(seed); + + const hdPathEVMChain = "m/44'/60'/0'/0"; + const index = 0; + final addressAtIndex = root.derivePath("$hdPathEVMChain/$index"); + + return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List)); + } + + Future? updateBalance() async => await _updateBalance(); + + List get erc20Currencies => evmChainErc20TokensBox.values.toList(); + + Future addErc20Token(Erc20Token token) async { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + + final newToken = createNewErc20TokenObject(token, iconPath); + + await evmChainErc20TokensBox.put(newToken.contractAddress, newToken); + + if (newToken.enabled) { + balance[newToken] = await _client.fetchERC20Balances( + _evmChainPrivateKey.address, + newToken.contractAddress, + ); + } else { + balance.remove(newToken); + } + } + + Future deleteErc20Token(Erc20Token token) async { + await token.delete(); + + balance.remove(token); + _updateBalance(); + } + + Future getErc20Token(String contractAddress) async => + await _client.getErc20Token(contractAddress); + + void _onNewTransaction() { + _updateBalance(); + _updateTransactions(); + } + + @override + Future renameWalletFiles(String newWalletName) async { + final transactionHistoryFileNameForWallet = getTransactionHistoryFileName(); + + final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); + final currentWalletFile = File(currentWalletPath); + + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type); + final currentTransactionsFile = File('$currentDirPath/$transactionHistoryFileNameForWallet'); + + // Copies current wallet files into new wallet name's dir and files + if (currentWalletFile.existsSync()) { + final newWalletPath = await pathForWallet(name: newWalletName, type: type); + await currentWalletFile.copy(newWalletPath); + } + if (currentTransactionsFile.existsSync()) { + final newDirPath = await pathForWalletDir(name: newWalletName, type: type); + await currentTransactionsFile.copy('$newDirPath/$transactionHistoryFileNameForWallet'); + } + + // Delete old name's dir and files + await Directory(currentDirPath).delete(recursive: true); + } + + void _setTransactionUpdateTimer() { + if (_transactionsUpdateTimer?.isActive ?? false) { + _transactionsUpdateTimer!.cancel(); + } + + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) { + _updateTransactions(); + _updateBalance(); + }); + } + + /// Scan Providers: + /// + /// EtherScan for Ethereum. + /// + /// PolygonScan for Polygon. + void updateScanProviderUsageState(bool isEnabled) { + if (isEnabled) { + _updateTransactions(); + _setTransactionUpdateTimer(); + } else { + _transactionsUpdateTimer?.cancel(); + } + } + + @override + String signMessage(String message, {String? address}) => + bytesToHex(_evmChainPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); + + Web3Client? getWeb3Client() => _client.getWeb3Client(); +} diff --git a/cw_ethereum/lib/ethereum_wallet_addresses.dart b/cw_evm/lib/evm_chain_wallet_addresses.dart similarity index 63% rename from cw_ethereum/lib/ethereum_wallet_addresses.dart rename to cw_evm/lib/evm_chain_wallet_addresses.dart index 4a3492e6f..d5d39f21d 100644 --- a/cw_ethereum/lib/ethereum_wallet_addresses.dart +++ b/cw_evm/lib/evm_chain_wallet_addresses.dart @@ -1,13 +1,15 @@ +import 'dart:developer'; + import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; -part 'ethereum_wallet_addresses.g.dart'; +part 'evm_chain_wallet_addresses.g.dart'; -class EthereumWalletAddresses = EthereumWalletAddressesBase with _$EthereumWalletAddresses; +class EVMChainWalletAddresses = EVMChainWalletAddressesBase with _$EVMChainWalletAddresses; -abstract class EthereumWalletAddressesBase extends WalletAddresses with Store { - EthereumWalletAddressesBase(WalletInfo walletInfo) +abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store { + EVMChainWalletAddressesBase(WalletInfo walletInfo) : address = '', super(walletInfo); @@ -27,7 +29,7 @@ abstract class EthereumWalletAddressesBase extends WalletAddresses with Store { addressesMap[address] = ''; await saveAddressesInBox(); } catch (e) { - print(e.toString()); + log(e.toString()); } } } diff --git a/cw_evm/lib/evm_chain_wallet_creation_credentials.dart b/cw_evm/lib/evm_chain_wallet_creation_credentials.dart new file mode 100644 index 000000000..7c3271daf --- /dev/null +++ b/cw_evm/lib/evm_chain_wallet_creation_credentials.dart @@ -0,0 +1,29 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; + +class EVMChainNewWalletCredentials extends WalletCredentials { + EVMChainNewWalletCredentials({required String name, WalletInfo? walletInfo}) + : super(name: name, walletInfo: walletInfo); +} + +class EVMChainRestoreWalletFromSeedCredentials extends WalletCredentials { + EVMChainRestoreWalletFromSeedCredentials({ + required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo, + }) : super(name: name, password: password, walletInfo: walletInfo); + + final String mnemonic; +} + +class EVMChainRestoreWalletFromPrivateKey extends WalletCredentials { + EVMChainRestoreWalletFromPrivateKey({ + required String name, + required String password, + required this.privateKey, + WalletInfo? walletInfo, + }) : super(name: name, password: password, walletInfo: walletInfo); + + final String privateKey; +} diff --git a/cw_evm/lib/evm_chain_wallet_service.dart b/cw_evm/lib/evm_chain_wallet_service.dart new file mode 100644 index 000000000..988a38684 --- /dev/null +++ b/cw_evm/lib/evm_chain_wallet_service.dart @@ -0,0 +1,50 @@ +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; +import 'package:hive/hive.dart'; + +abstract class EVMChainWalletService extends WalletService< + EVMChainNewWalletCredentials, + EVMChainRestoreWalletFromSeedCredentials, + EVMChainRestoreWalletFromPrivateKey> { + EVMChainWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + @override + WalletType getType(); + + @override + Future create(EVMChainNewWalletCredentials credentials); + + @override + Future openWallet(String name, String password); + + @override + Future rename(String currentName, String password, String newName); + + @override + Future restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials); + + @override + Future restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials); + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + } +} diff --git a/cw_ethereum/lib/erc20_balance.dart b/cw_evm/lib/evm_erc20_balance.dart similarity index 76% rename from cw_ethereum/lib/erc20_balance.dart rename to cw_evm/lib/evm_erc20_balance.dart index 7d11f8e45..1727d7962 100644 --- a/cw_ethereum/lib/erc20_balance.dart +++ b/cw_evm/lib/evm_erc20_balance.dart @@ -3,10 +3,9 @@ import 'dart:math'; import 'package:cw_core/balance.dart'; -class ERC20Balance extends Balance { - ERC20Balance(this.balance, {this.exponent = 18}) - : super(balance.toInt(), - balance.toInt()); +class EVMChainERC20Balance extends Balance { + EVMChainERC20Balance(this.balance, {this.exponent = 18}) + : super(balance.toInt(), balance.toInt()); final BigInt balance; final int exponent; @@ -28,7 +27,7 @@ class ERC20Balance extends Balance { 'exponent': exponent, }); - static ERC20Balance? fromJSON(String? jsonSource) { + static EVMChainERC20Balance? fromJSON(String? jsonSource) { if (jsonSource == null) { return null; } @@ -36,12 +35,12 @@ class ERC20Balance extends Balance { final decoded = json.decode(jsonSource) as Map; try { - return ERC20Balance( + return EVMChainERC20Balance( BigInt.parse(decoded['balanceInWei']), exponent: decoded['exponent'], ); } catch (e) { - return ERC20Balance(BigInt.zero); + return EVMChainERC20Balance(BigInt.zero); } } } diff --git a/cw_ethereum/lib/file.dart b/cw_evm/lib/file.dart similarity index 100% rename from cw_ethereum/lib/file.dart rename to cw_evm/lib/file.dart diff --git a/cw_ethereum/lib/pending_ethereum_transaction.dart b/cw_evm/lib/pending_evm_chain_transaction.dart similarity index 91% rename from cw_ethereum/lib/pending_ethereum_transaction.dart rename to cw_evm/lib/pending_evm_chain_transaction.dart index d47630fd6..8129de728 100644 --- a/cw_ethereum/lib/pending_ethereum_transaction.dart +++ b/cw_evm/lib/pending_evm_chain_transaction.dart @@ -4,14 +4,14 @@ import 'dart:typed_data'; import 'package:cw_core/pending_transaction.dart'; import 'package:web3dart/crypto.dart'; -class PendingEthereumTransaction with PendingTransaction { +class PendingEVMChainTransaction with PendingTransaction { final Function sendTransaction; final Uint8List signedTransaction; final BigInt fee; final String amount; final int exponent; - PendingEthereumTransaction({ + PendingEVMChainTransaction({ required this.sendTransaction, required this.signedTransaction, required this.fee, diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml new file mode 100644 index 000000000..c202cc72a --- /dev/null +++ b/cw_evm/pubspec.yaml @@ -0,0 +1,45 @@ +name: cw_evm +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: '>=3.0.6 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + web3dart: ^2.7.1 + erc20: ^1.0.1 + bip39: ^1.0.6 + bip32: ^2.0.0 + hex: ^0.2.0 + http: ^1.1.0 + hive: ^2.2.3 + collection: ^1.17.1 + shared_preferences: ^2.0.15 + cw_core: + path: ../cw_core + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.1.11 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 + flutter_lints: ^2.0.0 + +flutter: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic diff --git a/cw_evm/test/cw_evm_test.dart b/cw_evm/test/cw_evm_test.dart new file mode 100644 index 000000000..6a4dea276 --- /dev/null +++ b/cw_evm/test/cw_evm_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_evm/cw_evm.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/cw_polygon/lib/pending_polygon_transaction.dart b/cw_polygon/lib/pending_polygon_transaction.dart deleted file mode 100644 index 50f1f0638..000000000 --- a/cw_polygon/lib/pending_polygon_transaction.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'dart:typed_data'; - -import 'package:cw_ethereum/pending_ethereum_transaction.dart'; - -class PendingPolygonTransaction extends PendingEthereumTransaction { - PendingPolygonTransaction({ - required Function sendTransaction, - required Uint8List signedTransaction, - required BigInt fee, - required String amount, - required int exponent, - }) : super( - amount: amount, - sendTransaction: sendTransaction, - signedTransaction: signedTransaction, - fee: fee, - exponent: exponent, - ); -} diff --git a/cw_polygon/lib/polygon_client.dart b/cw_polygon/lib/polygon_client.dart index 876f4c60d..055b42f87 100644 --- a/cw_polygon/lib/polygon_client.dart +++ b/cw_polygon/lib/polygon_client.dart @@ -1,12 +1,12 @@ import 'dart:convert'; -import 'package:cw_ethereum/ethereum_client.dart'; -import 'package:cw_polygon/polygon_transaction_model.dart'; -import 'package:cw_ethereum/.secrets.g.dart' as secrets; +import 'package:cw_evm/evm_chain_client.dart'; +import 'package:cw_evm/.secrets.g.dart' as secrets; +import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:flutter/foundation.dart'; import 'package:web3dart/web3dart.dart'; -class PolygonClient extends EthereumClient { +class PolygonClient extends EVMChainClient { @override Transaction createTransaction({ required EthereumAddress from, @@ -28,7 +28,7 @@ class PolygonClient extends EthereumClient { int get chainId => 137; @override - Future> fetchTransactions(String address, + Future> fetchTransactions(String address, {String? contractAddress}) async { try { final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", { @@ -43,7 +43,9 @@ class PolygonClient extends EthereumClient { if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { return (jsonResponse['result'] as List) - .map((e) => PolygonTransactionModel.fromJson(e as Map)) + .map( + (e) => EVMChainTransactionModel.fromJson(e as Map, 'MATIC'), + ) .toList(); } diff --git a/cw_polygon/lib/polygon_exceptions.dart b/cw_polygon/lib/polygon_exceptions.dart deleted file mode 100644 index 2d08106b6..000000000 --- a/cw_polygon/lib/polygon_exceptions.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_ethereum/ethereum_exceptions.dart'; - -class PolygonTransactionCreationException extends EthereumTransactionCreationException { - PolygonTransactionCreationException(CryptoCurrency currency) : super(currency); -} diff --git a/cw_polygon/lib/polygon_formatter.dart b/cw_polygon/lib/polygon_formatter.dart deleted file mode 100644 index f016db7ab..000000000 --- a/cw_polygon/lib/polygon_formatter.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:intl/intl.dart'; - -const polygonAmountLength = 12; -const polygonAmountDivider = 1000000000000; -final polygonAmountFormat = NumberFormat() - ..maximumFractionDigits = polygonAmountLength - ..minimumFractionDigits = 1; - -class PolygonFormatter { - static int parsePolygonAmount(String amount) { - try { - return (double.parse(amount) * polygonAmountDivider).round(); - } catch (_) { - return 0; - } - } - - static double parsePolygonAmountToDouble(int amount) { - try { - return amount / polygonAmountDivider; - } catch (_) { - return 0; - } - } -} diff --git a/cw_polygon/lib/polygon_transaction_credentials.dart b/cw_polygon/lib/polygon_transaction_credentials.dart deleted file mode 100644 index 6611e15da..000000000 --- a/cw_polygon/lib/polygon_transaction_credentials.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/output_info.dart'; -import 'package:cw_ethereum/ethereum_transaction_credentials.dart'; -import 'package:cw_polygon/polygon_transaction_priority.dart'; - -class PolygonTransactionCredentials extends EthereumTransactionCredentials { - PolygonTransactionCredentials( - List outputs, { - required PolygonTransactionPriority? priority, - required CryptoCurrency currency, - final int? feeRate, - }) : super( - outputs, - currency: currency, - priority: priority, - feeRate: feeRate, - ); -} diff --git a/cw_polygon/lib/polygon_transaction_history.dart b/cw_polygon/lib/polygon_transaction_history.dart index a06b8be4a..8674882cd 100644 --- a/cw_polygon/lib/polygon_transaction_history.dart +++ b/cw_polygon/lib/polygon_transaction_history.dart @@ -1,77 +1,19 @@ -import 'dart:convert'; import 'dart:core'; -import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_ethereum/file.dart'; + +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_polygon/polygon_transaction_info.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cw_core/transaction_history.dart'; -part 'polygon_transaction_history.g.dart'; - -const transactionsHistoryFileName = 'polygon_transactions.json'; - -class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory; - -abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase - with Store { - PolygonTransactionHistoryBase({required this.walletInfo, required String password}) - : _password = password { - transactions = ObservableMap(); - } - - final WalletInfo walletInfo; - String _password; - - Future init() async => await _load(); +class PolygonTransactionHistory extends EVMChainTransactionHistory { + PolygonTransactionHistory({ + required super.walletInfo, + required super.password, + }); @override - Future save() async { - try { - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); - final path = '$dirPath/$transactionsHistoryFileName'; - final data = json.encode({'transactions': transactions}); - await writeData(path: path, password: _password, data: data); - } catch (e, s) { - print('Error while saving polygon transaction history: ${e.toString()}'); - print(s); - } - } + String getTransactionHistoryFileName() => 'polygon_transactions.json'; @override - void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction; - - @override - void addMany(Map transactions) => - this.transactions.addAll(transactions); - - Future> _read() async { - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); - final path = '$dirPath/$transactionsHistoryFileName'; - final content = await read(path: path, password: _password); - if (content.isEmpty) { - return {}; - } - return json.decode(content) as Map; - } - - Future _load() async { - try { - final content = await _read(); - final txs = content['transactions'] as Map? ?? {}; - - txs.entries.forEach((entry) { - final val = entry.value; - - if (val is Map) { - final tx = PolygonTransactionInfo.fromJson(val); - _update(tx); - } - }); - } catch (e) { - print(e); - } - } - - void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction; + EVMChainTransactionInfo getTransactionInfo(Map val) => + PolygonTransactionInfo.fromJson(val); } diff --git a/cw_polygon/lib/polygon_transaction_info.dart b/cw_polygon/lib/polygon_transaction_info.dart index f1976a601..1fbe1c5d4 100644 --- a/cw_polygon/lib/polygon_transaction_info.dart +++ b/cw_polygon/lib/polygon_transaction_info.dart @@ -1,32 +1,21 @@ import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_ethereum/ethereum_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; -class PolygonTransactionInfo extends EthereumTransactionInfo { +class PolygonTransactionInfo extends EVMChainTransactionInfo { PolygonTransactionInfo({ - required String id, - required int height, - required BigInt ethAmount, - int exponent = 18, - required TransactionDirection direction, - required DateTime date, - required bool isPending, - required BigInt ethFee, - required int confirmations, - String tokenSymbol = "MATIC", - required String? to, - }) : super( - confirmations: confirmations, - id: id, - height: height, - ethAmount: ethAmount, - exponent: exponent, - direction: direction, - date: date, - isPending: isPending, - ethFee: ethFee, - to: to, - tokenSymbol: tokenSymbol, - ); + required super.id, + required super.height, + required super.ethAmount, + required super.ethFee, + required super.tokenSymbol, + required super.direction, + required super.isPending, + required super.date, + required super.confirmations, + required super.to, + required super.from, + super.exponent, + }); factory PolygonTransactionInfo.fromJson(Map data) { return PolygonTransactionInfo( @@ -41,9 +30,10 @@ class PolygonTransactionInfo extends EthereumTransactionInfo { confirmations: data['confirmations'] as int, tokenSymbol: data['tokenSymbol'] as String, to: data['to'], + from: data['from'], ); } @override - String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC'; + String get feeCurrency => 'MATIC'; } diff --git a/cw_polygon/lib/polygon_transaction_model.dart b/cw_polygon/lib/polygon_transaction_model.dart deleted file mode 100644 index 704d674e5..000000000 --- a/cw_polygon/lib/polygon_transaction_model.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:cw_ethereum/ethereum_transaction_model.dart'; - -class PolygonTransactionModel extends EthereumTransactionModel { - PolygonTransactionModel({ - required DateTime date, - required String hash, - required String from, - required String to, - required BigInt amount, - required int gasUsed, - required BigInt gasPrice, - required String contractAddress, - required int confirmations, - required int blockNumber, - required String? tokenSymbol, - required int? tokenDecimal, - required bool isError, - }) : super( - amount: amount, - date: date, - hash: hash, - from: from, - to: to, - gasPrice: gasPrice, - gasUsed: gasUsed, - confirmations: confirmations, - contractAddress: contractAddress, - blockNumber: blockNumber, - tokenDecimal: tokenDecimal, - tokenSymbol: tokenSymbol, - isError: isError, - ); - - factory PolygonTransactionModel.fromJson(Map json) => PolygonTransactionModel( - date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000), - hash: json["hash"], - from: json["from"], - to: json["to"], - amount: BigInt.parse(json["value"]), - gasUsed: int.parse(json["gasUsed"]), - gasPrice: BigInt.parse(json["gasPrice"]), - contractAddress: json["contractAddress"], - confirmations: int.parse(json["confirmations"]), - blockNumber: int.parse(json["blockNumber"]), - tokenSymbol: json["tokenSymbol"] ?? "MATIC", - tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""), - isError: json["isError"] == "1", - ); -} diff --git a/cw_polygon/lib/polygon_transaction_priority.dart b/cw_polygon/lib/polygon_transaction_priority.dart deleted file mode 100644 index dba1dab55..000000000 --- a/cw_polygon/lib/polygon_transaction_priority.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:cw_ethereum/ethereum_transaction_priority.dart'; - -class PolygonTransactionPriority extends EthereumTransactionPriority { - const PolygonTransactionPriority({required String title, required int raw, required int tip}) - : super(title: title, raw: raw, tip: tip); - - static const List all = [fast, medium, slow]; - static const PolygonTransactionPriority slow = - PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1); - static const PolygonTransactionPriority medium = - PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2); - static const PolygonTransactionPriority fast = - PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4); - - static PolygonTransactionPriority deserialize({required int raw}) { - switch (raw) { - case 0: - return slow; - case 1: - return medium; - case 2: - return fast; - default: - throw Exception('Unexpected token: $raw for PolygonTransactionPriority deserialize'); - } - } - - @override - String get units => 'gas'; - - @override - String toString() { - var label = ''; - - switch (this) { - case PolygonTransactionPriority.slow: - label = 'Slow'; - break; - case PolygonTransactionPriority.medium: - label = 'Medium'; - break; - case PolygonTransactionPriority.fast: - label = 'Fast'; - break; - default: - break; - } - - return label; - } -} diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index 13ee13f01..60c7ad2ff 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -1,366 +1,109 @@ -import 'dart:async'; import 'dart:convert'; -import 'dart:io'; -import 'dart:math'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/cake_hive.dart'; -import 'package:cw_core/node.dart'; -import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_core/sync_status.dart'; -import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/wallet_addresses.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_ethereum/erc20_balance.dart'; -import 'package:cw_ethereum/ethereum_formatter.dart'; -import 'package:cw_ethereum/file.dart'; import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/evm_erc20_balance.dart'; +import 'package:cw_evm/file.dart'; import 'package:cw_polygon/default_polygon_erc20_tokens.dart'; -import 'package:cw_polygon/polygon_client.dart'; -import 'package:cw_polygon/polygon_exceptions.dart'; -import 'package:cw_polygon/polygon_formatter.dart'; -import 'package:cw_polygon/polygon_transaction_credentials.dart'; -import 'package:cw_polygon/polygon_transaction_history.dart'; import 'package:cw_polygon/polygon_transaction_info.dart'; -import 'package:cw_polygon/polygon_transaction_model.dart'; -import 'package:cw_polygon/polygon_transaction_priority.dart'; -import 'package:cw_polygon/polygon_wallet_addresses.dart'; -import 'package:hive/hive.dart'; -import 'package:hex/hex.dart'; -import 'package:mobx/mobx.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:web3dart/crypto.dart'; -import 'package:web3dart/web3dart.dart'; -import 'package:bip39/bip39.dart' as bip39; -import 'package:bip32/bip32.dart' as bip32; +import 'package:cw_polygon/polygon_client.dart'; +import 'package:cw_polygon/polygon_transaction_history.dart'; -part 'polygon_wallet.g.dart'; - -class PolygonWallet = PolygonWalletBase with _$PolygonWallet; - -abstract class PolygonWalletBase - extends WalletBase with Store { - PolygonWalletBase({ - required WalletInfo walletInfo, - String? mnemonic, - String? privateKey, - required String password, - ERC20Balance? initialBalance, - }) : syncStatus = const NotConnectedSyncStatus(), - _password = password, - _mnemonic = mnemonic, - _hexPrivateKey = privateKey, - _isTransactionUpdating = false, - _client = PolygonClient(), - walletAddresses = PolygonWalletAddresses(walletInfo), - balance = ObservableMap.of( - {CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero)}), - super(walletInfo) { - this.walletInfo = walletInfo; - transactionHistory = PolygonTransactionHistory(walletInfo: walletInfo, password: password); - - if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) { - CakeHive.registerAdapter(Erc20TokenAdapter()); - } - - _sharedPrefs.complete(SharedPreferences.getInstance()); - } - - final String? _mnemonic; - final String? _hexPrivateKey; - final String _password; - - late final Box polygonErc20TokensBox; - - late final EthPrivateKey _polygonPrivateKey; - - late final PolygonClient _client; - - EthPrivateKey get polygonPrivateKey => _polygonPrivateKey; - - int? _gasPrice; - int? _estimatedGas; - bool _isTransactionUpdating; - - // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter - Timer? _transactionsUpdateTimer; +class PolygonWallet extends EVMChainWallet { + PolygonWallet({ + required super.walletInfo, + required super.password, + super.mnemonic, + super.initialBalance, + super.privateKey, + required super.client, + }) : super(nativeCurrency: CryptoCurrency.maticpoly); @override - WalletAddresses walletAddresses; - - @override - @observable - SyncStatus syncStatus; - - @override - @observable - late ObservableMap balance; - - final Completer _sharedPrefs = Completer(); - - Future init() async { + Future initErc20TokensBox() async { final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}"; if (await CakeHive.boxExists(boxName)) { - polygonErc20TokensBox = await CakeHive.openBox(boxName); + evmChainErc20TokensBox = await CakeHive.openBox(boxName); } else { - polygonErc20TokensBox = await CakeHive.openBox(boxName.replaceAll(" ", "")); + evmChainErc20TokensBox = await CakeHive.openBox(boxName.replaceAll(" ", "")); } - await walletAddresses.init(); - await transactionHistory.init(); - _polygonPrivateKey = await getPrivateKey( - mnemonic: _mnemonic, - privateKey: _hexPrivateKey, - password: _password, + } + + @override + void addInitialTokens() { + final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens; + + for (var token in initialErc20Tokens) { + evmChainErc20TokensBox.put(token.contractAddress, token); + } + } + + @override + Future checkIfScanProviderIsEnabled() async { + bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true; + return isPolygonScanEnabled; + } + + @override + String getTransactionHistoryFileName() => 'polygon_transactions.json'; + + @override + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) { + return Erc20Token( + name: token.name, + symbol: token.symbol, + contractAddress: token.contractAddress, + decimal: token.decimal, + enabled: token.enabled, + tag: token.tag ?? "MATIC", + iconPath: iconPath, ); - walletAddresses.address = _polygonPrivateKey.address.toString(); - await save(); } @override - int calculateEstimatedFee(TransactionPriority priority, int? amount) { - try { - if (priority is PolygonTransactionPriority) { - final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); - return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); - } - - return 0; - } catch (e) { - return 0; - } - } - - @override - Future changePassword(String password) { - throw UnimplementedError("changePassword"); - } - - @override - void close() { - _client.stop(); - _transactionsUpdateTimer?.cancel(); - } - - @action - @override - Future connectToNode({required Node node}) async { - try { - syncStatus = ConnectingSyncStatus(); - - final isConnected = _client.connect(node); - - if (!isConnected) { - throw Exception("Polygon Node connection failed"); - } - - _client.setListeners(_polygonPrivateKey.address, _onNewTransaction); - - _setTransactionUpdateTimer(); - - syncStatus = ConnectedSyncStatus(); - } catch (e) { - syncStatus = FailedSyncStatus(); - } - } - - @override - Future createTransaction(Object credentials) async { - final credentials0 = credentials as PolygonTransactionCredentials; - final outputs = credentials0.outputs; - final hasMultiDestination = outputs.length > 1; - - final CryptoCurrency transactionCurrency = - balance.keys.firstWhere((element) => element.title == credentials0.currency.title); - - final erc20Balance = balance[transactionCurrency]!; - BigInt totalAmount = BigInt.zero; - int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; - num amountToPolygonMultiplier = pow(10, exponent); - - // so far this can not be made with Polygon as Polygon does not support multiple recipients - if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { - throw PolygonTransactionCreationException(transactionCurrency); - } - - final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble( - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); - totalAmount = BigInt.from(totalOriginalAmount * amountToPolygonMultiplier); - - if (erc20Balance.balance < totalAmount) { - throw PolygonTransactionCreationException(transactionCurrency); - } - } else { - final output = outputs.first; - // since the fees are taken from Ethereum - // then no need to subtract the fees from the amount if send all - final BigInt allAmount; - if (transactionCurrency is Erc20Token) { - allAmount = erc20Balance.balance; - } else { - allAmount = - erc20Balance.balance - BigInt.from(calculateEstimatedFee(credentials0.priority!, null)); - } - final totalOriginalAmount = - EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0); - totalAmount = - output.sendAll ? allAmount : BigInt.from(totalOriginalAmount * amountToPolygonMultiplier); - - if (erc20Balance.balance < totalAmount) { - throw PolygonTransactionCreationException(transactionCurrency); - } - } - - final pendingPolygonTransaction = await _client.signTransaction( - privateKey: _polygonPrivateKey, - toAddress: credentials0.outputs.first.isParsedAddress - ? credentials0.outputs.first.extractedAddress! - : credentials0.outputs.first.address, - amount: totalAmount.toString(), - gas: _estimatedGas!, - priority: credentials0.priority!, - currency: transactionCurrency, - exponent: exponent, - contractAddress: - transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, + EVMChainTransactionInfo getTransactionInfo( + EVMChainTransactionModel transactionModel, String address) { + final model = PolygonTransactionInfo( + id: transactionModel.hash, + height: transactionModel.blockNumber, + ethAmount: transactionModel.amount, + direction: transactionModel.from == address + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + isPending: false, + date: transactionModel.date, + confirmations: transactionModel.confirmations, + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, + exponent: transactionModel.tokenDecimal ?? 18, + tokenSymbol: transactionModel.tokenSymbol ?? "MATIC", + to: transactionModel.to, + from: transactionModel.from, ); - - return pendingPolygonTransaction; - } - - Future _updateTransactions() async { - try { - if (_isTransactionUpdating) { - return; - } - bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true; - if (!isPolygonScanEnabled) { - return; - } - - _isTransactionUpdating = true; - final transactions = await fetchTransactions(); - transactionHistory.addMany(transactions); - await transactionHistory.save(); - _isTransactionUpdating = false; - } catch (_) { - _isTransactionUpdating = false; - } + return model; } @override - Future> fetchTransactions() async { - final address = _polygonPrivateKey.address.hex; - final transactions = await _client.fetchTransactions(address); - - final List>> polygonErc20TokensTransactions = []; - - for (var token in balance.keys) { - if (token is Erc20Token) { - polygonErc20TokensTransactions.add( - _client.fetchTransactions( - address, - contractAddress: token.contractAddress, - ), - ); - } - } - - final tokensTransaction = await Future.wait(polygonErc20TokensTransactions); - transactions.addAll(tokensTransaction.expand((element) => element)); - - final Map result = {}; - - for (var transactionModel in transactions) { - if (transactionModel.isError) { - continue; - } - - result[transactionModel.hash] = PolygonTransactionInfo( - id: transactionModel.hash, - height: transactionModel.blockNumber, - ethAmount: transactionModel.amount, - direction: transactionModel.from == address - ? TransactionDirection.outgoing - : TransactionDirection.incoming, - isPending: false, - date: transactionModel.date, - confirmations: transactionModel.confirmations, - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, - exponent: transactionModel.tokenDecimal ?? 18, - tokenSymbol: transactionModel.tokenSymbol ?? "MATIC", - to: transactionModel.to, - ); - } - - return result; + EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password) { + return PolygonTransactionHistory(walletInfo: walletInfo, password: password); } - @override - Object get keys => throw UnimplementedError("keys"); - - @override - Future rescan({required int height}) { - throw UnimplementedError("rescan"); - } - - @override - Future save() async { - await walletAddresses.updateAddressesInBox(); - final path = await makePath(); - await write(path: path, password: _password, data: toJSON()); - await transactionHistory.save(); - } - - @override - String? get seed => _mnemonic; - - @override - String get privateKey => HEX.encode(_polygonPrivateKey.privateKey); - - @action - @override - Future startSync() async { - try { - syncStatus = AttemptingSyncStatus(); - await _updateBalance(); - await _updateTransactions(); - _gasPrice = await _client.getGasUnitPrice(); - _estimatedGas = await _client.getEstimatedGas(); - - Timer.periodic( - const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice()); - Timer.periodic(const Duration(seconds: 10), - (timer) async => _estimatedGas = await _client.getEstimatedGas()); - - syncStatus = SyncedSyncStatus(); - } catch (e) { - syncStatus = FailedSyncStatus(); - } - } - - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - - String toJSON() => json.encode({ - 'mnemonic': _mnemonic, - 'private_key': privateKey, - 'balance': balance[currency]!.toJSON(), - }); - - static Future open({ - required String name, - required String password, - required WalletInfo walletInfo, - }) async { + static Future open( + {required String name, required String password, required WalletInfo walletInfo}) async { final path = await pathForWallet(name: name, type: walletInfo.type); final jsonSource = await read(path: path, password: password); final data = json.decode(jsonSource) as Map; final mnemonic = data['mnemonic'] as String?; final privateKey = data['private_key'] as String?; - final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero); + final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ?? + EVMChainERC20Balance(BigInt.zero); return PolygonWallet( walletInfo: walletInfo, @@ -368,158 +111,7 @@ abstract class PolygonWalletBase mnemonic: mnemonic, privateKey: privateKey, initialBalance: balance, + client: PolygonClient(), ); } - - Future _updateBalance() async { - balance[currency] = await _fetchMaticBalance(); - - await _fetchErc20Balances(); - await save(); - } - - Future _fetchMaticBalance() async { - final balance = await _client.getBalance(_polygonPrivateKey.address); - return ERC20Balance(balance.getInWei); - } - - Future _fetchErc20Balances() async { - for (var token in polygonErc20TokensBox.values) { - try { - if (token.enabled) { - balance[token] = await _client.fetchERC20Balances( - _polygonPrivateKey.address, - token.contractAddress, - ); - } else { - balance.remove(token); - } - } catch (_) {} - } - } - - Future getPrivateKey( - {String? mnemonic, String? privateKey, required String password}) async { - assert(mnemonic != null || privateKey != null); - - if (privateKey != null) { - return EthPrivateKey.fromHex(privateKey); - } - - final seed = bip39.mnemonicToSeed(mnemonic!); - - final root = bip32.BIP32.fromSeed(seed); - - const hdPathPolygon = "m/44'/60'/0'/0"; - const index = 0; - final addressAtIndex = root.derivePath("$hdPathPolygon/$index"); - - return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List)); - } - - @override - Future? updateBalance() async => await _updateBalance(); - - List get erc20Currencies => polygonErc20TokensBox.values.toList(); - - Future addErc20Token(Erc20Token token) async { - String? iconPath; - try { - iconPath = CryptoCurrency.all - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) - .iconPath; - } catch (_) {} - - final token0 = Erc20Token( - name: token.name, - symbol: token.symbol, - contractAddress: token.contractAddress, - decimal: token.decimal, - enabled: token.enabled, - tag: token.tag ?? "POLY", - iconPath: iconPath, - ); - - await polygonErc20TokensBox.put(token0.contractAddress, token0); - - if (token0.enabled) { - balance[token0] = await _client.fetchERC20Balances( - _polygonPrivateKey.address, - token0.contractAddress, - ); - } else { - balance.remove(token0); - } - } - - Future deleteErc20Token(Erc20Token token) async { - await token.delete(); - - balance.remove(token); - _updateBalance(); - } - - Future getErc20Token(String contractAddress) async => - await _client.getErc20Token(contractAddress); - - void _onNewTransaction() { - _updateBalance(); - _updateTransactions(); - } - - void addInitialTokens() { - final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens; - - for (var token in initialErc20Tokens) { - polygonErc20TokensBox.put(token.contractAddress, token); - } - } - - @override - Future renameWalletFiles(String newWalletName) async { - final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); - final currentWalletFile = File(currentWalletPath); - - final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type); - final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName'); - - // Copies current wallet files into new wallet name's dir and files - if (currentWalletFile.existsSync()) { - final newWalletPath = await pathForWallet(name: newWalletName, type: type); - await currentWalletFile.copy(newWalletPath); - } - if (currentTransactionsFile.existsSync()) { - final newDirPath = await pathForWalletDir(name: newWalletName, type: type); - await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName'); - } - - // Delete old name's dir and files - await Directory(currentDirPath).delete(recursive: true); - } - - void _setTransactionUpdateTimer() { - if (_transactionsUpdateTimer?.isActive ?? false) { - _transactionsUpdateTimer!.cancel(); - } - - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) { - _updateTransactions(); - _updateBalance(); - }); - } - - void updatePolygonScanUsageState(bool isEnabled) { - if (isEnabled) { - _updateTransactions(); - _setTransactionUpdateTimer(); - } else { - _transactionsUpdateTimer?.cancel(); - } - } - - @override - String signMessage(String message, {String? address}) => - bytesToHex(_polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); - - Web3Client? getWeb3Client() => _client.getWeb3Client(); } diff --git a/cw_polygon/lib/polygon_wallet_addresses.dart b/cw_polygon/lib/polygon_wallet_addresses.dart deleted file mode 100644 index 0a6a407c7..000000000 --- a/cw_polygon/lib/polygon_wallet_addresses.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:cw_ethereum/ethereum_wallet_addresses.dart'; - -class PolygonWalletAddresses extends EthereumWalletAddresses { - PolygonWalletAddresses(super.walletInfo); -} diff --git a/cw_polygon/lib/polygon_wallet_creation_credentials.dart b/cw_polygon/lib/polygon_wallet_creation_credentials.dart deleted file mode 100644 index 74c7c5ed7..000000000 --- a/cw_polygon/lib/polygon_wallet_creation_credentials.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_info.dart'; -class PolygonNewWalletCredentials extends WalletCredentials { - PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo}) - : super(name: name, walletInfo: walletInfo); -} - -class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials { - PolygonRestoreWalletFromSeedCredentials( - {required String name, - required String password, - required this.mnemonic, - WalletInfo? walletInfo}) - : super(name: name, password: password, walletInfo: walletInfo); - - final String mnemonic; -} - -class PolygonRestoreWalletFromPrivateKey extends WalletCredentials { - PolygonRestoreWalletFromPrivateKey( - {required String name, - required String password, - required this.privateKey, - WalletInfo? walletInfo}) - : super(name: name, password: password, walletInfo: walletInfo); - - final String privateKey; -} diff --git a/cw_polygon/lib/polygon_wallet_service.dart b/cw_polygon/lib/polygon_wallet_service.dart index 43c6269f6..451dc7288 100644 --- a/cw_polygon/lib/polygon_wallet_service.dart +++ b/cw_polygon/lib/polygon_wallet_service.dart @@ -1,32 +1,34 @@ -import 'dart:io'; - -import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_service.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cw_ethereum/ethereum_mnemonics.dart'; -import 'package:cw_polygon/polygon_wallet.dart'; import 'package:bip39/bip39.dart' as bip39; -import 'package:hive/hive.dart'; -import 'polygon_wallet_creation_credentials.dart'; -import 'package:collection/collection.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; +import 'package:cw_evm/evm_chain_wallet_service.dart'; +import 'package:cw_polygon/polygon_client.dart'; +import 'package:cw_polygon/polygon_mnemonics_exception.dart'; +import 'package:cw_polygon/polygon_wallet.dart'; -class PolygonWalletService extends WalletService { - PolygonWalletService(this.walletInfoSource); +class PolygonWalletService extends EVMChainWalletService { + PolygonWalletService( + super.walletInfoSource, { + required this.client, + }); - final Box walletInfoSource; + late PolygonClient client; @override - Future create(PolygonNewWalletCredentials credentials) async { + WalletType getType() => WalletType.polygon; + + @override + Future create(EVMChainNewWalletCredentials credentials) async { final strength = credentials.seedPhraseLength == 24 ? 256 : 128; final mnemonic = bip39.generateMnemonic(strength: strength); + final wallet = PolygonWallet( walletInfo: credentials.walletInfo!, mnemonic: mnemonic, password: credentials.password!, + client: client, ); await wallet.init(); @@ -36,18 +38,11 @@ class PolygonWalletService extends WalletService WalletType.polygon; - - @override - Future isWalletExit(String name) async => - File(await pathForWallet(name: name, type: getType())).existsSync(); - @override Future openWallet(String name, String password) async { final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); - final wallet = await PolygonWalletBase.open( + final wallet = await PolygonWallet.open( name: name, password: password, walletInfo: walletInfo, @@ -60,19 +55,13 @@ class PolygonWalletService extends WalletService remove(String wallet) async { - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; - await walletInfoSource.delete(walletInfo.key); - } + Future restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async { - @override - Future restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async { final wallet = PolygonWallet( password: credentials.password!, privateKey: credentials.privateKey, walletInfo: credentials.walletInfo!, + client: client, ); await wallet.init(); @@ -83,15 +72,17 @@ class PolygonWalletService extends WalletService restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async { + Future restoreFromSeed( + EVMChainRestoreWalletFromSeedCredentials credentials) async { if (!bip39.validateMnemonic(credentials.mnemonic)) { - throw EthereumMnemonicIsIncorrectException(); + throw PolygonMnemonicIsIncorrectException(); } final wallet = PolygonWallet( password: credentials.password!, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, + client: client, ); await wallet.init(); @@ -105,7 +96,7 @@ class PolygonWalletService extends WalletService rename(String currentName, String password, String newName) async { final currentWalletInfo = walletInfoSource.values .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); - final currentWallet = await PolygonWalletBase.open( + final currentWallet = await PolygonWallet.open( password: password, name: currentName, walletInfo: currentWalletInfo); await currentWallet.renameWalletFiles(newName); diff --git a/cw_polygon/pubspec.yaml b/cw_polygon/pubspec.yaml index e99e6dbbb..505838d7c 100644 --- a/cw_polygon/pubspec.yaml +++ b/cw_polygon/pubspec.yaml @@ -16,15 +16,12 @@ dependencies: path: ../cw_core cw_ethereum: path: ../cw_ethereum - mobx: ^2.0.7+4 - intl: ^0.18.0 - bip39: ^1.0.6 - hive: ^2.2.3 - collection: ^1.17.1 + cw_evm: + path: ../cw_evm web3dart: ^2.7.1 - bip32: ^2.0.0 - hex: ^0.2.0 - shared_preferences: ^2.0.15 + hive: ^2.2.3 + bip39: ^1.0.6 + collection: ^1.17.1 dev_dependencies: @@ -32,8 +29,6 @@ dev_dependencies: sdk: flutter flutter_lints: ^2.0.0 build_runner: ^2.1.11 - mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index abafc2f26..d7c174e1a 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -2,17 +2,17 @@ part of 'ethereum.dart'; class CWEthereum extends Ethereum { @override - List getEthereumWordList(String language) => EthereumMnemonics.englishWordlist; + List getEthereumWordList(String language) => EVMChainMnemonics.englishWordlist; WalletService createEthereumWalletService(Box walletInfoSource) => - EthereumWalletService(walletInfoSource); + EthereumWalletService(walletInfoSource, client: EthereumClient()); @override WalletCredentials createEthereumNewWalletCredentials({ required String name, WalletInfo? walletInfo, }) => - EthereumNewWalletCredentials(name: name, walletInfo: walletInfo); + EVMChainNewWalletCredentials(name: name, walletInfo: walletInfo); @override WalletCredentials createEthereumRestoreWalletFromSeedCredentials({ @@ -20,7 +20,7 @@ class CWEthereum extends Ethereum { required String mnemonic, required String password, }) => - EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); + EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); @override WalletCredentials createEthereumRestoreWalletFromPrivateKey({ @@ -28,37 +28,37 @@ class CWEthereum extends Ethereum { required String privateKey, required String password, }) => - EthereumRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); @override String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address; @override String getPrivateKey(WalletBase wallet) { - final privateKeyHolder = (wallet as EthereumWallet).ethPrivateKey; + final privateKeyHolder = (wallet as EthereumWallet).evmChainPrivateKey; String stringKey = bytesToHex(privateKeyHolder.privateKey); return stringKey; } @override String getPublicKey(WalletBase wallet) { - final privateKeyInUnitInt = (wallet as EthereumWallet).ethPrivateKey; + final privateKeyInUnitInt = (wallet as EthereumWallet).evmChainPrivateKey; final publicKey = privateKeyInUnitInt.address.hex; return publicKey; } @override - TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium; + TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium; @override - TransactionPriority getEthereumTransactionPrioritySlow() => EthereumTransactionPriority.slow; + TransactionPriority getEthereumTransactionPrioritySlow() => EVMChainTransactionPriority.slow; @override - List getTransactionPriorities() => EthereumTransactionPriority.all; + List getTransactionPriorities() => EVMChainTransactionPriority.all; @override TransactionPriority deserializeEthereumTransactionPriority(int raw) => - EthereumTransactionPriority.deserialize(raw: raw); + EVMChainTransactionPriority.deserialize(raw: raw); Object createEthereumTransactionCredentials( List outputs, { @@ -66,7 +66,7 @@ class CWEthereum extends Ethereum { required CryptoCurrency currency, int? feeRate, }) => - EthereumTransactionCredentials( + EVMChainTransactionCredentials( outputs .map((out) => OutputInfo( fiatAmount: out.fiatAmount, @@ -78,7 +78,7 @@ class CWEthereum extends Ethereum { isParsedAddress: out.isParsedAddress, formattedCryptoAmount: out.formattedCryptoAmount)) .toList(), - priority: priority as EthereumTransactionPriority, + priority: priority as EVMChainTransactionPriority, currency: currency, feeRate: feeRate, ); @@ -89,15 +89,15 @@ class CWEthereum extends Ethereum { required CryptoCurrency currency, required int feeRate, }) => - EthereumTransactionCredentials( + EVMChainTransactionCredentials( outputs, - priority: priority as EthereumTransactionPriority?, + priority: priority as EVMChainTransactionPriority?, currency: currency, feeRate: feeRate, ); @override - int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount); + int formatterEthereumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount); @override double formatterEthereumAmountToDouble( @@ -105,7 +105,7 @@ class CWEthereum extends Ethereum { assert(transaction != null || amount != null); if (transaction != null) { - transaction as EthereumTransactionInfo; + transaction as EVMChainTransactionInfo; return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent); } else { return (amount!) / BigInt.from(10).pow(exponent); @@ -134,7 +134,7 @@ class CWEthereum extends Ethereum { @override CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { - transaction as EthereumTransactionInfo; + transaction as EVMChainTransactionInfo; if (transaction.tokenSymbol == CryptoCurrency.eth.title) { return CryptoCurrency.eth; } @@ -146,7 +146,7 @@ class CWEthereum extends Ethereum { @override void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) { - (wallet as EthereumWallet).updateEtherscanUsageState(isEnabled); + (wallet as EthereumWallet).updateScanProviderUsageState(isEnabled); } @override diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index 066b29d43..6e5fbe2c6 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -2,17 +2,17 @@ part of 'polygon.dart'; class CWPolygon extends Polygon { @override - List getPolygonWordList(String language) => EthereumMnemonics.englishWordlist; + List getPolygonWordList(String language) => EVMChainMnemonics.englishWordlist; WalletService createPolygonWalletService(Box walletInfoSource) => - PolygonWalletService(walletInfoSource); + PolygonWalletService(walletInfoSource, client: PolygonClient()); @override WalletCredentials createPolygonNewWalletCredentials({ required String name, WalletInfo? walletInfo, }) => - PolygonNewWalletCredentials(name: name, walletInfo: walletInfo); + EVMChainNewWalletCredentials(name: name, walletInfo: walletInfo); @override WalletCredentials createPolygonRestoreWalletFromSeedCredentials({ @@ -20,7 +20,7 @@ class CWPolygon extends Polygon { required String mnemonic, required String password, }) => - PolygonRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); + EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); @override WalletCredentials createPolygonRestoreWalletFromPrivateKey({ @@ -28,37 +28,37 @@ class CWPolygon extends Polygon { required String privateKey, required String password, }) => - PolygonRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); @override String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address; @override String getPrivateKey(WalletBase wallet) { - final privateKeyHolder = (wallet as PolygonWallet).polygonPrivateKey; + final privateKeyHolder = (wallet as PolygonWallet).evmChainPrivateKey; String stringKey = bytesToHex(privateKeyHolder.privateKey); return stringKey; } @override String getPublicKey(WalletBase wallet) { - final privateKeyInUnitInt = (wallet as PolygonWallet).polygonPrivateKey; + final privateKeyInUnitInt = (wallet as PolygonWallet).evmChainPrivateKey; final publicKey = privateKeyInUnitInt.address.hex; return publicKey; } @override - TransactionPriority getDefaultTransactionPriority() => PolygonTransactionPriority.medium; + TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium; @override - TransactionPriority getPolygonTransactionPrioritySlow() => PolygonTransactionPriority.slow; + TransactionPriority getPolygonTransactionPrioritySlow() => EVMChainTransactionPriority.slow; @override - List getTransactionPriorities() => PolygonTransactionPriority.all; + List getTransactionPriorities() => EVMChainTransactionPriority.all; @override TransactionPriority deserializePolygonTransactionPriority(int raw) => - PolygonTransactionPriority.deserialize(raw: raw); + EVMChainTransactionPriority.deserialize(raw: raw); Object createPolygonTransactionCredentials( List outputs, { @@ -66,7 +66,7 @@ class CWPolygon extends Polygon { required CryptoCurrency currency, int? feeRate, }) => - PolygonTransactionCredentials( + EVMChainTransactionCredentials( outputs .map((out) => OutputInfo( fiatAmount: out.fiatAmount, @@ -78,7 +78,7 @@ class CWPolygon extends Polygon { isParsedAddress: out.isParsedAddress, formattedCryptoAmount: out.formattedCryptoAmount)) .toList(), - priority: priority as PolygonTransactionPriority, + priority: priority as EVMChainTransactionPriority, currency: currency, feeRate: feeRate, ); @@ -89,15 +89,15 @@ class CWPolygon extends Polygon { required CryptoCurrency currency, required int feeRate, }) => - PolygonTransactionCredentials( + EVMChainTransactionCredentials( outputs, - priority: priority as PolygonTransactionPriority?, + priority: priority as EVMChainTransactionPriority?, currency: currency, feeRate: feeRate, ); @override - int formatterPolygonParseAmount(String amount) => PolygonFormatter.parsePolygonAmount(amount); + int formatterPolygonParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount); @override double formatterPolygonAmountToDouble( @@ -105,7 +105,7 @@ class CWPolygon extends Polygon { assert(transaction != null || amount != null); if (transaction != null) { - transaction as PolygonTransactionInfo; + transaction as EVMChainTransactionInfo; return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent); } else { return (amount!) / BigInt.from(10).pow(exponent); @@ -134,7 +134,7 @@ class CWPolygon extends Polygon { @override CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { - transaction as PolygonTransactionInfo; + transaction as EVMChainTransactionInfo; if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title) { return CryptoCurrency.maticpoly; } @@ -146,7 +146,7 @@ class CWPolygon extends Polygon { @override void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) { - (wallet as PolygonWallet).updatePolygonScanUsageState(isEnabled); + (wallet as PolygonWallet).updateScanProviderUsageState(isEnabled); } @override diff --git a/lib/src/screens/dashboard/pages/nft_details_page.dart b/lib/src/screens/dashboard/pages/nft_details_page.dart index 4bddb550b..bb642fd4b 100644 --- a/lib/src/screens/dashboard/pages/nft_details_page.dart +++ b/lib/src/screens/dashboard/pages/nft_details_page.dart @@ -101,26 +101,26 @@ class NFTDetailsPage extends BasePage { SizedBox(height: 16), _NFTSingleInfoTile( infoType: S.current.name, - infoValue: nftAsset.normalizedMetadata?.name ?? '', + infoValue: nftAsset.normalizedMetadata?.name ?? '---', ), if (nftAsset.normalizedMetadata?.description != null) ...[ SizedBox(height: 16), _NFTSingleInfoTile( - infoType: 'Description', - infoValue: nftAsset.normalizedMetadata?.description ?? '', + infoType: S.current.description, + infoValue: nftAsset.normalizedMetadata?.description ?? '---', ), ], SizedBox(height: 16), _NFTSingleInfoTile( - infoType: 'Contract Name', - infoValue: nftAsset.name ?? '', + infoType: S.current.contractName, + infoValue: nftAsset.name ?? '---', ), SizedBox(height: 8), _NFTSingleInfoTile( - infoType: 'Contract Symbol', - infoValue: nftAsset.symbol ?? '', + infoType: S.current.contractSymbol, + infoValue: nftAsset.symbol ?? '---', ), ], ), diff --git a/lib/src/screens/dashboard/widgets/nft_tile_widget.dart b/lib/src/screens/dashboard/widgets/nft_tile_widget.dart index 0be027f44..e7391b970 100644 --- a/lib/src/screens/dashboard/widgets/nft_tile_widget.dart +++ b/lib/src/screens/dashboard/widgets/nft_tile_widget.dart @@ -13,22 +13,15 @@ class NFTTileWidget extends StatelessWidget { @override Widget build(BuildContext context) { return InkWell( - onTap: () => Navigator.pushNamed(context, Routes.nftDetailsPage, - arguments: nftAsset), + onTap: () => Navigator.pushNamed(context, Routes.nftDetailsPage, arguments: nftAsset), child: Container( width: double.infinity, margin: const EdgeInsets.only(left: 16, right: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30.0), border: Border.all( - color: Theme.of(context) - .extension()! - .cardBorderColor, - width: 1, - ), - color: Theme.of(context) - .extension()! - .syncedBackgroundColor, + color: Theme.of(context).extension()!.cardBorderColor, width: 1), + color: Theme.of(context).extension()!.syncedBackgroundColor, ), child: Row( children: [ @@ -40,14 +33,10 @@ class NFTTileWidget extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.0), border: Border.all( - color: Theme.of(context) - .extension()! - .cardBorderColor, + color: Theme.of(context).extension()!.cardBorderColor, width: 1, ), - color: Theme.of(context) - .extension()! - .syncedBackgroundColor, + color: Theme.of(context).extension()!.syncedBackgroundColor, ), child: NFTImageWidget( imageUrl: nftAsset.normalizedMetadata?.imageUrl, @@ -59,27 +48,23 @@ class NFTTileWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${nftAsset.name ?? ''} - ${nftAsset.symbol ?? ''}', + '${nftAsset.name ?? '---'} - ${nftAsset.symbol ?? '---'}', style: TextStyle( fontSize: 12, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, + color: Theme.of(context).extension()!.labelTextColor, height: 1, ), ), SizedBox(height: 8), Text( - nftAsset.normalizedMetadata?.name ?? nftAsset.name ?? "", + nftAsset.normalizedMetadata?.name ?? nftAsset.name ?? "---", style: TextStyle( fontSize: 20, fontFamily: 'Lato', fontWeight: FontWeight.w900, - color: Theme.of(context) - .extension()! - .assetTitleColor, + color: Theme.of(context).extension()!.assetTitleColor, height: 1, ), ), @@ -92,4 +77,3 @@ class NFTTileWidget extends StatelessWidget { ); } } - diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index f9ff466a7..a3bf281ca 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -239,6 +239,8 @@ abstract class TransactionDetailsViewModelBase with Store { StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), if (showRecipientAddress && tx.to != null) StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + if (tx.direction == TransactionDirection.incoming && tx.from != null) + StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), ]; items.addAll(_items); @@ -271,8 +273,10 @@ abstract class TransactionDetailsViewModelBase with Store { StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), if (tx.feeFormatted()?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), - if (showRecipientAddress && tx.to != null) + if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing) StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + if (tx.direction == TransactionDirection.incoming && tx.from != null) + StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), ]; items.addAll(_items); diff --git a/model_generator.sh b/model_generator.sh index 32d863aeb..e7231f19a 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -1,9 +1,8 @@ cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. -cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs \ No newline at end of file diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index f8ff07bd2..7ee08460d 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -764,9 +764,12 @@ "confirmed_tx": "مؤكد", "transaction_details_source_address": "عنوان المصدر", "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ", + "contractName": "ﺪﻘﻌﻟﺍ ﻢﺳﺍ", + "contractSymbol": "ﺪﻘﻌﻟﺍ ﺰﻣﺭ", + "description": "ﻒﺻﻭ", "camera_consent": ".ﻞﻴﺻﺎﻔﺘﻟﺍ ﻰﻠﻋ ﻝﻮﺼﺤﻠﻟ ﻢﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺔﻴﺻﻮﺼﺨﻟﺍ ﺔﺳﺎﻴﺳ ﻦﻣ ﻖﻘﺤﺘﻟﺍ ﻰﺟﺮﻳ .${provider} ﻝﻮﻠ", - "no_relays": " ﺕﻼﺣﺮﻤﻟﺍ ﻻ", - "choose_relay": " ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ", + "no_relays": "ﺕﻼﺣﺮﻤﻟﺍ ﻻ", + "choose_relay": "ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ", "no_relays_message": ".ﻪﺑ ﺹﺎﺨﻟﺍ Nostr ﻞﺠﺳ ﻰﻟﺇ ﺕﻼﺣﺮﻤﻟﺍ ﺔﻓﺎﺿﻹ ﻢﻠﺘﺴﻤﻟﺍ ﺩﺎﺷﺭﺇ ﻰﺟﺮﻳ .ﺕﻼﺣﺮﻣ ﻱﺃ ﻰﻠﻋ ﻱﻮﺘﺤﻳ ﻻ", "no_relay_on_domain": ".ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﺡﺎﺘﻣ ﺮﻴﻏ ﻞﻴﺣﺮﺘﻟﺍ ﻥﺃ ﻭﺃ ﻡﺪﺨﺘﺴﻤﻟﺍ ﻝﺎﺠﻤﻟ ﻞﻴﺣﺮﺗ ﺪ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 8e6a6c1ee..58c27d5e8 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -760,6 +760,9 @@ "confirmed_tx": "Потвърдено", "transaction_details_source_address": "Адрес на източника", "pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.", + "contractName": "Име на договора", + "contractSymbol": "Договор Символ", + "description": "Описание", "camera_consent": "Вашият фотоапарат ще бъде използван за заснемане на изображение с цел идентификация от ${provider}. Моля, проверете тяхната политика за поверителност за подробности.", "no_relays": "Без релета", "choose_relay": "Моля, изберете реле, което да използвате", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index a739fa05b..92874e315 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -760,6 +760,9 @@ "confirmed_tx": "Potvrzeno", "transaction_details_source_address": "Zdrojová adresa", "pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.", + "contractName": "Název smlouvy", + "contractSymbol": "Symbol smlouvy", + "description": "Popis", "camera_consent": "Váš fotoaparát použije k pořízení snímku pro účely identifikace ${provider}. Podrobnosti najdete v jejich Zásadách ochrany osobních údajů.", "no_relays": "Žádná relé", "choose_relay": "Vyberte relé, které chcete použít", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 872b8bec2..9aca5ba28 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Bestätigt", "transaction_details_source_address": "Quelladresse", "pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.", + "contractName": "Vertragsname", + "contractSymbol": "Vertragssymbol", + "description": "Beschreibung", "camera_consent": "Mit Ihrer Kamera wird bis zum ${provider} ein Bild zur Identifizierung aufgenommen. Weitere Informationen finden Sie in deren Datenschutzbestimmungen.", "no_relays": "Keine Relais", "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 3ec496b6c..d54b7baa4 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -769,6 +769,9 @@ "confirmed_tx": "Confirmed", "transaction_details_source_address": "Source address", "pause_wallet_creation": "Ability to create Haven Wallet is currently paused.", + "contractName": "Contract Name", + "contractSymbol": "Contract Symbol", + "description": "Description", "camera_consent": "Your camera will be used to capture an image for identification purposes by ${provider}. Please check their Privacy Policy for details.", "no_relays": "No relays", "choose_relay": "Please choose a relay to use", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index ab97e764d..5f9a8e53b 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Confirmado", "transaction_details_source_address": "Dirección de la fuente", "pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.", + "contractName": "Nombre del contrato", + "contractSymbol": "Símbolo de contrato", + "description": "Descripción", "camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulte su Política de privacidad para obtener más detalles.", "no_relays": "Sin relevos", "choose_relay": "Por favor elija un relé para usar", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 619c35276..523253bf4 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Confirmé", "transaction_details_source_address": "Adresse source", "pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.", + "contractName": "Nom du contrat", + "contractSymbol": "Symbole du contrat", + "description": "Description", "camera_consent": "Votre appareil photo sera utilisé pour capturer une image à des fins d'identification par ${provider}. Veuillez consulter leur politique de confidentialité pour plus de détails.", "no_relays": "Pas de relais", "choose_relay": "Veuillez choisir un relais à utiliser", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index dd38a4ec3..2f2343434 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -750,6 +750,9 @@ "confirmed_tx": "Tabbatar", "transaction_details_source_address": "Adireshin Incord", "pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.", + "contractName": "Sunan Kwangila", + "contractSymbol": "Alamar Kwangila", + "description": "Bayani", "camera_consent": "Za a yi amfani da kyamarar ku don ɗaukar hoto don dalilai na tantancewa ta ${provider}. Da fatan za a duba Manufar Sirri don cikakkun bayanai.", "no_relays": "Babu relays", "choose_relay": "Da fatan za a zaɓi gudun ba da sanda don amfani", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b37aa7c12..a3950b44e 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -768,6 +768,9 @@ "confirmed_tx": "की पुष्टि", "transaction_details_source_address": "स्रोत पता", "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।", + "contractName": "अनुबंध का नाम", + "contractSymbol": "अनुबंध चिह्न", + "description": "विवरण", "camera_consent": "आपके कैमरे का उपयोग ${provider} द्वारा पहचान उद्देश्यों के लिए एक छवि कैप्चर करने के लिए किया जाएगा। विवरण के लिए कृपया उनकी गोपनीयता नीति जांचें।", "no_relays": "कोई रिले नहीं", "choose_relay": "कृपया उपयोग करने के लिए एक रिले चुनें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index c1ba66bb2..8e3365c96 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -766,6 +766,9 @@ "confirmed_tx": "Potvrđen", "transaction_details_source_address": "Adresa izvora", "pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.", + "contractName": "Naziv ugovora", + "contractSymbol": "Simbol ugovora", + "description": "Opis", "camera_consent": "Vaš će fotoaparat koristiti za snimanje slike u svrhu identifikacije od strane ${provider}. Pojedinosti potražite u njihovoj politici privatnosti.", "no_relays": "Nema releja", "choose_relay": "Odaberite relej za korištenje", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b736f3977..27bfc4c7b 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -756,6 +756,9 @@ "confirmed_tx": "Dikonfirmasi", "transaction_details_source_address": "Alamat sumber", "pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.", + "contractName": "Nama Kontrak", + "contractSymbol": "Simbol Kontrak", + "description": "Keterangan", "camera_consent": "Kamera Anda akan digunakan untuk mengambil gambar untuk tujuan identifikasi oleh ${provider}. Silakan periksa Kebijakan Privasi mereka untuk detailnya.", "no_relays": "Tidak ada relay", "choose_relay": "Silakan pilih relai yang akan digunakan", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index adcaa3204..a29ac5c93 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Confermato", "transaction_details_source_address": "Indirizzo di partenza", "pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.", + "contractName": "Nome del contratto", + "contractSymbol": "Simbolo del contratto", + "description": "Descrizione", "camera_consent": "La tua fotocamera verrà utilizzata per acquisire un'immagine a scopo identificativo da ${provider}. Si prega di controllare la loro Informativa sulla privacy per i dettagli.", "no_relays": "Nessun relè", "choose_relay": "Scegli un relè da utilizzare", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 9dabebc45..df317392f 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -768,6 +768,9 @@ "confirmed_tx": "確認済み", "transaction_details_source_address": "ソースアドレス", "pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。", + "contractName": "契約名", + "contractSymbol": "契約記号", + "description": "説明", "camera_consent": "あなたのカメラは、${provider}_ までに識別目的で画像を撮影するために使用されます。詳細については、プライバシー ポリシーをご確認ください。", "no_relays": "リレーなし", "choose_relay": "使用するリレーを選択してください", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index ee41a369b..5c3affbc5 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -766,6 +766,9 @@ "confirmed_tx": "확인", "transaction_details_source_address": "소스 주소", "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.", + "contractName": "계약명", + "contractSymbol": "계약 기호", + "description": "설명", "camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요.", "no_relays": "릴레이 없음", "choose_relay": "사용할 릴레이를 선택해주세요", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 199d72660..27c9bdeeb 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -766,6 +766,9 @@ "confirmed_tx": "အတည်ပြုသည်", "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ", "pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။", + "contractName": "စာချုပ်အမည်", + "contractSymbol": "စာချုပ်သင်္ကေတ", + "description": "ဖော်ပြချက်", "camera_consent": "မှတ်ပုံတင်ခြင်းရည်ရွယ်ချက်များအတွက် ${provider} တွင် သင့်ကင်မရာကို အသုံးပြုပါမည်။ အသေးစိတ်အတွက် ၎င်းတို့၏ ကိုယ်ရေးကိုယ်တာမူဝါဒကို စစ်ဆေးပါ။", "no_relays": "Relay မရှိပါ။", "choose_relay": "အသုံးပြုရန် relay ကိုရွေးချယ်ပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 9f27236aa..51f25e64a 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Bevestigd", "transaction_details_source_address": "Bron adres", "pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.", + "contractName": "Contractnaam", + "contractSymbol": "Contractsymbool", + "description": "Beschrijving", "camera_consent": "Uw camera wordt gebruikt om vóór ${provider} een beeld vast te leggen voor identificatiedoeleinden. Raadpleeg hun privacybeleid voor meer informatie.", "no_relays": "Geen relais", "choose_relay": "Kies een relais dat u wilt gebruiken", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index c6e4806b1..4363ef5ae 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Potwierdzony", "transaction_details_source_address": "Adres źródłowy", "pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.", + "contractName": "Nazwa umowy", + "contractSymbol": "Symbol kontraktu", + "description": "Opis", "camera_consent": "Twój aparat zostanie użyty do przechwycenia obrazu w celach identyfikacyjnych przez ${provider}. Aby uzyskać szczegółowe informacje, sprawdź ich Politykę prywatności.", "no_relays": "Żadnych przekaźników", "choose_relay": "Wybierz przekaźnik, którego chcesz użyć", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index b9694d223..7cfd261eb 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -767,6 +767,9 @@ "confirmed_tx": "Confirmado", "transaction_details_source_address": "Endereço de Origem", "pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.", + "contractName": "Nome do contrato", + "contractSymbol": "Símbolo do Contrato", + "description": "Descrição", "camera_consent": "Sua câmera será usada para capturar uma imagem para fins de identificação por ${provider}. Por favor, verifique a Política de Privacidade para obter detalhes.", "no_relays": "Sem relés", "choose_relay": "Escolha um relé para usar", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 3e5d2cec2..a4c34f5c5 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Подтвержденный", "transaction_details_source_address": "Адрес источника", "pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.", + "contractName": "Название контракта", + "contractSymbol": "Символ контракта", + "description": "Описание", "camera_consent": "Ваша камера будет использоваться для захвата изображения в целях идентификации ${provider}. Пожалуйста, ознакомьтесь с их Политикой конфиденциальности для получения подробной информации.", "no_relays": "Нет реле", "choose_relay": "Пожалуйста, выберите реле для использования", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index a9fe67959..603d04067 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -766,6 +766,9 @@ "confirmed_tx": "ซึ่งยืนยันแล้ว", "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด", "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว", + "contractName": "ชื่อสัญญา", + "contractSymbol": "สัญลักษณ์สัญญา", + "description": "คำอธิบาย", "camera_consent": "กล้องของคุณจะถูกนำมาใช้เพื่อจับภาพเพื่อวัตถุประสงค์ในการระบุตัวตนภายใน ${provider} โปรดตรวจสอบนโยบายความเป็นส่วนตัวเพื่อดูรายละเอียด", "no_relays": "ไม่มีรีเลย์", "choose_relay": "กรุณาเลือกรีเลย์ที่จะใช้", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 3d05cc700..a8ec4df3e 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -762,6 +762,9 @@ "confirmed_tx": "Nakumpirma", "transaction_details_source_address": "SOURCE ADDRESS", "pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.", + "contractName": "Pangalan ng Kontrata", + "contractSymbol": "Simbolo ng Kontrata", + "description": "Paglalarawan", "camera_consent": "Gagamitin ang iyong camera upang kumuha ng larawan para sa mga layunin ng pagkakakilanlan sa pamamagitan ng ${provider}. Pakisuri ang kanilang Patakaran sa Privacy para sa mga detalye.", "no_relays": "Walang mga relay", "choose_relay": "Mangyaring pumili ng relay na gagamitin", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 62243838d..337a85ff8 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -766,6 +766,9 @@ "confirmed_tx": "Onaylanmış", "transaction_details_source_address": "Kaynak adresi", "pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.", + "contractName": "Sözleşme Adı", + "contractSymbol": "Sözleşme Sembolü", + "description": "Tanım", "camera_consent": "Kameranız ${provider} tarihine kadar tanımlama amacıyla bir görüntü yakalamak için kullanılacaktır. Ayrıntılar için lütfen Gizlilik Politikalarını kontrol edin.", "no_relays": "Röle yok", "choose_relay": "Lütfen kullanmak için bir röle seçin", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index def086b2e..7cc41ab43 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -768,6 +768,9 @@ "confirmed_tx": "Підтверджений", "transaction_details_source_address": "Адреса джерела", "pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.", + "contractName": "Назва контракту", + "contractSymbol": "Контракт символ", + "description": "опис", "camera_consent": "Ваша камера використовуватиметься для зйомки зображення з метою ідентифікації ${provider}. Будь ласка, ознайомтеся з їхньою політикою конфіденційності, щоб дізнатися більше.", "no_relays": "Без реле", "choose_relay": "Будь ласка, виберіть реле для використання", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 34b1042ac..11fc67c4e 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -760,9 +760,12 @@ "confirmed_tx": "تصدیق", "transaction_details_source_address": "ماخذ ایڈریس", "pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ", + "contractName": "ﻡﺎﻧ ﺎﮐ ﮦﺪﮨﺎﻌﻣ", + "contractSymbol": "ﺖﻣﻼﻋ ﯽﮐ ﮦﺪﮨﺎﻌﻣ", + "description": "ﻞﯿﺼﻔﺗ", "camera_consent": "۔ﮟﯿﮭﮑﯾﺩ ﯽﺴﯿﻟﺎﭘ ﯽﺴﯾﻮﯿﺋﺍﺮﭘ ﯽﮐ ﻥﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﺕﻼ${provider}ﯿﺼﻔﺗ ۔ﺎﮔ ﮯﺋﺎﺟ ﺎﯿﮐ ﻝﺎﻤﻌﺘﺳﺍ ﮯﯿﻟ", - "no_relays": " ۔ﮟﯿﮩﻧ ﮯﻠﯾﺭ ﯽﺋﻮﮐ", - "choose_relay": " ۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ", + "no_relays": "۔ﮟﯿﮩﻧ ﮯﻠﯾﺭ ﯽﺋﻮﮐ", + "choose_relay": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ", "no_relays_message": "۔ﮟﯾﺮﮐ ﻞﻣﺎﺷ ﮯﻠﯾﺭ ﮟﯿﻣ ﮈﺭﺎﮑﯾﺭ ﺮﭩﺳﻮﻧ ﮯﻨﭘﺍ ﮦﻭ ﮧﮐ ﮟﯾﺩ ﺖﯾﺍﺪﮨ ﻮﮐ ﮦﺪﻨﻨﮐ ﻝﻮﺻﻭ ﻡﺮﮐ ﮦﺍﺮﺑ ۔", "no_relay_on_domain": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮯﮨ ﮟﯿﮩﻧ ﺏﺎﯿﺘﺳﺩ ﮯﻠﯾﺭ ﺎﯾ ﮯﮨ ﮟ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 12769ec91..142d9f5bf 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -762,6 +762,9 @@ "confirmed_tx": "Jẹrisi", "transaction_details_source_address": "Adirẹsi orisun", "pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.", + "contractName": "Orukọ adehun", + "contractSymbol": "Aami adehun", + "description": "Apejuwe", "camera_consent": "Kamẹra rẹ yoo ṣee lo lati ya aworan kan fun awọn idi idanimọ nipasẹ ${provider}. Jọwọ ṣayẹwo Ilana Aṣiri wọn fun awọn alaye.", "no_relays": "Ko si relays", "choose_relay": "Jọwọ yan yii lati lo", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index cdffd8141..92904034b 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -767,6 +767,9 @@ "confirmed_tx": "确认的", "transaction_details_source_address": "源地址", "pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。", + "contractName": "合约名称", + "contractSymbol": "合约符号", + "description": "描述", "camera_consent": "${provider} 将使用您的相机拍摄图像以供识别之用。请查看他们的隐私政策了解详情。", "no_relays": "无继电器", "choose_relay": "请选择要使用的继电器", diff --git a/tool/configure.dart b/tool/configure.dart index 4421d4f3f..8297d8a39 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -525,19 +525,24 @@ 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'; + +"""; + const ethereumCWHeaders = """ +import 'package:cw_evm/evm_chain_formatter.dart'; +import 'package:cw_evm/evm_chain_mnemonics.dart'; +import 'package:cw_evm/evm_chain_transaction_credentials.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; + +import 'package:cw_ethereum/ethereum_client.dart'; +import 'package:cw_ethereum/ethereum_wallet.dart'; +import 'package:cw_ethereum/ethereum_wallet_service.dart'; + import 'package:eth_sig_util/util/utils.dart'; import 'package:hive/hive.dart'; import 'package:web3dart/web3dart.dart'; -"""; - const ethereumCWHeaders = """ -import 'package:cw_ethereum/ethereum_formatter.dart'; -import 'package:cw_ethereum/ethereum_mnemonics.dart'; -import 'package:cw_ethereum/ethereum_transaction_credentials.dart'; -import 'package:cw_ethereum/ethereum_transaction_info.dart'; -import 'package:cw_ethereum/ethereum_wallet.dart'; -import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart'; -import 'package:cw_ethereum/ethereum_wallet_service.dart'; -import 'package:cw_ethereum/ethereum_transaction_priority.dart'; + """; const ethereumCwPart = "part 'cw_ethereum.dart';"; const ethereumContent = """ @@ -612,19 +617,24 @@ 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'; + """; const polygonCWHeaders = """ -import 'package:cw_polygon/polygon_formatter.dart'; -import 'package:cw_polygon/polygon_transaction_credentials.dart'; -import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:cw_evm/evm_chain_formatter.dart'; +import 'package:cw_evm/evm_chain_mnemonics.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:cw_evm/evm_chain_transaction_credentials.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; + +import 'package:cw_polygon/polygon_client.dart'; import 'package:cw_polygon/polygon_wallet.dart'; -import 'package:cw_polygon/polygon_wallet_creation_credentials.dart'; import 'package:cw_polygon/polygon_wallet_service.dart'; -import 'package:cw_polygon/polygon_transaction_priority.dart'; -import 'package:cw_ethereum/ethereum_mnemonics.dart'; + +import 'package:hive/hive.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:eth_sig_util/util/utils.dart'; + """; const polygonCwPart = "part 'cw_polygon.dart';"; const polygonContent = """ @@ -924,6 +934,10 @@ Future generatePubspec( cw_polygon: path: ./cw_polygon """; + const cwEVM = """ + cw_evm: + path: ./cw_evm + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -964,6 +978,10 @@ Future generatePubspec( output += '\n$cwHaven'; } + if (hasEthereum || hasPolygon) { + output += '\n$cwEVM'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); diff --git a/tool/generate_secrets_config.dart b/tool/generate_secrets_config.dart index 1d533b0b1..58e7b8839 100644 --- a/tool/generate_secrets_config.dart +++ b/tool/generate_secrets_config.dart @@ -4,7 +4,7 @@ import 'utils/secret_key.dart'; import 'utils/utils.dart'; const configPath = 'tool/.secrets-config.json'; -const ethereumConfigPath = 'tool/.ethereum-secrets-config.json'; +const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; Future main(List args) async => generateSecretsConfig(args); @@ -17,7 +17,7 @@ Future generateSecretsConfig(List args) async { }); final configFile = File(configPath); - final ethereumConfigFile = File(ethereumConfigPath); + final evmChainsConfigFile = File(evmChainsConfigPath); final secrets = {}; secrets.addAll(extraInfo); @@ -49,7 +49,7 @@ Future generateSecretsConfig(List args) async { await configFile.writeAsString(secretsJson); secrets.clear(); - SecretKey.ethereumSecrets.forEach((sec) { + SecretKey.evmChainsSecrets.forEach((sec) { if (secrets[sec.name] != null) { return; } @@ -59,5 +59,5 @@ Future generateSecretsConfig(List args) async { secretsJson = JsonEncoder.withIndent(' ').convert(secrets); - await ethereumConfigFile.writeAsString(secretsJson); + await evmChainsConfigFile.writeAsString(secretsJson); } diff --git a/tool/import_secrets_config.dart b/tool/import_secrets_config.dart index 5987b9005..83e345f78 100644 --- a/tool/import_secrets_config.dart +++ b/tool/import_secrets_config.dart @@ -5,8 +5,8 @@ import 'utils/utils.dart'; const configPath = 'tool/.secrets-config.json'; const outputPath = 'lib/.secrets.g.dart'; -const ethereumConfigPath = 'tool/.ethereum-secrets-config.json'; -const ethereumOutputPath = 'cw_ethereum/lib/.secrets.g.dart'; +const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; +const evmChainsOutputPath = 'cw_evm/lib/.secrets.g.dart'; Future main(List args) async => importSecretsConfig(); @@ -15,11 +15,11 @@ Future importSecretsConfig() async { final input = json.decode(File(configPath).readAsStringSync()) as Map; final output = input.keys.fold('', (String acc, String val) => acc + generateConst(val, input)); - final ethereumOutputFile = File(ethereumOutputPath); - final ethereumInput = - json.decode(File(ethereumConfigPath).readAsStringSync()) as Map; - final ethereumOutput = ethereumInput.keys - .fold('', (String acc, String val) => acc + generateConst(val, ethereumInput)); + final evmChainsOutputFile = File(evmChainsOutputPath); + final evmChainsInput = + json.decode(File(evmChainsConfigPath).readAsStringSync()) as Map; + final evmChainsOutput = evmChainsInput.keys + .fold('', (String acc, String val) => acc + generateConst(val, evmChainsInput)); if (outputFile.existsSync()) { await outputFile.delete(); @@ -27,9 +27,9 @@ Future importSecretsConfig() async { await outputFile.writeAsString(output); - if (ethereumOutputFile.existsSync()) { - await ethereumOutputFile.delete(); + if (evmChainsOutputFile.existsSync()) { + await evmChainsOutputFile.delete(); } - await ethereumOutputFile.writeAsString(ethereumOutput); + await evmChainsOutputFile.writeAsString(evmChainsOutput); } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index e6f625426..f991c43cf 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -36,10 +36,10 @@ class SecretKey { SecretKey('robinhoodApplicationId', () => ''), SecretKey('robinhoodCIdApiSecret', () => ''), SecretKey('walletConnectProjectId', () => ''), - SecretKey('moralisApiKey', () => '') + SecretKey('moralisApiKey', () => ''), ]; - static final ethereumSecrets = [ + static final evmChainsSecrets = [ SecretKey('etherScanApiKey', () => ''), SecretKey('polygonScanApiKey', () => ''), ];