From 76059c36745e253d2717bec59fdbca4b00127de9 Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 12:03:57 -0800 Subject: [PATCH 1/5] fix: display swaps and contract calls correctly in tx history (closes #3) - Preserve contract call metadata (direction, label, method) when token transfers merge with normal txs in fetchRecentTransactions - Handle 'contract' direction in counterparty display for home and address detail list views - Add decoded calldata display to transaction detail view, fetching raw input from Blockscout and using decodeCalldata from approval.js - Show 'Unknown contract call' with raw hex for unrecognized calldata - Export decodeCalldata from approval.js for reuse --- src/popup/index.html | 9 ++++ src/popup/views/addressDetail.js | 5 +- src/popup/views/approval.js | 2 +- src/popup/views/home.js | 5 +- src/popup/views/transactionDetail.js | 72 ++++++++++++++++++++++++++++ src/shared/transactions.js | 11 ++++- 6 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/popup/index.html b/src/popup/index.html index 889aa51..78fae1a 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -945,6 +945,15 @@
To
+
Transaction hash
diff --git a/src/popup/views/addressDetail.js b/src/popup/views/addressDetail.js index a91cf20..3f4f4f1 100644 --- a/src/popup/views/addressDetail.js +++ b/src/popup/views/addressDetail.js @@ -185,7 +185,10 @@ function renderTransactions(txs) { let html = ""; let i = 0; for (const tx of txs) { - const counterparty = tx.direction === "sent" ? tx.to : tx.from; + const counterparty = + tx.direction === "sent" || tx.direction === "contract" + ? tx.to + : tx.from; const ensName = ensNameMap.get(counterparty) || null; const dirLabel = tx.directionLabel; const amountStr = tx.value diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index 359b506..58319ed 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -453,4 +453,4 @@ function init(ctx) { }); } -module.exports = { init, show }; +module.exports = { init, show, decodeCalldata }; diff --git a/src/popup/views/home.js b/src/popup/views/home.js index 62b352b..78fbff2 100644 --- a/src/popup/views/home.js +++ b/src/popup/views/home.js @@ -102,7 +102,10 @@ function renderHomeTxList(ctx) { let html = ""; let i = 0; for (const tx of homeTxs) { - const counterparty = tx.direction === "sent" ? tx.to : tx.from; + const counterparty = + tx.direction === "sent" || tx.direction === "contract" + ? tx.to + : tx.from; const dirLabel = tx.directionLabel; const amountStr = tx.value ? escapeHtml(tx.value + " " + tx.symbol) diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 8ecbfe3..53c2005 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -13,6 +13,8 @@ const { } = require("./helpers"); const { state } = require("../../shared/state"); const makeBlockie = require("ethereum-blockies-base64"); +const { log, debugFetch } = require("../../shared/log"); +const { decodeCalldata } = require("./approval"); const EXT_ICON = `` + @@ -85,9 +87,15 @@ function show(tx) { fromEns: tx.fromEns || null, toEns: tx.toEns || null, directionLabel: tx.directionLabel || null, + direction: tx.direction || null, + isContractCall: tx.isContractCall || false, + method: tx.method || null, }, }; render(); + if (tx.isContractCall || tx.direction === "contract") { + loadCalldata(tx.hash, tx.to); + } } function render() { @@ -121,6 +129,10 @@ function render() { nativeEl.parentElement.classList.add("hidden"); } + // Hide calldata section by default; loadCalldata will show it if needed + const calldataSection = $("tx-detail-calldata-section"); + if (calldataSection) calldataSection.classList.add("hidden"); + $("tx-detail-time").textContent = isoDate(tx.timestamp) + " (" + timeAgo(tx.timestamp) + ")"; $("tx-detail-status").textContent = tx.isError ? "Failed" : "Success"; @@ -137,6 +149,66 @@ function render() { }); } +function renderDecodedCalldata(decoded) { + let html = `
${escapeHtml(decoded.name)}
`; + if (decoded.description) { + html += `
${escapeHtml(decoded.description)}
`; + } + for (const detail of decoded.details || []) { + html += `
${escapeHtml(detail.label)}: `; + if (detail.address) { + const dot = addressDotHtml(detail.address); + html += `${dot}${copyableHtml(detail.value, "break-all")}`; + } else { + html += escapeHtml(detail.value); + } + html += `
`; + } + return html; +} + +async function loadCalldata(txHash, toAddress) { + const section = $("tx-detail-calldata-section"); + const container = $("tx-detail-calldata"); + if (!section || !container) return; + + try { + const resp = await debugFetch( + state.blockscoutUrl + "/transactions/" + txHash, + ); + if (!resp.ok) return; + const txData = await resp.json(); + const inputData = txData.raw_input || txData.input || null; + if (!inputData || inputData === "0x") return; + + const decoded = decodeCalldata(inputData, toAddress || ""); + if (decoded) { + container.innerHTML = renderDecodedCalldata(decoded); + } else { + const method = txData.method || "Unknown method"; + let html = `
Unknown contract call
`; + html += `
${escapeHtml(method)}
`; + const displayData = + inputData.length > 202 + ? inputData.slice(0, 202) + "…" + : inputData; + html += `
${copyableHtml(inputData, "break-all")}
`; + container.innerHTML = html; + } + section.classList.remove("hidden"); + + // Bind copy handlers for new elements + section.querySelectorAll("[data-copy]").forEach((el) => { + el.onclick = () => { + navigator.clipboard.writeText(el.dataset.copy); + showFlash("Copied!"); + }; + }); + } catch (e) { + log.errorf("loadCalldata failed:", e.message); + } +} + function init(_ctx) { ctx = _ctx; $("btn-tx-back").addEventListener("click", () => { diff --git a/src/shared/transactions.js b/src/shared/transactions.js index f926784..d58fb86 100644 --- a/src/shared/transactions.js +++ b/src/shared/transactions.js @@ -139,9 +139,18 @@ async function fetchRecentTransactions(address, blockscoutUrl, count = 25) { // When a token transfer shares a hash with a normal tx, the normal tx // is the contract call (0 ETH) and the token transfer has the real - // amount and symbol. Replace the normal tx with the token transfer. + // amount and symbol. Replace the normal tx with the token transfer, + // but preserve contract call metadata (direction, label, method) so + // swaps and other contract interactions display correctly. for (const tt of ttJson.items || []) { const parsed = parseTokenTransfer(tt, addrLower); + const existing = txsByHash.get(parsed.hash); + if (existing && existing.direction === "contract") { + parsed.direction = "contract"; + parsed.directionLabel = existing.directionLabel; + parsed.isContractCall = true; + parsed.method = existing.method; + } txsByHash.set(parsed.hash, parsed); } From 3fd3e30f44faef0e7adec0a0b429779987d0c2f1 Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 12:07:47 -0800 Subject: [PATCH 2/5] fix: label swap methods as "Swap" in tx lists, remove unused variable - Map known DEX methods (execute, swap, multicall, etc.) to "Swap" label instead of raw method name like "Execute" - Remove unused displayData variable in transactionDetail.js Addresses review feedback on PR #10. --- src/popup/views/transactionDetail.js | 4 ---- src/shared/transactions.js | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 53c2005..04c9859 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -188,10 +188,6 @@ async function loadCalldata(txHash, toAddress) { const method = txData.method || "Unknown method"; let html = `
Unknown contract call
`; html += `
${escapeHtml(method)}
`; - const displayData = - inputData.length > 202 - ? inputData.slice(0, 202) + "…" - : inputData; html += `
${copyableHtml(inputData, "break-all")}
`; container.innerHTML = html; } diff --git a/src/shared/transactions.js b/src/shared/transactions.js index d58fb86..e12d310 100644 --- a/src/shared/transactions.js +++ b/src/shared/transactions.js @@ -37,7 +37,21 @@ function parseTx(tx, addrLower) { if (token) { symbol = token.symbol; } - const label = method.charAt(0).toUpperCase() + method.slice(1); + // Map known DEX methods to "Swap" for cleaner display + const SWAP_METHODS = new Set([ + "execute", + "swap", + "swapExactTokensForTokens", + "swapTokensForExactTokens", + "swapExactETHForTokens", + "swapTokensForExactETH", + "swapExactTokensForETH", + "swapETHForExactTokens", + "multicall", + ]); + const label = SWAP_METHODS.has(method) + ? "Swap" + : method.charAt(0).toUpperCase() + method.slice(1); direction = "contract"; directionLabel = label; value = ""; From aaeb38d7c664f465cc20bbf2ceb54e04df20bb0b Mon Sep 17 00:00:00 2001 From: user Date: Fri, 27 Feb 2026 13:00:07 -0800 Subject: [PATCH 3/5] fix: show Swap type label and heading on transaction detail page --- src/popup/index.html | 8 +++++++- src/popup/views/transactionDetail.js | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/popup/index.html b/src/popup/index.html index 78fae1a..e0c6130 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -920,7 +920,13 @@ > < Back -

Transaction

+

+ Transaction +

+
Status
diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 04c9859..70d4af2 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -129,6 +129,21 @@ function render() { nativeEl.parentElement.classList.add("hidden"); } + // Show type label for contract interactions (Swap, Execute, etc.) + const typeSection = $("tx-detail-type-section"); + const typeEl = $("tx-detail-type"); + const headingEl = $("tx-detail-heading"); + if (tx.direction === "contract" && tx.directionLabel) { + if (typeSection) { + typeEl.textContent = tx.directionLabel; + typeSection.classList.remove("hidden"); + } + if (headingEl) headingEl.textContent = tx.directionLabel; + } else { + if (typeSection) typeSection.classList.add("hidden"); + if (headingEl) headingEl.textContent = "Transaction"; + } + // Hide calldata section by default; loadCalldata will show it if needed const calldataSection = $("tx-detail-calldata-section"); if (calldataSection) calldataSection.classList.add("hidden"); From 8824237db622ffdf1f93f26d4389d32cf1ee6e6c Mon Sep 17 00:00:00 2001 From: user Date: Fri, 27 Feb 2026 13:01:53 -0800 Subject: [PATCH 4/5] fix: match approval view display consistency for decoded calldata - Restructured calldata section to use same well layout as approval view: Action label + bold name + structured details - Always show raw data section below decoded well - Unknown contract calls show method name in well instead of inline --- src/popup/index.html | 26 +++++++++--- src/popup/views/transactionDetail.js | 63 ++++++++++++++++------------ 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/popup/index.html b/src/popup/index.html index e0c6130..bc25e3d 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -952,13 +952,27 @@
+
Transaction hash
diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 70d4af2..43d482d 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -164,28 +164,14 @@ function render() { }); } -function renderDecodedCalldata(decoded) { - let html = `
${escapeHtml(decoded.name)}
`; - if (decoded.description) { - html += `
${escapeHtml(decoded.description)}
`; - } - for (const detail of decoded.details || []) { - html += `
${escapeHtml(detail.label)}: `; - if (detail.address) { - const dot = addressDotHtml(detail.address); - html += `${dot}${copyableHtml(detail.value, "break-all")}`; - } else { - html += escapeHtml(detail.value); - } - html += `
`; - } - return html; -} - async function loadCalldata(txHash, toAddress) { const section = $("tx-detail-calldata-section"); - const container = $("tx-detail-calldata"); - if (!section || !container) return; + const actionEl = $("tx-detail-calldata-action"); + const detailsEl = $("tx-detail-calldata-details"); + const wellEl = $("tx-detail-calldata-well"); + const rawSection = $("tx-detail-rawdata-section"); + const rawEl = $("tx-detail-rawdata"); + if (!section || !actionEl || !detailsEl) return; try { const resp = await debugFetch( @@ -198,14 +184,39 @@ async function loadCalldata(txHash, toAddress) { const decoded = decodeCalldata(inputData, toAddress || ""); if (decoded) { - container.innerHTML = renderDecodedCalldata(decoded); + // Render decoded calldata matching approval view style + actionEl.textContent = decoded.name; + let detailsHtml = ""; + if (decoded.description) { + detailsHtml += `
${escapeHtml(decoded.description)}
`; + } + for (const d of decoded.details || []) { + detailsHtml += `
`; + detailsHtml += `
${escapeHtml(d.label)}
`; + if (d.address) { + const dot = addressDotHtml(d.address); + detailsHtml += `
${dot}${copyableHtml(d.value, "break-all")}
`; + } else { + detailsHtml += `
${escapeHtml(d.value)}
`; + } + detailsHtml += `
`; + } + detailsEl.innerHTML = detailsHtml; + if (wellEl) wellEl.classList.remove("hidden"); } else { - const method = txData.method || "Unknown method"; - let html = `
Unknown contract call
`; - html += `
${escapeHtml(method)}
`; - html += `
${copyableHtml(inputData, "break-all")}
`; - container.innerHTML = html; + // Unknown contract call — show method name in well + const method = txData.method || "Unknown contract call"; + actionEl.textContent = method; + detailsEl.innerHTML = ""; + if (wellEl) wellEl.classList.remove("hidden"); } + + // Always show raw data + if (rawSection && rawEl) { + rawEl.innerHTML = copyableHtml(inputData, "break-all"); + rawSection.classList.remove("hidden"); + } + section.classList.remove("hidden"); // Bind copy handlers for new elements From 593619967677c4a8f3eb3378e98c35431316ed7d Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 13:03:43 -0800 Subject: [PATCH 5/5] fix: place color dot next to address, not title, matching convention --- src/popup/views/transactionDetail.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 43d482d..1091005 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -44,11 +44,11 @@ function txAddressHtml(address, ensName, title) { const extLink = `${EXT_ICON}`; let html = `
${blockie}
`; if (title) { - html += `
${dot}${escapeHtml(title)}
`; + html += `
${escapeHtml(title)}
`; } if (ensName) { html += - `
${title ? "" : dot}` + + `
${dot}` + copyableHtml(ensName, "") + extLink + `
` + @@ -57,7 +57,7 @@ function txAddressHtml(address, ensName, title) { `
`; } else { html += - `
${title ? "" : dot}` + + `
${dot}` + copyableHtml(address, "break-all") + extLink + `
`;