// Post-broadcast transaction status views: wait, success, error. const { $, showView, addressTitle, escapeHtml, renderAddressHtml, attachCopyHandlers, copyableHtml, etherscanLinkHtml, } = require("./helpers"); const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList"); const { state, saveState, currentNetwork } = require("../../shared/state"); const { getProvider } = require("../../shared/balances"); const { log } = require("../../shared/log"); 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 title = addressTitle(address, state.wallets); return renderAddressHtml(address, { title }); } function txHashHtml(hash) { const link = `${currentNetwork().explorerUrl}/tx/${hash}`; return copyableHtml(hash, "break-all") + etherscanLinkHtml(link); } function blockNumberHtml(blockNumber) { const num = String(blockNumber); const link = `${currentNetwork().explorerUrl}/block/${num}`; return copyableHtml(num) + etherscanLinkHtml(link); } 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 `${currentNetwork().explorerUrl}/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 += `
`; } html += `
`; return html; } function renderSuccess() { const d = state.viewData; if (!d || !d.hash) return; const hasDecoded = d.decoded && d.decoded.details; // When decoded details are present, the Amount and To are already // shown inside the decoded well — hide the top-level duplicates. const summarySection = $("success-tx-summary").parentElement; const toSection = $("success-tx-to").parentElement; if (hasDecoded) { summarySection.classList.add("hidden"); toSection.classList.add("hidden"); } else { summarySection.classList.remove("hidden"); toSection.classList.remove("hidden"); $("success-tx-summary").textContent = d.amount + " " + d.symbol; $("success-tx-to").innerHTML = toAddressHtml(d.to); } $("success-tx-block").innerHTML = blockNumberHtml(d.blockNumber); $("success-tx-hash").innerHTML = txHashHtml(d.hash); // Show decoded calldata details if present const decodedEl = $("success-tx-decoded"); if (decodedEl && hasDecoded) { 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 };