// 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 };