Compare commits
1 Commits
fix/cross-
...
feature/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f01a662000 |
@@ -97,26 +97,18 @@ async function importMnemonic(ctx) {
|
|||||||
const pw = validatePassword();
|
const pw = validatePassword();
|
||||||
if (!pw) return;
|
if (!pw) return;
|
||||||
const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic);
|
const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic);
|
||||||
const addrDup = state.wallets.find((w) =>
|
const duplicate = state.wallets.find(
|
||||||
w.addresses.some(
|
(w) =>
|
||||||
(a) => a.address.toLowerCase() === firstAddress.toLowerCase(),
|
w.type === "hd" &&
|
||||||
),
|
w.addresses[0] &&
|
||||||
|
w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
|
||||||
);
|
);
|
||||||
if (addrDup) {
|
if (duplicate) {
|
||||||
showFlash(
|
showFlash(
|
||||||
"An address from this phrase already exists in " +
|
"This recovery phrase is already added (" + duplicate.name + ").",
|
||||||
addrDup.name +
|
|
||||||
".",
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const xpubDup = state.wallets.find(
|
|
||||||
(w) => (w.type === "hd" || w.type === "xprv") && w.xpub === xpub,
|
|
||||||
);
|
|
||||||
if (xpubDup) {
|
|
||||||
showFlash("This recovery phrase matches " + xpubDup.name + ".");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const encrypted = await encryptWithPassword(mnemonic, pw);
|
const encrypted = await encryptWithPassword(mnemonic, pw);
|
||||||
const walletNum = state.wallets.length + 1;
|
const walletNum = state.wallets.length + 1;
|
||||||
const wallet = {
|
const wallet = {
|
||||||
@@ -170,11 +162,16 @@ async function importPrivateKey(ctx) {
|
|||||||
}
|
}
|
||||||
const pw = validatePassword();
|
const pw = validatePassword();
|
||||||
if (!pw) return;
|
if (!pw) return;
|
||||||
const duplicate = state.wallets.find((w) =>
|
const duplicate = state.wallets.find(
|
||||||
w.addresses.some((a) => a.address.toLowerCase() === addr.toLowerCase()),
|
(w) =>
|
||||||
|
w.type === "key" &&
|
||||||
|
w.addresses[0] &&
|
||||||
|
w.addresses[0].address.toLowerCase() === addr.toLowerCase(),
|
||||||
);
|
);
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
showFlash("This address already exists in " + duplicate.name + ".");
|
showFlash(
|
||||||
|
"This private key is already added (" + duplicate.name + ").",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const encrypted = await encryptWithPassword(key, pw);
|
const encrypted = await encryptWithPassword(key, pw);
|
||||||
@@ -211,22 +208,14 @@ async function importXprvKey(ctx) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { xpub, firstAddress } = result;
|
const { xpub, firstAddress } = result;
|
||||||
const addrDup = state.wallets.find((w) =>
|
const duplicate = state.wallets.find(
|
||||||
w.addresses.some(
|
(w) =>
|
||||||
(a) => a.address.toLowerCase() === firstAddress.toLowerCase(),
|
(w.type === "hd" || w.type === "xprv") &&
|
||||||
),
|
w.addresses[0] &&
|
||||||
|
w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
|
||||||
);
|
);
|
||||||
if (addrDup) {
|
if (duplicate) {
|
||||||
showFlash(
|
showFlash("This key is already added (" + duplicate.name + ").");
|
||||||
"An address from this key already exists in " + addrDup.name + ".",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const xpubDup = state.wallets.find(
|
|
||||||
(w) => (w.type === "hd" || w.type === "xprv") && w.xpub === xpub,
|
|
||||||
);
|
|
||||||
if (xpubDup) {
|
|
||||||
showFlash("This key matches " + xpubDup.name + ".");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pw = validatePassword();
|
const pw = validatePassword();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const { getSignerForAddress } = require("../../shared/wallet");
|
|||||||
const { decryptWithPassword } = require("../../shared/vault");
|
const { decryptWithPassword } = require("../../shared/vault");
|
||||||
const { formatUsd, getPrice } = require("../../shared/prices");
|
const { formatUsd, getPrice } = require("../../shared/prices");
|
||||||
const { getProvider } = require("../../shared/balances");
|
const { getProvider } = require("../../shared/balances");
|
||||||
const { isScamAddress } = require("../../shared/scamlist");
|
const { isScamAddress, isNullOrBurnAddress } = require("../../shared/scamlist");
|
||||||
const { ERC20_ABI } = require("../../shared/constants");
|
const { ERC20_ABI } = require("../../shared/constants");
|
||||||
const { log } = require("../../shared/log");
|
const { log } = require("../../shared/log");
|
||||||
const makeBlockie = require("ethereum-blockies-base64");
|
const makeBlockie = require("ethereum-blockies-base64");
|
||||||
@@ -38,6 +38,28 @@ const EXT_ICON =
|
|||||||
`</svg></span>`;
|
`</svg></span>`;
|
||||||
|
|
||||||
let pendingTx = null;
|
let pendingTx = null;
|
||||||
|
// Track active warnings so async checks can append without overwriting.
|
||||||
|
let activeWarnings = [];
|
||||||
|
|
||||||
|
function renderWarnings(el, warnings) {
|
||||||
|
activeWarnings = warnings.slice();
|
||||||
|
if (warnings.length > 0) {
|
||||||
|
el.innerHTML = warnings
|
||||||
|
.map(
|
||||||
|
(w) =>
|
||||||
|
`<div class="border border-border border-dashed p-2 mb-1 text-xs font-bold">WARNING: ${w}</div>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
el.classList.remove("hidden");
|
||||||
|
} else {
|
||||||
|
el.classList.add("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendWarning(el, message) {
|
||||||
|
activeWarnings.push(message);
|
||||||
|
renderWarnings(el, activeWarnings);
|
||||||
|
}
|
||||||
|
|
||||||
function restore() {
|
function restore() {
|
||||||
const d = state.viewData;
|
const d = state.viewData;
|
||||||
@@ -165,29 +187,24 @@ function show(txInfo) {
|
|||||||
$("confirm-balance").textContent = valueWithUsd(bal + " ETH", balUsd);
|
$("confirm-balance").textContent = valueWithUsd(bal + " ETH", balUsd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for warnings
|
// Check for warnings (synchronous checks first, async checks added later)
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
if (isScamAddress(txInfo.to)) {
|
if (isScamAddress(txInfo.to)) {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
"This address is on a known scam/fraud list. Do not send funds to this address.",
|
"This address is on a known scam/fraud list. Do not send funds to this address.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (isNullOrBurnAddress(txInfo.to)) {
|
||||||
|
warnings.push(
|
||||||
|
"This is a null or burn address. Funds sent here will be permanently lost.",
|
||||||
|
);
|
||||||
|
}
|
||||||
if (txInfo.to.toLowerCase() === txInfo.from.toLowerCase()) {
|
if (txInfo.to.toLowerCase() === txInfo.from.toLowerCase()) {
|
||||||
warnings.push("You are sending to your own address.");
|
warnings.push("You are sending to your own address.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const warningsEl = $("confirm-warnings");
|
const warningsEl = $("confirm-warnings");
|
||||||
if (warnings.length > 0) {
|
renderWarnings(warningsEl, warnings);
|
||||||
warningsEl.innerHTML = warnings
|
|
||||||
.map(
|
|
||||||
(w) =>
|
|
||||||
`<div class="border border-border border-dashed p-2 mb-1 text-xs font-bold">WARNING: ${w}</div>`,
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
warningsEl.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
warningsEl.classList.add("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for errors
|
// Check for errors
|
||||||
const errors = [];
|
const errors = [];
|
||||||
@@ -243,8 +260,11 @@ function show(txInfo) {
|
|||||||
state.viewData = { pendingTx: txInfo };
|
state.viewData = { pendingTx: txInfo };
|
||||||
showView("confirm-tx");
|
showView("confirm-tx");
|
||||||
|
|
||||||
// Reset recipient warning to hidden (space always reserved, no layout shift)
|
// Hide the legacy recipient warning element (warnings now unified)
|
||||||
$("confirm-recipient-warning").style.visibility = "hidden";
|
const legacyWarningEl = $("confirm-recipient-warning");
|
||||||
|
if (legacyWarningEl) {
|
||||||
|
legacyWarningEl.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
estimateGas(txInfo);
|
estimateGas(txInfo);
|
||||||
checkRecipientHistory(txInfo);
|
checkRecipientHistory(txInfo);
|
||||||
@@ -291,19 +311,24 @@ async function estimateGas(txInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkRecipientHistory(txInfo) {
|
async function checkRecipientHistory(txInfo) {
|
||||||
const el = $("confirm-recipient-warning");
|
const warningsEl = $("confirm-warnings");
|
||||||
try {
|
try {
|
||||||
const provider = getProvider(state.rpcUrl);
|
const provider = getProvider(state.rpcUrl);
|
||||||
// Skip warning for contract addresses — they may legitimately
|
|
||||||
// have zero outgoing transactions (getTransactionCount returns
|
|
||||||
// the nonce, i.e. sent-tx count only).
|
|
||||||
const code = await provider.getCode(txInfo.to);
|
const code = await provider.getCode(txInfo.to);
|
||||||
if (code && code !== "0x") {
|
if (code && code !== "0x") {
|
||||||
|
// Recipient is a contract address — warn the user
|
||||||
|
appendWarning(
|
||||||
|
warningsEl,
|
||||||
|
"The recipient is a contract address. Sending tokens directly to a contract may result in permanent loss of funds.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const txCount = await provider.getTransactionCount(txInfo.to);
|
const txCount = await provider.getTransactionCount(txInfo.to);
|
||||||
if (txCount === 0) {
|
if (txCount === 0) {
|
||||||
el.style.visibility = "visible";
|
appendWarning(
|
||||||
|
warningsEl,
|
||||||
|
"The recipient address has ZERO transaction history. This may indicate a fresh or unused address. Double-check the address before sending.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.errorf("recipient history check failed:", e.message);
|
log.errorf("recipient history check failed:", e.message);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user