From 54e6f6c180ad6ebfe1af70a7efcbfd3b146426ed Mon Sep 17 00:00:00 2001 From: sneak Date: Fri, 27 Feb 2026 12:50:24 +0700 Subject: [PATCH] Show tx status screens after dApp transaction approval Previously the approval popup closed immediately after the user entered their password, giving zero feedback about whether the transaction was broadcast or confirmed. Now: 1. Background sends the broadcast result back to the popup via sendResponse callback (txHash or error) 2. Popup shows wait-tx screen on success (with polling timer) or error-tx screen on failure 3. Wait-tx polls for confirmation and transitions to success-tx 4. Done button closes the approval window txStatus.init() moved before the approval early-return so the wait/success/error views are wired up in the approval popup. Done buttons detect the approval context and call window.close() instead of navigating to address detail. --- src/background/index.js | 5 ++- src/popup/index.js | 4 +-- src/popup/views/approval.js | 70 +++++++++++++++++++++++++++++++------ src/popup/views/txStatus.js | 8 +++++ 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/background/index.js b/src/background/index.js index 887f2a2..861bf5d 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -607,10 +607,13 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { const connected = signer.connect(provider); const tx = await connected.sendTransaction(approval.txParams); approval.resolve({ txHash: tx.hash }); + sendResponse({ txHash: tx.hash }); } catch (e) { + const errMsg = e.shortMessage || e.message; approval.resolve({ - error: { message: e.shortMessage || e.message }, + error: { message: errMsg }, }); + sendResponse({ error: errMsg }); } })(); return true; diff --git a/src/popup/index.js b/src/popup/index.js index df17fab..af43b14 100644 --- a/src/popup/index.js +++ b/src/popup/index.js @@ -169,8 +169,9 @@ async function init() { await saveState(); } - // Always init approval — it may run in its own popup window + // Always init approval and txStatus — they may run in the approval popup window approval.init(ctx); + txStatus.init(ctx); // Check for approval mode const params = new URLSearchParams(window.location.search); @@ -202,7 +203,6 @@ async function init() { addressToken.init(ctx); send.init(ctx); confirmTx.init(ctx); - txStatus.init(ctx); transactionDetail.init(ctx); receive.init(ctx); addToken.init(ctx); diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index 04f36f8..c9732f9 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -3,6 +3,7 @@ const { state, saveState } = require("../../shared/state"); const { formatEther, formatUnits, Interface } = require("ethers"); const { ERC20_ABI } = require("../../shared/constants"); const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList"); +const txStatus = require("./txStatus"); const runtime = typeof browser !== "undefined" ? browser.runtime : chrome.runtime; @@ -117,9 +118,38 @@ function decodeCalldata(data, toAddress) { } function showTxApproval(details) { + const toAddr = details.txParams.to; + const token = toAddr ? TOKEN_BY_ADDRESS.get(toAddr.toLowerCase()) : null; + const ethValue = formatEther(details.txParams.value || "0"); + + // Build txInfo for status screens + pendingTxDetails = { + from: state.activeAddress, + to: toAddr || "", + amount: formatTxValue(ethValue), + token: "ETH", + tokenSymbol: token ? token.symbol : null, + }; + + // If this is an ERC-20 call, try to extract the real recipient and amount + const decoded = decodeCalldata(details.txParams.data, toAddr || ""); + if (decoded && decoded.details) { + for (const d of decoded.details) { + if (d.label === "Recipient" && d.address) { + pendingTxDetails.to = d.address; + } + if (d.label === "Amount") { + pendingTxDetails.amount = d.value; + } + } + if (token) { + pendingTxDetails.token = toAddr; + pendingTxDetails.tokenSymbol = token.symbol; + } + } + $("approve-tx-hostname").textContent = details.hostname; $("approve-tx-from").innerHTML = approvalAddressHtml(state.activeAddress); - const toAddr = details.txParams.to; // Show token symbol next to contract address if known const symbol = toAddr ? tokenLabel(toAddr) : null; @@ -141,8 +171,7 @@ function showTxApproval(details) { $("approve-tx-value").textContent = formatTxValue(formatEther(details.txParams.value || "0")) + " ETH"; - // Decode calldata - const decoded = decodeCalldata(details.txParams.data, toAddr || ""); + // Decode calldata (reuse decoded from above) const decodedEl = $("approve-tx-decoded"); if (decoded) { $("approve-tx-action").textContent = decoded.name; @@ -204,6 +233,7 @@ function show(id) { } let approvalId = null; +let pendingTxDetails = null; function init(ctx) { $("approve-remember").addEventListener("change", async () => { @@ -234,13 +264,33 @@ function init(ctx) { }); $("btn-approve-tx").addEventListener("click", () => { - runtime.sendMessage({ - type: "AUTISTMASK_TX_RESPONSE", - id: approvalId, - approved: true, - password: $("approve-tx-password").value, - }); - window.close(); + const password = $("approve-tx-password").value; + if (!password) { + $("approve-tx-error").textContent = "Please enter your password."; + $("approve-tx-error").classList.remove("hidden"); + return; + } + $("approve-tx-error").classList.add("hidden"); + $("btn-approve-tx").disabled = true; + $("btn-approve-tx").classList.add("text-muted"); + + runtime.sendMessage( + { + type: "AUTISTMASK_TX_RESPONSE", + id: approvalId, + approved: true, + password: password, + }, + (response) => { + if (response && response.txHash) { + txStatus.showWait(pendingTxDetails, response.txHash); + } else { + const msg = + (response && response.error) || "Transaction failed."; + txStatus.showError(pendingTxDetails, null, msg); + } + }, + ); }); $("btn-reject-tx").addEventListener("click", () => { diff --git a/src/popup/views/txStatus.js b/src/popup/views/txStatus.js index f94f245..a36fd31 100644 --- a/src/popup/views/txStatus.js +++ b/src/popup/views/txStatus.js @@ -161,7 +161,15 @@ function renderError() { showView("error-tx"); } +function isApprovalPopup() { + return new URLSearchParams(window.location.search).has("approval"); +} + function navigateBack() { + if (isApprovalPopup()) { + window.close(); + return; + } if (state.selectedToken) { ctx.showAddressToken(); } else {