stack_wallet/lib/wallets/wallet/wallet.dart

834 lines
26 KiB
Dart
Raw Normal View History

import 'dart:async';
import 'package:isar/isar.dart';
import 'package:meta/meta.dart';
import 'package:mutex/mutex.dart';
2024-05-27 23:56:22 +00:00
import '../../db/isar/main_db.dart';
import '../../models/isar/models/blockchain_data/address.dart';
import '../../models/isar/models/ethereum/eth_contract.dart';
import '../../models/keys/view_only_wallet_data.dart';
import '../../models/node_model.dart';
import '../../models/paymint/fee_object_model.dart';
import '../../services/event_bus/events/global/node_connection_status_changed_event.dart';
import '../../services/event_bus/events/global/refresh_percent_changed_event.dart';
import '../../services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import '../../services/event_bus/global_event_bus.dart';
import '../../services/node_service.dart';
import '../../utilities/amount/amount.dart';
import '../../utilities/constants.dart';
import '../../utilities/enums/sync_type_enum.dart';
import '../../utilities/flutter_secure_storage_interface.dart';
import '../../utilities/logger.dart';
import '../../utilities/paynym_is_api.dart';
import '../../utilities/prefs.dart';
import '../crypto_currency/crypto_currency.dart';
import '../isar/models/wallet_info.dart';
import '../models/tx_data.dart';
import 'impl/banano_wallet.dart';
import 'impl/bitcoin_frost_wallet.dart';
import 'impl/bitcoin_wallet.dart';
import 'impl/bitcoincash_wallet.dart';
2024-08-26 17:38:58 +00:00
import 'impl/cardano_wallet.dart';
2024-06-20 20:40:52 +00:00
import 'impl/dash_wallet.dart';
import 'impl/dogecoin_wallet.dart';
import 'impl/ecash_wallet.dart';
import 'impl/epiccash_wallet.dart';
import 'impl/ethereum_wallet.dart';
import 'impl/firo_wallet.dart';
import 'impl/litecoin_wallet.dart';
import 'impl/monero_wallet.dart';
import 'impl/namecoin_wallet.dart';
import 'impl/nano_wallet.dart';
import 'impl/particl_wallet.dart';
import 'impl/peercoin_wallet.dart';
import 'impl/solana_wallet.dart';
import 'impl/stellar_wallet.dart';
import 'impl/sub_wallets/eth_token_wallet.dart';
import 'impl/tezos_wallet.dart';
import 'impl/wownero_wallet.dart';
import 'intermediate/cryptonote_wallet.dart';
import 'wallet_mixin_interfaces/electrumx_interface.dart';
import 'wallet_mixin_interfaces/lelantus_interface.dart';
import 'wallet_mixin_interfaces/mnemonic_interface.dart';
import 'wallet_mixin_interfaces/multi_address_interface.dart';
import 'wallet_mixin_interfaces/paynym_interface.dart';
import 'wallet_mixin_interfaces/private_key_interface.dart';
import 'wallet_mixin_interfaces/spark_interface.dart';
import 'wallet_mixin_interfaces/view_only_option_interface.dart';
abstract class Wallet<T extends CryptoCurrency> {
// default to Transaction class. For TransactionV2 set to 2
int get isarTransactionVersion => 1;
2024-01-24 00:33:40 +00:00
// whether the wallet currently supports multiple recipients per tx
bool get supportsMultiRecipient => false;
Wallet(this.cryptoCurrency);
//============================================================================
// ========== Properties =====================================================
final T cryptoCurrency;
late final MainDB mainDB;
late final SecureStorageInterface secureStorageInterface;
2023-11-06 21:37:18 +00:00
late final NodeService nodeService;
2023-09-18 21:28:31 +00:00
late final Prefs prefs;
final refreshMutex = Mutex();
late final String _walletId;
WalletInfo get info =>
mainDB.isar.walletInfo.where().walletIdEqualTo(walletId).findFirstSync()!;
bool get isConnected => _isConnected;
bool get shouldAutoSync => _shouldAutoSync;
set shouldAutoSync(bool shouldAutoSync) {
if (_shouldAutoSync != shouldAutoSync) {
_shouldAutoSync = shouldAutoSync;
if (!shouldAutoSync) {
_periodicRefreshTimer?.cancel();
_periodicRefreshTimer = null;
_stopNetworkAlivePinging();
} else {
_startNetworkAlivePinging();
refresh();
}
}
}
// ===== private properties ===========================================
Timer? _periodicRefreshTimer;
Timer? _networkAliveTimer;
bool _shouldAutoSync = false;
bool _isConnected = false;
2023-11-06 21:37:18 +00:00
void xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(
2024-05-27 23:56:22 +00:00
bool flag,
) {
2023-11-06 21:37:18 +00:00
_isConnected = flag;
}
//============================================================================
// ========== Wallet Info Convenience Getters ================================
String get walletId => _walletId;
/// Attempt to fetch the most recent chain height.
/// On failure return the last cached height.
Future<int> get chainHeight async {
try {
// attempt updating the walletInfo's cached height
await updateChainHeight();
} catch (e, s) {
// do nothing on failure (besides logging)
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
}
// return regardless of whether it was updated or not as we want a
// number even if it isn't the most recent
return info.cachedChainHeight;
}
//============================================================================
// ========== Static Main ====================================================
/// Create a new wallet and save [walletInfo] to db.
static Future<Wallet> create({
required WalletInfo walletInfo,
required MainDB mainDB,
required SecureStorageInterface secureStorageInterface,
2023-09-18 21:28:31 +00:00
required NodeService nodeService,
required Prefs prefs,
String? mnemonic,
String? mnemonicPassphrase,
String? privateKey,
ViewOnlyWalletData? viewOnlyData,
}) async {
// TODO: rework soon?
if (walletInfo.isViewOnly && viewOnlyData == null) {
throw Exception("Missing view key while creating view only wallet!");
}
final Wallet wallet = await _construct(
walletInfo: walletInfo,
mainDB: mainDB,
secureStorageInterface: secureStorageInterface,
2023-09-18 21:28:31 +00:00
nodeService: nodeService,
prefs: prefs,
);
if (wallet is ViewOnlyOptionInterface && walletInfo.isViewOnly) {
await secureStorageInterface.write(
key: getViewOnlyWalletDataSecStoreKey(walletId: walletInfo.walletId),
value: viewOnlyData!.toJsonEncodedString(),
);
} else if (wallet is MnemonicInterface) {
if (wallet is CryptonoteWallet) {
// currently a special case due to the xmr/wow libraries handling their
2024-01-17 18:52:33 +00:00
// own mnemonic generation on new wallet creation
// if its a restore we must set them
if (mnemonic != null) {
if ((await secureStorageInterface.read(
key: mnemonicKey(walletId: walletInfo.walletId),
)) ==
null) {
await secureStorageInterface.write(
key: mnemonicKey(walletId: walletInfo.walletId),
value: mnemonic,
);
}
if (mnemonicPassphrase != null) {
if ((await secureStorageInterface.read(
key: mnemonicPassphraseKey(walletId: walletInfo.walletId),
)) ==
null) {
await secureStorageInterface.write(
key: mnemonicPassphraseKey(walletId: walletInfo.walletId),
value: mnemonicPassphrase,
);
}
}
}
} else {
await secureStorageInterface.write(
key: mnemonicKey(walletId: walletInfo.walletId),
value: mnemonic!,
);
await secureStorageInterface.write(
key: mnemonicPassphraseKey(walletId: walletInfo.walletId),
value: mnemonicPassphrase!,
);
}
2023-11-14 15:57:48 +00:00
}
2024-01-10 16:08:12 +00:00
// TODO [prio=low] handle eth differently?
// This would need to be changed if we actually end up allowing eth wallets
// to be created with a private key instead of mnemonic only
if (wallet is PrivateKeyInterface && wallet is! EthereumWallet) {
2023-11-14 15:57:48 +00:00
await secureStorageInterface.write(
key: privateKeyKey(walletId: walletInfo.walletId),
value: privateKey!,
);
}
// Store in db after wallet creation
await wallet.mainDB.isar.writeTxn(() async {
2024-01-17 17:16:18 +00:00
await wallet.mainDB.isar.walletInfo.put(walletInfo);
});
return wallet;
}
/// Load an existing wallet via [WalletInfo] using [walletId].
static Future<Wallet> load({
required String walletId,
required MainDB mainDB,
required SecureStorageInterface secureStorageInterface,
2023-09-18 21:28:31 +00:00
required NodeService nodeService,
required Prefs prefs,
}) async {
final walletInfo = await mainDB.isar.walletInfo
.where()
.walletIdEqualTo(walletId)
.findFirst();
if (walletInfo == null) {
throw Exception(
"WalletInfo not found for $walletId when trying to call Wallet.load()",
);
}
return await _construct(
walletInfo: walletInfo,
mainDB: mainDB,
secureStorageInterface: secureStorageInterface,
2023-09-18 21:28:31 +00:00
nodeService: nodeService,
prefs: prefs,
);
}
// TODO: [prio=low] refactor to more generalized token rather than eth specific
2024-01-10 23:40:42 +00:00
static Wallet loadTokenWallet({
required EthereumWallet ethWallet,
required EthContract contract,
2024-01-11 23:18:58 +00:00
}) {
2024-01-10 23:40:42 +00:00
final Wallet wallet = EthTokenWallet(
ethWallet,
contract,
);
wallet.prefs = ethWallet.prefs;
wallet.nodeService = ethWallet.nodeService;
wallet.secureStorageInterface = ethWallet.secureStorageInterface;
wallet.mainDB = ethWallet.mainDB;
return wallet.._walletId = ethWallet.info.walletId;
2024-01-10 23:40:42 +00:00
}
//============================================================================
// ========== Static Util ====================================================
2023-09-18 21:28:31 +00:00
// secure storage key
static String mnemonicKey({
required String walletId,
}) =>
"${walletId}_mnemonic";
2023-09-18 21:28:31 +00:00
// secure storage key
static String mnemonicPassphraseKey({
required String walletId,
}) =>
"${walletId}_mnemonicPassphrase";
2023-09-18 21:28:31 +00:00
// secure storage key
static String privateKeyKey({
required String walletId,
}) =>
"${walletId}_privateKey";
// secure storage key
static String getViewOnlyWalletDataSecStoreKey({
required String walletId,
}) =>
"${walletId}_viewOnlyWalletData";
//============================================================================
// ========== Private ========================================================
/// Construct wallet instance by [WalletType] from [walletInfo]
static Future<Wallet> _construct({
required WalletInfo walletInfo,
required MainDB mainDB,
required SecureStorageInterface secureStorageInterface,
2023-09-18 21:28:31 +00:00
required NodeService nodeService,
required Prefs prefs,
}) async {
2023-09-18 21:28:31 +00:00
final Wallet wallet = _loadWallet(
walletInfo: walletInfo,
);
wallet.prefs = prefs;
2023-11-06 21:37:18 +00:00
wallet.nodeService = nodeService;
2024-01-19 21:42:38 +00:00
if (wallet is ElectrumXInterface || wallet is BitcoinFrostWallet) {
2023-09-18 21:28:31 +00:00
// initialize electrumx instance
await wallet.updateNode();
}
return wallet
..secureStorageInterface = secureStorageInterface
..mainDB = mainDB
.._walletId = walletInfo.walletId;
}
2023-09-18 21:28:31 +00:00
static Wallet _loadWallet({
required WalletInfo walletInfo,
}) {
2024-05-15 21:20:45 +00:00
final net = walletInfo.coin.network;
switch (walletInfo.coin.runtimeType) {
case const (Banano):
return BananoWallet(net);
case const (Bitcoin):
return BitcoinWallet(net);
case const (BitcoinFrost):
return BitcoinFrostWallet(net);
case const (Bitcoincash):
return BitcoincashWallet(net);
2024-08-26 17:38:58 +00:00
case const (Cardano):
return CardanoWallet(net);
2024-06-20 20:40:52 +00:00
case const (Dash):
return DashWallet(net);
2024-05-15 21:20:45 +00:00
case const (Dogecoin):
2024-05-15 21:54:28 +00:00
return DogecoinWallet(net);
2024-05-15 21:20:45 +00:00
case const (Ecash):
2024-05-15 21:54:28 +00:00
return EcashWallet(net);
2023-11-14 15:57:17 +00:00
2024-05-15 21:20:45 +00:00
case const (Epiccash):
2024-05-15 21:54:28 +00:00
return EpiccashWallet(net);
2023-11-06 21:37:18 +00:00
2024-05-15 21:20:45 +00:00
case const (Ethereum):
2024-05-15 21:54:28 +00:00
return EthereumWallet(net);
2024-01-10 16:08:12 +00:00
2024-05-15 21:20:45 +00:00
case const (Firo):
2024-05-15 21:54:28 +00:00
return FiroWallet(net);
2023-11-16 21:30:01 +00:00
2024-05-15 21:20:45 +00:00
case const (Litecoin):
2024-05-15 21:54:28 +00:00
return LitecoinWallet(net);
2024-01-05 00:37:46 +00:00
2024-05-15 21:20:45 +00:00
case const (Monero):
2024-05-15 21:54:28 +00:00
return MoneroWallet(net);
2024-05-15 21:20:45 +00:00
case const (Namecoin):
2024-05-15 21:54:28 +00:00
return NamecoinWallet(net);
2024-05-15 21:20:45 +00:00
case const (Nano):
2024-05-15 21:54:28 +00:00
return NanoWallet(net);
2023-11-15 21:59:01 +00:00
2024-05-15 21:20:45 +00:00
case const (Particl):
2024-05-15 21:54:28 +00:00
return ParticlWallet(net);
2024-05-15 21:20:45 +00:00
case const (Peercoin):
2024-05-15 21:54:28 +00:00
return PeercoinWallet(net);
2024-05-09 22:44:49 +00:00
2024-05-15 21:20:45 +00:00
case const (Solana):
2024-05-15 21:54:28 +00:00
return SolanaWallet(net);
2024-03-20 00:50:42 +00:00
2024-05-15 21:20:45 +00:00
case const (Stellar):
2024-05-15 21:54:28 +00:00
return StellarWallet(net);
2024-01-11 23:18:58 +00:00
2024-05-15 21:20:45 +00:00
case const (Tezos):
2024-05-15 21:54:28 +00:00
return TezosWallet(net);
2023-11-20 16:37:28 +00:00
2024-05-15 21:20:45 +00:00
case const (Wownero):
2024-05-15 21:54:28 +00:00
return WowneroWallet(net);
default:
// should never hit in reality
2023-11-06 17:37:23 +00:00
throw Exception("Unknown crypto currency: ${walletInfo.coin}");
}
}
void _startNetworkAlivePinging() {
// call once on start right away
_periodicPingCheck();
// then periodically check
_networkAliveTimer = Timer.periodic(
Constants.networkAliveTimerDuration,
(_) async {
_periodicPingCheck();
},
);
}
void _periodicPingCheck() async {
2024-04-24 21:42:17 +00:00
final bool hasNetwork = await pingCheck();
if (_isConnected != hasNetwork) {
2024-04-24 21:42:17 +00:00
final NodeConnectionStatus status = hasNetwork
? NodeConnectionStatus.connected
: NodeConnectionStatus.disconnected;
GlobalEventBus.instance.fire(
NodeConnectionStatusChangedEvent(
status,
walletId,
2024-05-15 21:20:45 +00:00
cryptoCurrency,
),
);
_isConnected = hasNetwork;
if (status == NodeConnectionStatus.disconnected) {
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.unableToSync,
walletId,
cryptoCurrency,
),
);
}
if (hasNetwork) {
unawaited(refresh());
}
}
}
void _stopNetworkAlivePinging() {
_networkAliveTimer?.cancel();
_networkAliveTimer = null;
}
//============================================================================
// ========== Must override ==================================================
/// Create and sign a transaction in preparation to submit to network
Future<TxData> prepareSend({required TxData txData});
/// Broadcast transaction to network. On success update local wallet state to
/// reflect updated balance, transactions, utxos, etc.
Future<TxData> confirmSend({required TxData txData});
2023-09-18 21:28:31 +00:00
/// Recover a wallet by scanning the blockchain. If called on a new wallet a
/// normal recovery should occur. When called on an existing wallet and
/// [isRescan] is false then it should throw. Otherwise this function should
/// delete all locally stored blockchain data and refetch it.
Future<void> recover({required bool isRescan});
2023-10-31 17:13:26 +00:00
Future<void> updateNode();
2023-09-18 21:28:31 +00:00
Future<void> updateTransactions();
Future<void> updateBalance();
/// returns true if new utxos were added to local db
2024-01-05 00:37:46 +00:00
Future<bool> updateUTXOs();
/// updates the wallet info's cachedChainHeight
Future<void> updateChainHeight();
Future<Amount> estimateFeeFor(Amount amount, int feeRate);
Future<FeeObject> get fees;
Future<bool> pingCheck();
Future<void> checkSaveInitialReceivingAddress();
//===========================================
/// add transaction to local db temporarily. Used for quickly updating ui
/// before refresh can fetch data from server
Future<TxData> updateSentCachedTxData({required TxData txData}) async {
if (txData.tempTx != null) {
await mainDB.updateOrPutTransactionV2s([txData.tempTx!]);
}
return txData;
}
2023-11-06 21:37:18 +00:00
NodeModel getCurrentNode() {
2024-05-15 21:20:45 +00:00
final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency) ??
cryptoCurrency.defaultNode;
2023-11-06 21:37:18 +00:00
return node;
}
2023-09-18 21:28:31 +00:00
// Should fire events
Future<void> refresh() async {
2024-10-01 22:13:36 +00:00
final refreshCompleter = Completer<void>();
final future = refreshCompleter.future.then(
(_) {
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.synced,
walletId,
cryptoCurrency,
),
);
if (shouldAutoSync) {
_periodicRefreshTimer ??=
Timer.periodic(const Duration(seconds: 150), (timer) async {
// chain height check currently broken
// if ((await chainHeight) != (await storedChainHeight)) {
// TODO: [prio=med] some kind of quick check if wallet needs to refresh to replace the old refreshIfThereIsNewData call
// if (await refreshIfThereIsNewData()) {
unawaited(refresh());
// }
// }
});
}
},
onError: (Object error, StackTrace strace) {
GlobalEventBus.instance.fire(
NodeConnectionStatusChangedEvent(
NodeConnectionStatus.disconnected,
walletId,
cryptoCurrency,
),
);
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.unableToSync,
walletId,
cryptoCurrency,
),
);
Logging.instance.log(
"Caught exception in refreshWalletData(): $error\n$strace",
level: LogLevel.Error,
);
},
);
unawaited(_refresh(refreshCompleter));
return future;
}
// Should fire events
Future<void> _refresh(Completer<void> completer) async {
// Awaiting this lock could be dangerous.
// Since refresh is periodic (generally)
if (refreshMutex.isLocked) {
return;
}
2024-05-29 19:29:45 +00:00
final start = DateTime.now();
2023-09-18 21:28:31 +00:00
2024-10-01 22:13:36 +00:00
bool tAlive = true;
final t = Timer.periodic(const Duration(seconds: 1), (timer) async {
if (tAlive) {
final pingSuccess = await pingCheck();
if (!pingSuccess) {
tAlive = false;
}
} else {
timer.cancel();
}
});
void _checkAlive() {
if (!tAlive) throw Exception("refresh alive ping failure");
}
final viewOnly = this is ViewOnlyOptionInterface &&
(this as ViewOnlyOptionInterface).isViewOnly;
try {
// this acquire should be almost instant due to above check.
// Slight possibility of race but should be irrelevant
await refreshMutex.acquire();
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.syncing,
walletId,
2024-05-15 21:20:45 +00:00
cryptoCurrency,
),
);
2024-10-01 22:13:36 +00:00
_checkAlive();
// add some small buffer before making calls.
// this can probably be removed in the future but was added as a
// debugging feature
await Future<void>.delayed(const Duration(milliseconds: 300));
2024-10-01 22:13:36 +00:00
_checkAlive();
2024-01-13 21:49:29 +00:00
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
final Set<String> codesToCheck = {};
2024-10-01 22:13:36 +00:00
_checkAlive();
if (this is PaynymInterface && !viewOnly) {
2024-01-13 21:49:29 +00:00
// isSegwit does not matter here at all
final myCode =
await (this as PaynymInterface).getPaymentCode(isSegwit: false);
2024-10-01 22:13:36 +00:00
_checkAlive();
2024-01-13 21:49:29 +00:00
final nym = await PaynymIsApi().nym(myCode.toString());
2024-10-01 22:13:36 +00:00
_checkAlive();
2024-01-13 21:49:29 +00:00
if (nym.value != null) {
for (final follower in nym.value!.followers) {
codesToCheck.add(follower.code);
}
2024-10-01 22:13:36 +00:00
_checkAlive();
2024-01-13 21:49:29 +00:00
for (final following in nym.value!.following) {
codesToCheck.add(following.code);
}
}
2024-10-01 22:13:36 +00:00
_checkAlive();
2024-01-13 21:49:29 +00:00
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
2024-10-01 22:13:36 +00:00
_checkAlive();
await updateChainHeight();
2024-10-01 22:13:36 +00:00
_checkAlive();
if (this is BitcoinFrostWallet) {
await (this as BitcoinFrostWallet).lookAhead();
}
2024-10-01 22:13:36 +00:00
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is MultiAddressInterface) {
if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await (this as MultiAddressInterface)
.checkReceivingAddressForTransactions();
}
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is MultiAddressInterface) {
if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await (this as MultiAddressInterface)
.checkChangeAddressForTransactions();
}
2023-11-20 15:15:36 +00:00
}
2024-10-01 22:13:36 +00:00
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
if (this is SparkInterface && !viewOnly) {
// this should be called before updateTransactions()
await (this as SparkInterface).refreshSparkData();
}
2024-10-01 22:13:36 +00:00
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
final fetchFuture = updateTransactions();
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
final utxosRefreshFuture = updateUTXOs();
// if (currentHeight != storedHeight) {
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId));
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
await utxosRefreshFuture;
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
await fetchFuture;
2024-01-13 21:49:29 +00:00
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (!viewOnly && this is PaynymInterface && codesToCheck.isNotEmpty) {
2024-10-01 22:13:36 +00:00
_checkAlive();
2024-01-13 21:49:29 +00:00
await (this as PaynymInterface)
.checkForNotificationTransactionsTo(codesToCheck);
// check utxos again for notification outputs
2024-10-01 22:13:36 +00:00
_checkAlive();
2024-01-13 21:49:29 +00:00
await updateUTXOs();
}
2024-10-01 22:13:36 +00:00
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
// await getAllTxsToWatch();
2024-10-01 22:13:36 +00:00
_checkAlive();
2023-11-20 15:15:36 +00:00
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is LelantusInterface && !viewOnly) {
2024-06-06 17:21:50 +00:00
if (info.otherData[WalletInfoKeys.enableLelantusScanning] as bool? ??
false) {
await (this as LelantusInterface).refreshLelantusData();
2024-10-01 22:13:36 +00:00
_checkAlive();
}
2023-11-20 15:15:36 +00:00
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId));
2024-10-01 22:13:36 +00:00
_checkAlive();
await updateBalance();
2024-10-01 22:13:36 +00:00
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId));
2024-10-01 22:13:36 +00:00
tAlive = false; // interrupt timer as its not needed anymore
2024-10-01 22:13:36 +00:00
completer.complete();
} catch (error, strace) {
2024-10-01 22:13:36 +00:00
completer.completeError(error, strace);
} finally {
2024-10-01 22:13:36 +00:00
t.cancel();
refreshMutex.release();
2024-10-01 22:13:36 +00:00
if (!completer.isCompleted) {
completer.completeError(
"finally block hit before completer completed",
StackTrace.current,
);
}
2024-05-29 19:29:45 +00:00
Logging.instance.log(
"Refresh for "
"${info.name}: ${DateTime.now().difference(start)}",
level: LogLevel.Info,
);
}
}
2023-10-31 17:13:26 +00:00
Future<void> exit() async {
_periodicRefreshTimer?.cancel();
_networkAliveTimer?.cancel();
2024-02-06 21:07:32 +00:00
// If the syncing pref is currentWalletOnly or selectedWalletsAtStartup (and
// this wallet isn't in walletIdsSyncOnStartup), then we close subscriptions.
switch (prefs.syncType) {
case SyncingType.currentWalletOnly:
// Close the subscription for this coin's chain height.
// NOTE: This does not work now that the subscription is shared
2024-05-15 21:20:45 +00:00
// await (await ChainHeightServiceManager.getService(cryptoCurrency))
// ?.cancelListen();
2024-02-06 21:07:32 +00:00
case SyncingType.selectedWalletsAtStartup:
// Close the subscription if this wallet is not in the list to be synced.
if (!prefs.walletIdsSyncOnStartup.contains(walletId)) {
// Check if there's another wallet of this coin on the sync list.
2024-04-24 21:42:17 +00:00
final List<String> walletIds = [];
for (final id in prefs.walletIdsSyncOnStartup) {
final wallet = mainDB.isar.walletInfo
.where()
.walletIdEqualTo(id)
.findFirstSync()!;
2024-05-15 21:20:45 +00:00
if (wallet.coin == cryptoCurrency) {
walletIds.add(id);
}
}
// TODO [prio=low]: use a query instead of iterating thru wallets.
// If there are no other wallets of this coin, then close the sub.
if (walletIds.isEmpty) {
// NOTE: This does not work now that the subscription is shared
// await (await ChainHeightServiceManager.getService(
2024-05-15 21:20:45 +00:00
// cryptoCurrency))
// ?.cancelListen();
}
2024-02-06 21:07:32 +00:00
}
case SyncingType.allWalletsOnStartup:
// Do nothing.
break;
}
2023-10-31 17:13:26 +00:00
}
2023-09-18 21:28:31 +00:00
@mustCallSuper
2023-10-31 17:13:26 +00:00
Future<void> init() async {
await checkSaveInitialReceivingAddress();
final address = await getCurrentReceivingAddress();
if (address != null) {
await info.updateReceivingAddress(
newAddress: address.value,
isar: mainDB.isar,
);
}
2023-10-31 17:13:26 +00:00
}
// ===========================================================================
FilterOperation? get transactionFilterOperation => null;
FilterOperation? get receivingAddressFilterOperation;
FilterOperation? get changeAddressFilterOperation;
Future<Address?> getCurrentReceivingAddress() async {
return await _addressQuery(receivingAddressFilterOperation);
}
Future<Address?> getCurrentChangeAddress() async {
return await _addressQuery(changeAddressFilterOperation);
}
Future<Address?> _addressQuery(FilterOperation? filterOperation) async {
return await mainDB.isar.addresses
.buildQuery<Address>(
whereClauses: [
IndexWhereClause.equalTo(
indexName: r"walletId",
value: [walletId],
),
],
filter: filterOperation,
sortBy: [
const SortProperty(
property: r"derivationIndex",
sort: Sort.desc,
),
],
)
.findFirst();
}
}