Add transaction confirmation screen and password modal
All checks were successful
check / check (push) Successful in 13s

New send flow: Send → Confirm → Password → Broadcast.

Send view: collects To (with ENS resolution), Amount, Token.
"Review" button advances to confirmation. No password field.

Confirm Transaction view: shows From, To (with ENS name),
Amount (with USD value), and runs pre-send checks:
- Scam address warning (checked against local blocklist)
- Self-send warning
- Insufficient balance error (disables Send button)

Password modal: full-screen overlay, appears only after user
clicks Send on the confirmation screen. Decrypts the wallet
secret, signs and broadcasts the transaction. Wrong password
is caught inline.

scamlist.js: hardcoded set of known scam/fraud addresses
(Tornado Cash sanctioned, drainer contracts, address
poisoning). Checked locally, no external API.
This commit is contained in:
2026-02-25 18:55:42 +07:00
parent 023d8441bc
commit 2b2137716c
6 changed files with 349 additions and 83 deletions

View File

@@ -1,90 +1,53 @@
const { parseEther } = require("ethers");
const { $, showError } = require("./helpers");
const { state } = require("../../shared/state");
const { getSignerForAddress } = require("../../shared/wallet");
const { decryptWithPassword } = require("../../shared/vault");
const {
getProvider,
invalidateBalanceCache,
} = require("../../shared/balances");
// Send view: collect To, Amount, Token. Then go to confirmation.
const { $, showError, hideError } = require("./helpers");
const { state, currentAddress } = require("../../shared/state");
const { getProvider } = require("../../shared/balances");
function init(ctx) {
$("btn-send-confirm").addEventListener("click", async () => {
$("btn-send-review").addEventListener("click", async () => {
const to = $("send-to").value.trim();
const amount = $("send-amount").value.trim();
if (!to) {
showError("send-status", "Please enter a recipient address.");
$("send-status").classList.remove("hidden");
showError("send-error", "Please enter a recipient address.");
return;
}
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
showError("send-status", "Please enter a valid amount.");
$("send-status").classList.remove("hidden");
showError("send-error", "Please enter a valid amount.");
return;
}
hideError("send-error");
// Resolve ENS if needed
let resolvedTo = to;
let ensName = null;
if (to.includes(".") && !to.startsWith("0x")) {
const statusEl = $("send-status");
statusEl.textContent = "Resolving " + to + "...";
statusEl.classList.remove("hidden");
try {
const provider = getProvider(state.rpcUrl);
const resolved = await provider.resolveName(to);
if (!resolved) {
showError("send-status", "Could not resolve " + to);
showError("send-error", "Could not resolve " + to);
return;
}
resolvedTo = resolved;
ensName = to;
} catch (e) {
showError("send-status", "Failed to resolve ENS name.");
showError("send-error", "Failed to resolve ENS name.");
return;
}
}
const password = $("send-password").value;
if (!password) {
showError("send-status", "Please enter your password.");
$("send-status").classList.remove("hidden");
return;
}
const wallet = state.wallets[state.selectedWallet];
let decryptedSecret;
const statusEl = $("send-status");
statusEl.textContent = "Decrypting...";
statusEl.classList.remove("hidden");
try {
decryptedSecret = await decryptWithPassword(
wallet.encryptedSecret,
password,
);
} catch (e) {
showError("send-status", "Wrong password.");
return;
}
statusEl.textContent = "Sending...";
try {
const signer = getSignerForAddress(
wallet,
state.selectedAddress,
decryptedSecret,
);
const provider = getProvider(state.rpcUrl);
const connectedSigner = signer.connect(provider);
const tx = await connectedSigner.sendTransaction({
to: resolvedTo,
value: parseEther(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);
}
const token = $("send-token").value;
const addr = currentAddress();
ctx.showConfirmTx({
from: addr.address,
to: resolvedTo,
ensName: ensName,
amount: amount,
token: token,
balance: addr.balance,
});
});
$("btn-send-back").addEventListener("click", ctx.showAddressDetail);