Token auto-discovery, tx history, balance polling, EIP-6963, UI overhaul
All checks were successful
check / check (push) Successful in 14s
All checks were successful
check / check (push) Successful in 14s
Major changes: - Fetch token balances and tx history from Blockscout API (configurable) - Remove manual token discovery (discoverTokens) in favor of Blockscout - HD address gap scanning on mnemonic import - Duplicate mnemonic detection on wallet add - EIP-6963 multi-wallet discovery + selectedAddress updates in inpage - Two-tier balance refresh: 10s while popup open, 60s background - Fix $0.00 flash before prices load (return null when no prices) - No-layout-shift: min-height on total value element - Aligned balance columns (42ch address width, consistent USD column) - All errors use flash messages instead of off-screen error divs - Settings gear in global title bar, add-wallet moved to settings pane - Settings wells with light grey background, configurable Blockscout URL - Consistent "< Back" buttons top-left on all views - Address titles (Address 1.1, 1.2, etc.) on main and detail views - Send view shows current balance of selected asset - Clickable affordance policy added to README - Shortened mnemonic backup warning - Fix broken background script constant imports
This commit is contained in:
108
src/shared/transactions.js
Normal file
108
src/shared/transactions.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// Transaction history fetching via Blockscout v2 API.
|
||||
// Fetches normal transactions and ERC-20 token transfers,
|
||||
// merges them, and returns the most recent entries.
|
||||
|
||||
const { formatEther, formatUnits } = require("ethers");
|
||||
const { log } = require("./log");
|
||||
|
||||
function formatTxValue(val) {
|
||||
const parts = val.split(".");
|
||||
if (parts.length === 1) return val;
|
||||
const dec = parts[1].slice(0, 6).replace(/0+$/, "") || "0";
|
||||
return parts[0] + "." + dec;
|
||||
}
|
||||
|
||||
function parseTx(tx, addrLower) {
|
||||
const from = tx.from?.hash || "";
|
||||
const to = tx.to?.hash || "";
|
||||
return {
|
||||
hash: tx.hash,
|
||||
blockNumber: tx.block_number,
|
||||
timestamp: Math.floor(new Date(tx.timestamp).getTime() / 1000),
|
||||
from: from,
|
||||
to: to,
|
||||
value: formatTxValue(formatEther(tx.value || "0")),
|
||||
symbol: "ETH",
|
||||
direction: from.toLowerCase() === addrLower ? "sent" : "received",
|
||||
isError: tx.status !== "ok",
|
||||
};
|
||||
}
|
||||
|
||||
function parseTokenTransfer(tt, addrLower) {
|
||||
const from = tt.from?.hash || "";
|
||||
const to = tt.to?.hash || "";
|
||||
const decimals = parseInt(tt.total?.decimals || "18", 10);
|
||||
const rawValue = tt.total?.value || "0";
|
||||
return {
|
||||
hash: tt.transaction_hash,
|
||||
blockNumber: tt.block_number,
|
||||
timestamp: Math.floor(new Date(tt.timestamp).getTime() / 1000),
|
||||
from: from,
|
||||
to: to,
|
||||
value: formatTxValue(formatUnits(rawValue, decimals)),
|
||||
symbol: tt.token?.symbol || "?",
|
||||
direction: from.toLowerCase() === addrLower ? "sent" : "received",
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
|
||||
log.debugf("fetchRecentTransactions", address);
|
||||
const addrLower = address.toLowerCase();
|
||||
|
||||
const [txResp, ttResp] = await Promise.all([
|
||||
fetch(
|
||||
blockscoutUrl +
|
||||
"/addresses/" +
|
||||
address +
|
||||
"/transactions?limit=" +
|
||||
count,
|
||||
),
|
||||
fetch(
|
||||
blockscoutUrl +
|
||||
"/addresses/" +
|
||||
address +
|
||||
"/token-transfers?limit=" +
|
||||
count +
|
||||
"&type=ERC-20",
|
||||
),
|
||||
]);
|
||||
|
||||
if (!txResp.ok) {
|
||||
log.errorf(
|
||||
"blockscout transactions:",
|
||||
txResp.status,
|
||||
txResp.statusText,
|
||||
);
|
||||
}
|
||||
if (!ttResp.ok) {
|
||||
log.errorf(
|
||||
"blockscout token-transfers:",
|
||||
ttResp.status,
|
||||
ttResp.statusText,
|
||||
);
|
||||
}
|
||||
|
||||
const txJson = txResp.ok ? await txResp.json() : {};
|
||||
const ttJson = ttResp.ok ? await ttResp.json() : {};
|
||||
|
||||
const txs = [];
|
||||
|
||||
for (const tx of txJson.items || []) {
|
||||
txs.push(parseTx(tx, addrLower));
|
||||
}
|
||||
|
||||
// Deduplicate: skip token transfers whose tx hash is already in the list
|
||||
const seenHashes = new Set(txs.map((t) => t.hash));
|
||||
for (const tt of ttJson.items || []) {
|
||||
if (seenHashes.has(tt.transaction_hash)) continue;
|
||||
txs.push(parseTokenTransfer(tt, addrLower));
|
||||
}
|
||||
|
||||
txs.sort((a, b) => b.blockNumber - a.blockNumber);
|
||||
const result = txs.slice(0, count);
|
||||
log.debugf("fetchRecentTransactions done, count:", result.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { fetchRecentTransactions };
|
||||
Reference in New Issue
Block a user