CW-551-Refactor-EVM-Chains (#1256)

* feat: Create central package for EVM chains

* chore: Cleanup pubspec and add core evm dependencies

* feat: Replicated core evm chain files, time to start fixing the issues

* feat: Setup evm central package to handle all evm chains

* feat: Link up Polygon and Ethereum wallets to the centra evm package, fix bugs and issues, and optimze for better performance

* feat: Setup and adjust configs to reflect new evm configurations

* Remove unneeded file

* fix: Changes done while re-reviewing entire structure and refactor

* fix: Add evm chain wallet path to imports in configure file

* feat: Adjust implementation of parent class, remove unneeded files, remove windows, linux and mac directories, restructure the evm child classes

* fix: Make EVMChainWallet a central abstract class and adjust accordingly

* fix: Adjust transaction info, restructure EVMWalletChain to be an abstract, adjust external facing interfaces for polygon and ethereum, adjust configuration for ethereum and polygon in configure file

* fix: Testing issues

* fix: Add localization for nft tile and details page texts and add dashes for null responses

* fix: merge conflicts

* Minor fixes for building Monero.com

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
Adegoke David 2024-01-30 19:01:48 +01:00 committed by GitHub
parent b92ccb5c0b
commit 7410daacff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
90 changed files with 1775 additions and 2041 deletions

View file

@ -1,7 +1,7 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
class DefaultErc20Tokens {
class DefaultEthereumErc20Tokens {
final List<Erc20Token> _defaultTokens = [
Erc20Token(
name: "USD Coin",

View file

@ -1,47 +0,0 @@
import 'dart:convert';
import 'dart:math';
import 'package:cw_core/balance.dart';
class ERC20Balance extends Balance {
ERC20Balance(this.balance, {this.exponent = 18})
: super(balance.toInt(),
balance.toInt());
final BigInt balance;
final int exponent;
@override
String get formattedAdditionalBalance {
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
return formattedBalance.substring(0, min(12, formattedBalance.length));
}
@override
String get formattedAvailableBalance {
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
return formattedBalance.substring(0, min(12, formattedBalance.length));
}
String toJSON() => json.encode({
'balanceInWei': balance.toString(),
'exponent': exponent,
});
static ERC20Balance? fromJSON(String? jsonSource) {
if (jsonSource == null) {
return null;
}
final decoded = json.decode(jsonSource) as Map;
try {
return ERC20Balance(
BigInt.parse(decoded['balanceInWei']),
exponent: decoded['exponent'],
);
} catch (e) {
return ERC20Balance(BigInt.zero);
}
}
}

View file

@ -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<EtherAmount> getBalance(EthereumAddress address) async {
try {
return await _client!.getBalance(address);
} catch (_) {
return EtherAmount.zero();
}
}
Future<int> getGasUnitPrice() async {
try {
final gasPrice = await _client!.getGasPrice();
return gasPrice.getInWei.toInt();
} catch (_) {
return 0;
}
}
Future<int> getEstimatedGas() async {
try {
final estimatedGas = await _client!.estimateGas();
return estimatedGas.toInt();
} catch (_) {
return 0;
}
}
Future<PendingEthereumTransaction> 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<String> 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<ERC20Balance> 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<Erc20Token?> 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<List<EthereumTransactionModel>> fetchTransactions(String address,
@override
Future<List<EVMChainTransactionModel>> 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<String, dynamic>;
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300 && _jsonResponse['status'] != 0) {
return (_jsonResponse['result'] as List)
.map((e) => EthereumTransactionModel.fromJson(e as Map<String, dynamic>))
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
return (jsonResponse['result'] as List)
.map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
.toList();
}
return [];
} catch (e) {
print(e);
log(e.toString());
return [];
}
}
Web3Client? getWeb3Client() {
return _client;
}
// Future<int> _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;
// }
}

View file

@ -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;
}

View file

@ -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;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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.';
}

View file

@ -1,17 +0,0 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
class EthereumTransactionCredentials {
EthereumTransactionCredentials(
this.outputs, {
required this.priority,
required this.currency,
this.feeRate,
});
final List<OutputInfo> outputs;
final EthereumTransactionPriority? priority;
final int? feeRate;
final CryptoCurrency currency;
}

View file

@ -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<EthereumTransactionInfo> with Store {
EthereumTransactionHistoryBase({required this.walletInfo, required String password})
: _password = password {
transactions = ObservableMap<String, EthereumTransactionInfo>();
}
final WalletInfo walletInfo;
String _password;
Future<void> init() async => await _load();
class EthereumTransactionHistory extends EVMChainTransactionHistory {
EthereumTransactionHistory({
required super.walletInfo,
required super.password,
});
@override
Future<void> 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<String, EthereumTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
Future<Map<String, dynamic>> _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<String, dynamic>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = EthereumTransactionInfo.fromJson(val);
_update(tx);
}
});
} catch (e) {
print(e);
}
}
void _update(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
EthereumTransactionInfo.fromJson(val);
}

View file

@ -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<String, dynamic> 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<String, dynamic> 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';
}

View file

@ -1,48 +0,0 @@
//! Model used for in parsing transactions fetched using etherscan
class EthereumTransactionModel {
final DateTime date;
final String hash;
final String from;
final String to;
final BigInt amount;
final int gasUsed;
final BigInt gasPrice;
final String contractAddress;
final int confirmations;
final int blockNumber;
final String? tokenSymbol;
final int? tokenDecimal;
final bool isError;
EthereumTransactionModel({
required this.date,
required this.hash,
required this.from,
required this.to,
required this.amount,
required this.gasUsed,
required this.gasPrice,
required this.contractAddress,
required this.confirmations,
required this.blockNumber,
required this.tokenSymbol,
required this.tokenDecimal,
required this.isError,
});
factory EthereumTransactionModel.fromJson(Map<String, dynamic> json) => EthereumTransactionModel(
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"] ?? "ETH",
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
isError: json["isError"] == "1",
);
}

