2024-01-10 16:08:12 +00:00
|
|
|
// /*
|
|
|
|
// * This file is part of Stack Wallet.
|
|
|
|
// *
|
|
|
|
// * Copyright (c) 2023 Cypher Stack
|
|
|
|
// * All Rights Reserved.
|
|
|
|
// * The code is distributed under GPLv3 license, see LICENSE file for details.
|
|
|
|
// * Generated by Cypher Stack on 2023-05-26
|
|
|
|
// *
|
|
|
|
// */
|
|
|
|
//
|
|
|
|
// import 'dart:async';
|
|
|
|
//
|
|
|
|
// import 'package:bip39/bip39.dart' as bip39;
|
|
|
|
// import 'package:ethereum_addresses/ethereum_addresses.dart';
|
|
|
|
// import 'package:http/http.dart';
|
|
|
|
// import 'package:isar/isar.dart';
|
|
|
|
// import 'package:stackwallet/db/hive/db.dart';
|
|
|
|
// import 'package:stackwallet/db/isar/main_db.dart';
|
|
|
|
// import 'package:stackwallet/models/balance.dart';
|
|
|
|
// import 'package:stackwallet/models/isar/models/isar_models.dart';
|
|
|
|
// import 'package:stackwallet/models/node_model.dart';
|
|
|
|
// import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
|
|
|
// import 'package:stackwallet/services/coins/coin_service.dart';
|
|
|
|
// import 'package:stackwallet/services/ethereum/ethereum_api.dart';
|
|
|
|
// 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/updated_in_background_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';
|
|
|
|
// import 'package:stackwallet/services/mixins/eth_token_cache.dart';
|
|
|
|
// import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
|
|
|
// import 'package:stackwallet/services/mixins/wallet_db.dart';
|
|
|
|
// import 'package:stackwallet/services/node_service.dart';
|
|
|
|
// import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
|
|
|
// import 'package:stackwallet/utilities/amount/amount.dart';
|
|
|
|
// import 'package:stackwallet/utilities/constants.dart';
|
|
|
|
// import 'package:stackwallet/utilities/default_nodes.dart';
|
|
|
|
// import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|
|
|
// import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
|
|
|
// import 'package:stackwallet/utilities/eth_commons.dart';
|
|
|
|
// import 'package:stackwallet/utilities/extensions/extensions.dart';
|
|
|
|
// import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
|
|
|
// import 'package:stackwallet/utilities/logger.dart';
|
|
|
|
// import 'package:stackwallet/utilities/prefs.dart';
|
|
|
|
// import 'package:stackwallet/widgets/crypto_notifications.dart';
|
|
|
|
// import 'package:tuple/tuple.dart';
|
|
|
|
// import 'package:web3dart/web3dart.dart' as web3;
|
|
|
|
//
|
|
|
|
// const int MINIMUM_CONFIRMATIONS = 3;
|
|
|
|
//
|
|
|
|
// class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|
|
|
// EthereumWallet({
|
|
|
|
// required String walletId,
|
|
|
|
// required String walletName,
|
|
|
|
// required Coin coin,
|
|
|
|
// required SecureStorageInterface secureStore,
|
|
|
|
// required TransactionNotificationTracker tracker,
|
|
|
|
// MainDB? mockableOverride,
|
|
|
|
// }) {
|
|
|
|
// txTracker = tracker;
|
|
|
|
// _walletId = walletId;
|
|
|
|
// _walletName = walletName;
|
|
|
|
// _coin = coin;
|
|
|
|
// _secureStore = secureStore;
|
|
|
|
// initCache(walletId, coin);
|
|
|
|
// initWalletDB(mockableOverride: mockableOverride);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Balance getCachedTokenBalance(EthContract contract) {
|
|
|
|
// final jsonString = DB.instance.get<dynamic>(
|
|
|
|
// boxName: _walletId,
|
|
|
|
// key: TokenCacheKeys.tokenBalance(contract.address),
|
|
|
|
// ) as String?;
|
|
|
|
// if (jsonString == null) {
|
|
|
|
// return Balance(
|
|
|
|
// total: Amount(
|
|
|
|
// rawValue: BigInt.zero,
|
|
|
|
// fractionDigits: contract.decimals,
|
|
|
|
// ),
|
|
|
|
// spendable: Amount(
|
|
|
|
// rawValue: BigInt.zero,
|
|
|
|
// fractionDigits: contract.decimals,
|
|
|
|
// ),
|
|
|
|
// blockedTotal: Amount(
|
|
|
|
// rawValue: BigInt.zero,
|
|
|
|
// fractionDigits: contract.decimals,
|
|
|
|
// ),
|
|
|
|
// pendingSpendable: Amount(
|
|
|
|
// rawValue: BigInt.zero,
|
|
|
|
// fractionDigits: contract.decimals,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// return Balance.fromJson(
|
|
|
|
// jsonString,
|
|
|
|
// contract.decimals,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// bool longMutex = false;
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<List<Transaction>> get transactions => db
|
|
|
|
// .getTransactions(walletId)
|
|
|
|
// .filter()
|
|
|
|
// .otherDataEqualTo(
|
|
|
|
// null) // eth txns with other data where other data is the token contract address
|
|
|
|
// .sortByTimestampDesc()
|
|
|
|
// .findAll();
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<String> get currentReceivingAddress async {
|
|
|
|
// final address = await _currentReceivingAddress;
|
|
|
|
// return checksumEthereumAddress(
|
|
|
|
// address?.value ?? _credentials.address.hexEip55);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<void> initializeExisting() async {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "initializeExisting() ${coin.prettyName} wallet",
|
|
|
|
// level: LogLevel.Info,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await _initCredentials(
|
|
|
|
// (await mnemonicString)!,
|
|
|
|
// (await mnemonicPassphrase)!,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// if (getCachedId() == null) {
|
|
|
|
// throw Exception(
|
|
|
|
// "Attempted to initialize an existing wallet using an unknown wallet ID!");
|
|
|
|
// }
|
|
|
|
// await _prefs.init();
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<void> initializeNew(
|
|
|
|
// ({String mnemonicPassphrase, int wordCount})? data,
|
|
|
|
// ) async {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Generating new ${coin.prettyName} wallet.",
|
|
|
|
// level: LogLevel.Info,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// if (getCachedId() != null) {
|
|
|
|
// throw Exception(
|
|
|
|
// "Attempted to initialize a new wallet using an existing wallet ID!");
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// await _prefs.init();
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// await _generateNewWallet(data);
|
|
|
|
// } catch (e, s) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Exception rethrown from initializeNew(): $e\n$s",
|
|
|
|
// level: LogLevel.Fatal,
|
|
|
|
// );
|
|
|
|
// rethrow;
|
|
|
|
// }
|
|
|
|
// await Future.wait([
|
|
|
|
// updateCachedId(walletId),
|
|
|
|
// updateCachedIsFavorite(false),
|
|
|
|
// ]);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Future<void> _generateNewWallet(
|
|
|
|
// ({String mnemonicPassphrase, int wordCount})? data,
|
|
|
|
// ) async {
|
|
|
|
// // Logging.instance
|
|
|
|
// // .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info);
|
|
|
|
// // if (!integrationTestFlag) {
|
|
|
|
// // try {
|
|
|
|
// // final features = await electrumXClient.getServerFeatures();
|
|
|
|
// // Logging.instance.log("features: $features", level: LogLevel.Info);
|
|
|
|
// // switch (coin) {
|
|
|
|
// // case Coin.namecoin:
|
|
|
|
// // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
|
|
|
|
// // throw Exception("genesis hash does not match main net!");
|
|
|
|
// // }
|
|
|
|
// // break;
|
|
|
|
// // default:
|
|
|
|
// // throw Exception(
|
|
|
|
// // "Attempted to generate a EthereumWallet using a non eth coin type: ${coin.name}");
|
|
|
|
// // }
|
|
|
|
// // } catch (e, s) {
|
|
|
|
// // Logging.instance.log("$e/n$s", level: LogLevel.Info);
|
|
|
|
// // }
|
|
|
|
// // }
|
|
|
|
//
|
|
|
|
// // this should never fail - sanity check
|
|
|
|
// if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) {
|
|
|
|
// throw Exception(
|
|
|
|
// "Attempted to overwrite mnemonic on generate new wallet!");
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// final int strength;
|
|
|
|
// if (data == null || data.wordCount == 12) {
|
|
|
|
// strength = 128;
|
|
|
|
// } else if (data.wordCount == 24) {
|
|
|
|
// strength = 256;
|
|
|
|
// } else {
|
|
|
|
// throw Exception("Invalid word count");
|
|
|
|
// }
|
|
|
|
// final String mnemonic = bip39.generateMnemonic(strength: strength);
|
|
|
|
// final String passphrase = data?.mnemonicPassphrase ?? "";
|
|
|
|
// await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic);
|
|
|
|
// await _secureStore.write(
|
|
|
|
// key: '${_walletId}_mnemonicPassphrase',
|
|
|
|
// value: passphrase,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await _generateAndSaveAddress(mnemonic, passphrase);
|
|
|
|
//
|
|
|
|
// Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// bool _isConnected = false;
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// bool get isConnected => _isConnected;
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// bool get isRefreshing => refreshMutex;
|
|
|
|
//
|
|
|
|
// bool refreshMutex = false;
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<Map<String, dynamic>> prepareSend({
|
|
|
|
// required String address,
|
|
|
|
// required Amount amount,
|
|
|
|
// Map<String, dynamic>? args,
|
|
|
|
// }) async {
|
|
|
|
// final feeRateType = args?["feeRate"];
|
|
|
|
// int fee = 0;
|
|
|
|
// final feeObject = await fees;
|
|
|
|
// switch (feeRateType) {
|
|
|
|
// case FeeRateType.fast:
|
|
|
|
// fee = feeObject.fast;
|
|
|
|
// break;
|
|
|
|
// case FeeRateType.average:
|
|
|
|
// fee = feeObject.medium;
|
|
|
|
// break;
|
|
|
|
// case FeeRateType.slow:
|
|
|
|
// fee = feeObject.slow;
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// final feeEstimate = await estimateFeeFor(amount, fee);
|
|
|
|
//
|
|
|
|
// // bool isSendAll = false;
|
|
|
|
// // final availableBalance = balance.spendable;
|
|
|
|
// // if (satoshiAmount == availableBalance) {
|
|
|
|
// // isSendAll = true;
|
|
|
|
// // }
|
|
|
|
// //
|
|
|
|
// // if (isSendAll) {
|
|
|
|
// // //Subtract fee amount from send amount
|
|
|
|
// // satoshiAmount -= feeEstimate;
|
|
|
|
// // }
|
|
|
|
//
|
|
|
|
// final client = getEthClient();
|
|
|
|
//
|
|
|
|
// final myAddress = await currentReceivingAddress;
|
|
|
|
// final myWeb3Address = web3.EthereumAddress.fromHex(myAddress);
|
|
|
|
//
|
|
|
|
// final est = await client.estimateGas(
|
|
|
|
// sender: myWeb3Address,
|
|
|
|
// to: web3.EthereumAddress.fromHex(address),
|
|
|
|
// gasPrice: web3.EtherAmount.fromUnitAndValue(
|
|
|
|
// web3.EtherUnit.wei,
|
|
|
|
// fee,
|
|
|
|
// ),
|
|
|
|
// amountOfGas: BigInt.from(_gasLimit),
|
|
|
|
// value: web3.EtherAmount.inWei(amount.raw),
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// final nonce = args?["nonce"] as int? ??
|
|
|
|
// await client.getTransactionCount(myWeb3Address,
|
|
|
|
// atBlock: const web3.BlockNum.pending());
|
|
|
|
//
|
|
|
|
// final nResponse = await EthereumAPI.getAddressNonce(address: myAddress);
|
|
|
|
// print("==============================================================");
|
|
|
|
// print("ETH client.estimateGas: $est");
|
|
|
|
// print("ETH estimateFeeFor : $feeEstimate");
|
|
|
|
// print("ETH nonce custom response: $nResponse");
|
|
|
|
// print("ETH actual nonce : $nonce");
|
|
|
|
// print("==============================================================");
|
|
|
|
//
|
|
|
|
// final tx = web3.Transaction(
|
|
|
|
// to: web3.EthereumAddress.fromHex(address),
|
|
|
|
// gasPrice: web3.EtherAmount.fromUnitAndValue(
|
|
|
|
// web3.EtherUnit.wei,
|
|
|
|
// fee,
|
|
|
|
// ),
|
|
|
|
// maxGas: _gasLimit,
|
|
|
|
// value: web3.EtherAmount.inWei(amount.raw),
|
|
|
|
// nonce: nonce,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// Map<String, dynamic> txData = {
|
|
|
|
// "fee": feeEstimate,
|
|
|
|
// "feeInWei": fee,
|
|
|
|
// "address": address,
|
|
|
|
// "recipientAmt": amount,
|
|
|
|
// "ethTx": tx,
|
|
|
|
// "chainId": (await client.getChainId()).toInt(),
|
|
|
|
// "nonce": tx.nonce,
|
|
|
|
// };
|
|
|
|
//
|
|
|
|
// return txData;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<String> confirmSend({required Map<String, dynamic> txData}) async {
|
|
|
|
// web3.Web3Client client = getEthClient();
|
|
|
|
//
|
|
|
|
// final txid = await client.sendTransaction(
|
|
|
|
// _credentials,
|
|
|
|
// txData["ethTx"] as web3.Transaction,
|
|
|
|
// chainId: txData["chainId"] as int,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// return txid;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<void> recoverFromMnemonic({
|
|
|
|
// required String mnemonic,
|
|
|
|
// String? mnemonicPassphrase,
|
|
|
|
// required int maxUnusedAddressGap,
|
|
|
|
// required int maxNumberOfIndexesToCheck,
|
|
|
|
// required int height,
|
|
|
|
// }) async {
|
|
|
|
// longMutex = true;
|
|
|
|
// final start = DateTime.now();
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// // check to make sure we aren't overwriting a mnemonic
|
|
|
|
// // this should never fail
|
|
|
|
// if ((await mnemonicString) != null ||
|
|
|
|
// (await this.mnemonicPassphrase) != null) {
|
|
|
|
// longMutex = false;
|
|
|
|
// throw Exception("Attempted to overwrite mnemonic on restore!");
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// await _secureStore.write(
|
|
|
|
// key: '${_walletId}_mnemonic', value: mnemonic.trim());
|
|
|
|
// await _secureStore.write(
|
|
|
|
// key: '${_walletId}_mnemonicPassphrase',
|
|
|
|
// value: mnemonicPassphrase ?? "",
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// String privateKey =
|
|
|
|
// getPrivateKey(mnemonic.trim(), mnemonicPassphrase ?? "");
|
|
|
|
// _credentials = web3.EthPrivateKey.fromHex(privateKey);
|
|
|
|
//
|
|
|
|
// final address = Address(
|
|
|
|
// walletId: walletId,
|
|
|
|
// value: _credentials.address.hexEip55,
|
|
|
|
// publicKey: [], // maybe store address bytes here? seems a waste of space though
|
|
|
|
// derivationIndex: 0,
|
|
|
|
// derivationPath: DerivationPath()..value = "$hdPathEthereum/0",
|
|
|
|
// type: AddressType.ethereum,
|
|
|
|
// subType: AddressSubType.receiving,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await db.putAddress(address);
|
|
|
|
//
|
|
|
|
// await Future.wait([
|
|
|
|
// updateCachedId(walletId),
|
|
|
|
// updateCachedIsFavorite(false),
|
|
|
|
// ]);
|
|
|
|
// } catch (e, s) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Exception rethrown from recoverFromMnemonic(): $e\n$s",
|
|
|
|
// level: LogLevel.Error);
|
|
|
|
// longMutex = false;
|
|
|
|
// rethrow;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// longMutex = false;
|
|
|
|
// final end = DateTime.now();
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "$walletName recovery time: ${end.difference(start).inMilliseconds} millis",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Future<List<Address>> _fetchAllOwnAddresses() => db
|
|
|
|
// .getAddresses(walletId)
|
|
|
|
// .filter()
|
|
|
|
// .not()
|
|
|
|
// .typeEqualTo(AddressType.nonWallet)
|
|
|
|
// .and()
|
|
|
|
// .group((q) => q
|
|
|
|
// .subTypeEqualTo(AddressSubType.receiving)
|
|
|
|
// .or()
|
|
|
|
// .subTypeEqualTo(AddressSubType.change))
|
|
|
|
// .findAll();
|
|
|
|
//
|
|
|
|
// Future<bool> refreshIfThereIsNewData() async {
|
|
|
|
// if (longMutex) return false;
|
|
|
|
// if (_hasCalledExit) return false;
|
|
|
|
// final currentChainHeight = await chainHeight;
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// bool needsRefresh = false;
|
|
|
|
// Set<String> txnsToCheck = {};
|
|
|
|
//
|
|
|
|
// for (final String txid in txTracker.pendings) {
|
|
|
|
// if (!txTracker.wasNotifiedConfirmed(txid)) {
|
|
|
|
// txnsToCheck.add(txid);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// for (String txid in txnsToCheck) {
|
|
|
|
// final response = await EthereumAPI.getEthTransactionByHash(txid);
|
|
|
|
// final txBlockNumber = response.value?.blockNumber;
|
|
|
|
//
|
|
|
|
// if (txBlockNumber != null) {
|
|
|
|
// final int txConfirmations = currentChainHeight - txBlockNumber;
|
|
|
|
// bool isUnconfirmed = txConfirmations < MINIMUM_CONFIRMATIONS;
|
|
|
|
// if (!isUnconfirmed) {
|
|
|
|
// needsRefresh = true;
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// if (!needsRefresh) {
|
|
|
|
// var allOwnAddresses = await _fetchAllOwnAddresses();
|
|
|
|
// final response = await EthereumAPI.getEthTransactions(
|
|
|
|
// address: allOwnAddresses.elementAt(0).value,
|
|
|
|
// );
|
|
|
|
// if (response.value != null) {
|
|
|
|
// final allTxs = response.value!;
|
|
|
|
// for (final element in allTxs) {
|
|
|
|
// final txid = element.hash;
|
|
|
|
// if ((await db
|
|
|
|
// .getTransactions(walletId)
|
|
|
|
// .filter()
|
|
|
|
// .txidMatches(txid)
|
|
|
|
// .findFirst()) ==
|
|
|
|
// null) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// " txid not found in address history already $txid",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// needsRefresh = true;
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// " refreshIfThereIsNewData get eth transactions failed: ${response.exception}",
|
|
|
|
// level: LogLevel.Error,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return needsRefresh;
|
|
|
|
// } catch (e, s) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Exception caught in refreshIfThereIsNewData: $e\n$s",
|
|
|
|
// level: LogLevel.Error);
|
|
|
|
// rethrow;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Future<void> getAllTxsToWatch() async {
|
|
|
|
// if (_hasCalledExit) return;
|
|
|
|
// List<Transaction> unconfirmedTxnsToNotifyPending = [];
|
|
|
|
// List<Transaction> unconfirmedTxnsToNotifyConfirmed = [];
|
|
|
|
//
|
|
|
|
// final currentChainHeight = await chainHeight;
|
|
|
|
//
|
|
|
|
// final txCount = await db.getTransactions(walletId).count();
|
|
|
|
//
|
|
|
|
// const paginateLimit = 50;
|
|
|
|
//
|
|
|
|
// for (int i = 0; i < txCount; i += paginateLimit) {
|
|
|
|
// final transactions = await db
|
|
|
|
// .getTransactions(walletId)
|
|
|
|
// .offset(i)
|
|
|
|
// .limit(paginateLimit)
|
|
|
|
// .findAll();
|
|
|
|
// for (final tx in transactions) {
|
|
|
|
// if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
|
|
|
// // get all transactions that were notified as pending but not as confirmed
|
|
|
|
// if (txTracker.wasNotifiedPending(tx.txid) &&
|
|
|
|
// !txTracker.wasNotifiedConfirmed(tx.txid)) {
|
|
|
|
// unconfirmedTxnsToNotifyConfirmed.add(tx);
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// // get all transactions that were not notified as pending yet
|
|
|
|
// if (!txTracker.wasNotifiedPending(tx.txid)) {
|
|
|
|
// unconfirmedTxnsToNotifyPending.add(tx);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // notify on unconfirmed transactions
|
|
|
|
// for (final tx in unconfirmedTxnsToNotifyPending) {
|
|
|
|
// final confirmations = tx.getConfirmations(currentChainHeight);
|
|
|
|
//
|
|
|
|
// if (tx.type == TransactionType.incoming) {
|
|
|
|
// CryptoNotificationsEventBus.instance.fire(
|
|
|
|
// CryptoNotificationEvent(
|
|
|
|
// title: "Incoming transaction",
|
|
|
|
// walletId: walletId,
|
|
|
|
// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
|
|
|
|
// shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS,
|
|
|
|
// txid: tx.txid,
|
|
|
|
// confirmations: confirmations,
|
|
|
|
// requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
|
|
|
// walletName: walletName,
|
|
|
|
// coin: coin,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await txTracker.addNotifiedPending(tx.txid);
|
|
|
|
// } else if (tx.type == TransactionType.outgoing) {
|
|
|
|
// CryptoNotificationsEventBus.instance.fire(
|
|
|
|
// CryptoNotificationEvent(
|
|
|
|
// title: "Sending transaction",
|
|
|
|
// walletId: walletId,
|
|
|
|
// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
|
|
|
|
// shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS,
|
|
|
|
// txid: tx.txid,
|
|
|
|
// confirmations: confirmations,
|
|
|
|
// requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
|
|
|
// walletName: walletName,
|
|
|
|
// coin: coin,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await txTracker.addNotifiedPending(tx.txid);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // notify on confirmed
|
|
|
|
// for (final tx in unconfirmedTxnsToNotifyConfirmed) {
|
|
|
|
// if (tx.type == TransactionType.incoming) {
|
|
|
|
// CryptoNotificationsEventBus.instance.fire(
|
|
|
|
// CryptoNotificationEvent(
|
|
|
|
// title: "Incoming transaction confirmed",
|
|
|
|
// walletId: walletId,
|
|
|
|
// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
|
|
|
|
// shouldWatchForUpdates: false,
|
|
|
|
// txid: tx.txid,
|
|
|
|
// requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
|
|
|
// walletName: walletName,
|
|
|
|
// coin: coin,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await txTracker.addNotifiedConfirmed(tx.txid);
|
|
|
|
// } else if (tx.type == TransactionType.outgoing) {
|
|
|
|
// CryptoNotificationsEventBus.instance.fire(
|
|
|
|
// CryptoNotificationEvent(
|
|
|
|
// title: "Outgoing transaction confirmed",
|
|
|
|
// walletId: walletId,
|
|
|
|
// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
|
|
|
|
// shouldWatchForUpdates: false,
|
|
|
|
// txid: tx.txid,
|
|
|
|
// requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
|
|
|
// walletName: walletName,
|
|
|
|
// coin: coin,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await txTracker.addNotifiedConfirmed(tx.txid);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// @override
|
|
|
|
// Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
|
|
|
// final txid = txData["txid"] as String;
|
|
|
|
// final addressString = checksumEthereumAddress(txData["address"] as String);
|
|
|
|
// final response = await EthereumAPI.getEthTransactionByHash(txid);
|
|
|
|
//
|
|
|
|
// final transaction = Transaction(
|
|
|
|
// walletId: walletId,
|
|
|
|
// txid: txid,
|
|
|
|
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
|
|
|
// type: TransactionType.outgoing,
|
|
|
|
// subType: TransactionSubType.none,
|
|
|
|
// // precision may be lost here hence the following amountString
|
|
|
|
// amount: (txData["recipientAmt"] as Amount).raw.toInt(),
|
|
|
|
// amountString: (txData["recipientAmt"] as Amount).toJsonString(),
|
|
|
|
// fee: (txData["fee"] as Amount).raw.toInt(),
|
|
|
|
// height: null,
|
|
|
|
// isCancelled: false,
|
|
|
|
// isLelantus: false,
|
|
|
|
// otherData: null,
|
|
|
|
// slateId: null,
|
|
|
|
// nonce: (txData["nonce"] as int?) ??
|
|
|
|
// response.value?.nonce.toBigIntFromHex.toInt(),
|
|
|
|
// inputs: [],
|
|
|
|
// outputs: [],
|
|
|
|
// numberOfMessages: null,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// Address? address = await db.getAddress(
|
|
|
|
// walletId,
|
|
|
|
// addressString,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// address ??= Address(
|
|
|
|
// walletId: walletId,
|
|
|
|
// value: addressString,
|
|
|
|
// publicKey: [],
|
|
|
|
// derivationIndex: -1,
|
|
|
|
// derivationPath: null,
|
|
|
|
// type: AddressType.ethereum,
|
|
|
|
// subType: AddressSubType.nonWallet,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// await db.addNewTransactionData(
|
|
|
|
// [
|
|
|
|
// Tuple2(transaction, address),
|
|
|
|
// ],
|
|
|
|
// walletId,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // @override
|
|
|
|
// // bool validateAddress(String address) {
|
|
|
|
// // return isValidEthereumAddress(address);
|
|
|
|
// // }
|
|
|
|
//
|
|
|
|
// Future<void> _refreshTransactions({bool isRescan = false}) async {
|
|
|
|
// String thisAddress = await currentReceivingAddress;
|
|
|
|
//
|
|
|
|
// int firstBlock = 0;
|
|
|
|
//
|
|
|
|
// if (!isRescan) {
|
|
|
|
// firstBlock =
|
|
|
|
// await db.getTransactions(walletId).heightProperty().max() ?? 0;
|
|
|
|
//
|
|
|
|
// if (firstBlock > 10) {
|
|
|
|
// // add some buffer
|
|
|
|
// firstBlock -= 10;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// final response = await EthereumAPI.getEthTransactions(
|
|
|
|
// address: thisAddress,
|
|
|
|
// firstBlock: isRescan ? 0 : firstBlock,
|
|
|
|
// includeTokens: true,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// if (response.value == null) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Failed to refresh transactions for ${coin.prettyName} $walletName "
|
|
|
|
// "$walletId: ${response.exception}",
|
|
|
|
// level: LogLevel.Warning,
|
|
|
|
// );
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// if (response.value!.isEmpty) {
|
|
|
|
// // no new transactions found
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// final txsResponse =
|
|
|
|
// await EthereumAPI.getEthTransactionNonces(response.value!);
|
|
|
|
//
|
|
|
|
// if (txsResponse.value != null) {
|
|
|
|
// final allTxs = txsResponse.value!;
|
|
|
|
// final List<Tuple2<Transaction, Address?>> txnsData = [];
|
|
|
|
// for (final tuple in allTxs) {
|
|
|
|
// final element = tuple.item1;
|
|
|
|
//
|
|
|
|
// Amount transactionAmount = element.value;
|
|
|
|
//
|
|
|
|
// bool isIncoming;
|
|
|
|
// bool txFailed = false;
|
|
|
|
// if (checksumEthereumAddress(element.from) == thisAddress) {
|
|
|
|
// if (element.isError) {
|
|
|
|
// txFailed = true;
|
|
|
|
// }
|
|
|
|
// isIncoming = false;
|
|
|
|
// } else if (checksumEthereumAddress(element.to) == thisAddress) {
|
|
|
|
// isIncoming = true;
|
|
|
|
// } else {
|
|
|
|
// continue;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// //Calculate fees (GasLimit * gasPrice)
|
|
|
|
// // int txFee = element.gasPrice * element.gasUsed;
|
|
|
|
// Amount txFee = element.gasCost;
|
|
|
|
//
|
|
|
|
// final String addressString = checksumEthereumAddress(element.to);
|
|
|
|
// final int height = element.blockNumber;
|
|
|
|
//
|
|
|
|
// final txn = Transaction(
|
|
|
|
// walletId: walletId,
|
|
|
|
// txid: element.hash,
|
|
|
|
// timestamp: element.timestamp,
|
|
|
|
// type:
|
|
|
|
// isIncoming ? TransactionType.incoming : TransactionType.outgoing,
|
|
|
|
// subType: TransactionSubType.none,
|
|
|
|
// amount: transactionAmount.raw.toInt(),
|
|
|
|
// amountString: transactionAmount.toJsonString(),
|
|
|
|
// fee: txFee.raw.toInt(),
|
|
|
|
// height: height,
|
|
|
|
// isCancelled: txFailed,
|
|
|
|
// isLelantus: false,
|
|
|
|
// slateId: null,
|
|
|
|
// otherData: null,
|
|
|
|
// nonce: tuple.item2,
|
|
|
|
// inputs: [],
|
|
|
|
// outputs: [],
|
|
|
|
// numberOfMessages: null,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// Address? transactionAddress = await db
|
|
|
|
// .getAddresses(walletId)
|
|
|
|
// .filter()
|
|
|
|
// .valueEqualTo(addressString)
|
|
|
|
// .findFirst();
|
|
|
|
//
|
|
|
|
// if (transactionAddress == null) {
|
|
|
|
// if (isIncoming) {
|
|
|
|
// transactionAddress = Address(
|
|
|
|
// walletId: walletId,
|
|
|
|
// value: addressString,
|
|
|
|
// publicKey: [],
|
|
|
|
// derivationIndex: 0,
|
|
|
|
// derivationPath: DerivationPath()..value = "$hdPathEthereum/0",
|
|
|
|
// type: AddressType.ethereum,
|
|
|
|
// subType: AddressSubType.receiving,
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// final myRcvAddr = await currentReceivingAddress;
|
|
|
|
// final isSentToSelf = myRcvAddr == addressString;
|
|
|
|
//
|
|
|
|
// transactionAddress = Address(
|
|
|
|
// walletId: walletId,
|
|
|
|
// value: addressString,
|
|
|
|
// publicKey: [],
|
|
|
|
// derivationIndex: isSentToSelf ? 0 : -1,
|
|
|
|
// derivationPath: isSentToSelf
|
|
|
|
// ? (DerivationPath()..value = "$hdPathEthereum/0")
|
|
|
|
// : null,
|
|
|
|
// type: AddressType.ethereum,
|
|
|
|
// subType: isSentToSelf
|
|
|
|
// ? AddressSubType.receiving
|
|
|
|
// : AddressSubType.nonWallet,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// txnsData.add(Tuple2(txn, transactionAddress));
|
|
|
|
// }
|
|
|
|
// await db.addNewTransactionData(txnsData, walletId);
|
|
|
|
//
|
|
|
|
// // quick hack to notify manager to call notifyListeners if
|
|
|
|
// // transactions changed
|
|
|
|
// if (txnsData.isNotEmpty) {
|
|
|
|
// GlobalEventBus.instance.fire(
|
|
|
|
// UpdatedInBackgroundEvent(
|
|
|
|
// "Transactions updated/added for: $walletId $walletName ",
|
|
|
|
// walletId,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Failed to refresh transactions with nonces for ${coin.prettyName} "
|
|
|
|
// "$walletName $walletId: ${txsResponse.exception}",
|
|
|
|
// level: LogLevel.Warning,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// void stopNetworkAlivePinging() {
|
|
|
|
// _networkAliveTimer?.cancel();
|
|
|
|
// _networkAliveTimer = null;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// void startNetworkAlivePinging() {
|
|
|
|
// // call once on start right away
|
|
|
|
// _periodicPingCheck();
|
|
|
|
//
|
|
|
|
// // then periodically check
|
|
|
|
// _networkAliveTimer = Timer.periodic(
|
|
|
|
// Constants.networkAliveTimerDuration,
|
|
|
|
// (_) async {
|
|
|
|
// _periodicPingCheck();
|
|
|
|
// },
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|