// Transaction confirmation view + password modal. // Shows transaction details, warnings, errors. On proceed, opens // password modal, decrypts secret, signs and broadcasts. const { parseEther } = require("ethers"); const { $, showError, hideError, showView } = require("./helpers"); const { state } = require("../../shared/state"); const { getSignerForAddress } = require("../../shared/wallet"); const { decryptWithPassword } = require("../../shared/vault"); const { formatUsd, getPrice } = require("../../shared/prices"); const { getProvider, invalidateBalanceCache, } = require("../../shared/balances"); const { isScamAddress } = require("../../shared/scamlist"); let pendingTx = null; function show(txInfo) { pendingTx = txInfo; $("confirm-from").textContent = txInfo.from; $("confirm-to").textContent = txInfo.to; const ensEl = $("confirm-to-ens"); if (txInfo.ensName) { ensEl.textContent = "(" + txInfo.ensName + ")"; ensEl.classList.remove("hidden"); } else { ensEl.classList.add("hidden"); } $("confirm-amount").textContent = txInfo.amount + " " + txInfo.token; const ethPrice = getPrice("ETH"); if (txInfo.token === "ETH" && ethPrice) { const usd = parseFloat(txInfo.amount) * ethPrice; $("confirm-amount-usd").textContent = formatUsd(usd); } else { $("confirm-amount-usd").textContent = ""; } // Check for warnings const warnings = []; if (isScamAddress(txInfo.to)) { warnings.push( "This address is on a known scam/fraud list. Do not send funds to this address.", ); } if (txInfo.to.toLowerCase() === txInfo.from.toLowerCase()) { warnings.push("You are sending to your own address."); } const warningsEl = $("confirm-warnings"); if (warnings.length > 0) { warningsEl.innerHTML = warnings .map( (w) => `
WARNING: ${w}
`, ) .join(""); warningsEl.classList.remove("hidden"); } else { warningsEl.classList.add("hidden"); } // Check for errors const errors = []; if ( txInfo.token === "ETH" && parseFloat(txInfo.amount) > parseFloat(txInfo.balance) ) { errors.push( "Insufficient balance. You have " + txInfo.balance + " ETH but are trying to send " + txInfo.amount + " ETH.", ); } const errorsEl = $("confirm-errors"); const sendBtn = $("btn-confirm-send"); if (errors.length > 0) { errorsEl.innerHTML = errors .map((e) => `
${e}
`) .join(""); errorsEl.classList.remove("hidden"); sendBtn.disabled = true; sendBtn.classList.add("text-muted"); } else { errorsEl.classList.add("hidden"); sendBtn.disabled = false; sendBtn.classList.remove("text-muted"); } $("confirm-fee").classList.add("hidden"); $("confirm-status").classList.add("hidden"); showView("confirm-tx"); } function showPasswordModal() { $("modal-password").value = ""; hideError("modal-password-error"); $("password-modal").classList.remove("hidden"); } function hidePasswordModal() { $("password-modal").classList.add("hidden"); } function init(ctx) { $("btn-confirm-send").addEventListener("click", () => { showPasswordModal(); }); $("btn-confirm-back").addEventListener("click", () => { showView("send"); }); $("btn-modal-cancel").addEventListener("click", () => { hidePasswordModal(); }); $("btn-modal-confirm").addEventListener("click", async () => { const password = $("modal-password").value; if (!password) { showError("modal-password-error", "Please enter your password."); return; } const wallet = state.wallets[state.selectedWallet]; let decryptedSecret; hideError("modal-password-error"); try { decryptedSecret = await decryptWithPassword( wallet.encryptedSecret, password, ); } catch (e) { showError("modal-password-error", "Wrong password."); return; } hidePasswordModal(); const statusEl = $("confirm-status"); statusEl.textContent = "Sending..."; statusEl.classList.remove("hidden"); try { const signer = getSignerForAddress( wallet, state.selectedAddress, decryptedSecret, ); const provider = getProvider(state.rpcUrl); const connectedSigner = signer.connect(provider); const tx = await connectedSigner.sendTransaction({ to: pendingTx.to, value: parseEther(pendingTx.amount), }); statusEl.textContent = "Sent. Waiting for confirmation..."; const receipt = await tx.wait(); statusEl.textContent = "Confirmed in block " + receipt.blockNumber + ". Tx: " + receipt.hash; invalidateBalanceCache(); ctx.doRefreshAndRender(); } catch (e) { statusEl.textContent = "Failed: " + (e.shortMessage || e.message); } }); } module.exports = { init, show };