Show tx status screens after dApp transaction approval
All checks were successful
check / check (push) Successful in 17s

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.
This commit is contained in:
2026-02-27 12:50:24 +07:00
parent 1ebc206201
commit 54e6f6c180
4 changed files with 74 additions and 13 deletions

View File

@@ -607,10 +607,13 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
const connected = signer.connect(provider); const connected = signer.connect(provider);
const tx = await connected.sendTransaction(approval.txParams); const tx = await connected.sendTransaction(approval.txParams);
approval.resolve({ txHash: tx.hash }); approval.resolve({ txHash: tx.hash });
sendResponse({ txHash: tx.hash });
} catch (e) { } catch (e) {
const errMsg = e.shortMessage || e.message;
approval.resolve({ approval.resolve({
error: { message: e.shortMessage || e.message }, error: { message: errMsg },
}); });
sendResponse({ error: errMsg });
} }
})(); })();
return true; return true;

View File

@@ -169,8 +169,9 @@ async function init() {
await saveState(); 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); approval.init(ctx);
txStatus.init(ctx);
// Check for approval mode // Check for approval mode
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
@@ -202,7 +203,6 @@ async function init() {
addressToken.init(ctx); addressToken.init(ctx);
send.init(ctx); send.init(ctx);
confirmTx.init(ctx); confirmTx.init(ctx);
txStatus.init(ctx);
transactionDetail.init(ctx); transactionDetail.init(ctx);
receive.init(ctx); receive.init(ctx);
addToken.init(ctx); addToken.init(ctx);

View File

@@ -3,6 +3,7 @@ const { state, saveState } = require("../../shared/state");
const { formatEther, formatUnits, Interface } = require("ethers"); const { formatEther, formatUnits, Interface } = require("ethers");
const { ERC20_ABI } = require("../../shared/constants"); const { ERC20_ABI } = require("../../shared/constants");
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList"); const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
const txStatus = require("./txStatus");
const runtime = const runtime =
typeof browser !== "undefined" ? browser.runtime : chrome.runtime; typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
@@ -117,9 +118,38 @@ function decodeCalldata(data, toAddress) {
} }
function showTxApproval(details) { 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-hostname").textContent = details.hostname;
$("approve-tx-from").innerHTML = approvalAddressHtml(state.activeAddress); $("approve-tx-from").innerHTML = approvalAddressHtml(state.activeAddress);
const toAddr = details.txParams.to;
// Show token symbol next to contract address if known // Show token symbol next to contract address if known
const symbol = toAddr ? tokenLabel(toAddr) : null; const symbol = toAddr ? tokenLabel(toAddr) : null;
@@ -141,8 +171,7 @@ function showTxApproval(details) {
$("approve-tx-value").textContent = $("approve-tx-value").textContent =
formatTxValue(formatEther(details.txParams.value || "0")) + " ETH"; formatTxValue(formatEther(details.txParams.value || "0")) + " ETH";
// Decode calldata // Decode calldata (reuse decoded from above)
const decoded = decodeCalldata(details.txParams.data, toAddr || "");
const decodedEl = $("approve-tx-decoded"); const decodedEl = $("approve-tx-decoded");
if (decoded) { if (decoded) {
$("approve-tx-action").textContent = decoded.name; $("approve-tx-action").textContent = decoded.name;
@@ -204,6 +233,7 @@ function show(id) {
} }
let approvalId = null; let approvalId = null;
let pendingTxDetails = null;
function init(ctx) { function init(ctx) {
$("approve-remember").addEventListener("change", async () => { $("approve-remember").addEventListener("change", async () => {
@@ -234,13 +264,33 @@ function init(ctx) {
}); });
$("btn-approve-tx").addEventListener("click", () => { $("btn-approve-tx").addEventListener("click", () => {
runtime.sendMessage({ 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", type: "AUTISTMASK_TX_RESPONSE",
id: approvalId, id: approvalId,
approved: true, approved: true,
password: $("approve-tx-password").value, password: password,
}); },
window.close(); (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", () => { $("btn-reject-tx").addEventListener("click", () => {

View File

@@ -161,7 +161,15 @@ function renderError() {
showView("error-tx"); showView("error-tx");
} }
function isApprovalPopup() {
return new URLSearchParams(window.location.search).has("approval");
}
function navigateBack() { function navigateBack() {
if (isApprovalPopup()) {
window.close();
return;
}
if (state.selectedToken) { if (state.selectedToken) {
ctx.showAddressToken(); ctx.showAddressToken();
} else { } else {