Parallelize address scanning and unify address display formatting
Some checks failed
check / check (push) Has been cancelled

Scanning: check all gap-limit addresses in parallel per batch instead
of sequentially. For a wallet with 1 used address this reduces from
12 sequential RPC round-trips to 1 parallel batch + 1 small follow-up.

Display: add shared formatAddressHtml(address, ensName, maxLen) and
escapeHtml() to helpers.js. Use them in confirm-tx (was missing color
dot entirely) and approval view. Remove duplicate escapeHtml from
addressDetail.js.
This commit is contained in:
2026-02-26 03:46:25 +07:00
parent 1dfc006cb9
commit 0d543288b2
5 changed files with 84 additions and 47 deletions

View File

@@ -166,44 +166,54 @@ async function lookupTokenInfo(contractAddress, rpcUrl) {
}
// Derive HD addresses starting from index 0 and check for on-chain activity.
// Stops after gapLimit consecutive addresses with zero balance and zero tx count.
// Checks gapLimit addresses in parallel per batch. Stops when an entire
// batch has no used addresses (i.e. gapLimit consecutive empty addresses).
// Returns { addresses: [{ address, index }], nextIndex }.
async function scanForAddresses(xpub, rpcUrl, gapLimit = 5) {
log.debugf("scanForAddresses start, gapLimit:", gapLimit);
const provider = getProvider(rpcUrl);
const used = [];
let gap = 0;
let index = 0;
let checked = 0;
let checkUpTo = gapLimit;
while (gap < gapLimit) {
const addr = deriveAddressFromXpub(xpub, index);
let balance, txCount;
try {
[balance, txCount] = await Promise.all([
provider.getBalance(addr),
provider.getTransactionCount(addr),
]);
} catch (e) {
log.errorf(
"scanForAddresses check failed",
addr,
e.shortMessage || e.message,
);
// Treat RPC failure as empty to avoid infinite loop
gap++;
index++;
continue;
while (checked < checkUpTo) {
const batch = [];
for (let i = checked; i < checkUpTo; i++) {
const addr = deriveAddressFromXpub(xpub, i);
batch.push({ addr, index: i });
}
if (balance > 0n || txCount > 0) {
used.push({ address: addr, index });
gap = 0;
log.debugf("scanForAddresses used", addr, "index:", index);
} else {
gap++;
const results = await Promise.all(
batch.map(async ({ addr, index }) => {
try {
const [balance, txCount] = await Promise.all([
provider.getBalance(addr),
provider.getTransactionCount(addr),
]);
return { addr, index, isUsed: balance > 0n || txCount > 0 };
} catch (e) {
log.errorf(
"scanForAddresses check failed",
addr,
e.shortMessage || e.message,
);
return { addr, index, isUsed: false };
}
}),
);
checked = checkUpTo;
for (const r of results) {
if (r.isUsed) {
used.push({ address: r.addr, index: r.index });
log.debugf("scanForAddresses used", r.addr, "index:", r.index);
checkUpTo = Math.max(checkUpTo, r.index + 1 + gapLimit);
}
}
index++;
}
used.sort((a, b) => a.index - b.index);
const nextIndex = used.length > 0 ? used[used.length - 1].index + 1 : 1;
log.infof(
"scanForAddresses done, found:",