diff --git a/src/popup/index.html b/src/popup/index.html index d513b64..6145360 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -441,30 +441,57 @@ < Back

Confirm Transaction

-
-
From
+ + +
+
Type
+
+
+ + + + +
+
From
-
-
To
+
+
To
-
-
Amount
+
+
Amount
- +
+
Your balance
+
+
+
+
` + + `` + + `` + + `` + + ``; let pendingTx = null; let elapsedTimer = null; +function etherscanTokenLink(address) { + return `https://etherscan.io/token/${address}`; +} + function show(txInfo) { pendingTx = txInfo; + const isErc20 = txInfo.token !== "ETH"; + const symbol = isErc20 ? txInfo.tokenSymbol || "?" : "ETH"; + + // Transaction type + if (isErc20) { + $("confirm-type").textContent = + "ERC-20 token transfer (" + symbol + ")"; + } else { + $("confirm-type").textContent = "Native ETH transfer"; + } + + // Token contract section (ERC-20 only) + const tokenSection = $("confirm-token-section"); + if (isErc20) { + const link = etherscanTokenLink(txInfo.token); + $("confirm-token-contract").innerHTML = + escapeHtml(txInfo.token) + + ` ${EXT_ICON}`; + tokenSection.classList.remove("hidden"); + } else { + tokenSection.classList.add("hidden"); + } + + // From const fromTitle = addressTitle(txInfo.from, state.wallets); $("confirm-from").innerHTML = formatAddressHtml( txInfo.from, @@ -31,6 +75,8 @@ function show(txInfo) { null, fromTitle, ); + + // To const toTitle = addressTitle(txInfo.to, state.wallets); $("confirm-to").innerHTML = formatAddressHtml( txInfo.to, @@ -38,20 +84,44 @@ function show(txInfo) { null, toTitle, ); - - // Hide the separate ENS element — it's now inline in the address display $("confirm-to-ens").classList.add("hidden"); - $("confirm-amount").textContent = txInfo.amount + " " + txInfo.token; - + // Amount + $("confirm-amount").textContent = txInfo.amount + " " + symbol; const ethPrice = getPrice("ETH"); - if (txInfo.token === "ETH" && ethPrice) { + const tokenPrice = getPrice(symbol); + if (isErc20 && tokenPrice) { + const usd = parseFloat(txInfo.amount) * tokenPrice; + $("confirm-amount-usd").textContent = formatUsd(usd); + } else if (!isErc20 && ethPrice) { const usd = parseFloat(txInfo.amount) * ethPrice; $("confirm-amount-usd").textContent = formatUsd(usd); } else { $("confirm-amount-usd").textContent = ""; } + // Balance + if (isErc20) { + const bal = txInfo.tokenBalance || "0"; + $("confirm-balance").textContent = bal + " " + symbol; + if (tokenPrice) { + $("confirm-balance-usd").textContent = formatUsd( + parseFloat(bal) * tokenPrice, + ); + } else { + $("confirm-balance-usd").textContent = ""; + } + } else { + $("confirm-balance").textContent = (txInfo.balance || "0") + " ETH"; + if (ethPrice) { + $("confirm-balance-usd").textContent = formatUsd( + parseFloat(txInfo.balance || "0") * ethPrice, + ); + } else { + $("confirm-balance-usd").textContent = ""; + } + } + // Check for warnings const warnings = []; if (isScamAddress(txInfo.to)) { @@ -78,10 +148,24 @@ function show(txInfo) { // Check for errors const errors = []; - if ( - txInfo.token === "ETH" && - parseFloat(txInfo.amount) > parseFloat(txInfo.balance) - ) { + if (isErc20) { + const tokenBal = parseFloat(txInfo.tokenBalance || "0"); + if (parseFloat(txInfo.amount) > tokenBal) { + errors.push( + "Insufficient " + + symbol + + " balance. You have " + + txInfo.tokenBalance + + " " + + symbol + + " but are trying to send " + + txInfo.amount + + " " + + symbol + + ".", + ); + } + } else if (parseFloat(txInfo.amount) > parseFloat(txInfo.balance)) { errors.push( "Insufficient balance. You have " + txInfo.balance + @@ -106,9 +190,60 @@ function show(txInfo) { sendBtn.classList.remove("text-muted"); } - $("confirm-fee").classList.add("hidden"); + // Gas estimate — show placeholder then fetch async + $("confirm-fee").classList.remove("hidden"); + $("confirm-fee-amount").textContent = "Estimating..."; + $("confirm-fee-usd").textContent = ""; $("confirm-status").classList.add("hidden"); showView("confirm-tx"); + + estimateGas(txInfo); +} + +async function estimateGas(txInfo) { + try { + const provider = getProvider(state.rpcUrl); + const feeData = await provider.getFeeData(); + const gasPrice = feeData.gasPrice; + let gasLimit; + + if (txInfo.token === "ETH") { + gasLimit = await provider.estimateGas({ + from: txInfo.from, + to: txInfo.to, + value: parseEther(txInfo.amount), + }); + } else { + const contract = new Contract(txInfo.token, ERC20_ABI, provider); + const decimals = await contract.decimals(); + const amount = parseUnits(txInfo.amount, decimals); + gasLimit = await contract.transfer.estimateGas(txInfo.to, amount, { + from: txInfo.from, + }); + } + + const gasCostWei = gasLimit * gasPrice; + const gasCostEth = formatEther(gasCostWei); + // Format to 6 significant decimal places + const parts = gasCostEth.split("."); + const dec = + parts.length > 1 + ? parts[1].slice(0, 6).replace(/0+$/, "") || "0" + : "0"; + const feeStr = parts[0] + "." + dec + " ETH"; + $("confirm-fee-amount").textContent = feeStr; + + const ethPrice = getPrice("ETH"); + if (ethPrice) { + $("confirm-fee-usd").textContent = formatUsd( + parseFloat(gasCostEth) * ethPrice, + ); + } + } catch (e) { + log.errorf("gas estimation failed:", e.message); + $("confirm-fee-amount").textContent = "Unable to estimate"; + $("confirm-fee-usd").textContent = ""; + } } function showPasswordModal() { @@ -169,10 +304,23 @@ function init(ctx) { ); const provider = getProvider(state.rpcUrl); const connectedSigner = signer.connect(provider); - const tx = await connectedSigner.sendTransaction({ - to: pendingTx.to, - value: parseEther(pendingTx.amount), - }); + + let tx; + if (pendingTx.token === "ETH") { + tx = await connectedSigner.sendTransaction({ + to: pendingTx.to, + value: parseEther(pendingTx.amount), + }); + } else { + const contract = new Contract( + pendingTx.token, + ERC20_ABI, + connectedSigner, + ); + const decimals = await contract.decimals(); + const amount = parseUnits(pendingTx.amount, decimals); + tx = await contract.transfer(pendingTx.to, amount); + } // Disable send button immediately after broadcast const sendBtn = $("btn-confirm-send"); diff --git a/src/popup/views/send.js b/src/popup/views/send.js index efb4f5c..0749ceb 100644 --- a/src/popup/views/send.js +++ b/src/popup/views/send.js @@ -92,6 +92,16 @@ function init(_ctx) { const token = state.selectedToken || $("send-token").value; const addr = currentAddress(); + let tokenSymbol = null; + let tokenBalance = null; + if (token !== "ETH") { + const tb = (addr.tokenBalances || []).find( + (t) => t.address.toLowerCase() === token.toLowerCase(), + ); + tokenSymbol = tb ? tb.symbol : "?"; + tokenBalance = tb ? tb.balance || "0" : "0"; + } + ctx.showConfirmTx({ from: addr.address, to: resolvedTo, @@ -99,6 +109,8 @@ function init(_ctx) { amount: amount, token: token, balance: addr.balance, + tokenSymbol: tokenSymbol, + tokenBalance: tokenBalance, }); });