stack_wallet/lib/services/coins/ethereum/ethereum_wallet.dart

457 lines
13 KiB
Dart
Raw Normal View History

2022-12-13 17:39:19 +00:00
import 'dart:math';
import 'package:bip39/bip39.dart' as bip39;
import 'package:decimal/decimal.dart';
2022-12-14 10:15:22 +00:00
import 'package:stack_wallet_backup/generate_password.dart';
2022-12-13 17:39:19 +00:00
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/models/paymint/utxo_model.dart';
import 'package:stackwallet/services/price.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
2022-12-14 10:15:22 +00:00
import 'package:stackwallet/utilities/prefs.dart';
2022-12-13 17:39:19 +00:00
import 'package:string_to_hex/string_to_hex.dart';
import 'package:web3dart/web3dart.dart';
2022-12-14 16:09:24 +00:00
import 'package:web3dart/web3dart.dart' as Transaction;
2022-12-20 12:54:51 +00:00
2022-12-13 17:39:19 +00:00
import 'package:http/http.dart';
import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
2022-12-20 12:54:51 +00:00
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
2022-12-13 17:39:19 +00:00
const int MINIMUM_CONFIRMATIONS = 1;
const int DUST_LIMIT = 294;
const String GENESIS_HASH_MAINNET =
2022-12-14 10:15:22 +00:00
"0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa";
2022-12-13 17:39:19 +00:00
class EthereumWallet extends CoinServiceAPI {
@override
set isFavorite(bool markFavorite) {
DB.instance.put<dynamic>(
boxName: walletId, key: "isFavorite", value: markFavorite);
}
@override
bool get isFavorite {
try {
return DB.instance.get<dynamic>(boxName: walletId, key: "isFavorite")
as bool;
} catch (e, s) {
Logging.instance.log(
"isFavorite fetch failed (returning false by default): $e\n$s",
level: LogLevel.Error);
return false;
}
}
@override
Coin get coin => _coin;
late SecureStorageInterface _secureStore;
late PriceAPI _priceAPI;
2022-12-14 10:15:22 +00:00
final _prefs = Prefs.instance;
final _client = Web3Client(
2022-12-20 12:54:51 +00:00
"https://goerli.infura.io/v3/22677300bf774e49a458b73313ee56ba", Client());
2022-12-13 17:39:19 +00:00
2022-12-14 16:09:24 +00:00
late EthPrivateKey _credentials;
2022-12-13 17:39:19 +00:00
EthereumWallet(
{required String walletId,
required String walletName,
required Coin coin,
PriceAPI? priceAPI,
required SecureStorageInterface secureStore}) {
_walletId = walletId;
_walletName = walletName;
_coin = coin;
_priceAPI = priceAPI ?? PriceAPI(Client());
_secureStore = secureStore;
}
@override
bool shouldAutoSync = false;
@override
String get walletName => _walletName;
late String _walletName;
late Coin _coin;
@override
// TODO: implement allOwnAddresses
2022-12-14 16:09:24 +00:00
Future<List<String>> get allOwnAddresses =>
_allOwnAddresses ??= _fetchAllOwnAddresses();
Future<List<String>>? _allOwnAddresses;
Future<List<String>> _fetchAllOwnAddresses() async {
List<String> addresses = [];
final ownAddress = _credentials.address;
addresses.add(ownAddress.toString());
return addresses;
}
2022-12-13 17:39:19 +00:00
@override
2022-12-14 16:09:24 +00:00
Future<Decimal> get availableBalance async {
EtherAmount ethBalance = await _client.getBalance(_credentials.address);
return Decimal.parse(ethBalance.getValueInUnit(EtherUnit.ether).toString());
}
2022-12-13 17:39:19 +00:00
@override
// TODO: implement balanceMinusMaxFee
Future<Decimal> get balanceMinusMaxFee => throw UnimplementedError();
@override
2022-12-14 16:09:24 +00:00
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
2022-12-20 12:54:51 +00:00
print("CALLING CONFIRM SEND WITH $txData");
final gasPrice = await _client.getGasPrice();
print("GAS PRICE IS $gasPrice");
// final fee = "21,000" * (gasPrice! + 2 );
final tx = Transaction.Transaction(
to: EthereumAddress.fromHex(txData['addresss'] as String),
gasPrice: gasPrice,
maxGas: 21000,
value: EtherAmount.fromUnitAndValue(EtherUnit.ether, 1));
2022-12-14 16:09:24 +00:00
final transaction = await _client.sendTransaction(
_credentials,
2022-12-20 12:54:51 +00:00
tx,
2022-12-14 16:09:24 +00:00
);
return transaction;
2022-12-13 17:39:19 +00:00
}
@override
2022-12-14 16:09:24 +00:00
Future<String> get currentReceivingAddress async {
final _currentReceivingAddress = _credentials.address;
return _currentReceivingAddress.toString();
}
2022-12-13 17:39:19 +00:00
@override
2022-12-20 12:54:51 +00:00
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
print("CALLING ESTIMATE FEE");
2022-12-13 17:39:19 +00:00
// TODO: implement estimateFeeFor
2022-12-20 12:54:51 +00:00
// throw UnimplementedError();
return 1;
2022-12-13 17:39:19 +00:00
}
@override
Future<void> exit() {
// TODO: implement exit
throw UnimplementedError();
}
@override
2022-12-14 16:09:24 +00:00
Future<FeeObject> get fees => _feeObject ??= _getFees();
Future<FeeObject>? _feeObject;
Future<FeeObject> _getFees() async {
return FeeObject(
numberOfBlocksFast: 10,
numberOfBlocksAverage: 10,
numberOfBlocksSlow: 10,
fast: 1,
medium: 1,
slow: 1);
}
2022-12-13 17:39:19 +00:00
@override
Future<void> fullRescan(
int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) {
// TODO: implement fullRescan
throw UnimplementedError();
}
@override
Future<bool> generateNewAddress() {
// TODO: implement generateNewAddress
throw UnimplementedError();
}
2022-12-20 12:54:51 +00:00
bool _hasCalledExit = false;
2022-12-13 17:39:19 +00:00
@override
2022-12-20 12:54:51 +00:00
bool get hasCalledExit => _hasCalledExit;
2022-12-13 17:39:19 +00:00
@override
2022-12-14 10:15:22 +00:00
Future<void> initializeExisting() async {
Logging.instance.log("Opening existing ${coin.prettyName} wallet.",
level: LogLevel.Info);
if ((DB.instance.get<dynamic>(boxName: walletId, key: "id")) == null) {
throw Exception(
"Attempted to initialize an existing wallet using an unknown wallet ID!");
}
await _prefs.init();
final data =
DB.instance.get<dynamic>(boxName: walletId, key: "latest_tx_model")
as TransactionData?;
if (data != null) {
_transactionData = Future(() => data);
}
2022-12-13 17:39:19 +00:00
}
@override
2022-12-14 10:15:22 +00:00
Future<void> initializeNew() async {
await _prefs.init();
final String mnemonic = bip39.generateMnemonic(strength: 256);
2022-12-14 16:09:24 +00:00
print("Mnemonic is $mnemonic");
_credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic));
2022-12-14 10:15:22 +00:00
final String password = generatePassword();
var rng = Random.secure();
2022-12-14 16:09:24 +00:00
Wallet wallet = Wallet.createNew(_credentials, password, rng);
2022-12-14 10:15:22 +00:00
await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic);
await DB.instance
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'receivingAddresses', value: ["0"]);
await DB.instance
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
await DB.instance
.put<dynamic>(boxName: walletId, key: "changeIndex", value: 0);
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'blocked_tx_hashes',
value: ["0xdefault"],
); // A list of transaction hashes to represent frozen utxos in wallet
// initialize address book entries
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'addressBookEntries',
value: <String, String>{});
await DB.instance
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
2022-12-13 17:39:19 +00:00
}
2022-12-14 16:09:24 +00:00
bool _isConnected = false;
2022-12-13 17:39:19 +00:00
@override
2022-12-14 16:09:24 +00:00
bool get isConnected => _isConnected;
2022-12-13 17:39:19 +00:00
2022-12-14 16:09:24 +00:00
bool refreshMutex = false;
2022-12-13 17:39:19 +00:00
@override
2022-12-14 16:09:24 +00:00
bool get isRefreshing => refreshMutex;
2022-12-13 17:39:19 +00:00
@override
// TODO: implement maxFee
Future<int> get maxFee => throw UnimplementedError();
@override
2022-12-14 10:15:22 +00:00
Future<List<String>> get mnemonic => _getMnemonicList();
2022-12-20 12:54:51 +00:00
// Future<int> get chainHeight async {
// try {
// final result = await _client.getSyncStatus();
// print("HEIGHT IS $result");
// return 1 as int;
// } catch (e, s) {
// Logging.instance.log("Exception caught in chainHeight: $e\n$s",
// level: LogLevel.Error);
// return -1;
// }
// }
//
// int get storedChainHeight {
// final storedHeight = DB.instance
// .get<dynamic>(boxName: walletId, key: "storedChainHeight") as int?;
// return storedHeight ?? 0;
// }
//
// Future<void> updateStoredChainHeight({required int newHeight}) async {
// await DB.instance.put<dynamic>(
// boxName: walletId, key: "storedChainHeight", value: newHeight);
// }
2022-12-14 10:15:22 +00:00
Future<List<String>> _getMnemonicList() async {
final mnemonicString =
await _secureStore.read(key: '${_walletId}_mnemonic');
if (mnemonicString == null) {
return [];
}
final List<String> data = mnemonicString.split(' ');
return data;
}
2022-12-13 17:39:19 +00:00
@override
// TODO: implement pendingBalance
Future<Decimal> get pendingBalance => throw UnimplementedError();
2022-12-20 12:54:51 +00:00
// Future<Decimal> transactionFee(int satoshiAmount) {}
2022-12-13 17:39:19 +00:00
@override
Future<Map<String, dynamic>> prepareSend(
{required String address,
required int satoshiAmount,
2022-12-20 12:54:51 +00:00
Map<String, dynamic>? args}) async {
print("CALLING PREPARE SEND ${Decimal.fromInt(satoshiAmount)}");
Map<String, dynamic> txData = {
"fee": 0,
"addresss": address,
"recipientAmt": satoshiAmount,
};
return txData;
2022-12-13 17:39:19 +00:00
}
@override
Future<void> recoverFromMnemonic(
{required String mnemonic,
required int maxUnusedAddressGap,
required int maxNumberOfIndexesToCheck,
2022-12-14 16:09:24 +00:00
required int height}) async {
await _prefs.init();
print("Mnemonic is $mnemonic");
_credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic));
final String password = generatePassword();
var rng = Random.secure();
Wallet wallet = Wallet.createNew(_credentials, password, rng);
await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic);
await DB.instance
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'receivingAddresses', value: ["0"]);
await DB.instance
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
await DB.instance
.put<dynamic>(boxName: walletId, key: "changeIndex", value: 0);
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'blocked_tx_hashes',
value: ["0xdefault"],
); // A list of transaction hashes to represent frozen utxos in wallet
// initialize address book entries
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'addressBookEntries',
value: <String, String>{});
await DB.instance
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
2022-12-13 17:39:19 +00:00
}
@override
2022-12-20 12:54:51 +00:00
Future<void> refresh() async {
if (refreshMutex) {
Logging.instance.log("$walletId $walletName refreshMutex denied",
level: LogLevel.Info);
return;
} else {
refreshMutex = true;
}
try {
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.syncing,
walletId,
coin,
),
);
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
// final currentHeight = await chainHeight;
const storedHeight = 1; //await storedChainHeight;
} catch (error, strace) {
refreshMutex = false;
GlobalEventBus.instance.fire(
NodeConnectionStatusChangedEvent(
NodeConnectionStatus.disconnected,
walletId,
coin,
),
);
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.unableToSync,
walletId,
coin,
),
);
Logging.instance.log(
"Caught exception in refreshWalletData(): $error\n$strace",
level: LogLevel.Warning);
}
2022-12-13 17:39:19 +00:00
}
@override
Future<String> send(
{required String toAddress,
required int amount,
Map<String, String> args = const {}}) {
// TODO: implement send
throw UnimplementedError();
}
@override
Future<bool> testNetworkConnection() {
// TODO: implement testNetworkConnection
throw UnimplementedError();
}
@override
2022-12-14 16:09:24 +00:00
// TODO: Check difference between total and available balance for eth
Future<Decimal> get totalBalance async {
EtherAmount ethBalance = await _client.getBalance(_credentials.address);
2022-12-20 12:54:51 +00:00
print(
"BALANCE NOW IS ${ethBalance.getValueInUnit(EtherUnit.ether).toString()}");
2022-12-14 16:09:24 +00:00
return Decimal.parse(ethBalance.getValueInUnit(EtherUnit.ether).toString());
}
2022-12-13 17:39:19 +00:00
@override
2022-12-14 10:15:22 +00:00
Future<TransactionData> get transactionData =>
_transactionData ??= _fetchTransactionData();
Future<TransactionData>? _transactionData;
TransactionData? cachedTxData;
2022-12-13 17:39:19 +00:00
@override
// TODO: implement unspentOutputs
Future<List<UtxoObject>> get unspentOutputs => throw UnimplementedError();
@override
Future<void> updateNode(bool shouldRefresh) {
// TODO: implement updateNode
throw UnimplementedError();
}
@override
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) {
// TODO: implement updateSentCachedTxData
throw UnimplementedError();
}
@override
bool validateAddress(String address) {
// TODO: implement validateAddress
2022-12-14 16:09:24 +00:00
return true;
2022-12-13 17:39:19 +00:00
}
2022-12-14 10:15:22 +00:00
Future<TransactionData> _fetchTransactionData() async {
throw UnimplementedError();
}
2022-12-13 17:39:19 +00:00
@override
String get walletId => _walletId;
late String _walletId;
@override
@override
set walletName(String newName) => _walletName = newName;
}