Split popup into one file per view
All checks were successful
check / check (push) Successful in 4s

popup/index.js reduced to ~75 lines: loads state, builds a
shared context object, initializes all views, shows first screen.

Each view in popup/views/:
  helpers.js      — $(), showError, hideError, showView
  welcome.js      — welcome screen
  addWallet.js    — unified create/import recovery phrase
  importKey.js    — private key import
  home.js         — wallet list, total value, address derivation
  addressDetail.js — address view, token list, QR, copy
  send.js         — send form, ENS resolution, tx broadcast
  receive.js      — QR + copy
  addToken.js     — token lookup, common token picker
  settings.js     — RPC endpoint
  approval.js     — dApp approval (stub)

Views communicate via a ctx object with shared callbacks
(renderWalletList, showAddressDetail, doRefreshAndRender, etc).
This commit is contained in:
2026-02-25 18:51:41 +07:00
parent f50a2a0389
commit 023d8441bc
12 changed files with 673 additions and 581 deletions

93
src/popup/views/send.js Normal file
View File

@@ -0,0 +1,93 @@
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");
function init(ctx) {
$("btn-send-confirm").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");
return;
}
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
showError("send-status", "Please enter a valid amount.");
$("send-status").classList.remove("hidden");
return;
}
let resolvedTo = to;
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);
return;
}
resolvedTo = resolved;
} catch (e) {
showError("send-status", "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);
}
});
$("btn-send-back").addEventListener("click", ctx.showAddressDetail);
}
module.exports = { init };