// Post-broadcast transaction status views: wait, success, error. const { $, showView, showFlash, addressDotHtml, addressTitle, escapeHtml, } = require("./helpers"); const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList"); const { state, saveState } = require("../../shared/state"); const { getProvider } = require("../../shared/balances"); const { log } = require("../../shared/log"); const EXT_ICON = `` + `` + `` + `` + ``; 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 = `${EXT_ICON}`; const title = addressTitle(address, state.wallets); if (title) { return ( `
${dot}${escapeHtml(title)}
` + `
${escapeHtml(address)}${extLink}
` ); } return `
${dot}${escapeHtml(address)}${extLink}
`; } function txHashHtml(hash) { const link = `https://etherscan.io/tx/${hash}`; const extLink = `${EXT_ICON}`; return ( `${escapeHtml(hash)}` + 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, decoded: txInfo.decoded || null, }; renderSuccess(); ctx.doRefreshAndRender(); } function tokenLabel(address) { const t = TOKEN_BY_ADDRESS.get(address.toLowerCase()); return t ? t.symbol : null; } function etherscanTokenLink(address) { return `https://etherscan.io/token/${address}`; } function decodedDetailsHtml(decoded) { if (!decoded || !decoded.details) return ""; let html = ""; if (decoded.name) { html += `
Action
`; html += `
${escapeHtml(decoded.name)}
`; } if (decoded.description) { html += `
Description
`; html += `
${escapeHtml(decoded.description)}
`; } for (const d of decoded.details) { html += `
`; html += `
${escapeHtml(d.label)}
`; if (d.address) { if (d.isToken) { const sym = tokenLabel(d.address) || "Unknown token"; html += `
${escapeHtml(sym)}
`; html += toAddressHtml(d.address); } else { html += toAddressHtml(d.address); } } else { html += `
${escapeHtml(d.value)}
`; } html += `
`; } return html; } 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); // Show decoded calldata details if present const decodedEl = $("success-tx-decoded"); if (decodedEl && d.decoded) { decodedEl.innerHTML = decodedDetailsHtml(d.decoded); decodedEl.classList.remove("hidden"); } else if (decodedEl) { decodedEl.classList.add("hidden"); } 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 };