Complete Ethereum wallet creation flow

This commit is contained in:
OmarHatem 2023-01-04 16:51:23 +02:00
parent 91ef1fdc7d
commit 3dd6351ae2
11 changed files with 197 additions and 22 deletions

View file

@ -0,0 +1,2 @@
-
uri: 10.0.2.2:7545

View file

@ -11,6 +11,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.ltc;
case WalletType.haven:
return CryptoCurrency.xhv;
case WalletType.ethereum:
return CryptoCurrency.eth;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}

View file

@ -1,13 +1,21 @@
import 'dart:convert';
import 'package:cw_core/balance.dart';
import 'package:web3dart/web3dart.dart';
class EthereumBalance extends Balance {
EthereumBalance(super.available, super.additional);
@override
// TODO: implement formattedAdditionalBalance
String get formattedAdditionalBalance => throw UnimplementedError();
String get formattedAdditionalBalance {
return EtherAmount.fromUnitAndValue(EtherUnit.ether, additional.toString())
.getInEther
.toString();
}
@override
// TODO: implement formattedAvailableBalance
String get formattedAvailableBalance => throw UnimplementedError();
String get formattedAvailableBalance =>
EtherAmount.fromUnitAndValue(EtherUnit.ether, available.toString()).getInEther.toString();
String toJSON() => json.encode({'available': available, 'additional': additional});
}

View file

@ -0,0 +1,25 @@
import 'package:cw_core/node.dart';
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
class EthereumClient {
late final Web3Client _client;
Future<bool> connect(Node node) async {
try {
_client = Web3Client(node.uriRaw, Client());
return true;
} catch (e) {
return false;
}
}
Future<EtherAmount> getBalance(String privateKey) async {
final private = EthPrivateKey.fromHex(privateKey);
return _client.getBalance(private.address);
}
Future<EtherAmount> getGasPrice() async => _client.getGasPrice();
}

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:cw_core/crypto_currency.dart';
@ -10,11 +11,13 @@ 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/ethereum_balance.dart';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_transaction_history.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
import 'package:cw_ethereum/file.dart';
import 'package:mobx/mobx.dart';
import 'package:web3dart/web3dart.dart';
part 'ethereum_wallet.g.dart';
@ -28,9 +31,12 @@ abstract class EthereumWalletBase
required this.mnemonic,
required this.privateKey,
required String password,
EthereumBalance? initialBalance,
}) : syncStatus = NotConnectedSyncStatus(),
_password = password,
walletAddresses = EthereumWalletAddresses(walletInfo),
balance = ObservableMap<CryptoCurrency, EthereumBalance>.of(
{CryptoCurrency.eth: initialBalance ?? EthereumBalance(0, 0)}),
super(walletInfo) {
this.walletInfo = walletInfo;
transactionHistory = EthereumTransactionHistory();
@ -40,46 +46,68 @@ abstract class EthereumWalletBase
final String privateKey;
final String _password;
late EthereumClient _client;
EtherAmount? _gasPrice;
@override
WalletAddresses walletAddresses;
@override
SyncStatus syncStatus;
@override
ObservableMap<CryptoCurrency, EthereumBalance> get balance => throw UnimplementedError();
@observable
late ObservableMap<CryptoCurrency, EthereumBalance> balance;
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
throw UnimplementedError();
throw UnimplementedError("calculateEstimatedFee");
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError();
throw UnimplementedError("changePassword");
}
@override
void close() {}
@override
Future<void> connectToNode({required Node node}) {
throw UnimplementedError();
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
final isConnected = await _client.connect(node);
if (!isConnected) {
throw Exception("Ethereum Node connection failed");
}
_updateBalance();
syncStatus = ConnectedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) {
throw UnimplementedError();
throw UnimplementedError("createTransaction");
}
@override
Future<Map<String, EthereumTransactionInfo>> fetchTransactions() {
throw UnimplementedError();
throw UnimplementedError("fetchTransactions");
}
@override
Object get keys => throw UnimplementedError();
Object get keys => throw UnimplementedError("keys");
@override
Future<void> rescan({required int height}) {
throw UnimplementedError();
throw UnimplementedError("rescan");
}
@override
@ -93,17 +121,47 @@ abstract class EthereumWalletBase
String get seed => mnemonic;
@override
Future<void> startSync() {
throw UnimplementedError();
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await _updateBalance();
_gasPrice = await _client.getGasPrice();
Timer.periodic(
const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasPrice());
syncStatus = SyncedSyncStatus();
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
syncStatus = FailedSyncStatus();
}
}
@override
WalletAddresses walletAddresses;
int feeRate() {
if (_gasPrice != null) {
return _gasPrice!.getInEther.toInt();
}
return 0;
}
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'mnemonic': mnemonic,
'balance': balance[currency]!.toJSON(),
// TODO: save other attributes
});
Future<void> _updateBalance() async {
balance[currency] = await _fetchBalances();
await save();
}
Future<EthereumBalance> _fetchBalances() async {
final balance = await _client.getBalance(privateKey);
return EthereumBalance(balance.getInEther.toInt(), balance.getInEther.toInt());
}
}

