diff --git a/src/popup/index.html b/src/popup/index.html index c716436..0e05b95 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -494,10 +494,80 @@ > Send -
+ + + + + + + + + + diff --git a/src/popup/index.js b/src/popup/index.js index 598dfa9..b28bc23 100644 --- a/src/popup/index.js +++ b/src/popup/index.js @@ -15,6 +15,7 @@ const addressDetail = require("./views/addressDetail"); const addressToken = require("./views/addressToken"); const send = require("./views/send"); const confirmTx = require("./views/confirmTx"); +const txStatus = require("./views/txStatus"); const receive = require("./views/receive"); const addToken = require("./views/addToken"); const settings = require("./views/settings"); @@ -111,6 +112,7 @@ async function init() { addressToken.init(ctx); send.init(ctx); confirmTx.init(ctx); + txStatus.init(ctx); receive.init(ctx); addToken.init(ctx); settings.init(ctx); diff --git a/src/popup/views/confirmTx.js b/src/popup/views/confirmTx.js index a8225c4..e7a4ca6 100644 --- a/src/popup/views/confirmTx.js +++ b/src/popup/views/confirmTx.js @@ -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 = `` + @@ -36,7 +37,6 @@ const EXT_ICON = ``; 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); } }); } diff --git a/src/popup/views/helpers.js b/src/popup/views/helpers.js index 2f25af7..0d37291 100644 --- a/src/popup/views/helpers.js +++ b/src/popup/views/helpers.js @@ -16,6 +16,9 @@ const VIEWS = [ "address-token", "send", "confirm-tx", + "wait-tx", + "success-tx", + "error-tx", "receive", "add-token", "settings", diff --git a/src/popup/views/txStatus.js b/src/popup/views/txStatus.js new file mode 100644 index 0000000..cbce68c --- /dev/null +++ b/src/popup/views/txStatus.js @@ -0,0 +1,154 @@ +// Post-broadcast transaction status views: wait, success, error. + +const { + $, + showView, + showFlash, + addressDotHtml, + escapeHtml, +} = require("./helpers"); +const { state } = 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}`; + return `