What changed, and why it matters
This commit changes how Stack Wallet's Epic Cash wallet shuts down and restarts its background listener. The old code forcibly closed the wallet object on every exit, which could leave the listener in a broken or stale state. The new code keeps the wallet object alive, stops only the listener, and ensures the listener is restarted when the wallet is reopened. This is a reliability fix that may also prevent bugs where the wallet appears online but cannot receive transactions.
Review whether keeping `_wallet` open across `exit()` is intentional and safe; ensure no other code paths assume `_wallet` is null after `exit()`. Verify that `stopListeners()` fully releases native resources and that repeated open/exit cycles do not leak memory or file descriptors. Consider adding tests for listener restart after exit.
Security signals we found
Lifecycle/resource management change in wallet shutdown path
Removal of wallet object nullification on exit
Listener state now explicitly checked and restored on re-open
Potential stale-pointer / use-after-close class of bug mitigated
Evidence from the diff
The patch refactors EpiccashWallet lifecycle management. In open(), if _wallet already exists, it now verifies the Epicbox listener is running and restarts it if not. In exit(), it removes await _wallet?.close(); _wallet = null; and instead calls _wallet?.stopListeners(). _listenToEpicbox() now uses _wallet!.startListeners() and _wallet!.updateEpicboxConfig() rather than the lower-level libEpic.startEpicboxListener. Scanning now stops/starts listeners via _wallet!.stopListeners() and _wallet!.isEpicboxListenerRunning(). The change reduces stale-pointer risk and avoids re-reading the wallet from secure storage to start the listener.
Changed components
lib/wallets/wallet/impl/epiccash_wallet.dartEpic Cash wallet listener lifecycleEpicbox background listenerInspect captured patch +13 / −13
diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart
index 7d1e500..e600900 100644
--- a/lib/wallets/wallet/impl/epiccash_wallet.dart
+++ b/lib/wallets/wallet/impl/epiccash_wallet.dart
@@ -77,7 +77,10 @@ class EpiccashWallet extends Bip39Wallet {
/// Should only be called once during wallet initialization.
Future<void> open() async {
if (_wallet != null) {
- Logging.instance.d("Wallet already open, skipping");
+ Logging.instance.d("Wallet already open, ensuring listener");
+ if (!await _wallet!.isEpicboxListenerRunning()) {
+ await _listenToEpicbox();
+ }
return;
}
@@ -125,6 +128,7 @@ class EpiccashWallet extends Bip39Wallet {
key: '${walletId}_epicboxConfig',
value: stringConfig,
);
+ _wallet?.updateEpicboxConfig(stringConfig);
// TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed
}
@@ -673,7 +677,7 @@ class EpiccashWallet extends Bip39Wallet {
final needsScanning = lastScannedBlock < chainHeight;
if (needsScanning) {
// Stop listener during active scanning to avoid potential conflicts
- libEpic.stopEpicboxListener(walletId: walletId);
+ await _wallet!.stopListeners();
}
// loop while scanning in chain in chunks (of blocks?)
@@ -706,7 +710,7 @@ class EpiccashWallet extends Bip39Wallet {
// Ensure listener is running after refresh.
// Use health check to verify the Rust listener task is actually alive,
// not just that we have a pointer (which could be stale).
- if (!libEpic.isEpicboxListenerRunning(walletId: walletId)) {
+ if (!await _wallet!.isEpicboxListenerRunning()) {
Logging.instance.d("Listener not running, starting it...");
await _listenToEpicbox();
} else {
@@ -720,13 +724,12 @@ class EpiccashWallet extends Bip39Wallet {
Future<void> _listenToEpicbox() async {
Logging.instance.d("STARTING WALLET LISTENER ....");
- final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
- libEpic.startEpicboxListener(
- walletId: walletId,
- wallet: wallet!,
- epicboxConfig: epicboxConfig.toString(),
- );
+ if (_wallet == null) {
+ throw Exception('Wallet not opened. Call open() first.');
+ }
+ _wallet!.updateEpicboxConfig(epicboxConfig.toString());
+ await _wallet!.startListeners();
}
// As opposed to fake config?
@@ -1653,13 +1656,10 @@ class EpiccashWallet extends Bip39Wallet {
@override
Future<void> exit() async {
- libEpic.stopEpicboxListener(walletId: walletId);
+ await _wallet?.stopListeners();
timer?.cancel();
timer = null;
- await _wallet?.close();
- _wallet = null;
-
await super.exit();
Logging.instance.d("EpicCash_wallet exit finished");
}
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.