View file

@ -12,11 +12,12 @@ environment:
dependencies:
flutter:
sdk: flutter
# web3dart: ^2.4.1
web3dart: ^2.4.1
mobx: ^2.0.7+4
bip39: ^1.0.6
ed25519_hd_key: ^2.2.0
hex: ^0.2.0
http: ^0.13.4
cw_core:
path: ../cw_core

View file

@ -24,6 +24,7 @@ const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = '10.0.2.2:7545';
Future defaultSettingsMigration(
{required int version,
@ -143,6 +144,12 @@ Future defaultSettingsMigration(
await validateBitcoinSavedTransactionPriority(sharedPreferences);
break;
case 20:
await addEthereumNodeList(nodes: nodes);
await changeEthereumCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
default:
break;
}
@ -228,6 +235,12 @@ Node? getHavenDefaultNode({required Box<Node> nodes}) {
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
}
Node? getEthereumDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == ethereumDefaultNodeUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
}
Node getMoneroDefaultNode({required Box<Node> nodes}) {
final timeZone = DateTime.now().timeZoneOffset.inHours;
var nodeUri = '';
@ -424,6 +437,8 @@ Future<void> checkCurrentNodes(
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences
.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences
.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentMoneroNode = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
@ -432,6 +447,8 @@ Future<void> checkCurrentNodes(
(node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentEthereumNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode =
@ -465,6 +482,13 @@ Future<void> checkCurrentNodes(
await sharedPreferences.setInt(
PreferencesKey.currentHavenNodeIdKey, node.key as int);
}
if (currentEthereumNodeServer == null) {
final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentEthereumNodeIdKey, node.key as int);
}
}
Future<void> resetBitcoinElectrumServer(
@ -501,3 +525,21 @@ Future<void> changeDefaultHavenNode(
await node.save();
});
}
Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultEthereumNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changeEthereumCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
final node = getEthereumDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
}

View file

@ -70,6 +70,22 @@ Future<List<Node>> loadDefaultHavenNodes() async {
return nodes;
}
Future<List<Node>> loadDefaultEthereumNodes() async {
final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml');
final loadedNodes = loadYaml(nodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.ethereum;
nodes.add(node);
}
}
return nodes;
}
Future resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();

View file

@ -5,6 +5,7 @@ class PreferencesKey {
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentHavenNodeIdKey = 'current_node_id_xhv';
static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentFiatCurrencyKey = 'current_fiat_currency';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';

View file

@ -30,7 +30,8 @@ class MenuWidgetState extends State<MenuWidget> {
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
this.havenIcon = Image.asset('assets/images/haven_menu.png');
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
final largeScreen = 731;
@ -47,6 +48,7 @@ class MenuWidgetState extends State<MenuWidget> {
Image bitcoinIcon;
Image litecoinIcon;
Image havenIcon;
Image ethereumIcon;
@override
void initState() {
@ -92,8 +94,6 @@ class MenuWidgetState extends State<MenuWidget> {
color: Theme.of(context).accentTextTheme!.overline!.decorationColor!);
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
color: Theme.of(context).accentTextTheme!.overline!.decorationColor!);
litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
havenIcon = Image.asset('assets/images/haven_menu.png');
return Row(
mainAxisSize: MainAxisSize.max,
@ -259,6 +259,8 @@ class MenuWidgetState extends State<MenuWidget> {
return litecoinIcon;
case WalletType.haven:
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
default:
throw Exception('No icon for ${type.toString()}');
}

View file

@ -318,10 +318,13 @@ abstract class SettingsStoreBase with Store {
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final havenNodeId = sharedPreferences
.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences
.getInt(PreferencesKey.currentEthereumNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId);
final packageInfo = await PackageInfo.fromPlatform();
final shouldShowYatPopup =
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
@ -344,6 +347,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.haven] = havenNode;
}
if (ethereumNode != null) {
nodes[WalletType.ethereum] = ethereumNode;
}
return SettingsStore(
sharedPreferences: sharedPreferences,
nodes: nodes,
@ -429,10 +436,13 @@ abstract class SettingsStoreBase with Store {
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final havenNodeId = sharedPreferences
.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences
.getInt(PreferencesKey.currentEthereumNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId);
if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode;
@ -449,6 +459,10 @@ abstract class SettingsStoreBase with Store {
if (havenNode != null) {
nodes[WalletType.haven] = havenNode;
}
if (ethereumNode != null) {
nodes[WalletType.ethereum] = ethereumNode;
}
}
Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
@ -469,6 +483,10 @@ abstract class SettingsStoreBase with Store {
await _sharedPreferences.setInt(
PreferencesKey.currentHavenNodeIdKey, node.key as int);
break;
case WalletType.ethereum:
await _sharedPreferences.setInt(
PreferencesKey.currentEthereumNodeIdKey, node.key as int);
break;
default:
break;
}