@@ -396,8 +396,8 @@ class EpiccashWallet extends Bip39Wallet {
396396 }
397397
398398 Logging .instance.d ("_startScans successfully at the tip" );
399- //Once scanner completes restart listener
400- await _listenToEpicbox ();
399+ // await _listenToEpicbox();
400+ // Epicbox listener already started before scanning.
401401 } catch (e, s) {
402402 Logging .instance.e ("_startScans failed: " , error: e, stackTrace: s);
403403 rethrow ;
@@ -775,6 +775,21 @@ class EpiccashWallet extends Bip39Wallet {
775775 await _generateAndStoreReceivingAddressForIndex (curAdd);
776776
777777 if (doScan) {
778+ // Start epicbox listener first for instant transaction appearance.
779+ await _listenToEpicbox ();
780+
781+ // Immediately check for epicbox transactions without node dependency.
782+ try {
783+ await _updateTransactionsWithoutNodeRefresh ();
784+ await updateBalance (); // Update balance to show pending transactions.
785+ } catch (e, s) {
786+ Logging .instance.w (
787+ "Initial epicbox transaction check failed" ,
788+ error: e,
789+ stackTrace: s,
790+ );
791+ }
792+
778793 await _startScans ();
779794
780795 unawaited (_startSync ());
@@ -896,6 +911,150 @@ class EpiccashWallet extends Bip39Wallet {
896911 }
897912 }
898913
914+ /// Updates transactions without refreshing from node (for epicbox-only transactions).
915+ Future <void > _updateTransactionsWithoutNodeRefresh () async {
916+ try {
917+ _hackedCheckTorNodePrefs ();
918+ final wallet = await secureStorageInterface.read (
919+ key: '${walletId }_wallet' ,
920+ );
921+ const refreshFromNode =
922+ 0 ; // Don't refresh from node, use cached/epicbox data.
923+
924+ final myAddresses =
925+ await mainDB
926+ .getAddresses (walletId)
927+ .filter ()
928+ .typeEqualTo (AddressType .mimbleWimble)
929+ .and ()
930+ .subTypeEqualTo (AddressSubType .receiving)
931+ .and ()
932+ .valueIsNotEmpty ()
933+ .valueProperty ()
934+ .findAll ();
935+ final myAddressesSet = myAddresses.toSet ();
936+
937+ final transactions = await epiccash.LibEpiccash .getTransactions (
938+ wallet: wallet! ,
939+ refreshFromNode: refreshFromNode,
940+ );
941+
942+ final List <TransactionV2 > txns = [];
943+
944+ final slatesToCommits = info.epicData? .slatesToCommits ?? {};
945+
946+ for (final tx in transactions) {
947+ final isIncoming =
948+ tx.txType == epic_models.TransactionType .TxReceived ||
949+ tx.txType == epic_models.TransactionType .TxReceivedCancelled ;
950+ final slateId = tx.txSlateId;
951+ final commitId = slatesToCommits[slateId]? ['commitId' ] as String ? ;
952+ final numberOfMessages = tx.messages? .messages.length;
953+ final onChainNote = tx.messages? .messages[0 ].message;
954+ final addressFrom = slatesToCommits[slateId]? ["from" ] as String ? ;
955+ final addressTo = slatesToCommits[slateId]? ["to" ] as String ? ;
956+
957+ final credit = int .parse (tx.amountCredited);
958+ final debit = int .parse (tx.amountDebited);
959+ final fee = int .tryParse (tx.fee ?? "0" ) ?? 0 ;
960+
961+ // Hack EPIC tx data into inputs and outputs.
962+ final List <OutputV2 > outputs = [];
963+ final List <InputV2 > inputs = [];
964+ final addressFromIsMine = myAddressesSet.contains (addressFrom);
965+ final addressToIsMine = myAddressesSet.contains (addressTo);
966+
967+ OutputV2 output = OutputV2 .isarCantDoRequiredInDefaultConstructor (
968+ scriptPubKeyHex: "00" ,
969+ valueStringSats: credit.toString (),
970+ addresses: [if (addressFrom != null ) addressFrom],
971+ walletOwns: true ,
972+ );
973+ final InputV2 input = InputV2 .isarCantDoRequiredInDefaultConstructor (
974+ scriptSigHex: null ,
975+ scriptSigAsm: null ,
976+ sequence: null ,
977+ outpoint: null ,
978+ addresses: [if (addressTo != null ) addressTo],
979+ valueStringSats: debit.toString (),
980+ witness: null ,
981+ innerRedeemScriptAsm: null ,
982+ coinbase: null ,
983+ walletOwns: true ,
984+ );
985+
986+ final TransactionType txType;
987+ if (isIncoming) {
988+ if (addressToIsMine && addressFromIsMine) {
989+ txType = TransactionType .sentToSelf;
990+ } else {
991+ txType = TransactionType .incoming;
992+ }
993+ output = output.copyWith (
994+ addresses: [
995+ myAddressesSet
996+ .first, // Must be changed if we ever do more than a single wallet address!!!
997+ ],
998+ walletOwns: true ,
999+ );
1000+ } else {
1001+ txType = TransactionType .outgoing;
1002+ }
1003+
1004+ outputs.add (output);
1005+ inputs.add (input);
1006+
1007+ final otherData = {
1008+ "isEpiccashTransaction" : true ,
1009+ "numberOfMessages" : numberOfMessages,
1010+ "slateId" : slateId,
1011+ "onChainNote" : onChainNote,
1012+ "isCancelled" :
1013+ tx.txType == epic_models.TransactionType .TxSentCancelled ||
1014+ tx.txType == epic_models.TransactionType .TxReceivedCancelled ,
1015+ "overrideFee" :
1016+ Amount (
1017+ rawValue: BigInt .from (fee),
1018+ fractionDigits: cryptoCurrency.fractionDigits,
1019+ ).toJsonString (),
1020+ };
1021+
1022+ final txn = TransactionV2 (
1023+ walletId: walletId,
1024+ blockHash: null ,
1025+ hash: commitId ?? tx.id.toString (),
1026+ txid: commitId ?? tx.id.toString (),
1027+ timestamp:
1028+ DateTime .parse (tx.creationTs).millisecondsSinceEpoch ~ / 1000 ,
1029+ height: tx.confirmed ? tx.kernelLookupMinHeight ?? 1 : null ,
1030+ inputs: List .unmodifiable (inputs),
1031+ outputs: List .unmodifiable (outputs),
1032+ version: 0 ,
1033+ type: txType,
1034+ subType: TransactionSubType .none,
1035+ otherData: jsonEncode (otherData),
1036+ );
1037+
1038+ txns.add (txn);
1039+ }
1040+
1041+ await mainDB.isar.writeTxn (() async {
1042+ await mainDB.isar.transactionV2s
1043+ .where ()
1044+ .walletIdEqualTo (walletId)
1045+ .deleteAll ();
1046+ await mainDB.isar.transactionV2s.putAll (txns);
1047+ });
1048+ } catch (e, s) {
1049+ Logging .instance.e (
1050+ "${cryptoCurrency .runtimeType } ${cryptoCurrency .network } net wallet"
1051+ " \" ${info .name }\" _${info .walletId } _updateTransactionsWithoutNodeRefresh() failed" ,
1052+ error: e,
1053+ stackTrace: s,
1054+ );
1055+ }
1056+ }
1057+
8991058 @override
9001059 Future <void > updateTransactions () async {
9011060 try {
0 commit comments