mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-22 18:54:47 +00:00
Complete Ethereum wallet creation flow
This commit is contained in:
parent
91ef1fdc7d
commit
3dd6351ae2
11 changed files with 197 additions and 22 deletions
2
assets/ethereum_server_list.yml
Normal file
2
assets/ethereum_server_list.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
-
|
||||
uri: 10.0.2.2:7545
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
25
cw_ethereum/lib/ethereum_client.dart
Normal file
25
cw_ethereum/lib/ethereum_client.dart
Normal 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();
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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()}');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue