fix(epic): apply new flutter_libepiccash patterns to epic cash wallet impl
What changed, and why it matters
This commit updates how the Epic Cash wallet in Stack Wallet talks to its underlying Rust library (flutter_libepiccash). It moves some configuration data (epicboxConfig) into wallet load/create calls and removes it from individual transaction/address calls, matching a new library API. It also replaces a real address-validation check with a very simple length check, which could let users paste in malformed or wrong addresses. The change looks like a compatibility/refactoring patch rather than a deliberate security fix, but the weaker address check is a small security regression.
Review whether the simplified address validation is intentional and sufficient; consider re-adding a proper Epic Cash address format check (e.g., regex for epicbox addresses and slatepack/on-chain address validation). Verify that all callers of validateSendAddress handle the new Future<bool> return correctly and that moving epicboxConfig does not leak sensitive server configuration.
Security signals we found
Address validation weakened from library-backed check to length heuristic
Library API migration touches wallet initialization, transaction creation, and address derivation
New wallet handle persistence added after recovery flows
Async signature change for validateSendAddress may affect downstream callers
Evidence from the diff
The diff adapts the Epic Cash Dart integration to new flutter_libepiccash patterns: EpicWallet.load/create/recover now require an epicboxConfig string, while createTransaction/getAddressInfo no longer take it. validateSendAddress in the interface becomes async. Most notably, the concrete address validation in epiccash.dart is replaced with a trivial address.isNotEmpty && address.length > 10 check (plus an ‘@’ epicbox shortcut), discarding the previous libEpic.validateSendAddress call. This reduces address-integrity guarantees and could allow invalid on-chain addresses or non-standard epicbox strings to pass UI validation.
Changed components
lib/wallets/crypto_currency/coins/epiccash.dartlib/wallets/wallet/impl/epiccash_wallet.dartlib/wl_gen/interfaces/libepiccash_interface.dartInspect captured patch +30 / −7
diff --git a/lib/wallets/crypto_currency/coins/epiccash.dart b/lib/wallets/crypto_currency/coins/epiccash.dart
index 42d6749..4411469 100644
--- a/lib/wallets/crypto_currency/coins/epiccash.dart
+++ b/lib/wallets/crypto_currency/coins/epiccash.dart
@@ -65,7 +65,12 @@ class Epiccash extends Bip39Currency {
}
}
- return libEpic.validateSendAddress(address: address);
+ if (address.contains("@")) {
+ return true; // Epicbox address format
+ }
+
+ // Very very basic (bad) check
+ return address.isNotEmpty && address.length > 10;
}
@override
diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart
index f7a2e69..7d451d2 100644
--- a/lib/wallets/wallet/impl/epiccash_wallet.dart
+++ b/lib/wallets/wallet/impl/epiccash_wallet.dart
@@ -160,9 +160,12 @@ class EpiccashWallet extends Bip39Wallet {
throw Exception('Wallet password not found');
}
+ final epicboxConfig = await getEpicBoxConfig();
+
_wallet = await epic.EpicWallet.load(
config: config,
password: password,
+ epicboxConfig: epicboxConfig.toString(),
);
final handle = _wallet!.handle;
@@ -186,14 +189,11 @@ class EpiccashWallet extends Bip39Wallet {
if (_wallet == null) {
throw Exception('Wallet not initialized');
}
- final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
-
// Create transaction with returnSlate: true for slatepack mode.
final result = await _wallet!.createTransaction(
amount: amount.raw.toInt(),
address: 'slate', // Not used in slate mode.
secretKeyIndex: 0,
- epicboxConfig: epicboxConfig.toString(),
minimumConfirmations:
minimumConfirmations ?? cryptoCurrency.minConfirms,
note: message ?? '',
@@ -632,7 +632,6 @@ class EpiccashWallet extends Bip39Wallet {
final walletAddress = await _wallet!.getAddressInfo(
index: index,
- epicboxConfig: epicboxConfig.toString(),
);
Logging.instance.d("WALLET_ADDRESS_IS $walletAddress");
@@ -850,6 +849,7 @@ class EpiccashWallet extends Bip39Wallet {
mnemonic: mnemonicString,
password: password,
name: name,
+ epicboxConfig: epicboxConfig.toString(),
); // Spawns worker isolate
// Store the wallet handle for listeners
@@ -891,10 +891,12 @@ class EpiccashWallet extends Bip39Wallet {
final password = await secureStorageInterface.read(
key: '${walletId}_password',
);
+ final epicboxConfig = await getEpicBoxConfig();
_wallet = await epic.EpicWallet.load(
config: config,
password: password!,
+ epicboxConfig: epicboxConfig.toString(),
); // Spawns worker isolate
// Store the wallet handle for listeners
@@ -957,7 +959,6 @@ class EpiccashWallet extends Bip39Wallet {
amount: txData.recipients!.first.amount.raw.toInt(),
address: txData.recipients!.first.address,
secretKeyIndex: 0,
- epicboxConfig: epicboxConfig.toString(),
minimumConfirmations: cryptoCurrency.minConfirms,
note: txData.noteOnChain!,
)).toRecord();
@@ -1090,6 +1091,7 @@ class EpiccashWallet extends Bip39Wallet {
final password = await secureStorageInterface.read(
key: '${walletId}_password',
);
+ final epicboxConfig = await getEpicBoxConfig();
// maybe there is some way to tel epic-wallet rust to fully rescan...
final result = await deleteEpicWallet(
@@ -1104,6 +1106,13 @@ class EpiccashWallet extends Bip39Wallet {
password: password!,
mnemonic: await getMnemonic(),
name: info.walletId,
+ epicboxConfig: epicboxConfig.toString(),
+ );
+
+ // Save wallet handle after recovery
+ await secureStorageInterface.write(
+ key: '${walletId}_wallet',
+ value: _wallet!.handle,
);
highestPercent = 0;
@@ -1134,6 +1143,13 @@ class EpiccashWallet extends Bip39Wallet {
password: password,
mnemonic: await getMnemonic(),
name: info.walletId,
+ epicboxConfig: epicboxConfig.toString(),
+ );
+
+ // Save wallet handle after recovery
+ await secureStorageInterface.write(
+ key: '${walletId}_wallet',
+ value: _wallet!.handle,
);
final epicData = ExtraEpiccashWalletInfo(
@@ -1200,6 +1216,8 @@ class EpiccashWallet extends Bip39Wallet {
final int curAdd = await _getCurrentIndex();
await _generateAndStoreReceivingAddressForIndex(curAdd);
+ await _ensureWalletOpen();
+
if (doScan) {
await _startScans();
diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart
index a063520..c58a922 100644
--- a/lib/wl_gen/interfaces/libepiccash_interface.dart
+++ b/lib/wl_gen/interfaces/libepiccash_interface.dart
@@ -79,7 +79,7 @@ abstract class LibEpicCashInterface {
List<String> getActiveListenerWalletIds();
- bool validateSendAddress({required String address});
+ Future<bool> validateSendAddress({required String address});
Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({
required String wallet,
Why this scored 37/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.