Add dust transaction filter to catch native ETH poisoning
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
Address poisoning attacks also use real native ETH dust transfers (e.g. 1 gwei) from look-alike addresses. Token-level filters cannot catch these. Add a configurable dust threshold (default 100,000 gwei / 0.0001 ETH) that hides transactions below the threshold from history. The threshold is editable in Settings and the filter can be disabled entirely. Document the specific attack tx in the README.
This commit is contained in:
19
README.md
19
README.md
@@ -524,11 +524,22 @@ indexes it as a real token transfer.
|
||||
accidentally interacting with a spoofed token that appeared in their balance
|
||||
via a fake Transfer event.
|
||||
|
||||
- **Dust transaction filtering**: A second wave of the same attack used real
|
||||
native ETH transfers instead of fake tokens. Transaction
|
||||
`0x2708ebddfb9b5fa3f7a89d3ea398ef9fd8771b83ed861ecb7c21cd55d18edc74` sent 1
|
||||
gwei (0.000000001 ETH) from `0xC3c6B3b4402bD78A9582aB6b00E747769344F37E` —
|
||||
another look-alike of the legitimate recipient `0xC3c693...`. Because this is
|
||||
a real ETH transfer (not a fake token), none of the token-level filters catch
|
||||
it. AutistMask hides transactions below a configurable dust threshold
|
||||
(default: 100,000 gwei / 0.0001 ETH). This is high enough to filter poisoning
|
||||
dust while low enough to preserve any transfer a user would plausibly care
|
||||
about. The threshold is user-configurable in Settings.
|
||||
|
||||
- **User-configurable**: All of the above filters (known symbol verification,
|
||||
low-holder threshold, fraud contract blocklist) are settings that default to
|
||||
on but can be individually disabled by the user. AutistMask is designed as a
|
||||
sharp tool — users who understand the risks can configure the wallet to show
|
||||
everything unfiltered, unix-style.
|
||||
low-holder threshold, fraud contract blocklist, dust threshold) are settings
|
||||
that default to on but can be individually disabled by the user. AutistMask is
|
||||
designed as a sharp tool — users who understand the risks can configure the
|
||||
wallet to show everything unfiltered, unix-style.
|
||||
|
||||
### Non-Goals
|
||||
|
||||
|
||||
@@ -565,6 +565,22 @@
|
||||
/>
|
||||
Hide transactions from detected fraud contracts
|
||||
</label>
|
||||
<label
|
||||
class="text-xs flex items-center gap-1 cursor-pointer mt-1"
|
||||
>
|
||||
<input type="checkbox" id="settings-hide-dust" />
|
||||
Hide dust transactions below
|
||||
</label>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
<input
|
||||
type="number"
|
||||
id="settings-dust-threshold"
|
||||
class="border border-border p-1 text-xs bg-bg text-fg"
|
||||
style="width: 10ch"
|
||||
min="0"
|
||||
/>
|
||||
<span class="text-xs text-muted">gwei</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-well p-3 mx-1 mb-3">
|
||||
|
||||
@@ -94,6 +94,8 @@ async function loadTransactions(address) {
|
||||
const result = filterTransactions(rawTxs, {
|
||||
hideLowHolderTokens: state.hideLowHolderTokens,
|
||||
hideFraudContracts: state.hideFraudContracts,
|
||||
hideDustTransactions: state.hideDustTransactions,
|
||||
dustThresholdGwei: state.dustThresholdGwei,
|
||||
fraudContracts: state.fraudContracts,
|
||||
});
|
||||
const txs = result.transactions;
|
||||
|
||||
@@ -125,6 +125,21 @@ function init(ctx) {
|
||||
await saveState();
|
||||
});
|
||||
|
||||
$("settings-hide-dust").checked = state.hideDustTransactions;
|
||||
$("settings-hide-dust").addEventListener("change", async () => {
|
||||
state.hideDustTransactions = $("settings-hide-dust").checked;
|
||||
await saveState();
|
||||
});
|
||||
|
||||
$("settings-dust-threshold").value = state.dustThresholdGwei;
|
||||
$("settings-dust-threshold").addEventListener("change", async () => {
|
||||
const val = parseInt($("settings-dust-threshold").value, 10);
|
||||
if (!isNaN(val) && val >= 0) {
|
||||
state.dustThresholdGwei = val;
|
||||
await saveState();
|
||||
}
|
||||
});
|
||||
|
||||
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
|
||||
|
||||
$("btn-settings-back").addEventListener("click", () => {
|
||||
|
||||
@@ -20,6 +20,8 @@ const DEFAULT_STATE = {
|
||||
rememberSiteChoice: true,
|
||||
hideLowHolderTokens: true,
|
||||
hideFraudContracts: true,
|
||||
hideDustTransactions: true,
|
||||
dustThresholdGwei: 100000,
|
||||
fraudContracts: [],
|
||||
tokenHolderCache: {},
|
||||
};
|
||||
@@ -44,6 +46,8 @@ async function saveState() {
|
||||
rememberSiteChoice: state.rememberSiteChoice,
|
||||
hideLowHolderTokens: state.hideLowHolderTokens,
|
||||
hideFraudContracts: state.hideFraudContracts,
|
||||
hideDustTransactions: state.hideDustTransactions,
|
||||
dustThresholdGwei: state.dustThresholdGwei,
|
||||
fraudContracts: state.fraudContracts,
|
||||
tokenHolderCache: state.tokenHolderCache,
|
||||
};
|
||||
@@ -82,6 +86,14 @@ async function loadState() {
|
||||
saved.hideFraudContracts !== undefined
|
||||
? saved.hideFraudContracts
|
||||
: true;
|
||||
state.hideDustTransactions =
|
||||
saved.hideDustTransactions !== undefined
|
||||
? saved.hideDustTransactions
|
||||
: true;
|
||||
state.dustThresholdGwei =
|
||||
saved.dustThresholdGwei !== undefined
|
||||
? saved.dustThresholdGwei
|
||||
: 100000;
|
||||
state.fraudContracts = saved.fraudContracts || [];
|
||||
state.tokenHolderCache = saved.tokenHolderCache || {};
|
||||
}
|
||||
|
||||
@@ -20,13 +20,15 @@ function formatTxValue(val) {
|
||||
function parseTx(tx, addrLower) {
|
||||
const from = tx.from?.hash || "";
|
||||
const to = tx.to?.hash || "";
|
||||
const rawWei = tx.value || "0";
|
||||
return {
|
||||
hash: tx.hash,
|
||||
blockNumber: tx.block_number,
|
||||
timestamp: Math.floor(new Date(tx.timestamp).getTime() / 1000),
|
||||
from: from,
|
||||
to: to,
|
||||
value: formatTxValue(formatEther(tx.value || "0")),
|
||||
value: formatTxValue(formatEther(rawWei)),
|
||||
valueGwei: Math.floor(Number(BigInt(rawWei) / BigInt(1000000000))),
|
||||
symbol: "ETH",
|
||||
direction: from.toLowerCase() === addrLower ? "sent" : "received",
|
||||
isError: tx.status !== "ok",
|
||||
@@ -47,6 +49,7 @@ function parseTokenTransfer(tt, addrLower) {
|
||||
from: from,
|
||||
to: to,
|
||||
value: formatTxValue(formatUnits(rawValue, decimals)),
|
||||
valueGwei: null,
|
||||
symbol: tt.token?.symbol || "?",
|
||||
direction: from.toLowerCase() === addrLower ? "sent" : "received",
|
||||
isError: false,
|
||||
@@ -160,6 +163,15 @@ function filterTransactions(txs, filters = {}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter dust transactions (below gwei threshold) if setting is on
|
||||
if (
|
||||
filters.hideDustTransactions &&
|
||||
tx.valueGwei !== null &&
|
||||
tx.valueGwei < (filters.dustThresholdGwei || 100000)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filtered.push(tx);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user