View file

@ -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<EthereumTransactionPriority> 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;
}
}

View file

@ -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<ERC20Balance, EthereumTransactionHistory, EthereumTransactionInfo>
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<CryptoCurrency, ERC20Balance>.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<Erc20Token> erc20TokensBox;
late final Box<Erc20Token> 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<bool> checkIfScanProviderIsEnabled() async {
bool isEtherscanEnabled = (await sharedPrefs.future).getBool("use_etherscan") ?? true;
return isEtherscanEnabled;
}
@override
WalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
Completer<SharedPreferences> _sharedPrefs = Completer();
Future<void> init() async {
Future<void> 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<void> movePreviousErc20BoxConfigsToNewBox() async {
// Opens a box specific to this wallet
ethereumErc20TokensBox = await CakeHive.openBox<Erc20Token>(
evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(
"${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<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@override
void close() {
_client.stop();
_transactionsUpdateTimer?.cancel();
}
@action
@override
Future<void> 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<PendingTransaction> 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<void> _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<Map<String, EthereumTransactionInfo>> fetchTransactions() async {
final address = _ethPrivateKey.address.hex;
final transactions = await _client.fetchTransactions(address);
final List<Future<List<EthereumTransactionModel>>> 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<String, EthereumTransactionInfo> 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<void> rescan({required int height}) {
throw UnimplementedError("rescan");
}
@override
Future<void> 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<void> 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<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'mnemonic': _mnemonic,
'private_key': privateKey,
'balance': balance[currency]!.toJSON(),
});
static Future<EthereumWallet> 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<void> _updateBalance() async {
balance[currency] = await _fetchEthBalance();
await _fetchErc20Balances();
await save();
}
Future<ERC20Balance> _fetchEthBalance() async {
final balance = await _client.getBalance(_ethPrivateKey.address);
return ERC20Balance(balance.getInWei);
}
Future<void> _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<EthPrivateKey> 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<int>));
}
Future<void>? updateBalance() async => await _updateBalance();
List<Erc20Token> get erc20Currencies => ethereumErc20TokensBox.values.toList();
Future<void> 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<void> deleteErc20Token(Erc20Token token) async {
await token.delete();
balance.remove(token);
_updateBalance();
}
Future<Erc20Token?> 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<void> 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<EthereumWallet> 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();
}

View file

@ -1,33 +0,0 @@
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';
class EthereumWalletAddresses = EthereumWalletAddressesBase with _$EthereumWalletAddresses;
abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
EthereumWalletAddressesBase(WalletInfo walletInfo)
: address = '',
super(walletInfo);
@override
String address;
@override
Future<void> init() async {
address = walletInfo.address;
await updateAddressesInBox();
}
@override
Future<void> updateAddressesInBox() async {
try {
addressesMap.clear();
addressesMap[address] = '';
await saveAddressesInBox();
} catch (e) {
print(e.toString());
}
}
}

View file

@ -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;
}

View file

@ -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<EthereumNewWalletCredentials,
EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromPrivateKey> {
EthereumWalletService(this.walletInfoSource);
class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
EthereumWalletService(super.walletInfoSource, {required this.client});
final Box<WalletInfo> walletInfoSource;
late EthereumClient client;
@override
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
WalletType getType() => WalletType.ethereum;
@override
Future<EthereumWallet> 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<EthereumNewWalletCredentials,
return wallet;
}
@override
WalletType getType() => WalletType.ethereum;
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<EthereumWallet> 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<EthereumNewWalletCredentials,
}
@override
Future<void> 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<void> 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<EthereumWallet> restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async {
Future<EthereumWallet> 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<EthereumNewWalletCredentials,
@override
Future<EthereumWallet> restoreFromSeed(
EthereumRestoreWalletFromSeedCredentials credentials) async {
EVMChainRestoreWalletFromSeedCredentials credentials) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EthereumMnemonicIsIncorrectException();
}
@ -93,6 +94,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
client: client,
);
await wallet.init();
@ -101,20 +103,4 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
return wallet;
}
@override
Future<void> 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);
}
}

View file

@ -1,39 +0,0 @@
import 'dart:io';
import 'package:cw_core/key.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
Future<void> write(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<void> writeData(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<String> read({required String path, required String password}) async {
final file = File(path);
if (!file.existsSync()) {
file.createSync();
}
final encrypted = file.readAsStringSync();
return decode(password: password, data: encrypted);
}

View file

@ -1,42 +0,0 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:cw_core/pending_transaction.dart';
import 'package:web3dart/crypto.dart';
class PendingEthereumTransaction with PendingTransaction {
final Function sendTransaction;
final Uint8List signedTransaction;
final BigInt fee;
final String amount;
final int exponent;
PendingEthereumTransaction({
required this.sendTransaction,
required this.signedTransaction,
required this.fee,
required this.amount,
required this.exponent,
});
@override
String get amountFormatted {
final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
return _amount.substring(0, min(10, _amount.length));
}
@override
Future<void> commit() async => await sendTransaction();
@override
String get feeFormatted {
final _fee = (fee / BigInt.from(pow(10, 18))).toString();
return _fee.substring(0, min(10, _fee.length));
}
@override
String get hex => bytesToHex(signedTransaction, include0x: true);
@override
String get id => '';
}

View file

@ -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