fix: a transaction history that lands after a network switch is dropped (closes #657)
Home, AddressDetail and AddressToken remember which network is active before they ask Blockscout for the transaction history. Each screen drops the answer if another network is active on the page or in storage when the answer lands. AddressDetail and AddressToken also drop an answer that lands once the screen shows another address. AddressToken also drops an answer that lands once the screen shows another token. A dropped answer draws no rows and saves no fraud contracts. A failed request goes through the same checks. A dropped failure shows no message. Holding the lock, the save adds new fraud contracts only if the page and the stored record still name the request's network. storedNetworkIs() reads the stored network, in src/shared/storedSettings.js. Model: opus-5-5
This commit is contained in:
@@ -22,6 +22,7 @@ const {
|
||||
filterTransactions,
|
||||
} = require("../../shared/transactions");
|
||||
const { resolveEnsNames } = require("../../shared/ens");
|
||||
const { storedNetworkIs } = require("../../shared/storedSettings");
|
||||
const {
|
||||
updateSendBalance,
|
||||
renderSendTokenSelect,
|
||||
@@ -98,6 +99,7 @@ let loadedTxs = [];
|
||||
let ensNameMap = new Map();
|
||||
|
||||
async function loadTransactions(address) {
|
||||
const networkId = state.networkId;
|
||||
try {
|
||||
const rawTxs = await fetchRecentTransactions(
|
||||
address,
|
||||
@@ -114,43 +116,74 @@ async function loadTransactions(address) {
|
||||
});
|
||||
const txs = result.transactions;
|
||||
|
||||
// Persist any newly discovered fraud contracts
|
||||
if (result.newFraudContracts.length > 0) {
|
||||
for (const addr of result.newFraudContracts) {
|
||||
if (!state.fraudContracts.includes(addr)) {
|
||||
state.fraudContracts.push(addr);
|
||||
}
|
||||
}
|
||||
await saveState();
|
||||
}
|
||||
|
||||
loadedTxs = txs;
|
||||
|
||||
// Collect ALL unique addresses (from + to) for ENS resolution so
|
||||
// that reverse lookups work for every displayed address, not just
|
||||
// the ones that were originally entered as ENS names.
|
||||
const counterparties = [
|
||||
...new Set(txs.flatMap((tx) => [tx.from, tx.to].filter(Boolean))),
|
||||
];
|
||||
let names = new Map();
|
||||
if (counterparties.length > 0) {
|
||||
try {
|
||||
ensNameMap = await resolveEnsNames(
|
||||
names = await resolveEnsNames(
|
||||
counterparties,
|
||||
state.rpcUrl,
|
||||
state.networkId,
|
||||
ctx.pageClosed,
|
||||
);
|
||||
} catch {
|
||||
ensNameMap = new Map();
|
||||
// The rows then show no ENS names.
|
||||
}
|
||||
}
|
||||
|
||||
// If the network changed during the lookups above, or the screen now
|
||||
// shows another address, nothing is saved or drawn. See
|
||||
// src/shared/storedSettings.js.
|
||||
if (
|
||||
state.networkId !== networkId ||
|
||||
!(await storedNetworkIs(networkId)) ||
|
||||
$("address-line").dataset.full !== address
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist any newly discovered fraud contracts, inside the save and
|
||||
// holding the lock. Nothing is added unless this page and the stored
|
||||
// record both still name the request's network.
|
||||
if (result.newFraudContracts.length > 0) {
|
||||
let saved = false;
|
||||
await saveState((stored) => {
|
||||
if (state.networkId !== networkId) return false;
|
||||
if (stored.networkId !== networkId) return false;
|
||||
for (const addr of result.newFraudContracts) {
|
||||
if (!state.fraudContracts.includes(addr)) {
|
||||
state.fraudContracts.push(addr);
|
||||
}
|
||||
}
|
||||
saved = true;
|
||||
});
|
||||
// If the network changed while the save waited for the lock,
|
||||
// nothing is drawn.
|
||||
if (!saved) return;
|
||||
}
|
||||
|
||||
loadedTxs = txs;
|
||||
ensNameMap = names;
|
||||
renderTransactions(txs);
|
||||
} catch (e) {
|
||||
// Cancelled by the popup closing, not failed: see pageClosed in
|
||||
// src/popup/index.js.
|
||||
if (ctx.pageClosed.aborted) return;
|
||||
log.errorf("loadTransactions failed:", e.message);
|
||||
// If the network changed, or the screen now shows another address,
|
||||
// the failure is not shown.
|
||||
if (
|
||||
state.networkId !== networkId ||
|
||||
!(await storedNetworkIs(networkId)) ||
|
||||
$("address-line").dataset.full !== address
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$("tx-list").innerHTML =
|
||||
'<div class="text-muted text-xs py-1">Failed to load transactions.</div>';
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const {
|
||||
filterTransactions,
|
||||
} = require("../../shared/transactions");
|
||||
const { resolveEnsNames } = require("../../shared/ens");
|
||||
const { storedNetworkIs } = require("../../shared/storedSettings");
|
||||
const {
|
||||
updateSendBalance,
|
||||
renderSendTokenSelect,
|
||||
@@ -170,6 +171,7 @@ function show() {
|
||||
}
|
||||
|
||||
async function loadTransactions(address, tokenId) {
|
||||
const networkId = state.networkId;
|
||||
try {
|
||||
const rawTxs = await fetchRecentTransactions(
|
||||
address,
|
||||
@@ -186,16 +188,6 @@ async function loadTransactions(address, tokenId) {
|
||||
});
|
||||
let txs = result.transactions;
|
||||
|
||||
// Persist any newly discovered fraud contracts
|
||||
if (result.newFraudContracts.length > 0) {
|
||||
for (const addr of result.newFraudContracts) {
|
||||
if (!state.fraudContracts.includes(addr)) {
|
||||
state.fraudContracts.push(addr);
|
||||
}
|
||||
}
|
||||
await saveState();
|
||||
}
|
||||
|
||||
// Filter to this token only
|
||||
if (tokenId === "ETH") {
|
||||
txs = txs.filter((tx) => tx.contractAddress === null);
|
||||
@@ -207,32 +199,75 @@ async function loadTransactions(address, tokenId) {
|
||||
);
|
||||
}
|
||||
|
||||
loadedTxs = txs;
|
||||
|
||||
// Collect ALL unique addresses for ENS resolution so reverse
|
||||
// lookups work for every displayed address.
|
||||
const counterparties = [
|
||||
...new Set(txs.flatMap((tx) => [tx.from, tx.to].filter(Boolean))),
|
||||
];
|
||||
let names = new Map();
|
||||
if (counterparties.length > 0) {
|
||||
try {
|
||||
ensNameMap = await resolveEnsNames(
|
||||
names = await resolveEnsNames(
|
||||
counterparties,
|
||||
state.rpcUrl,
|
||||
state.networkId,
|
||||
ctx.pageClosed,
|
||||
);
|
||||
} catch {
|
||||
ensNameMap = new Map();
|
||||
// The rows then show no ENS names.
|
||||
}
|
||||
}
|
||||
|
||||
// If the network changed during the lookups above, or the screen now
|
||||
// shows another address or token, nothing is saved or drawn. See
|
||||
// src/shared/storedSettings.js.
|
||||
if (
|
||||
state.networkId !== networkId ||
|
||||
!(await storedNetworkIs(networkId)) ||
|
||||
$("address-token-line").dataset.full !== address ||
|
||||
state.selectedToken !== tokenId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist any newly discovered fraud contracts, inside the save and
|
||||
// holding the lock. Nothing is added unless this page and the stored
|
||||
// record both still name the request's network.
|
||||
if (result.newFraudContracts.length > 0) {
|
||||
let saved = false;
|
||||
await saveState((stored) => {
|
||||
if (state.networkId !== networkId) return false;
|
||||
if (stored.networkId !== networkId) return false;
|
||||
for (const addr of result.newFraudContracts) {
|
||||
if (!state.fraudContracts.includes(addr)) {
|
||||
state.fraudContracts.push(addr);
|
||||
}
|
||||
}
|
||||
saved = true;
|
||||
});
|
||||
// If the network changed while the save waited for the lock,
|
||||
// nothing is drawn.
|
||||
if (!saved) return;
|
||||
}
|
||||
|
||||
loadedTxs = txs;
|
||||
ensNameMap = names;
|
||||
renderTransactions(txs);
|
||||
} catch (e) {
|
||||
// Cancelled by the popup closing, not failed: see pageClosed in
|
||||
// src/popup/index.js.
|
||||
if (ctx.pageClosed.aborted) return;
|
||||
log.errorf("loadTransactions failed:", e.message);
|
||||
// If the network changed, or the screen now shows another address or
|
||||
// token, the failure is not shown.
|
||||
if (
|
||||
state.networkId !== networkId ||
|
||||
!(await storedNetworkIs(networkId)) ||
|
||||
$("address-token-line").dataset.full !== address ||
|
||||
state.selectedToken !== tokenId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$("address-token-tx-list").innerHTML =
|
||||
'<div class="text-muted text-xs py-1">Failed to load transactions.</div>';
|
||||
}
|
||||
|
||||
+34
-6
@@ -44,6 +44,7 @@ const {
|
||||
fetchRecentTransactions,
|
||||
filterTransactions,
|
||||
} = require("../../shared/transactions");
|
||||
const { storedNetworkIs } = require("../../shared/storedSettings");
|
||||
const { log } = require("../../shared/log");
|
||||
|
||||
function findActiveAddr() {
|
||||
@@ -183,6 +184,7 @@ async function loadHomeTxs(ctx) {
|
||||
}
|
||||
if (allAddresses.length === 0) return;
|
||||
|
||||
const networkId = state.networkId;
|
||||
const filters = {
|
||||
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
||||
hideLowHolderTokens: state.hideLowHolderTokens,
|
||||
@@ -202,6 +204,15 @@ async function loadHomeTxs(ctx) {
|
||||
);
|
||||
const results = await Promise.all(fetches);
|
||||
|
||||
// If the network changed during the lookups, nothing is saved or
|
||||
// drawn. See src/shared/storedSettings.js.
|
||||
if (
|
||||
state.networkId !== networkId ||
|
||||
!(await storedNetworkIs(networkId))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge, deduplicate by hash, filter, sort, take 25
|
||||
const seen = new Set();
|
||||
let merged = [];
|
||||
@@ -215,14 +226,24 @@ async function loadHomeTxs(ctx) {
|
||||
|
||||
const filtered = filterTransactions(merged, filters);
|
||||
|
||||
// Persist any newly discovered fraud contracts
|
||||
// Persist any newly discovered fraud contracts, inside the save and
|
||||
// holding the lock. Nothing is added unless this page and the stored
|
||||
// record both still name the request's network.
|
||||
if (filtered.newFraudContracts.length > 0) {
|
||||
for (const addr of filtered.newFraudContracts) {
|
||||
if (!state.fraudContracts.includes(addr)) {
|
||||
state.fraudContracts.push(addr);
|
||||
let saved = false;
|
||||
await saveState((stored) => {
|
||||
if (state.networkId !== networkId) return false;
|
||||
if (stored.networkId !== networkId) return false;
|
||||
for (const addr of filtered.newFraudContracts) {
|
||||
if (!state.fraudContracts.includes(addr)) {
|
||||
state.fraudContracts.push(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
await saveState();
|
||||
saved = true;
|
||||
});
|
||||
// If the network changed while the save waited for the lock,
|
||||
// nothing is drawn.
|
||||
if (!saved) return;
|
||||
}
|
||||
|
||||
merged = filtered.transactions;
|
||||
@@ -234,6 +255,13 @@ async function loadHomeTxs(ctx) {
|
||||
// src/popup/index.js.
|
||||
if (ctx.pageClosed.aborted) return;
|
||||
log.errorf("loadHomeTxs failed:", e.message);
|
||||
// If the network changed, the failure is not shown.
|
||||
if (
|
||||
state.networkId !== networkId ||
|
||||
!(await storedNetworkIs(networkId))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const list = $("home-tx-list");
|
||||
if (list) {
|
||||
list.innerHTML =
|
||||
|
||||
Reference in New Issue
Block a user