2 Commits
Author SHA1 Message Date
sneak ef40190e9e fix: the confirmation screen reads its balance on the network it is drawn for (closes #558)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
ConfirmTx showed and checked the balance Send handed it, so a popup reopened onto a confirmation saved before a network switch stated the other network's balance and checked the amount against it.

Each time it is shown, ConfirmTx now reads the From address's ETH balance, and for a token send its token balance, from the node of the network it is drawn for, and gives that node to the fee estimate, which starts once the read is done. Until then the balance line says "Loading..." and Sign & Send stays disabled; a failed read is stated as unknown. Each message the read can bring has a box reserved from the first paint, so nothing moves when it returns.

Model: opus-5-5
2026-10-19 00:35:46 +00:00
clawbot e2e1c5d40e fix: the recipient warning says what it checked and skips addresses that have received ETH (closes #528)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
The transaction count the node reports is what an address has sent, so
an address that only receives, such as a savings address, got the "ZERO
transaction history" box on every send. The check now also asks the node
for the recipient's balance and warns only when both are zero. The box
says the address has never sent a transaction and holds no ETH, naming
the active network's native token as the contract warning does.

Model: opus-5-5
2026-10-19 02:34:13 +02:00
7 changed files with 86 additions and 26 deletions
+5 -3
View File
@@ -1022,7 +1022,7 @@ on the approval screen, the amount on the wait, success and error screens, the
transaction history and the transaction detail screen, and the refusal of a fee
above 1 ETH. A chain id that names no network reads `ETH`. Wherever this
document shows ETH as the label of a native balance, value or fee, in a "Native
ETH transfer" type line, in the contract-recipient warning or in that refusal,
ETH transfer" type line, in the two recipient warnings or in that refusal,
Sepolia shows `SepoliaETH`. The swap lines keep `ETH`, the router's own name for
the native currency, and the ETH/USD price line, shown on mainnet only, keeps
its fixed wording.
@@ -1740,8 +1740,10 @@ view would leave a wallet one click from deletion.
reserved either way
- Warnings: inline warnings from the local checks (scam address, self-send)
plus four reserved warning boxes made visible by the async checks —
recipient with no transaction history, recipient is a contract, burn
address, and an Etherscan phishing/scam label
recipient that has never sent a transaction and holds no ETH, recipient is
a contract, burn address, and an Etherscan phishing/scam label. An address
that has received ETH but never sent a transaction still holds that ETH,
so it gets no warning
- Errors (an invalid amount), plus five reserved error boxes, so the balance
and the fee arriving later move nothing — the amount exceeds the balance
(for a token whose scale is unknown it says the token's balance is
+6 -11
View File
@@ -634,17 +634,12 @@
</div>
</div>
<div id="confirm-warnings" class="mb-2 invisible"></div>
<div id="confirm-recipient-warning" class="mb-2 invisible">
<div
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
>
WARNING: The recipient address has ZERO transaction
history. This may indicate a fresh or unused address.
Double-check the address before sending.
</div>
</div>
<!-- Its sentence names the network's native token, so show()
in confirmTx.js sets it. -->
<!-- These two sentences name the network's native token, so
show() in confirmTx.js sets them. -->
<div
id="confirm-recipient-warning"
class="mb-2 border border-red-500 border-dashed p-2 text-xs font-bold text-red-500 invisible"
></div>
<div
id="confirm-contract-warning"
class="mb-2 border border-red-500 border-dashed p-2 text-xs font-bold text-red-500 invisible"
+8 -1
View File
@@ -243,7 +243,14 @@ function show(txInfo) {
nativeCurrencyByChainId(txInfo.chainId) +
" to this address and try again.";
// Shown later, once checkRecipientHistory() finds a contract.
// Both shown later, by checkRecipientHistory(). The first sentence says
// exactly what getFullWarnings() checks.
$("confirm-recipient-warning").textContent =
"WARNING: The recipient address has never sent a transaction and" +
" holds no " +
nativeCurrencyByChainId(txInfo.chainId) +
". This may indicate a fresh or unused address. Double-check the" +
" address before sending.";
$("confirm-contract-warning").textContent =
"WARNING: The recipient is a smart contract. Sending " +
nativeCurrencyByChainId(txInfo.chainId) +
+20 -10
View File
@@ -51,7 +51,8 @@ function getLocalWarnings(address, options = {}) {
/**
* Check an address against local lists AND via RPC queries.
* Async — performs network calls to check contract status and tx history.
* Async — performs network calls to check contract status, tx count and
* balance.
*
* @param {string} address - The target address to check.
* @param {object} provider - An ethers.js provider instance.
@@ -78,21 +79,30 @@ async function getFullWarnings(address, provider, options = {}) {
log.errorf("contract check failed:", e.shortMessage || e.message);
}
// Skip tx count check for contracts — they may legitimately have
// zero inbound EOA transactions.
// The transaction count is the number of transactions the address has
// sent, so an address that has only ever received has a count of zero.
// Native token it received stays in its balance until it sends a
// transaction, so the warning also needs a zero balance. A contract is
// skipped: it has its own warning above.
if (!isContract) {
try {
const txCount = await provider.getTransactionCount(address);
if (txCount === 0) {
warnings.push({
type: "new-address",
message:
"This address has never sent a transaction. Double-check it is correct.",
severity: "info",
});
const balance = await provider.getBalance(address);
if (balance === 0n) {
warnings.push({
type: "new-address",
message:
"This address has never sent a transaction and holds none of the network's native token. Double-check it is correct.",
severity: "info",
});
}
}
} catch (e) {
log.errorf("tx count check failed:", e.shortMessage || e.message);
log.errorf(
"tx count or balance check failed:",
e.shortMessage || e.message,
);
}
}
+39
View File
@@ -0,0 +1,39 @@
// The confirmation screen's warning for a recipient that has never sent a
// transaction (https://git.eeqj.de/sneak/AutistMask/issues/528).
//
// The node's transaction count for an address is the number of transactions
// it has sent, so an address that only ever receives, such as a savings
// address or an exchange deposit address, has a count of zero. The warning
// used to fire on that count alone. It now also needs a zero balance: ETH an
// address received stays in its balance until it sends a transaction.
"use strict";
const { getFullWarnings } = require("../src/shared/addressWarnings");
global.fetch = jest.fn(async () => {
throw new Error("tests must not perform network requests");
});
const RECIPIENT = "0x" + "b".repeat(40);
// The warning types getFullWarnings() returns for RECIPIENT when the node
// reports it as a regular address with `txCount` sent transactions and a
// balance of `balance` wei.
async function warningTypes(txCount, balance) {
const provider = {
getCode: async () => "0x",
getTransactionCount: async () => txCount,
getBalance: async () => balance,
};
const warnings = await getFullWarnings(RECIPIENT, provider);
return warnings.map((w) => w.type);
}
test("a recipient that has received 0.5 ETH and never sent a transaction gets no warning", async () => {
expect(await warningTypes(0, 500000000000000000n)).toEqual([]);
});
test("a recipient that has never sent a transaction and holds no ETH gets the warning", async () => {
expect(await warningTypes(0, 0n)).toEqual(["new-address"]);
});
+7
View File
@@ -271,6 +271,13 @@ describe.each([
);
});
test("the warning for a recipient that has never sent a transaction", async () => {
await confirmSend("0.1");
expect(text("confirm-recipient-warning")).toContain(
"has never sent a transaction and holds no " + symbol + ".",
);
});
// A token reports whatever symbol it likes. One reporting the label the
// wallet shows its native token under, on this network or any other, is
// a fake, exactly as one reporting `ETH` always was.
+1 -1
View File
@@ -74,7 +74,7 @@ test("ethers puts the URL in the error message, not in the short message", async
test("the recipient checks before a send", async () => {
await getFullWarnings(ADDRESS, getProvider(RPC_URL, "mainnet"));
expectFailureLoggedWithoutKey("contract check failed");
expectFailureLoggedWithoutKey("tx count check failed");
expectFailureLoggedWithoutKey("tx count or balance check failed");
});
test("the ENS reverse lookup", async () => {