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.
188 lines
5.6 KiB
JavaScript
188 lines
5.6 KiB
JavaScript
// Post-broadcast transaction status views: wait, success, error.
|
|
|
|
const {
|
|
$,
|
|
showView,
|
|
showFlash,
|
|
addressDotHtml,
|
|
escapeHtml,
|
|
} = require("./helpers");
|
|
const { state, saveState } = require("../../shared/state");
|
|
const { getProvider } = require("../../shared/balances");
|
|
const { log } = require("../../shared/log");
|
|
|
|
const EXT_ICON =
|
|
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
|
|
`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">` +
|
|
`<path d="M4.5 1.5H2a.5.5 0 00-.5.5v8a.5.5 0 00.5.5h8a.5.5 0 00.5-.5V7.5"/>` +
|
|
`<path d="M7 1.5h3.5V5M7 5.5L10.5 1.5"/>` +
|
|
`</svg></span>`;
|
|
|
|
let ctx;
|
|
let elapsedTimer = null;
|
|
let pollTimer = null;
|
|
|
|
function clearTimers() {
|
|
if (elapsedTimer) {
|
|
clearInterval(elapsedTimer);
|
|
elapsedTimer = null;
|
|
}
|
|
if (pollTimer) {
|
|
clearInterval(pollTimer);
|
|
pollTimer = null;
|
|
}
|
|
}
|
|
|
|
function toAddressHtml(address) {
|
|
const dot = addressDotHtml(address);
|
|
const link = `https://etherscan.io/address/${address}`;
|
|
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
|
return `<div class="flex items-center">${dot}<span class="break-all">${escapeHtml(address)}</span>${extLink}</div>`;
|
|
}
|
|
|
|
function txHashHtml(hash) {
|
|
const link = `https://etherscan.io/tx/${hash}`;
|
|
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
|
return (
|
|
`<span class="underline decoration-dashed cursor-pointer break-all" data-copy="${escapeHtml(hash)}">${escapeHtml(hash)}</span>` +
|
|
extLink
|
|
);
|
|
}
|
|
|
|
function attachCopyHandlers(viewId) {
|
|
document
|
|
.getElementById(viewId)
|
|
.querySelectorAll("[data-copy]")
|
|
.forEach((el) => {
|
|
el.onclick = () => {
|
|
navigator.clipboard.writeText(el.dataset.copy);
|
|
showFlash("Copied!");
|
|
};
|
|
});
|
|
}
|
|
|
|
function showWait(txInfo, txHash) {
|
|
clearTimers();
|
|
|
|
const symbol = txInfo.token === "ETH" ? "ETH" : txInfo.tokenSymbol || "?";
|
|
$("wait-tx-summary").textContent = txInfo.amount + " " + symbol;
|
|
$("wait-tx-to").innerHTML = toAddressHtml(txInfo.to);
|
|
$("wait-tx-hash").innerHTML = txHashHtml(txHash);
|
|
attachCopyHandlers("view-wait-tx");
|
|
|
|
const broadcastTime = Date.now();
|
|
$("wait-tx-status").textContent = "Waiting for confirmation... 0s";
|
|
|
|
elapsedTimer = setInterval(() => {
|
|
const elapsed = Math.floor((Date.now() - broadcastTime) / 1000);
|
|
$("wait-tx-status").textContent =
|
|
"Waiting for confirmation... " + elapsed + "s";
|
|
}, 1000);
|
|
|
|
const provider = getProvider(state.rpcUrl);
|
|
pollTimer = setInterval(async () => {
|
|
try {
|
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
if (receipt) {
|
|
showSuccess(txInfo, txHash, receipt.blockNumber);
|
|
}
|
|
} catch (e) {
|
|
log.errorf("poll receipt failed:", e.message);
|
|
}
|
|
|
|
const elapsed = Math.floor((Date.now() - broadcastTime) / 1000);
|
|
if (elapsed >= 60) {
|
|
showError(
|
|
txInfo,
|
|
txHash,
|
|
"Transaction was not confirmed within 60 seconds. It may still confirm later \u2014 check Etherscan.",
|
|
);
|
|
}
|
|
}, 10000);
|
|
|
|
showView("wait-tx");
|
|
}
|
|
|
|
function showSuccess(txInfo, txHash, blockNumber) {
|
|
clearTimers();
|
|
|
|
const symbol = txInfo.token === "ETH" ? "ETH" : txInfo.tokenSymbol || "?";
|
|
state.viewData = {
|
|
amount: txInfo.amount,
|
|
symbol: symbol,
|
|
to: txInfo.to,
|
|
hash: txHash,
|
|
blockNumber: blockNumber,
|
|
};
|
|
renderSuccess();
|
|
ctx.doRefreshAndRender();
|
|
}
|
|
|
|
function renderSuccess() {
|
|
const d = state.viewData;
|
|
if (!d || !d.hash) return;
|
|
$("success-tx-summary").textContent = d.amount + " " + d.symbol;
|
|
$("success-tx-to").innerHTML = toAddressHtml(d.to);
|
|
$("success-tx-block").textContent = String(d.blockNumber);
|
|
$("success-tx-hash").innerHTML = txHashHtml(d.hash);
|
|
attachCopyHandlers("view-success-tx");
|
|
showView("success-tx");
|
|
}
|
|
|
|
function showError(txInfo, txHash, message) {
|
|
clearTimers();
|
|
|
|
const symbol = txInfo.token === "ETH" ? "ETH" : txInfo.tokenSymbol || "?";
|
|
state.viewData = {
|
|
amount: txInfo.amount,
|
|
symbol: symbol,
|
|
to: txInfo.to,
|
|
hash: txHash || null,
|
|
message: message,
|
|
};
|
|
renderError();
|
|
}
|
|
|
|
function renderError() {
|
|
const d = state.viewData;
|
|
if (!d || !d.message) return;
|
|
$("error-tx-summary").textContent = d.amount + " " + d.symbol;
|
|
$("error-tx-to").innerHTML = toAddressHtml(d.to);
|
|
$("error-tx-message").textContent = d.message;
|
|
|
|
if (d.hash) {
|
|
$("error-tx-hash").innerHTML = txHashHtml(d.hash);
|
|
$("error-tx-hash-section").classList.remove("hidden");
|
|
attachCopyHandlers("view-error-tx");
|
|
} else {
|
|
$("error-tx-hash-section").classList.add("hidden");
|
|
}
|
|
|
|
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 {
|
|
ctx.showAddressDetail();
|
|
}
|
|
}
|
|
|
|
function init(_ctx) {
|
|
ctx = _ctx;
|
|
|
|
$("btn-success-tx-done").addEventListener("click", navigateBack);
|
|
$("btn-error-tx-done").addEventListener("click", navigateBack);
|
|
}
|
|
|
|
module.exports = { init, showWait, showError, renderSuccess, renderError };
|