From b687b5e8ad52e3a86b51a4118c3a618d3d80e172 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 21 Jan 2026 23:53:00 -0600 Subject: [PATCH 01/18] fix(epic): mirror cs_monero isolate pattern for flutter_epiccash & wallet impl TODO: test more, merge to flutter_libepiccash#main, update flutter_libepiccash submodule ref here, open PR to merge from here to staging --- crypto_plugins/flutter_libepiccash | 2 +- lib/wallets/wallet/impl/epiccash_wallet.dart | 169 +++++++++---------- 2 files changed, 82 insertions(+), 89 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 25120645d..bb87104d1 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 25120645dd3279e1682ea24c9b39cfbd221c2137 +Subproject commit bb87104d1bcbb541c1a62ee79187d79300350b83 diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 86ca76a69..f7a2e6907 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -39,6 +39,8 @@ import '../../models/tx_data.dart'; import '../intermediate/bip39_wallet.dart'; import '../supporting/epiccash_wallet_info_extension.dart'; +import 'package:flutter_libepiccash/flutter_libepiccash.dart' as epic; + // // refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart // @@ -49,6 +51,8 @@ class EpiccashWallet extends Bip39Wallet { NodeModel? _epicNode; Timer? timer; + epic.EpicWallet? _wallet; + double highestPercent = 0; Future get getSyncPercent async { final int lastScannedBlock = info.epicData?.lastScannedBlock ?? 0; @@ -87,12 +91,11 @@ class EpiccashWallet extends Bip39Wallet { Future cancelPendingTransactionAndPost(String txSlateId) async { try { _hackedCheckTorNodePrefs(); - final String wallet = (await secureStorageInterface.read( - key: '${walletId}_wallet', - ))!; + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } - final result = await libEpic.cancelTransaction( - wallet: wallet, + final result = await _wallet!.cancelTransaction( transactionId: txSlateId, ); Logging.instance.d("cancel $txSlateId result: $result"); @@ -145,10 +148,9 @@ class EpiccashWallet extends Bip39Wallet { // ================= Slatepack Operations =================================== Future _ensureWalletOpen() async { - final existing = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); - if (existing != null && existing.isNotEmpty) return existing; + if (_wallet != null) { + return _wallet!.handle; + } final config = await _getRealConfig(); final password = await secureStorageInterface.read( @@ -157,12 +159,18 @@ class EpiccashWallet extends Bip39Wallet { if (password == null) { throw Exception('Wallet password not found'); } - final opened = await libEpic.openWallet(config: config, password: password); + + _wallet = await epic.EpicWallet.load( + config: config, + password: password, + ); + + final handle = _wallet!.handle; await secureStorageInterface.write( key: '${walletId}_wallet', - value: opened, + value: handle, ); - return opened; + return handle; } /// Create a slatepack for sending Epic Cash. @@ -174,12 +182,14 @@ class EpiccashWallet extends Bip39Wallet { }) async { try { _hackedCheckTorNodePrefs(); - final handle = await _ensureWalletOpen(); + await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); // Create transaction with returnSlate: true for slatepack mode. - final result = await libEpic.createTransaction( - wallet: handle, + final result = await _wallet!.createTransaction( amount: amount.raw.toInt(), address: 'slate', // Not used in slate mode. secretKeyIndex: 0, @@ -256,11 +266,13 @@ class EpiccashWallet extends Bip39Wallet { Future receiveSlatepack(String slateJson) async { try { _hackedCheckTorNodePrefs(); - final handle = await _ensureWalletOpen(); + await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } // Receive and get updated slate JSON. - final received = await libEpic.txReceive( - wallet: handle, + final received = await _wallet!.txReceive( slateJson: slateJson, ); @@ -282,11 +294,13 @@ class EpiccashWallet extends Bip39Wallet { Future finalizeSlatepack(String slateJson) async { try { _hackedCheckTorNodePrefs(); - final handle = await _ensureWalletOpen(); + await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } // Finalize transaction. - final finalized = await libEpic.txFinalize( - wallet: handle, + final finalized = await _wallet!.txFinalize( slateJson: slateJson, ); @@ -451,16 +465,17 @@ class EpiccashWallet extends Bip39Wallet { int satoshiAmount, { bool ifErrorEstimateFee = false, }) async { - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } try { _hackedCheckTorNodePrefs(); final available = info.cachedBalance.spendable.raw.toInt(); - final transactionFees = await libEpic.getTransactionFees( - wallet: wallet!, + final transactionFees = await _wallet!.getTransactionFees( amount: satoshiAmount, minimumConfirmations: cryptoCurrency.minConfirms, - available: available, ); int realFee = 0; @@ -482,13 +497,15 @@ class EpiccashWallet extends Bip39Wallet { Future _startSync() async { _hackedCheckTorNodePrefs(); Logging.instance.d("request start sync"); - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } const int refreshFromNode = 1; if (!syncMutex.isLocked) { await syncMutex.protect(() async { // How does getWalletBalances start syncing???? - await libEpic.getWalletBalances( - wallet: wallet!, + await _wallet!.getBalances( refreshFromNode: refreshFromNode, minimumConfirmations: 10, ); @@ -508,13 +525,15 @@ class EpiccashWallet extends Bip39Wallet { > _allWalletBalances() async { _hackedCheckTorNodePrefs(); - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } const refreshFromNode = 0; - return await libEpic.getWalletBalances( - wallet: wallet!, + return (await _wallet!.getBalances( refreshFromNode: refreshFromNode, minimumConfirmations: cryptoCurrency.minConfirms, - ); + )).toRecord(); } Future _testEpicboxServer(EpicBoxConfigModel epicboxConfig) async { @@ -606,10 +625,12 @@ class EpiccashWallet extends Bip39Wallet { int index, EpicBoxConfigModel epicboxConfig, ) async { - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not initialized'); + } - final walletAddress = await libEpic.getAddressInfo( - wallet: wallet!, + final walletAddress = await _wallet!.getAddressInfo( index: index, epicboxConfig: epicboxConfig.toString(), ); @@ -631,10 +652,6 @@ class EpiccashWallet extends Bip39Wallet { Future _startScans() async { try { - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); - // max number of blocks to scan per loop iteration const scanChunkSize = 10000; @@ -661,8 +678,7 @@ class EpiccashWallet extends Bip39Wallet { "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", ); - final int nextScannedBlock = await libEpic.scanOutputs( - wallet: wallet!, + final int nextScannedBlock = await _wallet!.scanOutputs( startHeight: lastScannedBlock, numberOfBlocks: scanChunkSize, ); @@ -829,18 +845,15 @@ class EpiccashWallet extends Bip39Wallet { final String name = walletId; - await libEpic.initializeNewWallet( + _wallet = await epic.EpicWallet.create( config: stringConfig, mnemonic: mnemonicString, password: password, name: name, - ); + ); // Spawns worker isolate - //Open wallet - encodedWallet = await libEpic.openWallet( - config: stringConfig, - password: password, - ); + // Store the wallet handle for listeners + encodedWallet = _wallet!.handle; await secureStorageInterface.write( key: '${walletId}_wallet', value: encodedWallet, @@ -879,13 +892,15 @@ class EpiccashWallet extends Bip39Wallet { key: '${walletId}_password', ); - final walletOpen = await libEpic.openWallet( + _wallet = await epic.EpicWallet.load( config: config, password: password!, - ); + ); // Spawns worker isolate + + // Store the wallet handle for listeners await secureStorageInterface.write( key: '${walletId}_wallet', - value: walletOpen, + value: _wallet!.handle, ); await updateNode(); @@ -907,9 +922,6 @@ class EpiccashWallet extends Bip39Wallet { Future confirmSend({required TxData txData}) async { try { _hackedCheckTorNodePrefs(); - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); // TODO determine whether it is worth sending change to a change address. @@ -928,8 +940,7 @@ class EpiccashWallet extends Bip39Wallet { if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) { - final httpResult = await libEpic.txHttpSend( - wallet: wallet!, + final httpResult = await _wallet!.txHttpSend( selectionStrategyIsAll: 0, minimumConfirmations: cryptoCurrency.minConfirms, message: txData.noteOnChain ?? "", @@ -942,15 +953,14 @@ class EpiccashWallet extends Bip39Wallet { slateJson: '', ); } else { - transaction = await libEpic.createTransaction( - wallet: wallet!, + transaction = (await _wallet!.createTransaction( amount: txData.recipients!.first.amount.raw.toInt(), address: txData.recipients!.first.address, secretKeyIndex: 0, epicboxConfig: epicboxConfig.toString(), minimumConfirmations: cryptoCurrency.minConfirms, note: txData.noteOnChain!, - ); + )).toRecord(); } final Map txAddressInfo = {}; @@ -1087,23 +1097,15 @@ class EpiccashWallet extends Bip39Wallet { secureStore: secureStorageInterface, ); Logging.instance.w("Epic rescan temporary delete result: $result"); - await libEpic.recoverWallet( + + await _wallet?.close(); + _wallet = await epic.EpicWallet.recover( config: stringConfig, password: password!, mnemonic: await getMnemonic(), name: info.walletId, ); - //Open Wallet - final walletOpen = await libEpic.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); - highestPercent = 0; } else { await updateNode(); @@ -1126,7 +1128,8 @@ class EpiccashWallet extends Bip39Wallet { value: epicboxConfig.toString(), ); - await libEpic.recoverWallet( + await _wallet?.close(); + _wallet = await epic.EpicWallet.recover( config: stringConfig, password: password, mnemonic: await getMnemonic(), @@ -1148,16 +1151,6 @@ class EpiccashWallet extends Bip39Wallet { isar: mainDB.isar, ); - //Open Wallet - final walletOpen = await libEpic.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); - await _generateAndStoreReceivingAddressForIndex( epicData.receivingIndex, ); @@ -1333,9 +1326,6 @@ class EpiccashWallet extends Bip39Wallet { Future updateTransactions() async { try { _hackedCheckTorNodePrefs(); - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); const refreshFromNode = 1; final myAddresses = await mainDB @@ -1350,8 +1340,7 @@ class EpiccashWallet extends Bip39Wallet { .findAll(); final myAddressesSet = myAddresses.toSet(); - final transactions = await libEpic.getTransactions( - wallet: wallet!, + final transactions = await _wallet!.getTransactions( refreshFromNode: refreshFromNode, ); @@ -1365,8 +1354,8 @@ class EpiccashWallet extends Bip39Wallet { libEpic.txTypeIsReceiveCancelled(tx.txType); final slateId = tx.txSlateId; final commitId = slatesToCommits[slateId]?['commitId'] as String?; - final numberOfMessages = tx.messages?.length; - final onChainNote = tx.messages?.first.message; + final numberOfMessages = tx.messages?.messages.length; + final onChainNote = tx.messages?.messages.first.message; final addressFrom = slatesToCommits[slateId]?["from"] as String?; final addressTo = slatesToCommits[slateId]?["to"] as String?; @@ -1619,6 +1608,10 @@ class EpiccashWallet extends Bip39Wallet { libEpic.stopEpicboxListener(walletId: walletId); timer?.cancel(); timer = null; + + await _wallet?.close(); + _wallet = null; + await super.exit(); Logging.instance.d("EpicCash_wallet exit finished"); } From 446ea11abe52dea83cdb46118e108c8c3c3923f1 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 22 Jan 2026 13:50:22 -0600 Subject: [PATCH 02/18] fix(epic): apply new flutter_libepiccash patterns to epic cash wallet impl --- .../crypto_currency/coins/epiccash.dart | 7 ++++- lib/wallets/wallet/impl/epiccash_wallet.dart | 28 +++++++++++++++---- .../interfaces/libepiccash_interface.dart | 2 +- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/wallets/crypto_currency/coins/epiccash.dart b/lib/wallets/crypto_currency/coins/epiccash.dart index 42d67491d..441146920 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 f7a2e6907..7d451d256 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 a06352084..c58a9225c 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 getActiveListenerWalletIds(); - bool validateSendAddress({required String address}); + Future validateSendAddress({required String address}); Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({ required String wallet, From 6c4c31d1a77481f9c047d63f81656987aff18f17 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 22 Jan 2026 14:52:54 -0600 Subject: [PATCH 03/18] fix(epic): fix return types and response handling for epic cash --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index bb87104d1..a5c0a19e4 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit bb87104d1bcbb541c1a62ee79187d79300350b83 +Subproject commit a5c0a19e41ab59331ab32fb20332a6efbf413f38 From ed18ba26cd1acda92ba49b2fe2ed416672265ce0 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 22 Jan 2026 16:19:53 -0600 Subject: [PATCH 04/18] fix(epic): fix wallet opening and handling, add open, remove ensureWalletOpen --- lib/wallets/wallet/impl/epiccash_wallet.dart | 122 ++++++++++++------- 1 file changed, 76 insertions(+), 46 deletions(-) diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 7d451d256..7d1e5004a 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -73,6 +73,47 @@ class EpiccashWallet extends Bip39Wallet { return restorePercent < 0 ? 0.0 : restorePercent; } + /// Opens and initializes the Epic wallet instance. + /// Should only be called once during wallet initialization. + Future open() async { + if (_wallet != null) { + Logging.instance.d("Wallet already open, skipping"); + return; + } + + try { + final config = await _getRealConfig(); + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); + if (password == null) { + throw Exception('Wallet password not found'); + } + + final epicboxConfig = await getEpicBoxConfig(); + + _wallet = await epic.EpicWallet.load( + config: config, + password: password, + epicboxConfig: epicboxConfig.toString(), + ); + + // Store wallet handle + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: _wallet!.handle, + ); + + await _listenToEpicbox(); + + Logging.instance.d("Epic wallet opened successfully with persistent isolate"); + } catch (e, s) { + Logging.instance.e("Failed to open Epic wallet", error: e, stackTrace: s); + _wallet = null; + rethrow; + } + } + Future updateEpicboxConfig(String host, int port) async { final String stringConfig = jsonEncode({ "epicbox_domain": host, @@ -147,35 +188,6 @@ class EpiccashWallet extends Bip39Wallet { // ================= Slatepack Operations =================================== - Future _ensureWalletOpen() async { - if (_wallet != null) { - return _wallet!.handle; - } - - final config = await _getRealConfig(); - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - 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; - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: handle, - ); - return handle; - } - /// Create a slatepack for sending Epic Cash. Future createSlatepack({ required Amount amount, @@ -185,9 +197,8 @@ class EpiccashWallet extends Bip39Wallet { }) async { try { _hackedCheckTorNodePrefs(); - await _ensureWalletOpen(); if (_wallet == null) { - throw Exception('Wallet not initialized'); + throw Exception('Wallet not opened. Call open() first.'); } // Create transaction with returnSlate: true for slatepack mode. final result = await _wallet!.createTransaction( @@ -266,9 +277,8 @@ class EpiccashWallet extends Bip39Wallet { Future receiveSlatepack(String slateJson) async { try { _hackedCheckTorNodePrefs(); - await _ensureWalletOpen(); if (_wallet == null) { - throw Exception('Wallet not initialized'); + throw Exception('Wallet not opened. Call open() first.'); } // Receive and get updated slate JSON. @@ -294,9 +304,8 @@ class EpiccashWallet extends Bip39Wallet { Future finalizeSlatepack(String slateJson) async { try { _hackedCheckTorNodePrefs(); - await _ensureWalletOpen(); if (_wallet == null) { - throw Exception('Wallet not initialized'); + throw Exception('Wallet not opened. Call open() first.'); } // Finalize transaction. @@ -465,9 +474,8 @@ class EpiccashWallet extends Bip39Wallet { int satoshiAmount, { bool ifErrorEstimateFee = false, }) async { - await _ensureWalletOpen(); if (_wallet == null) { - throw Exception('Wallet not initialized'); + throw Exception('Wallet not opened. Call open() first.'); } try { _hackedCheckTorNodePrefs(); @@ -497,9 +505,8 @@ class EpiccashWallet extends Bip39Wallet { Future _startSync() async { _hackedCheckTorNodePrefs(); Logging.instance.d("request start sync"); - await _ensureWalletOpen(); if (_wallet == null) { - throw Exception('Wallet not initialized'); + throw Exception('Wallet not opened. Call open() first.'); } const int refreshFromNode = 1; if (!syncMutex.isLocked) { @@ -525,9 +532,8 @@ class EpiccashWallet extends Bip39Wallet { > _allWalletBalances() async { _hackedCheckTorNodePrefs(); - await _ensureWalletOpen(); if (_wallet == null) { - throw Exception('Wallet not initialized'); + throw Exception('Wallet not opened. Call open() first.'); } const refreshFromNode = 0; return (await _wallet!.getBalances( @@ -625,9 +631,8 @@ class EpiccashWallet extends Bip39Wallet { int index, EpicBoxConfigModel epicboxConfig, ) async { - await _ensureWalletOpen(); if (_wallet == null) { - throw Exception('Wallet not initialized'); + throw Exception('Wallet not opened. Call open() first.'); } final walletAddress = await _wallet!.getAddressInfo( @@ -815,6 +820,11 @@ class EpiccashWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { + if (_wallet != null) { + Logging.instance.d("Wallet already initialized, skipping init"); + return await super.init(); + } + if (isRestore != true) { String? encodedWallet = await secureStorageInterface.read( key: "${walletId}_wallet", @@ -881,6 +891,8 @@ class EpiccashWallet extends Bip39Wallet { epicData: epicData, isar: mainDB.isar, ); + + await _listenToEpicbox(); } else { try { Logging.instance.d( @@ -906,6 +918,8 @@ class EpiccashWallet extends Bip39Wallet { ); await updateNode(); + + await _listenToEpicbox(); } catch (e, s) { // do nothing, still allow user into wallet Logging.instance.w( @@ -1100,7 +1114,12 @@ class EpiccashWallet extends Bip39Wallet { ); Logging.instance.w("Epic rescan temporary delete result: $result"); - await _wallet?.close(); + // Close old wallet before recovery + if (_wallet != null) { + await _wallet!.close(); + _wallet = null; + } + _wallet = await epic.EpicWallet.recover( config: stringConfig, password: password!, @@ -1115,6 +1134,8 @@ class EpiccashWallet extends Bip39Wallet { value: _wallet!.handle, ); + await _listenToEpicbox(); + highestPercent = 0; } else { await updateNode(); @@ -1137,7 +1158,12 @@ class EpiccashWallet extends Bip39Wallet { value: epicboxConfig.toString(), ); - await _wallet?.close(); + // Close old wallet before recovery + if (_wallet != null) { + await _wallet!.close(); + _wallet = null; + } + _wallet = await epic.EpicWallet.recover( config: stringConfig, password: password, @@ -1152,6 +1178,8 @@ class EpiccashWallet extends Bip39Wallet { value: _wallet!.handle, ); + await _listenToEpicbox(); + final epicData = ExtraEpiccashWalletInfo( receivingIndex: 0, changeIndex: 0, @@ -1216,7 +1244,9 @@ class EpiccashWallet extends Bip39Wallet { final int curAdd = await _getCurrentIndex(); await _generateAndStoreReceivingAddressForIndex(curAdd); - await _ensureWalletOpen(); + if (_wallet == null) { + throw Exception('Wallet not opened. Call open() first.'); + } if (doScan) { await _startScans(); From 0038f7522172e02bbc17e25f206e9d27d6cb2fbc Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 22 Jan 2026 18:13:44 -0600 Subject: [PATCH 05/18] fix(epic): update flutter_libepiccash worker --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index a5c0a19e4..b40b288c4 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit a5c0a19e41ab59331ab32fb20332a6efbf413f38 +Subproject commit b40b288c4a84c29dee1e791e1acb249068cb039b From b1162d209005c17b5a3f0a6226d356a247662f84 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 22 Jan 2026 18:22:07 -0600 Subject: [PATCH 06/18] fix(epic): adjust exit behavior --- lib/wallets/wallet/impl/epiccash_wallet.dart | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 7d1e5004a..e60090036 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 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 _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 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"); } From b148fd8ad72ebbe60cbd670903ccb47cdf92e009 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 22 Jan 2026 20:57:04 -0600 Subject: [PATCH 07/18] fix(epic): shared static worker --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index b40b288c4..9fe051152 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit b40b288c4a84c29dee1e791e1acb249068cb039b +Subproject commit 9fe051152f0ff0a05e0ab9a9d1cf8efcc3dd55f3 From 3e27a371a8b45596c6903cb94466ce8fd7b37fe2 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 09:56:06 -0600 Subject: [PATCH 08/18] openWallet example --- lib/wl_gen/interfaces/libepiccash_interface.dart | 8 +++++++- ...EPIC_libepiccash_interface_impl.template.dart | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart index c58a9225c..d1999dce5 100644 --- a/lib/wl_gen/interfaces/libepiccash_interface.dart +++ b/lib/wl_gen/interfaces/libepiccash_interface.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import '../../utilities/dynamic_object.dart'; + export '../generated/libepiccash_interface_impl.dart'; abstract class LibEpicCashInterface { @@ -16,7 +18,11 @@ abstract class LibEpicCashInterface { required String name, }); - Future openWallet({required String config, required String password}); + Future openWallet({ + required String config, + required String password, + required String epicboxConfig, + }); Future recoverWallet({ required String config, diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart index 18cf45ab7..e14d5db08 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -2,8 +2,9 @@ import 'package:flutter_libepiccash/git_versions.dart' as epic_versions; import 'package:flutter_libepiccash/lib.dart'; import 'package:flutter_libepiccash/models/transaction.dart'; - //END_ON +import 'package:stackwallet/utilities/dynamic_object.dart'; + import '../interfaces/libepiccash_interface.dart'; LibEpicCashInterface get libEpic => _getLib(); @@ -197,11 +198,18 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { } @override - Future openWallet({ + Future openWallet({ required String config, required String password, - }) { - return LibEpiccash.openWallet(config: config, password: password); + required String epicboxConfig, + }) async { + final wallet = await EpicWallet.load( + config: config, + password: password, + epicboxConfig: epicboxConfig, + ); + + return DynamicObject(wallet); } @override From ee522c38432673338d74d5021c6258f942a12cca Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 10:05:49 -0600 Subject: [PATCH 09/18] formatting --- lib/wl_gen/interfaces/libepiccash_interface.dart | 3 ++- .../EPIC_libepiccash_interface_impl.template.dart | 13 ++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart index d1999dce5..ac69b0589 100644 --- a/lib/wl_gen/interfaces/libepiccash_interface.dart +++ b/lib/wl_gen/interfaces/libepiccash_interface.dart @@ -40,7 +40,8 @@ abstract class LibEpicCashInterface { required String address, }); - Future<({String commitId, String slateId, String slateJson})> createTransaction({ + Future<({String commitId, String slateId, String slateJson})> + createTransaction({ required String wallet, required int amount, required String address, diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart index e14d5db08..e8c67c9e8 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -35,10 +35,7 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { required String wallet, required String slateJson, }) { - return LibEpiccash.txReceive( - wallet: wallet, - slateJson: slateJson, - ); + return LibEpiccash.txReceive(wallet: wallet, slateJson: slateJson); } @override @@ -46,14 +43,12 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { required String wallet, required String slateJson, }) { - return LibEpiccash.txFinalize( - wallet: wallet, - slateJson: slateJson, - ); + return LibEpiccash.txFinalize(wallet: wallet, slateJson: slateJson); } @override - Future<({String commitId, String slateId, String slateJson})> createTransaction({ + Future<({String commitId, String slateId, String slateJson})> + createTransaction({ required String wallet, required int amount, required String address, From 9e5d2b974582e6fd4feda790cf284d56b6573442 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 11:28:05 -0600 Subject: [PATCH 10/18] wrap epicwallet in dyn obj. --- crypto_plugins/flutter_libepiccash | 2 +- lib/services/wallets.dart | 22 ++- lib/wallets/wallet/impl/epiccash_wallet.dart | 181 ++++++++---------- .../interfaces/libepiccash_interface.dart | 56 +++--- ...C_libepiccash_interface_impl.template.dart | 140 +++++++------- 5 files changed, 196 insertions(+), 205 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 9fe051152..e92c5e927 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 9fe051152f0ff0a05e0ab9a9d1cf8efcc3dd55f3 +Subproject commit e92c5e927cdab7fea730fd898021116e09ce8b99 diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index d8028421a..e1d38149b 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -126,13 +126,21 @@ class Wallets { if (info.coin is CryptonoteCurrency) { await _deleteCryptonoteWalletFilesHelper(info); } else if (info.coin is Epiccash) { - final deleteResult = await deleteEpicWallet( - walletId: walletId, - secureStore: secureStorage, - ); - Logging.instance.d( - "epic wallet: $walletId deleted with result: $deleteResult", - ); + if (wallet is! EpiccashWallet) { + Logging.instance.e( + "epic wallet: $walletId does not appear to exist???", + error: Exception(), + stackTrace: StackTrace.current, + ); + } else { + final deleteResult = await deleteEpicWallet( + wallet: wallet, + secureStore: secureStorage, + ); + Logging.instance.d( + "epic wallet: $walletId deleted with result: $deleteResult", + ); + } } else if (info.coin is Mimblewimblecoin) { final deleteResult = await deleteMimblewimblecoinWallet( walletId: walletId, diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index e60090036..4def973f0 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -28,6 +28,7 @@ import '../../../services/event_bus/events/global/wallet_sync_status_changed_eve import '../../../services/event_bus/global_event_bus.dart'; import '../../../utilities/amount/amount.dart'; import '../../../utilities/default_epicboxes.dart'; +import '../../../utilities/dynamic_object.dart'; import '../../../utilities/flutter_secure_storage_interface.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/stack_file_system.dart'; @@ -39,8 +40,6 @@ import '../../models/tx_data.dart'; import '../intermediate/bip39_wallet.dart'; import '../supporting/epiccash_wallet_info_extension.dart'; -import 'package:flutter_libepiccash/flutter_libepiccash.dart' as epic; - // // refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart // @@ -51,7 +50,7 @@ class EpiccashWallet extends Bip39Wallet { NodeModel? _epicNode; Timer? timer; - epic.EpicWallet? _wallet; + DynamicObject? _wallet; double highestPercent = 0; Future get getSyncPercent async { @@ -78,7 +77,7 @@ class EpiccashWallet extends Bip39Wallet { Future open() async { if (_wallet != null) { Logging.instance.d("Wallet already open, ensuring listener"); - if (!await _wallet!.isEpicboxListenerRunning()) { + if (!await libEpic.isEpicboxListenerRunning(wallet: _wallet!)) { await _listenToEpicbox(); } return; @@ -95,21 +94,17 @@ class EpiccashWallet extends Bip39Wallet { final epicboxConfig = await getEpicBoxConfig(); - _wallet = await epic.EpicWallet.load( + _wallet = await libEpic.openWallet( config: config, password: password, epicboxConfig: epicboxConfig.toString(), ); - // Store wallet handle - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: _wallet!.handle, - ); - await _listenToEpicbox(); - Logging.instance.d("Epic wallet opened successfully with persistent isolate"); + Logging.instance.d( + "Epic wallet opened successfully with persistent isolate", + ); } catch (e, s) { Logging.instance.e("Failed to open Epic wallet", error: e, stackTrace: s); _wallet = null; @@ -128,7 +123,7 @@ class EpiccashWallet extends Bip39Wallet { key: '${walletId}_epicboxConfig', value: stringConfig, ); - _wallet?.updateEpicboxConfig(stringConfig); + libEpic.updateEpicboxConfig(wallet: _wallet!, epicBoxConfig: stringConfig); // TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed } @@ -140,7 +135,8 @@ class EpiccashWallet extends Bip39Wallet { throw Exception('Wallet not initialized'); } - final result = await _wallet!.cancelTransaction( + final result = await libEpic.cancelTransaction( + wallet: _wallet!, transactionId: txSlateId, ); Logging.instance.d("cancel $txSlateId result: $result"); @@ -158,7 +154,8 @@ class EpiccashWallet extends Bip39Wallet { //Get the default Epicbox server and check if it's conected // bool isEpicboxConnected = await _testEpicboxServer( - // DefaultEpicBoxes.defaultEpicBoxServer.host, DefaultEpicBoxes.defaultEpicBoxServer.port ?? 443); + // DefaultEpicBoxes.defaultEpicBoxServer.host, + // DefaultEpicBoxes.defaultEpicBoxServer.port ?? 443); // if (isEpicboxConnected) { //Use default server for as Epicbox config @@ -205,7 +202,8 @@ class EpiccashWallet extends Bip39Wallet { throw Exception('Wallet not opened. Call open() first.'); } // Create transaction with returnSlate: true for slatepack mode. - final result = await _wallet!.createTransaction( + final result = await libEpic.createTransaction( + wallet: _wallet!, amount: amount.raw.toInt(), address: 'slate', // Not used in slate mode. secretKeyIndex: 0, @@ -286,7 +284,8 @@ class EpiccashWallet extends Bip39Wallet { } // Receive and get updated slate JSON. - final received = await _wallet!.txReceive( + final received = await libEpic.txReceive( + wallet: _wallet!, slateJson: slateJson, ); @@ -313,7 +312,8 @@ class EpiccashWallet extends Bip39Wallet { } // Finalize transaction. - final finalized = await _wallet!.txFinalize( + final finalized = await libEpic.txFinalize( + wallet: _wallet!, slateJson: slateJson, ); @@ -374,7 +374,8 @@ class EpiccashWallet extends Bip39Wallet { type = 'Incoming'; // Response slate - this means we're receiving. } else if (signedParticipants >= participants.length) { status = 'S3'; - type = 'Outgoing'; // Finalized slate - completed outgoing transaction. + type = + 'Outgoing'; // Finalized slate - completed outgoing transaction. } } @@ -420,7 +421,8 @@ class EpiccashWallet extends Bip39Wallet { // Check for common slate fields. return parsed is Map && (parsed.containsKey('id') || parsed.containsKey('slate_id')) && - (parsed.containsKey('amount') || parsed.containsKey('participant_data')); + (parsed.containsKey('amount') || + parsed.containsKey('participant_data')); } catch (e) { return false; } @@ -483,9 +485,9 @@ class EpiccashWallet extends Bip39Wallet { } try { _hackedCheckTorNodePrefs(); - final available = info.cachedBalance.spendable.raw.toInt(); - final transactionFees = await _wallet!.getTransactionFees( + final transactionFees = await libEpic.getTransactionFees( + wallet: _wallet!, amount: satoshiAmount, minimumConfirmations: cryptoCurrency.minConfirms, ); @@ -515,8 +517,9 @@ class EpiccashWallet extends Bip39Wallet { const int refreshFromNode = 1; if (!syncMutex.isLocked) { await syncMutex.protect(() async { - // How does getWalletBalances start syncing???? - await _wallet!.getBalances( + // How does getWalletBalances start syncing?????????!!!!! + await libEpic.getWalletBalances( + wallet: _wallet!, refreshFromNode: refreshFromNode, minimumConfirmations: 10, ); @@ -540,10 +543,11 @@ class EpiccashWallet extends Bip39Wallet { throw Exception('Wallet not opened. Call open() first.'); } const refreshFromNode = 0; - return (await _wallet!.getBalances( + return (await libEpic.getWalletBalances( + wallet: _wallet!, refreshFromNode: refreshFromNode, minimumConfirmations: cryptoCurrency.minConfirms, - )).toRecord(); + )); } Future _testEpicboxServer(EpicBoxConfigModel epicboxConfig) async { @@ -603,7 +607,8 @@ class EpiccashWallet extends Bip39Wallet { try { final int receivingIndex = info.epicData!.receivingIndex; // TODO: go through pendingarray and processed array and choose the index - // of the last one that has not been processed, or the index after the one most recently processed; + // of the last one that has not been processed, or the index after the + // one most recently processed; return receivingIndex; } catch (e, s) { Logging.instance.e("$e $s", error: e, stackTrace: s); @@ -639,8 +644,10 @@ class EpiccashWallet extends Bip39Wallet { throw Exception('Wallet not opened. Call open() first.'); } - final walletAddress = await _wallet!.getAddressInfo( + final walletAddress = await libEpic.getAddressInfo( + wallet: _wallet!, index: index, + epicboxConfig: epicboxConfig.toString(), ); Logging.instance.d("WALLET_ADDRESS_IS $walletAddress"); @@ -677,7 +684,7 @@ class EpiccashWallet extends Bip39Wallet { final needsScanning = lastScannedBlock < chainHeight; if (needsScanning) { // Stop listener during active scanning to avoid potential conflicts - await _wallet!.stopListeners(); + await libEpic.stopEpicboxListener(wallet: _wallet!); } // loop while scanning in chain in chunks (of blocks?) @@ -686,7 +693,8 @@ class EpiccashWallet extends Bip39Wallet { "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", ); - final int nextScannedBlock = await _wallet!.scanOutputs( + final int nextScannedBlock = await libEpic.scanOutputs( + wallet: _wallet!, startHeight: lastScannedBlock, numberOfBlocks: scanChunkSize, ); @@ -710,7 +718,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 (!await _wallet!.isEpicboxListenerRunning()) { + if (!await libEpic.isEpicboxListenerRunning(wallet: _wallet!)) { Logging.instance.d("Listener not running, starting it..."); await _listenToEpicbox(); } else { @@ -728,8 +736,11 @@ class EpiccashWallet extends Bip39Wallet { if (_wallet == null) { throw Exception('Wallet not opened. Call open() first.'); } - _wallet!.updateEpicboxConfig(epicboxConfig.toString()); - await _wallet!.startListeners(); + libEpic.updateEpicboxConfig( + wallet: _wallet!, + epicBoxConfig: epicboxConfig.toString(), + ); + await libEpic.startEpicboxListener(wallet: _wallet!); } // As opposed to fake config? @@ -829,12 +840,8 @@ class EpiccashWallet extends Bip39Wallet { } if (isRestore != true) { - String? encodedWallet = await secureStorageInterface.read( - key: "${walletId}_wallet", - ); - // check if should create a new wallet - if (encodedWallet == null) { + if (_wallet == null) { await updateNode(); final mnemonicString = await getMnemonic(); @@ -857,21 +864,14 @@ class EpiccashWallet extends Bip39Wallet { final String name = walletId; - _wallet = await epic.EpicWallet.create( + _wallet = await libEpic.initializeNewWallet( config: stringConfig, mnemonic: mnemonicString, password: password, name: name, - epicboxConfig: epicboxConfig.toString(), + epicBoxConfig: epicboxConfig.toString(), ); // Spawns worker isolate - // Store the wallet handle for listeners - encodedWallet = _wallet!.handle; - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: encodedWallet, - ); - //Store Epic box address info await _generateAndStoreReceivingAddressForIndex(0); @@ -908,18 +908,12 @@ class EpiccashWallet extends Bip39Wallet { ); final epicboxConfig = await getEpicBoxConfig(); - _wallet = await epic.EpicWallet.load( + _wallet = await libEpic.openWallet( config: config, password: password!, epicboxConfig: epicboxConfig.toString(), ); // Spawns worker isolate - // Store the wallet handle for listeners - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: _wallet!.handle, - ); - await updateNode(); await _listenToEpicbox(); @@ -959,7 +953,8 @@ class EpiccashWallet extends Bip39Wallet { if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) { - final httpResult = await _wallet!.txHttpSend( + final httpResult = await libEpic.txHttpSend( + wallet: _wallet!, selectionStrategyIsAll: 0, minimumConfirmations: cryptoCurrency.minConfirms, message: txData.noteOnChain ?? "", @@ -972,22 +967,23 @@ class EpiccashWallet extends Bip39Wallet { slateJson: '', ); } else { - transaction = (await _wallet!.createTransaction( + transaction = (await libEpic.createTransaction( + wallet: _wallet!, amount: txData.recipients!.first.amount.raw.toInt(), address: txData.recipients!.first.address, secretKeyIndex: 0, minimumConfirmations: cryptoCurrency.minConfirms, note: txData.noteOnChain!, - )).toRecord(); + )); } final Map txAddressInfo = {}; txAddressInfo['from'] = (await getCurrentReceivingAddress())!.value; txAddressInfo['to'] = txData.recipients!.first.address; - await _putSendToAddresses( - (commitId: transaction.commitId, slateId: transaction.slateId), - txAddressInfo, - ); + await _putSendToAddresses(( + commitId: transaction.commitId, + slateId: transaction.slateId, + ), txAddressInfo); return txData.copyWith(txid: transaction.slateId); } catch (e, s) { @@ -1112,29 +1108,23 @@ class EpiccashWallet extends Bip39Wallet { // maybe there is some way to tel epic-wallet rust to fully rescan... final result = await deleteEpicWallet( - walletId: walletId, + wallet: this, secureStore: secureStorageInterface, ); Logging.instance.w("Epic rescan temporary delete result: $result"); // Close old wallet before recovery if (_wallet != null) { - await _wallet!.close(); + await libEpic.close(wallet: _wallet!); _wallet = null; } - _wallet = await epic.EpicWallet.recover( + _wallet = await libEpic.recoverWallet( config: stringConfig, 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, + epicBoxConfig: epicboxConfig.toString(), ); await _listenToEpicbox(); @@ -1163,22 +1153,16 @@ class EpiccashWallet extends Bip39Wallet { // Close old wallet before recovery if (_wallet != null) { - await _wallet!.close(); + await libEpic.close(wallet: _wallet!); _wallet = null; } - _wallet = await epic.EpicWallet.recover( + _wallet = await libEpic.recoverWallet( config: stringConfig, 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, + epicBoxConfig: epicboxConfig.toString(), ); await _listenToEpicbox(); @@ -1306,7 +1290,8 @@ class EpiccashWallet extends Bip39Wallet { // 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 + // TODO: [prio=med] some kind of quick check if wallet needs to + // refresh to replace the old refreshIfThereIsNewData call // if (await refreshIfThereIsNewData()) { unawaited(refresh()); @@ -1391,7 +1376,8 @@ class EpiccashWallet extends Bip39Wallet { .findAll(); final myAddressesSet = myAddresses.toSet(); - final transactions = await _wallet!.getTransactions( + final transactions = await libEpic.getTransactions( + wallet: _wallet!, refreshFromNode: refreshFromNode, ); @@ -1405,8 +1391,8 @@ class EpiccashWallet extends Bip39Wallet { libEpic.txTypeIsReceiveCancelled(tx.txType); final slateId = tx.txSlateId; final commitId = slatesToCommits[slateId]?['commitId'] as String?; - final numberOfMessages = tx.messages?.messages.length; - final onChainNote = tx.messages?.messages.first.message; + final numberOfMessages = tx.messages?.length; + final onChainNote = tx.messages?.first.message; final addressFrom = slatesToCommits[slateId]?["from"] as String?; final addressTo = slatesToCommits[slateId]?["to"] as String?; @@ -1449,7 +1435,8 @@ class EpiccashWallet extends Bip39Wallet { output = output.copyWith( addresses: [ myAddressesSet - .first, // Must be changed if we ever do more than a single wallet address!!! + .first, // Must be changed if we ever do more than a single + // wallet address!!! ], walletOwns: true, ); @@ -1575,7 +1562,8 @@ class EpiccashWallet extends Bip39Wallet { Future updateNode() async { _epicNode = getCurrentNode(); - // TODO: [prio=low] move this out of secure storage if secure storage not needed + // 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', @@ -1623,7 +1611,8 @@ class EpiccashWallet extends Bip39Wallet { @override Future estimateFeeFor(Amount amount, BigInt feeRate) async { _hackedCheckTorNodePrefs(); - // setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function????? + // setting ifErrorEstimateFee doesn't do anything as its not used in the + // nativeFee function????? final int currentFee = await _nativeFee( amount.raw.toInt(), ifErrorEstimateFee: true, @@ -1656,7 +1645,7 @@ class EpiccashWallet extends Bip39Wallet { @override Future exit() async { - await _wallet?.stopListeners(); + if (_wallet != null) await libEpic.stopEpicboxListener(wallet: _wallet!); timer?.cancel(); timer = null; @@ -1688,16 +1677,15 @@ class EpiccashWallet extends Bip39Wallet { } Future deleteEpicWallet({ - required String walletId, + required EpiccashWallet wallet, required SecureStorageInterface secureStore, }) async { - final wallet = await secureStore.read(key: '${walletId}_wallet'); - String? config = await secureStore.read(key: '${walletId}_config'); + 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 = walletId.trim(); + final String name = wallet.walletId.trim(); final walletDir = '$path/$name'; final editConfig = jsonDecode(config as String); @@ -1706,14 +1694,15 @@ Future deleteEpicWallet({ config = jsonEncode(editConfig); } - if (wallet == null) { - return "Tried to delete non existent epic wallet file with walletId=$walletId"; + if (config == null) { + return "Tried to delete non existent epic wallet file with" + " walletId=${wallet.walletId}"; } else { try { - return libEpic.deleteWallet(wallet: wallet, config: config!); + return libEpic.deleteWallet(wallet: wallet._wallet!, config: config); } catch (e, s) { Logging.instance.e("$e\n$s", error: e, stackTrace: s); - return "deleteEpicWallet($walletId) failed..."; + return "deleteEpicWallet(${wallet.walletId}) failed..."; } } } diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart index ac69b0589..f0402ed4a 100644 --- a/lib/wl_gen/interfaces/libepiccash_interface.dart +++ b/lib/wl_gen/interfaces/libepiccash_interface.dart @@ -11,11 +11,12 @@ abstract class LibEpicCashInterface { bool txTypeIsReceiveCancelled(Enum value); bool txTypeIsSentCancelled(Enum value); - Future initializeNewWallet({ + Future initializeNewWallet({ required String config, required String mnemonic, required String password, required String name, + required String epicBoxConfig, }); Future openWallet({ @@ -24,15 +25,16 @@ abstract class LibEpicCashInterface { required String epicboxConfig, }); - Future recoverWallet({ + Future recoverWallet({ required String config, required String password, required String mnemonic, required String name, + required String epicBoxConfig, }); Future<({String commitId, String slateId})> txHttpSend({ - required String wallet, + required DynamicObject wallet, required int selectionStrategyIsAll, required int minimumConfirmations, required String message, @@ -42,57 +44,47 @@ abstract class LibEpicCashInterface { Future<({String commitId, String slateId, String slateJson})> createTransaction({ - required String wallet, + required DynamicObject wallet, required int amount, required String address, required int secretKeyIndex, - required String epicboxConfig, required int minimumConfirmations, required String note, bool returnSlate = false, }); Future<({String slateId, String commitId, String slateJson})> txReceive({ - required String wallet, + required DynamicObject wallet, required String slateJson, }); - Future<({String slateId, String commitId})> txFinalize({ - required String wallet, + Future<({String slateId, String commitId, String slateJson})> txFinalize({ + required DynamicObject wallet, required String slateJson, }); Future cancelTransaction({ - required String wallet, + required DynamicObject wallet, required String transactionId, }); Future> getTransactions({ - required String wallet, + required DynamicObject wallet, required int refreshFromNode, }); - void startEpicboxListener({ - required String walletId, - required String wallet, - required String epicboxConfig, - }); - - void stopEpicboxListener({required String walletId}); - - void stopAllEpicboxListeners(); + Future startEpicboxListener({required DynamicObject wallet}); - bool isEpicboxListenerRunning({required String walletId}); + Future stopEpicboxListener({required DynamicObject wallet}); - List getActiveListenerWalletIds(); + Future isEpicboxListenerRunning({required DynamicObject wallet}); Future validateSendAddress({required String address}); Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({ - required String wallet, + required DynamicObject wallet, required int amount, required int minimumConfirmations, - required int available, }); Future< @@ -104,26 +96,36 @@ abstract class LibEpicCashInterface { }) > getWalletBalances({ - required String wallet, + required DynamicObject wallet, required int refreshFromNode, required int minimumConfirmations, }); Future getAddressInfo({ - required String wallet, + required DynamicObject wallet, required int index, required String epicboxConfig, }); Future scanOutputs({ - required String wallet, + required DynamicObject wallet, required int startHeight, required int numberOfBlocks, }); Future getChainHeight({required String config}); - Future deleteWallet({required String wallet, required String config}); + Future close({required DynamicObject wallet}); + + Future deleteWallet({ + required DynamicObject wallet, + required String config, + }); + + void updateEpicboxConfig({ + required DynamicObject wallet, + required String epicBoxConfig, + }); 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 e8c67c9e8..b3a49c1ff 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -21,74 +21,78 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { @override Future cancelTransaction({ - required String wallet, + required DynamicObject wallet, required String transactionId, }) { - return LibEpiccash.cancelTransaction( - wallet: wallet, + return wallet.get().cancelTransaction( transactionId: transactionId, ); } @override Future<({String slateId, String commitId, String slateJson})> txReceive({ - required String wallet, + required DynamicObject wallet, required String slateJson, - }) { - return LibEpiccash.txReceive(wallet: wallet, slateJson: slateJson); + }) async { + return (await wallet.get().txReceive( + slateJson: slateJson, + )).toRecord(); } @override - Future<({String slateId, String commitId})> txFinalize({ - required String wallet, + Future<({String slateId, String commitId, String slateJson})> txFinalize({ + required DynamicObject wallet, required String slateJson, - }) { - return LibEpiccash.txFinalize(wallet: wallet, slateJson: slateJson); + }) async { + return (await wallet.get().txFinalize( + slateJson: slateJson, + )).toRecord(); } @override Future<({String commitId, String slateId, String slateJson})> createTransaction({ - required String wallet, + required DynamicObject wallet, required int amount, required String address, required int secretKeyIndex, - required String epicboxConfig, required int minimumConfirmations, required String note, bool returnSlate = false, - }) { - return LibEpiccash.createTransaction( - wallet: wallet, + }) async { + return (await wallet.get().createTransaction( amount: amount, address: address, secretKeyIndex: secretKeyIndex, - epicboxConfig: epicboxConfig, minimumConfirmations: minimumConfirmations, note: note, returnSlate: returnSlate, - ); + )).toRecord(); + } + + @override + void updateEpicboxConfig({ + required DynamicObject wallet, + required String epicBoxConfig, + }) { + return wallet.get().updateEpicboxConfig(epicBoxConfig); } @override Future deleteWallet({ - required String wallet, + required DynamicObject wallet, required String config, }) { - return LibEpiccash.deleteWallet(wallet: wallet, config: config); + return wallet.get().deleteWallet(config: config); } @override Future getAddressInfo({ - required String wallet, + required DynamicObject wallet, required int index, required String epicboxConfig, }) { - return LibEpiccash.getAddressInfo( - wallet: wallet, - index: index, - epicboxConfig: epicboxConfig, - ); + return wallet.get().getAddressInfo(index: index); } @override @@ -98,26 +102,22 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { @override Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({ - required String wallet, + required DynamicObject wallet, required int amount, required int minimumConfirmations, - required int available, }) { - return LibEpiccash.getTransactionFees( - wallet: wallet, + return wallet.get().getTransactionFees( amount: amount, minimumConfirmations: minimumConfirmations, - available: available, ); } @override Future> getTransactions({ - required String wallet, + required DynamicObject wallet, required int refreshFromNode, }) async { - final transactions = await LibEpiccash.getTransactions( - wallet: wallet, + final transactions = await wallet.get().getTransactions( refreshFromNode: refreshFromNode, ); @@ -166,30 +166,33 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { }) > getWalletBalances({ - required String wallet, + required DynamicObject wallet, required int refreshFromNode, required int minimumConfirmations, }) { - return LibEpiccash.getWalletBalances( - wallet: wallet, + return wallet.get().getBalancesRecord( refreshFromNode: refreshFromNode, minimumConfirmations: minimumConfirmations, ); } @override - Future initializeNewWallet({ + Future initializeNewWallet({ required String config, required String mnemonic, required String password, required String name, - }) { - return LibEpiccash.initializeNewWallet( + required String epicBoxConfig, + }) async { + final wallet = await EpicWallet.create( config: config, mnemonic: mnemonic, password: password, name: name, + epicboxConfig: epicBoxConfig, ); + + return DynamicObject(wallet); } @override @@ -208,69 +211,54 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { } @override - Future recoverWallet({ + Future recoverWallet({ required String config, required String password, required String mnemonic, required String name, - }) { - return LibEpiccash.recoverWallet( + required String epicBoxConfig, + }) async { + final wallet = EpicWallet.recover( config: config, password: password, mnemonic: mnemonic, name: name, + epicboxConfig: epicBoxConfig, ); + + return DynamicObject(wallet); } @override Future scanOutputs({ - required String wallet, + required DynamicObject wallet, required int startHeight, required int numberOfBlocks, }) { - return LibEpiccash.scanOutputs( - wallet: wallet, + return wallet.get().scanOutputs( startHeight: startHeight, numberOfBlocks: numberOfBlocks, ); } @override - void startEpicboxListener({ - required String walletId, - required String wallet, - required String epicboxConfig, - }) { - return LibEpiccash.startEpicboxListener( - walletId: walletId, - wallet: wallet, - epicboxConfig: epicboxConfig, - ); - } - - @override - void stopEpicboxListener({required String walletId}) { - return LibEpiccash.stopEpicboxListener(walletId: walletId); + Future startEpicboxListener({required DynamicObject wallet}) { + return wallet.get().startListener(); } @override - void stopAllEpicboxListeners() { - return LibEpiccash.stopAllEpicboxListeners(); + Future stopEpicboxListener({required DynamicObject wallet}) { + return wallet.get().stopListener(); } @override - bool isEpicboxListenerRunning({required String walletId}) { - return LibEpiccash.isEpicboxListenerRunning(walletId: walletId); - } - - @override - List getActiveListenerWalletIds() { - return LibEpiccash.getActiveListenerWalletIds(); + Future isEpicboxListenerRunning({required DynamicObject wallet}) { + return wallet.get().isEpicboxListenerRunning(); } @override Future<({String commitId, String slateId})> txHttpSend({ - required String wallet, + required DynamicObject wallet, required int selectionStrategyIsAll, required int minimumConfirmations, required String message, @@ -278,8 +266,7 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { required String address, }) { try { - return LibEpiccash.txHttpSend( - wallet: wallet, + return wallet.get().txHttpSend( selectionStrategyIsAll: selectionStrategyIsAll, minimumConfirmations: minimumConfirmations, message: message, @@ -307,8 +294,13 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { } @override - bool validateSendAddress({required String address}) { - return LibEpiccash.validateSendAddress(address: address); + Future validateSendAddress({required String address}) { + return EpicWallet.validateSendAddress(address: address); + } + + @override + Future close({required DynamicObject wallet}) { + return wallet.get().close(); } @override From 3c5c747310270f8d159163612fc058dad4b2fe9d Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 11:43:10 -0600 Subject: [PATCH 11/18] finish up delete epic wallet --- crypto_plugins/flutter_libepiccash | 2 +- lib/wallets/wallet/impl/epiccash_wallet.dart | 3 ++- lib/wl_gen/interfaces/libepiccash_interface.dart | 5 +---- .../EPIC_libepiccash_interface_impl.template.dart | 7 ++----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index e92c5e927..94e784441 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit e92c5e927cdab7fea730fd898021116e09ce8b99 +Subproject commit 94e784441a825b49676e53d73bef1f68ceb1dbb7 diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 4def973f0..c7571f5bb 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -1699,7 +1699,8 @@ Future deleteEpicWallet({ " walletId=${wallet.walletId}"; } else { try { - return libEpic.deleteWallet(wallet: wallet._wallet!, config: config); + if (wallet._wallet != null) await libEpic.close(wallet: wallet._wallet!); + return libEpic.deleteWallet(config: config); } catch (e, s) { Logging.instance.e("$e\n$s", error: e, stackTrace: s); return "deleteEpicWallet(${wallet.walletId}) failed..."; diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart index f0402ed4a..e06e75b03 100644 --- a/lib/wl_gen/interfaces/libepiccash_interface.dart +++ b/lib/wl_gen/interfaces/libepiccash_interface.dart @@ -117,10 +117,7 @@ abstract class LibEpicCashInterface { Future close({required DynamicObject wallet}); - Future deleteWallet({ - required DynamicObject wallet, - required String config, - }); + Future deleteWallet({required String config}); void updateEpicboxConfig({ required DynamicObject wallet, diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart index b3a49c1ff..d40208cf5 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -79,11 +79,8 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { } @override - Future deleteWallet({ - required DynamicObject wallet, - required String config, - }) { - return wallet.get().deleteWallet(config: config); + Future deleteWallet({required String config}) { + return EpicWallet.deleteWallet(config: config); } @override From 822d759e118882e56725de5c1a7b99c237985f30 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 23 Jan 2026 12:53:26 -0600 Subject: [PATCH 12/18] chore: update flutter_libepiccash ref --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 94e784441..b2e5ba599 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 94e784441a825b49676e53d73bef1f68ceb1dbb7 +Subproject commit b2e5ba599e4e479a90e3457e2f231a761b5d495a From 3c809bb06ec5d101f2bf09bb0ff29ec933a67222 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 13:57:44 -0600 Subject: [PATCH 13/18] fix a couple issues --- lib/wallets/wallet/impl/epiccash_wallet.dart | 88 +++++++++---------- ...C_libepiccash_interface_impl.template.dart | 2 +- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index c7571f5bb..1915256e6 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -74,43 +74,43 @@ class EpiccashWallet extends Bip39Wallet { /// Opens and initializes the Epic wallet instance. /// Should only be called once during wallet initialization. - Future open() async { - if (_wallet != null) { - Logging.instance.d("Wallet already open, ensuring listener"); - if (!await libEpic.isEpicboxListenerRunning(wallet: _wallet!)) { - await _listenToEpicbox(); - } - return; - } - - try { - final config = await _getRealConfig(); - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - throw Exception('Wallet password not found'); - } - - final epicboxConfig = await getEpicBoxConfig(); - - _wallet = await libEpic.openWallet( - config: config, - password: password, - epicboxConfig: epicboxConfig.toString(), - ); - - await _listenToEpicbox(); - - Logging.instance.d( - "Epic wallet opened successfully with persistent isolate", - ); - } catch (e, s) { - Logging.instance.e("Failed to open Epic wallet", error: e, stackTrace: s); - _wallet = null; - rethrow; - } - } + // Future open() async { + // if (_wallet != null) { + // Logging.instance.d("Wallet already open, ensuring listener"); + // if (!await libEpic.isEpicboxListenerRunning(wallet: _wallet!)) { + // await _listenToEpicbox(); + // } + // return; + // } + // + // try { + // final config = await _getRealConfig(); + // final password = await secureStorageInterface.read( + // key: '${walletId}_password', + // ); + // if (password == null) { + // throw Exception('Wallet password not found'); + // } + // + // final epicboxConfig = await getEpicBoxConfig(); + // + // _wallet = await libEpic.openWallet( + // config: config, + // password: password, + // epicboxConfig: epicboxConfig.toString(), + // ); + // + // await _listenToEpicbox(); + // + // Logging.instance.d( + // "Epic wallet opened successfully with persistent isolate", + // ); + // } catch (e, s) { + // Logging.instance.e("Failed to open Epic wallet", error: e, stackTrace: s); + // _wallet = null; + // rethrow; + // } + // } Future updateEpicboxConfig(String host, int port) async { final String stringConfig = jsonEncode({ @@ -834,14 +834,13 @@ class EpiccashWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { - if (_wallet != null) { - Logging.instance.d("Wallet already initialized, skipping init"); - return await super.init(); - } - if (isRestore != true) { + final existingWalletConfig = await secureStorageInterface.read( + key: '${walletId}_config', + ); + // check if should create a new wallet - if (_wallet == null) { + if (existingWalletConfig == null) { await updateNode(); final mnemonicString = await getMnemonic(); @@ -902,14 +901,13 @@ class EpiccashWallet extends Bip39Wallet { "initializeExisting() ${cryptoCurrency.prettyName} wallet", ); - final config = await _getRealConfig(); final password = await secureStorageInterface.read( key: '${walletId}_password', ); final epicboxConfig = await getEpicBoxConfig(); _wallet = await libEpic.openWallet( - config: config, + config: existingWalletConfig, password: password!, epicboxConfig: epicboxConfig.toString(), ); // Spawns worker isolate diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart index d40208cf5..b27429e49 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -215,7 +215,7 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { required String name, required String epicBoxConfig, }) async { - final wallet = EpicWallet.recover( + final wallet = await EpicWallet.recover( config: config, password: password, mnemonic: mnemonic, From c4a95d2526760d50d2f22c7ef3cfbb81d4e4d35e Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 23 Jan 2026 15:19:48 -0600 Subject: [PATCH 14/18] fix: fix Epic listener issue --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index b2e5ba599..7c27dfb27 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit b2e5ba599e4e479a90e3457e2f231a761b5d495a +Subproject commit 7c27dfb2791db7d1357229b7d0a6986a206a5e8a From 8398cc967d03544a56c82246370173b635b7d521 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 17:22:06 -0600 Subject: [PATCH 15/18] fix wl gen template import --- .../EPIC_libepiccash_interface_impl.template.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart index b27429e49..3b43c3fa9 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -2,9 +2,9 @@ import 'package:flutter_libepiccash/git_versions.dart' as epic_versions; import 'package:flutter_libepiccash/lib.dart'; import 'package:flutter_libepiccash/models/transaction.dart'; -//END_ON -import 'package:stackwallet/utilities/dynamic_object.dart'; +//END_ON +import '../../utilities/dynamic_object.dart'; import '../interfaces/libepiccash_interface.dart'; LibEpicCashInterface get libEpic => _getLib(); From 53fa71b563b2c0b08099dc2d87762880fe5b4ba3 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 20:33:54 -0600 Subject: [PATCH 16/18] fix epic lmao config --- crypto_plugins/flutter_libepiccash | 2 +- lib/wallets/wallet/impl/epiccash_wallet.dart | 76 ++++++------------- .../interfaces/libepiccash_interface.dart | 2 + ...C_libepiccash_interface_impl.template.dart | 5 ++ 4 files changed, 32 insertions(+), 53 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 7c27dfb27..4af7c1919 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 7c27dfb2791db7d1357229b7d0a6986a206a5e8a +Subproject commit 4af7c1919d12b18af9206f9d67f975b281e45ffa diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 1915256e6..f7fca914b 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 _getConfig() async { - if (_epicNode == null) { - await updateNode(); - } + Future _hasConfig() async => + (await secureStorageInterface.read(key: '${walletId}_config')) != null; + + Future _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 _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 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 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 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 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 e06e75b03..02ece22a3 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 3b43c3fa9..ecfdf4a25 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().updateEpicboxConfig(epicBoxConfig); } + @override + void updateConfig({required DynamicObject wallet, required String config}) { + return wallet.get().updateConfig(config); + } + @override Future deleteWallet({required String config}) { return EpicWallet.deleteWallet(config: config); From 24c5840b57bf09fa9bfc915f9dd7bb5d538208ec Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 20:53:03 -0600 Subject: [PATCH 17/18] add synchronous address validate call --- lib/wallets/crypto_currency/coins/epiccash.dart | 13 +++++-------- lib/wl_gen/interfaces/libepiccash_interface.dart | 2 ++ .../EPIC_libepiccash_interface_impl.template.dart | 6 ++++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/wallets/crypto_currency/coins/epiccash.dart b/lib/wallets/crypto_currency/coins/epiccash.dart index 441146920..97c1b18d3 100644 --- a/lib/wallets/crypto_currency/coins/epiccash.dart +++ b/lib/wallets/crypto_currency/coins/epiccash.dart @@ -65,12 +65,7 @@ class Epiccash extends Bip39Currency { } } - if (address.contains("@")) { - return true; // Epicbox address format - } - - // Very very basic (bad) check - return address.isNotEmpty && address.length > 10; + return libEpic.validateSendAddressSync(address: address); } @override @@ -143,7 +138,8 @@ class Epiccash extends Bip39Currency { // Check for common slate fields. return parsed is Map && (parsed.containsKey('id') || parsed.containsKey('slate_id')) && - (parsed.containsKey('amount') || parsed.containsKey('participant_data')); + (parsed.containsKey('amount') || + parsed.containsKey('participant_data')); } catch (e) { return false; } @@ -163,7 +159,8 @@ class Epiccash extends Bip39Currency { EpicTransactionMethod getTransactionMethod(String addressOrData) { if (isSlateJson(addressOrData)) { return EpicTransactionMethod.slatepack; - } else if (isEpicboxAddress(addressOrData) || isHttpAddress(addressOrData)) { + } else if (isEpicboxAddress(addressOrData) || + isHttpAddress(addressOrData)) { return EpicTransactionMethod.epicbox; } else { throw Exception("Unknown EpicTransactionMethod found!"); diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart index 02ece22a3..cdbbb19ce 100644 --- a/lib/wl_gen/interfaces/libepiccash_interface.dart +++ b/lib/wl_gen/interfaces/libepiccash_interface.dart @@ -81,6 +81,8 @@ abstract class LibEpicCashInterface { Future validateSendAddress({required String address}); + bool validateSendAddressSync({required String address}); + Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({ required DynamicObject wallet, required int amount, diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart index ecfdf4a25..4e78631bd 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -1,4 +1,5 @@ //ON +import 'package:flutter_libepiccash/epic_cash.dart' as epc; import 'package:flutter_libepiccash/git_versions.dart' as epic_versions; import 'package:flutter_libepiccash/lib.dart'; import 'package:flutter_libepiccash/models/transaction.dart'; @@ -300,6 +301,11 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { return EpicWallet.validateSendAddress(address: address); } + @override + bool validateSendAddressSync({required String address}) { + return epc.validateSendAddress(address) == "1"; //lol + } + @override Future close({required DynamicObject wallet}) { return wallet.get().close(); From 09a7f05f6a521962ada71f4721b5c6d0c8bdb72c Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 23 Jan 2026 20:53:31 -0600 Subject: [PATCH 18/18] fix initialization errors on sendview --- lib/pages/send_view/send_view.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 0964a898f..8761a7234 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -1259,6 +1259,14 @@ class _SendViewState extends ConsumerState { @override void initState() { coin = widget.coin; + isFiro = coin is Firo; + isEth = coin is Ethereum; + hasOptionalMemo = coin is Stellar || coin is Solana; + + _data = widget.autoFillData; + walletId = widget.walletId; + clipboard = widget.clipboard; + WidgetsBinding.instance.addPostFrameCallback((_) { ref.refresh(feeSheetSessionCacheProvider); ref.refresh(pIsExchangeAddress); @@ -1275,12 +1283,6 @@ class _SendViewState extends ConsumerState { _calculateFeesFuture = calculateFees( 0.toAmountAsRaw(fractionDigits: coin.fractionDigits), ); - _data = widget.autoFillData; - walletId = widget.walletId; - clipboard = widget.clipboard; - hasOptionalMemo = coin is Stellar || coin is Solana; - isFiro = coin is Firo; - isEth = coin is Ethereum; sendToController = TextEditingController(); cryptoAmountController = TextEditingController();