Add dedicated wait/success/error screens for transaction status

After broadcast, the user is taken to a full-screen wait view showing
the amount, recipient, tx hash (copyable + etherscan link), and a
count-up timer. The view polls every 10 seconds for confirmation.

On confirmation: navigates to success screen showing block number,
tx hash, and a Done button that returns to the address view.

On 60-second timeout or error: navigates to error screen with the
failure message, tx hash (if available), and Done button.

Replaces the previous inline confirm-status div that was crammed
onto the confirmation page.
This commit is contained in:
2026-02-27 12:06:32 +07:00
parent e58f113cda
commit d229000258
5 changed files with 238 additions and 66 deletions

View File

@@ -27,6 +27,7 @@ const { isScamAddress } = require("../../shared/scamlist");
const { ERC20_ABI } = require("../../shared/constants");
const { log } = require("../../shared/log");
const makeBlockie = require("ethereum-blockies-base64");
const txStatus = require("./txStatus");
const EXT_ICON =
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
@@ -36,7 +37,6 @@ const EXT_ICON =
`</svg></span>`;
let pendingTx = null;
let elapsedTimer = null;
function etherscanTokenLink(address) {
return `https://etherscan.io/token/${address}`;
@@ -217,7 +217,6 @@ function show(txInfo) {
// Gas estimate — show placeholder then fetch async
$("confirm-fee").classList.remove("hidden");
$("confirm-fee-amount").textContent = "Estimating...";
$("confirm-status").classList.add("hidden");
showView("confirm-tx");
estimateGas(txInfo);
@@ -309,10 +308,7 @@ function init(ctx) {
hidePasswordModal();
const statusEl = $("confirm-status");
statusEl.textContent = "Sending...";
statusEl.classList.remove("hidden");
let tx;
try {
const signer = getSignerForAddress(
wallet,
@@ -322,7 +318,6 @@ function init(ctx) {
const provider = getProvider(state.rpcUrl);
const connectedSigner = signer.connect(provider);
let tx;
if (pendingTx.token === "ETH") {
tx = await connectedSigner.sendTransaction({
to: pendingTx.to,
@@ -339,62 +334,10 @@ function init(ctx) {
tx = await contract.transfer(pendingTx.to, amount);
}
// Disable send button immediately after broadcast
const sendBtn = $("btn-confirm-send");
sendBtn.disabled = true;
sendBtn.classList.add("text-muted");
// Show etherscan link and elapsed timer
const broadcastTime = Date.now();
statusEl.innerHTML = "";
statusEl.appendChild(
document.createTextNode(
"Broadcast. Waiting for confirmation... ",
),
);
const timerSpan = document.createElement("span");
timerSpan.textContent = "(0s)";
statusEl.appendChild(timerSpan);
statusEl.appendChild(document.createElement("br"));
const txLink = document.createElement("a");
txLink.href = "https://etherscan.io/tx/" + tx.hash;
txLink.target = "_blank";
txLink.rel = "noopener";
txLink.className = "underline decoration-dashed break-all";
txLink.textContent = tx.hash;
statusEl.appendChild(document.createTextNode("Tx: "));
statusEl.appendChild(txLink);
if (elapsedTimer) clearInterval(elapsedTimer);
elapsedTimer = setInterval(() => {
const elapsed = Math.floor((Date.now() - broadcastTime) / 1000);
timerSpan.textContent = "(" + elapsed + "s)";
}, 1000);
const receipt = await tx.wait();
clearInterval(elapsedTimer);
elapsedTimer = null;
statusEl.innerHTML = "";
statusEl.appendChild(
document.createTextNode(
"Confirmed in block " + receipt.blockNumber + ". Tx: ",
),
);
const link = document.createElement("a");
link.href = "https://etherscan.io/tx/" + receipt.hash;
link.target = "_blank";
link.rel = "noopener";
link.className = "underline decoration-dashed break-all";
link.textContent = receipt.hash;
statusEl.appendChild(link);
ctx.doRefreshAndRender();
txStatus.showWait(pendingTx, tx.hash);
} catch (e) {
if (elapsedTimer) {
clearInterval(elapsedTimer);
elapsedTimer = null;
}
statusEl.textContent = "Failed: " + (e.shortMessage || e.message);
const hash = tx ? tx.hash : null;
txStatus.showError(pendingTx, hash, e.shortMessage || e.message);
}
});
}