What changed, and why it matters
This commit rewrites how the Epic Cash wallet stores and uses its configuration. Previously, the app saved a full JSON node/wallet configuration in secure storage and reused it later. Now it only stores a simple 'true' flag there and rebuilds the configuration fresh each time from the currently selected node. The change also removes iOS-specific path rewriting that happened when reading the stored config. The commit message is jokey ('fix epic lmao config'), so the security intent is unclear, but the change could fix a bug where stale or incorrect node settings were reused after the user changed nodes.
Review the native/Rust implementation of EpicWallet.updateConfig() to ensure it safely replaces in-memory configuration without leaking secrets or corrupting wallet state. Verify that secure storage migration handles existing full-config entries gracefully and that no code path still expects JSON in '${walletId}_config'. Consider adding tests for node-switching and wallet recovery.
Security signals we found
Removal of stored full JSON wallet configuration from secure storage
Configuration now built fresh from current node settings at each use
Elimination of iOS-specific JSON mutation of wallet_dir in stored config
Addition of native updateConfig() interface method (implementation not shown)
Commit message gives no security framing
Evidence from the diff
The patch refactors EpiccashWallet configuration handling: _getConfig() becomes _buildConfig() and always uses getCurrentNode() rather than updateNode(); _getRealConfig() and iOS wallet_dir mutation are removed; the secure-storage key ‘${walletId}_config’ now holds only the string ‘true’ as a creation flag; and updateNode() now calls a new libEpic.updateConfig() method instead of overwriting secure storage. Call sites for openWallet, recoverWallet, getChainHeight, and deleteEpicWallet now use freshly built configs. A new updateConfig() method is added to the generated LibEpicCashInterface and its template. The diff does not show the underlying Rust/native implementation of updateConfig().
Changed components
lib/wallets/wallet/impl/epiccash_wallet.dartlib/wl_gen/interfaces/libepiccash_interface.darttool/wl_templates/EPIC_libepiccash_interface_impl.template.dartcrypto_plugins/flutter_libepiccash (submodule reference only)Inspect captured patch +32 / −53
diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart
index 1915256..f7fca91 100644
--- a/lib/wallets/wallet/impl/epiccash_wallet.dart
+++ b/lib/wallets/wallet/impl/epiccash_wallet.dart
@@ -440,10 +440,12 @@ class EpiccashWallet extends Bip39Wallet {
// ================= Private =================================================
- Future<String> _getConfig() async {
- if (_epicNode == null) {
- await updateNode();
- }
+ Future<bool> _hasConfig() async =>
+ (await secureStorageInterface.read(key: '${walletId}_config')) != null;
+
+ Future<String> _buildConfig() async {
+ _epicNode ??= getCurrentNode();
+
final NodeModel node = _epicNode!;
final String nodeAddress = node.host;
final int port = node.port;
@@ -465,6 +467,7 @@ class EpiccashWallet extends Bip39Wallet {
"",
);
final String stringConfig = jsonEncode(config);
+
return stringConfig;
}
@@ -743,21 +746,6 @@ class EpiccashWallet extends Bip39Wallet {
await libEpic.startEpicboxListener(wallet: _wallet!);
}
- // As opposed to fake config?
- Future<String> _getRealConfig() async {
- String? config = await secureStorageInterface.read(
- key: '${walletId}_config',
- );
- if (Platform.isIOS) {
- final walletDir = await _currentWalletDirPath();
- final editConfig = jsonDecode(config as String);
-
- editConfig["wallet_dir"] = walletDir;
- config = jsonEncode(editConfig);
- }
- return config!;
- }
-
// TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index
int _calculateRestoreHeightFrom({required DateTime date}) {
final int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000;
@@ -835,23 +823,25 @@ class EpiccashWallet extends Bip39Wallet {
@override
Future<void> init({bool? isRestore}) async {
if (isRestore != true) {
- final existingWalletConfig = await secureStorageInterface.read(
- key: '${walletId}_config',
- );
+ final existingWalletConfig = await _hasConfig();
// check if should create a new wallet
- if (existingWalletConfig == null) {
+ if (!existingWalletConfig) {
await updateNode();
final mnemonicString = await getMnemonic();
final String password = generatePassword();
- final String stringConfig = await _getConfig();
final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
+ final String stringConfig = await _buildConfig();
+
+ // no need to save the config, just a string flag to know we have a
+ // wallet created
await secureStorageInterface.write(
key: '${walletId}_config',
- value: stringConfig,
+ value: "true",
);
+
await secureStorageInterface.write(
key: '${walletId}_password',
value: password,
@@ -907,7 +897,7 @@ class EpiccashWallet extends Bip39Wallet {
final epicboxConfig = await getEpicBoxConfig();
_wallet = await libEpic.openWallet(
- config: existingWalletConfig,
+ config: await _buildConfig(),
password: password!,
epicboxConfig: epicboxConfig.toString(),
); // Spawns worker isolate
@@ -1098,7 +1088,6 @@ class EpiccashWallet extends Bip39Wallet {
isar: mainDB.isar,
);
- final stringConfig = await _getRealConfig();
final password = await secureStorageInterface.read(
key: '${walletId}_password',
);
@@ -1118,7 +1107,7 @@ class EpiccashWallet extends Bip39Wallet {
}
_wallet = await libEpic.recoverWallet(
- config: stringConfig,
+ config: await _buildConfig(),
password: password!,
mnemonic: await getMnemonic(),
name: info.walletId,
@@ -1132,12 +1121,13 @@ class EpiccashWallet extends Bip39Wallet {
await updateNode();
final String password = generatePassword();
- final String stringConfig = await _getConfig();
final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
+ // no need to save the config, just a string flag to know we have a
+ // wallet created
await secureStorageInterface.write(
key: '${walletId}_config',
- value: stringConfig,
+ value: "true",
);
await secureStorageInterface.write(
key: '${walletId}_password',
@@ -1156,7 +1146,7 @@ class EpiccashWallet extends Bip39Wallet {
}
_wallet = await libEpic.recoverWallet(
- config: stringConfig,
+ config: await _buildConfig(),
password: password,
mnemonic: await getMnemonic(),
name: info.walletId,
@@ -1560,13 +1550,7 @@ class EpiccashWallet extends Bip39Wallet {
Future<void> updateNode() async {
_epicNode = getCurrentNode();
- // TODO: [prio=low] move this out of secure storage if secure storage not
- // needed
- final String stringConfig = await _getConfig();
- await secureStorageInterface.write(
- key: '${walletId}_config',
- value: stringConfig,
- );
+ libEpic.updateConfig(wallet: _wallet!, config: await _buildConfig());
// unawaited(refresh());
}
@@ -1598,7 +1582,7 @@ class EpiccashWallet extends Bip39Wallet {
@override
Future<void> updateChainHeight() async {
_hackedCheckTorNodePrefs();
- final config = await _getRealConfig();
+ final config = await _buildConfig();
final latestHeight = await libEpic.getChainHeight(config: config);
await info.updateCachedChainHeight(
newHeight: latestHeight,
@@ -1678,19 +1662,7 @@ Future<String> deleteEpicWallet({
required EpiccashWallet wallet,
required SecureStorageInterface secureStore,
}) async {
- String? config = await secureStore.read(key: '${wallet.walletId}_config');
- if (Platform.isIOS) {
- final Directory appDir = await StackFileSystem.applicationRootDirectory();
-
- final path = "${appDir.path}/epiccash";
- final String name = wallet.walletId.trim();
- final walletDir = '$path/$name';
-
- final editConfig = jsonDecode(config as String);
-
- editConfig["wallet_dir"] = walletDir;
- config = jsonEncode(editConfig);
- }
+ final config = await wallet._hasConfig() ? await wallet._buildConfig() : null;
if (config == null) {
return "Tried to delete non existent epic wallet file with"
diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart
index e06e75b..02ece22 100644
--- a/lib/wl_gen/interfaces/libepiccash_interface.dart
+++ b/lib/wl_gen/interfaces/libepiccash_interface.dart
@@ -124,6 +124,8 @@ abstract class LibEpicCashInterface {
required String epicBoxConfig,
});
+ void updateConfig({required DynamicObject wallet, required String config});
+
String getPluginVersion();
}
diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart
index 3b43c3f..ecfdf4a 100644
--- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart
+++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart
@@ -78,6 +78,11 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface {
return wallet.get<EpicWallet>().updateEpicboxConfig(epicBoxConfig);
}
+ @override
+ void updateConfig({required DynamicObject wallet, required String config}) {
+ return wallet.get<EpicWallet>().updateConfig(config);
+ }
+
@override
Future<String> deleteWallet({required String config}) {
return EpicWallet.deleteWallet(config: config);
Why this scored 45/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.