optimize/cleanup electrumx_interface wallet addresses saved on recover/rescan
What changed, and why it matters
This commit refactors how Stack Wallet saves wallet addresses during wallet recovery and rescanning for ElectrumX-based coins. It centralizes repeated address-gap-checking logic into a single helper function and changes how the highest used address index is tracked. The change appears intended to fix a bug where unused addresses beyond the gap limit could be incorrectly stored, which could lead to missing transactions or an incomplete wallet balance after restore. There is no clear evidence in the commit of a traditional security vulnerability such as theft of funds, but a bug in address discovery could affect wallet correctness and user funds visibility.
Treat as a wallet-correctness fix worth reviewing and including in the next release. QA should verify recovery/rescan behavior on fresh and used wallets for ElectrumX-based coins (including Firo and MWEB) to ensure all expected addresses and transactions are discovered. No immediate incident response is indicated, but users who restored wallets before this fix may want to rescan if they noticed missing transactions.
Security signals we found
Address discovery logic changed: highest used index now initialized to -1 and only updated on addresses with transaction history
Duplicated gap-check code consolidated into single helper, reducing risk of inconsistent behavior across wallet types
Potential bug fix: prior logic could retain unused addresses beyond the gap limit or mishandle wallets with no history
No explicit security claims, CVE, or attribution in commit message or diff
Evidence from the diff
The patch removes duplicated gap-limit handling code from FiroWallet, ElectrumXInterface, and MwebInterface and replaces it with a shared processGapCheckResults() helper. It also changes highestIndexWithHistory initialization from 0 to -1 in checkGaps() and checkGapsV2(), and now records highestIndexWithHistory = index only when an address has transaction history. Previously, the code initialized the highest index to 0 and returned the final loop index, which could cause addresses at index 0 or beyond the real last-used index to be retained. The new helper filters out addresses whose derivationIndex exceeds the highest index that actually had history. This is a correctness fix for HD wallet address discovery (BIP-32/44 gap limit handling).
Changed components
lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dartlib/wallets/wallet/impl/firo_wallet.dartlib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dartInspect captured patch +161 / −327
diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart
index e55052e..3bd04d8 100644
--- a/lib/wallets/wallet/impl/firo_wallet.dart
+++ b/lib/wallets/wallet/impl/firo_wallet.dart
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
-import 'dart:math';
import 'package:decimal/decimal.dart';
import 'package:isar_community/isar.dart';
@@ -81,17 +80,15 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
final List<Address> allAddressesOld =
await fetchAddressesForElectrumXScan();
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => convertAddressString(e.value))
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => convertAddressString(e.value))
+ .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => convertAddressString(e.value))
- .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => convertAddressString(e.value))
+ .toSet();
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -99,23 +96,21 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
allAddressesSet,
);
- final sparkCoins =
- await mainDB.isar.sparkCoins
- .where()
- .walletIdEqualToAnyLTagHash(walletId)
- .findAll();
+ final sparkCoins = await mainDB.isar.sparkCoins
+ .where()
+ .walletIdEqualToAnyLTagHash(walletId)
+ .findAll();
final List<Map<String, dynamic>> allTransactions = [];
// some lelantus transactions aren't fetched via wallet addresses so they
// will never show as confirmed in the gui.
- final unconfirmedTransactions =
- await mainDB.isar.transactionV2s
- .where()
- .walletIdEqualTo(walletId)
- .filter()
- .heightIsNull()
- .findAll();
+ final unconfirmedTransactions = await mainDB.isar.transactionV2s
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .heightIsNull()
+ .findAll();
for (final tx in unconfirmedTransactions) {
final txn = await electrumXCachedClient.getTransaction(
txHash: tx.txid,
@@ -154,13 +149,12 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
final currentHeight = await chainHeight;
for (final txHash in allTxHashes) {
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .walletIdEqualTo(walletId)
- .filter()
- .txidEqualTo(txHash["tx_hash"] as String)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .txidEqualTo(txHash["tx_hash"] as String)
+ .findFirst();
if (storedTx?.isConfirmed(
currentHeight,
@@ -214,8 +208,9 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
bool isSparkMint = false;
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
final bool isMySpark = sparkTxids.contains(txData["txid"] as String);
- final bool isMySpentSpark =
- missing.where((e) => e.txid == txData["txid"]).isNotEmpty;
+ final bool isMySpentSpark = missing
+ .where((e) => e.txid == txData["txid"])
+ .isNotEmpty;
final sparkCoinsInvolvedReceived = sparkCoins.where(
(e) =>
@@ -298,19 +293,17 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
if (output.addresses.isEmpty &&
output.scriptPubKeyHex.length >= 488) {
// likely spark related
- final opByte =
- output.scriptPubKeyHex
- .substring(0, 2)
- .toUint8ListFromHex
- .first;
+ final opByte = output.scriptPubKeyHex
+ .substring(0, 2)
+ .toUint8ListFromHex
+ .first;
if (opByte == OP_SPARKMINT || opByte == OP_SPARKSMINT) {
final serCoin = base64Encode(
output.scriptPubKeyHex.substring(2, 488).toUint8ListFromHex,
);
- final coin =
- sparkCoinsInvolvedReceived
- .where((e) => e.serializedCoinB64!.startsWith(serCoin))
- .firstOrNull;
+ final coin = sparkCoinsInvolvedReceived
+ .where((e) => e.serializedCoinB64!.startsWith(serCoin))
+ .firstOrNull;
if (coin == null) {
// not ours
@@ -403,10 +396,9 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
txid: txData["txid"] as String,
network: cryptoCurrency.network,
);
- spentSparkCoins =
- sparkCoinsInvolvedSpent
- .where((e) => tags.contains(e.lTagHash))
- .toList();
+ spentSparkCoins = sparkCoinsInvolvedSpent
+ .where((e) => tags.contains(e.lTagHash))
+ .toList();
} else if (isSparkSpend) {
parseAnonFees();
} else if (isSparkMint) {
@@ -490,11 +482,10 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
if (usedCoins.isNotEmpty) {
input = input.copyWith(
addresses: usedCoins.map((e) => e.address).toList(),
- valueStringSats:
- usedCoins
- .map((e) => e.value)
- .reduce((value, element) => value += element)
- .toString(),
+ valueStringSats: usedCoins
+ .map((e) => e.value)
+ .reduce((value, element) => value += element)
+ .toString(),
walletOwns: true,
);
wasSentFromThisWallet = true;
@@ -505,11 +496,10 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
spentSparkCoins.isNotEmpty) {
input = input.copyWith(
addresses: spentSparkCoins.map((e) => e.address).toList(),
- valueStringSats:
- spentSparkCoins
- .map((e) => e.value)
- .fold(BigInt.zero, (p, e) => p + e)
- .toString(),
+ valueStringSats: spentSparkCoins
+ .map((e) => e.value)
+ .fold(BigInt.zero, (p, e) => p + e)
+ .toString(),
walletOwns: true,
);
wasSentFromThisWallet = true;
@@ -755,53 +745,10 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
Future.wait(changeFutures),
]);
- final receiveResults = futuresResult[0];
- final changeResults = futuresResult[1];
-
- final List<Address> addressesToStore = [];
-
- int highestReceivingIndexWithHistory = 0;
-
- for (final tuple in receiveResults) {
- if (tuple.addresses.isEmpty) {
- if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
- await checkReceivingAddressForTransactions();
- }
- } else {
- highestReceivingIndexWithHistory = max(
- tuple.index,
- highestReceivingIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- int highestChangeIndexWithHistory = 0;
- // If restoring a wallet that never sent any funds with change, then set changeArray
- // manually. If we didn't do this, it'd store an empty array.
- for (final tuple in changeResults) {
- if (tuple.addresses.isEmpty) {
- await checkChangeAddressForTransactions();
- } else {
- highestChangeIndexWithHistory = max(
- tuple.index,
- highestChangeIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- // remove extra addresses to help minimize risk of creating a large gap
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.change &&
- e.derivationIndex > highestChangeIndexWithHistory,
- );
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.receiving &&
- e.derivationIndex > highestReceivingIndexWithHistory,
- );
+ final List<Address> addressesToStore = processGapCheckResults([
+ ...futuresResult[0],
+ ...futuresResult[1],
+ ]);
await mainDB.updateOrPutAddresses(addressesToStore);
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
index 05170c9..0aa674b 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
@@ -1072,7 +1072,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
) async {
final List<Address> addressArray = [];
int gapCounter = 0;
- int highestIndexWithHistory = 0;
+ int highestIndexWithHistory = -1;
for (
int index = 0;
@@ -1163,6 +1163,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
final List<Address> addressArray = [];
int gapCounter = 0;
int index = 0;
+ int highestIndexWithHistory = -1;
for (; gapCounter < cryptoCurrency.maxUnusedAddressGap; index++) {
Logging.instance.d(
@@ -1212,10 +1213,11 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
),
);
+ addressArray.add(address);
+
// check and add appropriate addresses
if (count > 0) {
- // add address to array
- addressArray.add(address);
+ highestIndexWithHistory = index;
// reset counter
gapCounter = 0;
// add info to derivations
@@ -1225,7 +1227,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
}
}
- return (addresses: addressArray, index: index);
+ return (addresses: addressArray, index: highestIndexWithHistory);
}
Future<List<Map<String, dynamic>>> fetchHistory(
@@ -1640,51 +1642,10 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
Future.wait(changeFutures),
]);
- final receiveResults = futuresResult[0];
- final changeResults = futuresResult[1];
-
- final List<Address> addressesToStore = [];
-
- int highestReceivingIndexWithHistory = 0;
-
- for (final tuple in receiveResults) {
- if (tuple.addresses.isEmpty) {
- await checkReceivingAddressForTransactions();
- } else {
- highestReceivingIndexWithHistory = max(
- tuple.index,
- highestReceivingIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- int highestChangeIndexWithHistory = 0;
- // If restoring a wallet that never sent any funds with change, then set changeArray
- // manually. If we didn't do this, it'd store an empty array.
- for (final tuple in changeResults) {
- if (tuple.addresses.isEmpty) {
- await checkChangeAddressForTransactions();
- } else {
- highestChangeIndexWithHistory = max(
- tuple.index,
- highestChangeIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- // remove extra addresses to help minimize risk of creating a large gap
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.change &&
- e.derivationIndex > highestChangeIndexWithHistory,
- );
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.receiving &&
- e.derivationIndex > highestReceivingIndexWithHistory,
- );
+ final List<Address> addressesToStore = processGapCheckResults([
+ ...futuresResult[0],
+ ...futuresResult[1],
+ ]);
await mainDB.updateOrPutAddresses(addressesToStore);
@@ -2177,6 +2138,24 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
return address;
}
+ List<Address> processGapCheckResults(
+ List<({int index, List<Address> addresses})> results,
+ ) {
+ final List<Address> result = [];
+ for (final tuple in results) {
+ if (tuple.addresses.isNotEmpty) {
+ int highestIndexWithHistory = -1;
+ highestIndexWithHistory = max(tuple.index, highestIndexWithHistory);
+
+ result.addAll(
+ tuple.addresses.where(
+ (e) => e.derivationIndex <= highestIndexWithHistory,
+ ),
+ );
+ }
+ }
+ return result;
+ }
// ============== View only ==================================================
@override
@@ -2277,48 +2256,8 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
Future.wait(changeFutures),
]);
- final receiveResults = futuresResult[0];
- final changeResults = futuresResult[1];
-
- int highestReceivingIndexWithHistory = 0;
-
- for (final tuple in receiveResults) {
- if (tuple.addresses.isEmpty) {
- await checkReceivingAddressForTransactions();
- } else {
- highestReceivingIndexWithHistory = max(
- tuple.index,
- highestReceivingIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- int highestChangeIndexWithHistory = 0;
- // If restoring a wallet that never sent any funds with change, then set changeArray
- // manually. If we didn't do this, it'd store an empty array.
- for (final tuple in changeResults) {
- if (tuple.addresses.isEmpty) {
- await checkChangeAddressForTransactions();
- } else {
- highestChangeIndexWithHistory = max(
- tuple.index,
- highestChangeIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- // remove extra addresses to help minimize risk of creating a large gap
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.change &&
- e.derivationIndex > highestChangeIndexWithHistory,
- );
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.receiving &&
- e.derivationIndex > highestReceivingIndexWithHistory,
+ addressesToStore.addAll(
+ processGapCheckResults([...futuresResult[0], ...futuresResult[1]]),
);
} else {
final clAddress = coinlib.Address.fromString(
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart
index 82f45ff..3ee2f89 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart
@@ -212,9 +212,9 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
try {
await db.transaction(() async {
final prev =
- await (db.select(db.mwebUtxos)..where(
- (e) => e.outputId.equals(utxo.outputId),
- )).getSingleOrNull();
+ await (db.select(db.mwebUtxos)
+ ..where((e) => e.outputId.equals(utxo.outputId)))
+ .getSingleOrNull();
if (prev == null) {
final newUtxo = MwebUtxosCompanion(
@@ -254,10 +254,9 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
blockHash: null, // ??
hash: "",
txid: fakeTxid,
- timestamp:
- utxo.height < 1
- ? DateTime.now().millisecondsSinceEpoch ~/ 1000
- : utxo.blockTime,
+ timestamp: utxo.height < 1
+ ? DateTime.now().millisecondsSinceEpoch ~/ 1000
+ : utxo.blockTime,
height: utxo.height,
inputs: [],
outputs: [
@@ -272,13 +271,11 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
type: TransactionType.incoming,
subType: TransactionSubType.mweb,
otherData: jsonEncode({
- TxV2OdKeys.overrideFee:
- Amount(
- rawValue:
- BigInt
- .zero, // TODO fill in correctly when we have a real txid
- fractionDigits: cryptoCurrency.fractionDigits,
- ).toJsonString(),
+ TxV2OdKeys.overrideFee: Amount(
+ rawValue: BigInt
+ .zero, // TODO fill in correctly when we have a real txid
+ fractionDigits: cryptoCurrency.fractionDigits,
+ ).toJsonString(),
}),
);
@@ -359,19 +356,18 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
}
Future<void> checkMwebSpends() async {
- final pending =
- await mainDB.isar.transactionV2s
- .where()
- .walletIdEqualTo(walletId)
- .filter()
- .heightIsNull()
- .and()
- .blockHashIsNull()
- .and()
- .subTypeEqualTo(TransactionSubType.mweb)
- .and()
- .typeEqualTo(TransactionType.outgoing)
- .findAll();
+ final pending = await mainDB.isar.transactionV2s
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .heightIsNull()
+ .and()
+ .blockHashIsNull()
+ .and()
+ .subTypeEqualTo(TransactionSubType.mweb)
+ .and()
+ .typeEqualTo(TransactionType.outgoing)
+ .findAll();
Logging.instance.f(pending);
@@ -391,11 +387,10 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
// dummy to show tx as confirmed. Need a better way to handle this as its kind of stupid, resulting in terrible UX
final dummyHeight = await chainHeight;
- TransactionV2? transaction =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(tx.txid, walletId)
- .findFirst();
+ TransactionV2? transaction = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(tx.txid, walletId)
+ .findFirst();
if (transaction == null || transaction.height == null) {
transaction = (transaction ?? tx).copyWith(height: dummyHeight);
@@ -504,19 +499,18 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
Logging.instance.d("Sent txHash: $txHash");
txData = txData.copyWith(
- usedUTXOs:
- txData.usedUTXOs!.map((e) {
- if (e is StandardInput) {
- return StandardInput(
- e.utxo.copyWith(used: true),
- derivePathType: e.derivePathType,
- );
- } else if (e is MwebInput) {
- return MwebInput(e.utxo.copyWith(used: true));
- } else {
- return e;
- }
- }).toList(),
+ usedUTXOs: txData.usedUTXOs!.map((e) {
+ if (e is StandardInput) {
+ return StandardInput(
+ e.utxo.copyWith(used: true),
+ derivePathType: e.derivePathType,
+ );
+ } else if (e is MwebInput) {
+ return MwebInput(e.utxo.copyWith(used: true));
+ } else {
+ return e;
+ }
+ }).toList(),
txHash: txHash,
txid: txHash,
);
@@ -530,8 +524,10 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
);
// Update used mweb utxos as used in database
- final usedMwebUtxos =
- txData.usedUTXOs!.whereType<MwebInput>().map((e) => e.utxo).toList();
+ final usedMwebUtxos = txData.usedUTXOs!
+ .whereType<MwebInput>()
+ .map((e) => e.utxo)
+ .toList();
Logging.instance.i("Used mweb inputs: $usedMwebUtxos");
@@ -557,10 +553,9 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
@override
Future<TxData> prepareSend({required TxData txData}) async {
- final hasMwebOutputs =
- txData.recipients!
- .where((e) => e.addressType == AddressType.mweb)
- .isNotEmpty;
+ final hasMwebOutputs = txData.recipients!
+ .where((e) => e.addressType == AddressType.mweb)
+ .isNotEmpty;
if (hasMwebOutputs) {
// assume pegin tx
txData = txData.copyWith(type: TxType.mwebPegIn);
@@ -571,10 +566,9 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
/// prepare mweb transaction where spending mweb outputs
Future<TxData> prepareSendMweb({required TxData txData}) async {
- final hasMwebOutputs =
- txData.recipients!
- .where((e) => e.addressType == AddressType.mweb)
- .isNotEmpty;
+ final hasMwebOutputs = txData.recipients!
+ .where((e) => e.addressType == AddressType.mweb)
+ .isNotEmpty;
final type = hasMwebOutputs ? TxType.mweb : TxType.mwebPegOut;
@@ -594,25 +588,23 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
try {
final currentHeight = await chainHeight;
- final spendableUtxos =
- await mainDB.isar.utxos
- .where()
- .walletIdEqualTo(walletId)
- .filter()
- .isBlockedEqualTo(false)
- .and()
- .group((q) => q.usedEqualTo(false).or().usedIsNull())
- .and()
- .valueGreaterThan(0)
- .findAll();
+ final spendableUtxos = await mainDB.isar.utxos
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .isBlockedEqualTo(false)
+ .and()
+ .group((q) => q.usedEqualTo(false).or().usedIsNull())
+ .and()
+ .valueGreaterThan(0)
+ .findAll();
spendableUtxos.removeWhere(
- (e) =>
- !e.isConfirmed(
- currentHeight,
- cryptoCurrency.minConfirms,
- cryptoCurrency.minCoinbaseConfirms,
- ),
+ (e) => !e.isConfirmed(
+ currentHeight,
+ cryptoCurrency.minConfirms,
+ cryptoCurrency.minCoinbaseConfirms,
+ ),
);
if (spendableUtxos.isEmpty) {
@@ -713,9 +705,9 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
try {
final currentHeight = await chainHeight;
final db = Drift.get(walletId);
- final mwebUtxos =
- await (db.select(db.mwebUtxos)
- ..where((e) => e.used.equals(false))).get();
+ final mwebUtxos = await (db.select(
+ db.mwebUtxos,
+ )..where((e) => e.used.equals(false))).get();
Amount satoshiBalanceTotal = Amount(
rawValue: BigInt.zero,
@@ -871,53 +863,10 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
Future.wait(changeFutures),
]);
- final receiveResults = futuresResult[0];
- final changeResults = futuresResult[1];
-
- final List<Address> addressesToStore = [];
-
- int highestReceivingIndexWithHistory = 0;
-
- for (final tuple in receiveResults) {
- if (tuple.addresses.isEmpty) {
- if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
- await checkReceivingAddressForTransactions();
- }
- } else {
- highestReceivingIndexWithHistory = math.max(
- tuple.index,
- highestReceivingIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- int highestChangeIndexWithHistory = 0;
- // If restoring a wallet that never sent any funds with change, then set changeArray
- // manually. If we didn't do this, it'd store an empty array.
- for (final tuple in changeResults) {
- if (tuple.addresses.isEmpty) {
- await checkChangeAddressForTransactions();
- } else {
- highestChangeIndexWithHistory = math.max(
- tuple.index,
- highestChangeIndexWithHistory,
- );
- addressesToStore.addAll(tuple.addresses);
- }
- }
-
- // remove extra addresses to help minimize risk of creating a large gap
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.change &&
- e.derivationIndex > highestChangeIndexWithHistory,
- );
- addressesToStore.removeWhere(
- (e) =>
- e.subType == AddressSubType.receiving &&
- e.derivationIndex > highestReceivingIndexWithHistory,
- );
+ final List<Address> addressesToStore = processGapCheckResults([
+ ...futuresResult[0],
+ ...futuresResult[1],
+ ]);
await mainDB.updateOrPutAddresses(addressesToStore);
});
@@ -979,18 +928,17 @@ mixin MwebInterface<T extends ElectrumXCurrencyInterface>
);
BigInt maxBI(BigInt a, BigInt b) => a > b ? a : b;
- final posUtxos =
- utxos
- .where(
- (utxo) => processedTx.inputs.any(
- (input) =>
- input.prevOut.hash.toHex ==
- Uint8List.fromList(
- utxo.id.toUint8ListFromHex.reversed.toList(),
- ).toHex,
- ),
- )
- .toList();
+ final posUtxos = utxos
+ .where(
+ (utxo) => processedTx.inputs.any(
+ (input) =>
+ input.prevOut.hash.toHex ==
+ Uint8List.fromList(
+ utxo.id.toUint8ListFromHex.reversed.toList(),
+ ).toHex,
+ ),
+ )
+ .toList();
final posOutputSum = processedTx.outputs.fold(
BigInt.zero,
Why this scored 31/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.