update epic when epic box is changed receiving address
What changed, and why it matters
This commit changes how Stack Wallet handles Epic Cash receiving addresses when the Epic Box server (a messaging relay for this privacy coin) is changed. It removes some default servers, renames others, and adds logic to regenerate and store the wallet's receiving address whenever the Epic Box configuration changes. The change appears to be a bug fix to keep the wallet's displayed address in sync with the active relay server, rather than a security vulnerability fix.
Treat as a routine functional bug fix. Reviewers should verify that the new default server list and renamed IDs are intentional, that the secure-storage key rename does not unexpectedly discard user-configured servers, and that address regeneration correctly preserves transaction history. No immediate security response is indicated by the diff alone.
Security signals we found
Change to default server list and server IDs could affect which trusted relay users connect to
Secure-storage key rename may invalidate previously stored user Epic Box configs, causing fallback to defaults
Receiving address is now regenerated when Epic Box config changes, reducing risk of address/server mismatch
Address replacement now explicitly preserves linked transactions in the local database
No explicit security framing, CVE, or researcher attribution in commit or supplied references
Evidence from the diff
The patch modifies DefaultEpicBoxes to reduce the default Epic Box server list and rename IDs (e.g., ‘americas’ -> ‘default_stack’, ‘epiccashCom’ -> ‘default_epiccashCom’). In epiccash_wallet.dart, it changes the secure-storage key for the Epic Box config from ${walletId}_epicboxConfig to ${walletId}_epicboxConfigNewNewNew, adds _updateAddressInDB() to preserve transaction links when replacing the stored receiving address, and calls _generateAndStoreReceivingAddressForIndex(0) after Epic Box config updates and wallet open/recover flows. The old thisWalletAddress() helper is folded into _generateAndStoreReceivingAddressForIndex().
Changed components
lib/utilities/default_epicboxes.dartlib/wallets/wallet/impl/epiccash_wallet.dartEpic Cash wallet receiving-address generationEpic Box server configuration storageInspect captured patch +61 / −58
diff --git a/lib/utilities/default_epicboxes.dart b/lib/utilities/default_epicboxes.dart
index f655dd9..3ab7f17 100644
--- a/lib/utilities/default_epicboxes.dart
+++ b/lib/utilities/default_epicboxes.dart
@@ -13,14 +13,13 @@ import '../models/epicbox_server_model.dart';
abstract class DefaultEpicBoxes {
static const String defaultName = "Default";
- static List<EpicBoxServerModel> get all => [americas, asia, europe];
- static List<String> get defaultIds => ['americas', 'asia', 'europe'];
+ static List<EpicBoxServerModel> get all => [defaultEpicBoxServer, americas];
static EpicBoxServerModel get epiccashCom => EpicBoxServerModel(
host: 'epicbox.epiccash.com',
port: 443,
name: 'Official',
- id: 'epiccashCom',
+ id: 'default_epiccashCom',
useSSL: true,
enabled: true,
isFailover: true,
@@ -31,29 +30,7 @@ abstract class DefaultEpicBoxes {
host: 'epicbox.stackwallet.com',
port: 443,
name: 'Stack Wallet',
- id: 'americas',
- useSSL: true,
- enabled: true,
- isFailover: true,
- isDown: false,
- );
-
- static EpicBoxServerModel get asia => EpicBoxServerModel(
- host: 'epicbox.hyperbig.com',
- port: 443,
- name: 'Asia',
- id: 'asia',
- useSSL: true,
- enabled: true,
- isFailover: true,
- isDown: false,
- );
-
- static EpicBoxServerModel get europe => EpicBoxServerModel(
- host: 'epicbox.fastepic.eu',
- port: 443,
- name: 'Europe',
- id: 'europe',
+ id: 'default_stack',
useSSL: true,
enabled: true,
isFailover: true,
diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart
index 2a6be2f..042138e 100644
--- a/lib/wallets/wallet/impl/epiccash_wallet.dart
+++ b/lib/wallets/wallet/impl/epiccash_wallet.dart
@@ -8,6 +8,7 @@ import 'package:mutex/mutex.dart';
import 'package:stack_wallet_backup/generate_password.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
+import '../../../exceptions/main_db/main_db_exception.dart';
import '../../../exceptions/wallet/node_tor_mismatch_config_exception.dart';
import '../../../models/balance.dart';
import '../../../models/epic_slatepack_models.dart';
@@ -120,10 +121,12 @@ class EpiccashWallet extends Bip39Wallet {
"epicbox_address_index": 0,
});
await secureStorageInterface.write(
- key: '${walletId}_epicboxConfig',
+ key: '${walletId}_epicboxConfigNewNewNew',
value: stringConfig,
);
libEpic.updateEpicboxConfig(wallet: _wallet!, epicBoxConfig: stringConfig);
+
+ await _generateAndStoreReceivingAddressForIndex(0);
// TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed
}
@@ -150,14 +153,21 @@ class EpiccashWallet extends Bip39Wallet {
Future<EpicBoxConfigModel> getEpicBoxConfig() async {
// check for user-configured epicbox first
final storedConfig = await secureStorageInterface.read(
- key: '${walletId}_epicboxConfig',
+ key: '${walletId}_epicboxConfigNewNewNew',
);
if (storedConfig != null && storedConfig.isNotEmpty) {
try {
return EpicBoxConfigModel.fromString(storedConfig);
- } catch (e) {
- Logging.instance.w("Failed to parse stored epicbox config: $e");
+ } catch (e, s) {
+ Logging.instance.e(
+ "Failed to parse stored epicbox config $storedConfig."
+ " Falling back to default.",
+ error: e,
+ stackTrace: s,
+ );
}
+ } else {
+ Logging.instance.i("No stored epic box config. Falling back to default.");
}
// fall back to default
@@ -558,7 +568,7 @@ class EpiccashWallet extends Bip39Wallet {
return response is String && response.contains("Challenge");
} catch (e, s) {
Logging.instance.w(
- "_testEpicBoxConnection failed on \"$host:$port\"",
+ "_testEpicboxServer failed on \"$host:$port\"",
error: e,
stackTrace: s,
);
@@ -605,8 +615,33 @@ class EpiccashWallet extends Bip39Wallet {
}
}
+ Future<void> _updateAddressInDB(Address address) async {
+ try {
+ final storedAddress = await getCurrentReceivingAddress();
+ await mainDB.isar.writeTxn(() async {
+ if (storedAddress == null) {
+ await mainDB.isar.addresses.put(address);
+ } else {
+ address.id = storedAddress.id;
+ await storedAddress.transactions.load();
+ final txns = storedAddress.transactions.toList();
+ await mainDB.isar.addresses.delete(storedAddress.id);
+ await mainDB.isar.addresses.put(address);
+ address.transactions.addAll(txns);
+ await address.transactions.save();
+ }
+ });
+ } catch (e) {
+ throw MainDBException("failed _updateAddressInDB: $address", e);
+ }
+ }
+
/// Only index 0 is currently used in stack wallet.
Future<Address> _generateAndStoreReceivingAddressForIndex(int index) async {
+ if (_wallet == null) {
+ throw Exception('Wallet not opened. Call open() first.');
+ }
+
// Since only 0 is a valid index in stack wallet at this time, lets just
// throw is not zero
if (index != 0) {
@@ -614,29 +649,10 @@ class EpiccashWallet extends Bip39Wallet {
}
final epicBoxConfig = await getEpicBoxConfig();
- final address = await thisWalletAddress(index, epicBoxConfig);
-
- if (info.cachedReceivingAddress != address.value) {
- await info.updateReceivingAddress(
- newAddress: address.value,
- isar: mainDB.isar,
- );
- }
- return address;
- }
-
- Future<Address> thisWalletAddress(
- int index,
- EpicBoxConfigModel epicboxConfig,
- ) async {
- if (_wallet == null) {
- throw Exception('Wallet not opened. Call open() first.');
- }
-
final walletAddress = await libEpic.getAddressInfo(
wallet: _wallet!,
index: index,
- epicboxConfig: epicboxConfig.toString(),
+ epicboxConfig: epicBoxConfig.toString(),
);
Logging.instance.d("WALLET_ADDRESS_IS $walletAddress");
@@ -650,7 +666,14 @@ class EpiccashWallet extends Bip39Wallet {
subType: AddressSubType.receiving,
publicKey: [], // ??
);
- await mainDB.updateOrPutAddresses([address]);
+ await _updateAddressInDB(address);
+ if (info.cachedReceivingAddress != address.value) {
+ await info.updateReceivingAddress(
+ newAddress: address.value,
+ isar: mainDB.isar,
+ );
+ }
+
return address;
}
@@ -833,7 +856,7 @@ class EpiccashWallet extends Bip39Wallet {
value: password,
);
await secureStorageInterface.write(
- key: '${walletId}_epicboxConfig',
+ key: '${walletId}_epicboxConfigNewNewNew',
value: epicboxConfig.toString(),
);
@@ -890,6 +913,9 @@ class EpiccashWallet extends Bip39Wallet {
await updateNode();
+ // ensure address is up to date with epic box uri
+ await _generateAndStoreReceivingAddressForIndex(0);
+
await _listenToEpicbox();
} catch (e, s) {
// do nothing, still allow user into wallet
@@ -1100,6 +1126,10 @@ class EpiccashWallet extends Bip39Wallet {
epicBoxConfig: epicboxConfig.toString(),
);
+ await _generateAndStoreReceivingAddressForIndex(
+ info.epicData?.receivingIndex ?? 0,
+ );
+
await _listenToEpicbox();
highestPercent = 0;
@@ -1121,7 +1151,7 @@ class EpiccashWallet extends Bip39Wallet {
);
await secureStorageInterface.write(
- key: '${walletId}_epicboxConfig',
+ key: '${walletId}_epicboxConfigNewNewNew',
value: epicboxConfig.toString(),
);
@@ -1201,10 +1231,6 @@ class EpiccashWallet extends Bip39Wallet {
// await epicUpdateCreationHeight(await chainHeight);
// }
- // this will always be zero????
- final int curAdd = await _getCurrentIndex();
- await _generateAndStoreReceivingAddressForIndex(curAdd);
-
if (_wallet == null) {
throw Exception('Wallet not opened. Call open() first.');
}
Why this scored 31/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.