Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f47d17a98f |
@@ -846,6 +846,14 @@ wherever shown. If a formatting rule applies in one place, it applies in every
|
|||||||
place. Users should never see the same value rendered differently on two
|
place. Users should never see the same value rendered differently on two
|
||||||
screens.
|
screens.
|
||||||
|
|
||||||
|
The native token's label is the active network's `nativeCurrency` in
|
||||||
|
`src/shared/networks.js`: `ETH` on mainnet, `SepoliaETH` on Sepolia. Wherever
|
||||||
|
this document shows ETH as the label of a native balance, value or fee, or in a
|
||||||
|
"Native ETH transfer" type line, Sepolia shows `SepoliaETH`. The swap lines keep
|
||||||
|
`ETH`, the router's own name for the native currency, and the ETH/USD price
|
||||||
|
line, the contract-recipient warning and the refusal of a fee above 1 ETH keep
|
||||||
|
their fixed wording.
|
||||||
|
|
||||||
**Specific Exception — Truncation:** On some non-critical display locations, we
|
**Specific Exception — Truncation:** On some non-critical display locations, we
|
||||||
may truncate _a small number_ of characters from the middle of an address solely
|
may truncate _a small number_ of characters from the middle of an address solely
|
||||||
due to display size constraints. Wherever possible, and, notably, **in all
|
due to display size constraints. Wherever possible, and, notably, **in all
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-04: The native token's label follows the active network
|
||||||
|
([#372](https://git.eeqj.de/sneak/AutistMask/issues/372)). `networks.js` gives
|
||||||
|
each network a `nativeCurrency` and nothing read it: every screen wrote `ETH`,
|
||||||
|
so on Sepolia the balance, the value and the fee all read `ETH`. The balance
|
||||||
|
lists, Send, confirmation, approval, wait, success and error screens, and the
|
||||||
|
transaction history now read `nativeCurrency`, which is `ETH` on mainnet and
|
||||||
|
`SepoliaETH` on Sepolia.
|
||||||
- 2026-10-04: A swap whose deadline is later than a JavaScript date can hold is
|
- 2026-10-04: A swap whose deadline is later than a JavaScript date can hold is
|
||||||
decoded ([#437](https://git.eeqj.de/sneak/AutistMask/issues/437)). A date
|
decoded ([#437](https://git.eeqj.de/sneak/AutistMask/issues/437)). A date
|
||||||
reaches only to 275760-09-13, so a later deadline, such as the `uint256`
|
reaches only to 275760-09-13, so a later deadline, such as the `uint256`
|
||||||
|
|||||||
@@ -690,14 +690,13 @@
|
|||||||
Your balance does not cover this amount plus the network
|
Your balance does not cover this amount plus the network
|
||||||
fee. Please go back and send a smaller amount.
|
fee. Please go back and send a smaller amount.
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Its sentence names the network's native token, so show()
|
||||||
|
in confirmTx.js sets it. -->
|
||||||
<div
|
<div
|
||||||
id="confirm-gas-error"
|
id="confirm-gas-error"
|
||||||
class="mb-2 border border-border border-dashed p-2 text-xs"
|
class="mb-2 border border-border border-dashed p-2 text-xs"
|
||||||
style="visibility: hidden"
|
style="visibility: hidden"
|
||||||
>
|
></div>
|
||||||
You do not have enough ETH to pay the network fee for this
|
|
||||||
transfer. Please add ETH to this address and try again.
|
|
||||||
</div>
|
|
||||||
<!-- Its sentence names why the fee could not be estimated,
|
<!-- Its sentence names why the fee could not be estimated,
|
||||||
so show() in confirmTx.js sets it. -->
|
so show() in confirmTx.js sets it. -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const {
|
|||||||
addressTitle,
|
addressTitle,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
renderAddressHtml,
|
renderAddressHtml,
|
||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
goBack,
|
goBack,
|
||||||
@@ -153,6 +154,7 @@ async function loadTransactions(address) {
|
|||||||
const rawTxs = await fetchRecentTransactions(
|
const rawTxs = await fetchRecentTransactions(
|
||||||
address,
|
address,
|
||||||
state.blockscoutUrl,
|
state.blockscoutUrl,
|
||||||
|
nativeCurrency(),
|
||||||
);
|
);
|
||||||
const result = filterTransactions(rawTxs, {
|
const result = filterTransactions(rawTxs, {
|
||||||
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const {
|
|||||||
addressTitle,
|
addressTitle,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
balanceLine,
|
balanceLine,
|
||||||
unknownableAmount,
|
unknownableAmount,
|
||||||
renderAddressHtml,
|
renderAddressHtml,
|
||||||
@@ -106,7 +107,7 @@ function show() {
|
|||||||
let symbol, amount, price;
|
let symbol, amount, price;
|
||||||
const knownToken = TOKEN_BY_ADDRESS.get(tokenId.toLowerCase());
|
const knownToken = TOKEN_BY_ADDRESS.get(tokenId.toLowerCase());
|
||||||
if (tokenId === "ETH") {
|
if (tokenId === "ETH") {
|
||||||
symbol = "ETH";
|
symbol = nativeCurrency();
|
||||||
amount = parseFloat(addr.balance || "0");
|
amount = parseFloat(addr.balance || "0");
|
||||||
price = getPrice("ETH");
|
price = getPrice("ETH");
|
||||||
} else {
|
} else {
|
||||||
@@ -226,6 +227,7 @@ async function loadTransactions(address, tokenId) {
|
|||||||
const rawTxs = await fetchRecentTransactions(
|
const rawTxs = await fetchRecentTransactions(
|
||||||
address,
|
address,
|
||||||
state.blockscoutUrl,
|
state.blockscoutUrl,
|
||||||
|
nativeCurrency(),
|
||||||
);
|
);
|
||||||
const result = filterTransactions(rawTxs, {
|
const result = filterTransactions(rawTxs, {
|
||||||
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const {
|
|||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
onViewLeave,
|
onViewLeave,
|
||||||
formatFee,
|
formatFee,
|
||||||
|
nativeCurrency,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, saveState } = require("../../shared/state");
|
const { state, saveState } = require("../../shared/state");
|
||||||
const { networkByChainId } = require("../../shared/networks");
|
const { networkByChainId } = require("../../shared/networks");
|
||||||
@@ -330,7 +331,10 @@ function showTxApproval(details) {
|
|||||||
const ethUsd = ethPrice ? parseFloat(ethValueFormatted) * ethPrice : null;
|
const ethUsd = ethPrice ? parseFloat(ethValueFormatted) * ethPrice : null;
|
||||||
const usdStr = formatUsd(ethUsd);
|
const usdStr = formatUsd(ethUsd);
|
||||||
$("approve-tx-value").textContent =
|
$("approve-tx-value").textContent =
|
||||||
ethValueFormatted + " ETH" + (usdStr ? " (" + usdStr + ")" : "");
|
ethValueFormatted +
|
||||||
|
" " +
|
||||||
|
nativeCurrency() +
|
||||||
|
(usdStr ? " (" + usdStr + ")" : "");
|
||||||
|
|
||||||
showTxFee(approvedTx);
|
showTxFee(approvedTx);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const {
|
|||||||
addressTitle,
|
addressTitle,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
renderAddressHtml,
|
renderAddressHtml,
|
||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
goBack,
|
goBack,
|
||||||
@@ -90,7 +91,7 @@ function show(txInfo) {
|
|||||||
// The raw symbol is the price-table key; the capped one is what the
|
// The raw symbol is the price-table key; the capped one is what the
|
||||||
// screen says. Truncating before the lookup would silently drop the
|
// screen says. Truncating before the lookup would silently drop the
|
||||||
// price of any token whose symbol is long enough to be capped.
|
// price of any token whose symbol is long enough to be capped.
|
||||||
const rawSymbol = isErc20 ? txInfo.tokenSymbol || "?" : "ETH";
|
const rawSymbol = isErc20 ? txInfo.tokenSymbol || "?" : nativeCurrency();
|
||||||
const symbol = displaySymbol(rawSymbol);
|
const symbol = displaySymbol(rawSymbol);
|
||||||
|
|
||||||
// Transaction type
|
// Transaction type
|
||||||
@@ -98,7 +99,7 @@ function show(txInfo) {
|
|||||||
$("confirm-type").textContent =
|
$("confirm-type").textContent =
|
||||||
"ERC-20 token transfer (" + symbol + ")";
|
"ERC-20 token transfer (" + symbol + ")";
|
||||||
} else {
|
} else {
|
||||||
$("confirm-type").textContent = "Native ETH transfer";
|
$("confirm-type").textContent = "Native " + symbol + " transfer";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token contract section (ERC-20 only)
|
// Token contract section (ERC-20 only)
|
||||||
@@ -162,7 +163,7 @@ function show(txInfo) {
|
|||||||
const bal = txInfo.balance || "0";
|
const bal = txInfo.balance || "0";
|
||||||
const balUsd = ethPrice ? parseFloat(bal) * ethPrice : null;
|
const balUsd = ethPrice ? parseFloat(bal) * ethPrice : null;
|
||||||
$("confirm-balance").textContent = valueWithUsd(
|
$("confirm-balance").textContent = valueWithUsd(
|
||||||
truncateAmountNeverZero(bal) + " ETH",
|
truncateAmountNeverZero(bal) + " " + symbol,
|
||||||
balUsd,
|
balUsd,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -197,6 +198,12 @@ function show(txInfo) {
|
|||||||
// estimate landing later never moves anything.
|
// estimate landing later never moves anything.
|
||||||
$("confirm-amount-fee-error").classList.toggle("hidden", isErc20);
|
$("confirm-amount-fee-error").classList.toggle("hidden", isErc20);
|
||||||
$("confirm-gas-error").classList.toggle("hidden", !isErc20);
|
$("confirm-gas-error").classList.toggle("hidden", !isErc20);
|
||||||
|
$("confirm-gas-error").textContent =
|
||||||
|
"You do not have enough " +
|
||||||
|
nativeCurrency() +
|
||||||
|
" to pay the network fee for this transfer. Please add " +
|
||||||
|
nativeCurrency() +
|
||||||
|
" to this address and try again.";
|
||||||
|
|
||||||
// The fee-unknown message names its cause, which is also known here.
|
// The fee-unknown message names its cause, which is also known here.
|
||||||
// Without the token's scale estimateGas() cannot encode the transfer, so
|
// Without the token's scale estimateGas() cannot encode the transfer, so
|
||||||
@@ -245,7 +252,9 @@ function show(txInfo) {
|
|||||||
// touches already occupies its space, so re-running it never moves anything.
|
// touches already occupies its space, so re-running it never moves anything.
|
||||||
function renderValidation(txInfo) {
|
function renderValidation(txInfo) {
|
||||||
const isErc20 = txInfo.token !== "ETH";
|
const isErc20 = txInfo.token !== "ETH";
|
||||||
const symbol = isErc20 ? displaySymbol(txInfo.tokenSymbol || "?") : "ETH";
|
const symbol = isErc20
|
||||||
|
? displaySymbol(txInfo.tokenSymbol || "?")
|
||||||
|
: nativeCurrency();
|
||||||
|
|
||||||
const { canSend, codes } = validateTransfer({
|
const { canSend, codes } = validateTransfer({
|
||||||
isErc20,
|
isErc20,
|
||||||
@@ -258,7 +267,7 @@ function renderValidation(txInfo) {
|
|||||||
|
|
||||||
// Messages carrying the user's own numbers are built here; the fixed
|
// Messages carrying the user's own numbers are built here; the fixed
|
||||||
// sentences live in the reserved elements in index.html, except the
|
// sentences live in the reserved elements in index.html, except the
|
||||||
// fee-unknown one, which show() sets.
|
// gas and fee-unknown ones, which show() sets.
|
||||||
const messages = [];
|
const messages = [];
|
||||||
if (codes.includes(CODES.AMOUNT_INVALID)) {
|
if (codes.includes(CODES.AMOUNT_INVALID)) {
|
||||||
messages.push("Please enter a valid amount to send.");
|
messages.push("Please enter a valid amount to send.");
|
||||||
@@ -287,9 +296,13 @@ function renderValidation(txInfo) {
|
|||||||
messages.push(
|
messages.push(
|
||||||
"Insufficient balance. You have " +
|
"Insufficient balance. You have " +
|
||||||
truncateAmountNeverZero(txInfo.balance || "0") +
|
truncateAmountNeverZero(txInfo.balance || "0") +
|
||||||
" ETH but are trying to send " +
|
" " +
|
||||||
|
symbol +
|
||||||
|
" but are trying to send " +
|
||||||
txInfo.amount +
|
txInfo.amount +
|
||||||
" ETH.",
|
" " +
|
||||||
|
symbol +
|
||||||
|
".",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +395,9 @@ async function estimateGas(txInfo) {
|
|||||||
$("confirm-fee-reserve").textContent =
|
$("confirm-fee-reserve").textContent =
|
||||||
"up to " +
|
"up to " +
|
||||||
truncateAmountNeverZero(formatEther(gasCostWei)) +
|
truncateAmountNeverZero(formatEther(gasCostWei)) +
|
||||||
" ETH reserved";
|
" " +
|
||||||
|
nativeCurrency() +
|
||||||
|
" reserved";
|
||||||
setVisible("confirm-fee-reserve", true);
|
setVisible("confirm-fee-reserve", true);
|
||||||
} else {
|
} else {
|
||||||
// No spread to report: either there is no estimate, or the node
|
// No spread to report: either there is no estimate, or the node
|
||||||
|
|||||||
@@ -252,6 +252,14 @@ function unknownableAmount(balance) {
|
|||||||
return Number.isFinite(n) ? n : null;
|
return Number.isFinite(n) ? n : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The active network's native token symbol, `ETH` on mainnet and `SepoliaETH`
|
||||||
|
// on Sepolia, as src/shared/networks.js names it. Every screen labels a native
|
||||||
|
// amount with this. The "ETH" that state.selectedToken and txInfo.token hold
|
||||||
|
// is the native token's id, not its label, and stays "ETH" on every network.
|
||||||
|
function nativeCurrency() {
|
||||||
|
return currentNetwork().nativeCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
// A network fee in wei as the confirmation and approval screens both show it:
|
// A network fee in wei as the confirmation and approval screens both show it:
|
||||||
// the ETH figure through truncateAmountNeverZero(), then its USD value when the
|
// the ETH figure through truncateAmountNeverZero(), then its USD value when the
|
||||||
// ETH price is known. The USD value is of the exact fee, not of the truncated
|
// ETH price is known. The USD value is of the exact fee, not of the truncated
|
||||||
@@ -261,7 +269,10 @@ function formatFee(wei) {
|
|||||||
const ethPrice = getPrice("ETH");
|
const ethPrice = getPrice("ETH");
|
||||||
const usd = ethPrice ? formatUsd(parseFloat(eth) * ethPrice) : "";
|
const usd = ethPrice ? formatUsd(parseFloat(eth) * ethPrice) : "";
|
||||||
return (
|
return (
|
||||||
truncateAmountNeverZero(eth) + " ETH" + (usd ? " (" + usd + ")" : "")
|
truncateAmountNeverZero(eth) +
|
||||||
|
" " +
|
||||||
|
nativeCurrency() +
|
||||||
|
(usd ? " (" + usd + ")" : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +314,7 @@ function balanceLine(symbol, amount, price, tokenId) {
|
|||||||
|
|
||||||
function balanceLinesForAddress(addr, trackedTokens, showZero) {
|
function balanceLinesForAddress(addr, trackedTokens, showZero) {
|
||||||
let html = balanceLine(
|
let html = balanceLine(
|
||||||
"ETH",
|
nativeCurrency(),
|
||||||
parseFloat(addr.balance || "0"),
|
parseFloat(addr.balance || "0"),
|
||||||
getPrice("ETH"),
|
getPrice("ETH"),
|
||||||
"ETH",
|
"ETH",
|
||||||
@@ -660,6 +671,7 @@ module.exports = {
|
|||||||
balanceLinesForAddress,
|
balanceLinesForAddress,
|
||||||
addressHoldsFunds,
|
addressHoldsFunds,
|
||||||
unknownableAmount,
|
unknownableAmount,
|
||||||
|
nativeCurrency,
|
||||||
formatFee,
|
formatFee,
|
||||||
addressColor,
|
addressColor,
|
||||||
addressDotHtml,
|
addressDotHtml,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const {
|
|||||||
addressTitle,
|
addressTitle,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
renderAddressHtml,
|
renderAddressHtml,
|
||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
pushCurrentView,
|
pushCurrentView,
|
||||||
@@ -68,7 +69,7 @@ function renderTotalValue() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ethBal = parseFloat(addr.balance || "0");
|
const ethBal = parseFloat(addr.balance || "0");
|
||||||
const ethStr = ethBal.toFixed(4) + " ETH";
|
const ethStr = ethBal.toFixed(4) + " " + nativeCurrency();
|
||||||
const ethUsd = ethPrice ? " (" + formatUsd(ethBal * ethPrice) + ")" : "";
|
const ethUsd = ethPrice ? " (" + formatUsd(ethBal * ethPrice) + ")" : "";
|
||||||
el.textContent = ethStr + ethUsd;
|
el.textContent = ethStr + ethUsd;
|
||||||
|
|
||||||
@@ -182,7 +183,11 @@ async function loadHomeTxs(ctx) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const fetches = allAddresses.map((addr) =>
|
const fetches = allAddresses.map((addr) =>
|
||||||
fetchRecentTransactions(addr, state.blockscoutUrl),
|
fetchRecentTransactions(
|
||||||
|
addr,
|
||||||
|
state.blockscoutUrl,
|
||||||
|
nativeCurrency(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const results = await Promise.all(fetches);
|
const results = await Promise.all(fetches);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const {
|
|||||||
showFlash,
|
showFlash,
|
||||||
addressTitle,
|
addressTitle,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
escapeHtml,
|
||||||
|
nativeCurrency,
|
||||||
renderAddressHtml,
|
renderAddressHtml,
|
||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
goBack,
|
goBack,
|
||||||
@@ -124,7 +126,7 @@ function updateToValidation() {
|
|||||||
|
|
||||||
function renderSendTokenSelect(addr) {
|
function renderSendTokenSelect(addr) {
|
||||||
const sel = $("send-token");
|
const sel = $("send-token");
|
||||||
sel.innerHTML = '<option value="ETH">ETH</option>';
|
sel.innerHTML = `<option value="ETH">${escapeHtml(nativeCurrency())}</option>`;
|
||||||
const fraudSet = new Set(
|
const fraudSet = new Set(
|
||||||
(state.fraudContracts || []).map((a) => a.toLowerCase()),
|
(state.fraudContracts || []).map((a) => a.toLowerCase()),
|
||||||
);
|
);
|
||||||
@@ -204,7 +206,8 @@ function updateSendBalance() {
|
|||||||
$("send-balance").textContent =
|
$("send-balance").textContent =
|
||||||
"Current balance: " +
|
"Current balance: " +
|
||||||
truncateAmountNeverZero(addr.balance || "0") +
|
truncateAmountNeverZero(addr.balance || "0") +
|
||||||
" ETH";
|
" " +
|
||||||
|
nativeCurrency();
|
||||||
} else {
|
} else {
|
||||||
const symbol = resolveSymbol(
|
const symbol = resolveSymbol(
|
||||||
token,
|
token,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const {
|
|||||||
etherscanLinkHtml,
|
etherscanLinkHtml,
|
||||||
explorerUrl,
|
explorerUrl,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
goBack,
|
goBack,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state } = require("../../shared/state");
|
const { state } = require("../../shared/state");
|
||||||
@@ -41,8 +42,10 @@ function getTransactionType(tx) {
|
|||||||
return "Token Approval";
|
return "Token Approval";
|
||||||
return "Contract Call";
|
return "Contract Call";
|
||||||
}
|
}
|
||||||
if (tx.symbol && tx.symbol !== "ETH") return "ERC-20 Token Transfer";
|
if (tx.symbol && tx.symbol !== nativeCurrency()) {
|
||||||
return "Native ETH Transfer";
|
return "ERC-20 Token Transfer";
|
||||||
|
}
|
||||||
|
return "Native " + nativeCurrency() + " Transfer";
|
||||||
}
|
}
|
||||||
|
|
||||||
function blockieHtml(address) {
|
function blockieHtml(address) {
|
||||||
@@ -227,7 +230,7 @@ function populateOnChainDetails(txData) {
|
|||||||
showDetailField(
|
showDetailField(
|
||||||
"tx-detail-fee-section",
|
"tx-detail-fee-section",
|
||||||
"tx-detail-fee",
|
"tx-detail-fee",
|
||||||
feeEth + " ETH",
|
feeEth + " " + nativeCurrency(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const {
|
|||||||
etherscanLinkHtml,
|
etherscanLinkHtml,
|
||||||
explorerUrl,
|
explorerUrl,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
clearViewStack,
|
clearViewStack,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { resolveTokenSymbol } = require("../../shared/approvalAmount");
|
const { resolveTokenSymbol } = require("../../shared/approvalAmount");
|
||||||
@@ -88,7 +89,7 @@ function startWait(txInfo, txHash, broadcastTime, pollNow) {
|
|||||||
|
|
||||||
const symbol =
|
const symbol =
|
||||||
txInfo.token === "ETH"
|
txInfo.token === "ETH"
|
||||||
? "ETH"
|
? nativeCurrency()
|
||||||
: displaySymbol(txInfo.tokenSymbol || "?");
|
: displaySymbol(txInfo.tokenSymbol || "?");
|
||||||
$("wait-tx-summary").textContent = txInfo.amount + " " + symbol;
|
$("wait-tx-summary").textContent = txInfo.amount + " " + symbol;
|
||||||
$("wait-tx-to").innerHTML = toAddressHtml(txInfo.to);
|
$("wait-tx-to").innerHTML = toAddressHtml(txInfo.to);
|
||||||
@@ -223,7 +224,7 @@ function showSuccess(txInfo, txHash, blockNumber) {
|
|||||||
|
|
||||||
const symbol =
|
const symbol =
|
||||||
txInfo.token === "ETH"
|
txInfo.token === "ETH"
|
||||||
? "ETH"
|
? nativeCurrency()
|
||||||
: displaySymbol(txInfo.tokenSymbol || "?");
|
: displaySymbol(txInfo.tokenSymbol || "?");
|
||||||
state.viewData = {
|
state.viewData = {
|
||||||
amount: txInfo.amount,
|
amount: txInfo.amount,
|
||||||
@@ -320,7 +321,7 @@ function showError(txInfo, txHash, message) {
|
|||||||
|
|
||||||
const symbol =
|
const symbol =
|
||||||
txInfo.token === "ETH"
|
txInfo.token === "ETH"
|
||||||
? "ETH"
|
? nativeCurrency()
|
||||||
: displaySymbol(txInfo.tokenSymbol || "?");
|
: displaySymbol(txInfo.tokenSymbol || "?");
|
||||||
state.viewData = {
|
state.viewData = {
|
||||||
amount: txInfo.amount,
|
amount: txInfo.amount,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function normalizeAddress(addr) {
|
|||||||
return (addr || "").toLowerCase();
|
return (addr || "").toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTx(tx, addrLower) {
|
function parseTx(tx, addrLower, nativeCurrency) {
|
||||||
const from = tx.from?.hash || "";
|
const from = tx.from?.hash || "";
|
||||||
const to = tx.to?.hash || "";
|
const to = tx.to?.hash || "";
|
||||||
const rawWei = tx.value || "0";
|
const rawWei = tx.value || "0";
|
||||||
@@ -36,7 +36,7 @@ function parseTx(tx, addrLower) {
|
|||||||
const method = tx.method || null;
|
const method = tx.method || null;
|
||||||
|
|
||||||
// For contract calls, produce a meaningful label instead of "0.0000 ETH"
|
// For contract calls, produce a meaningful label instead of "0.0000 ETH"
|
||||||
let symbol = "ETH";
|
let symbol = nativeCurrency;
|
||||||
let value = formatTxValue(formatEther(rawWei));
|
let value = formatTxValue(formatEther(rawWei));
|
||||||
let exactValue = formatEther(rawWei);
|
let exactValue = formatEther(rawWei);
|
||||||
let rawAmount = rawWei;
|
let rawAmount = rawWei;
|
||||||
@@ -221,7 +221,14 @@ function mergeTransactions(txs, tokenTransfers) {
|
|||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
|
// `nativeCurrency` is the active network's native token symbol from
|
||||||
|
// networks.js (`ETH`, `SepoliaETH`), which a native entry is labelled with.
|
||||||
|
async function fetchRecentTransactions(
|
||||||
|
address,
|
||||||
|
blockscoutUrl,
|
||||||
|
nativeCurrency,
|
||||||
|
count = 25,
|
||||||
|
) {
|
||||||
log.debugf("fetchRecentTransactions", address);
|
log.debugf("fetchRecentTransactions", address);
|
||||||
const addrLower = normalizeAddress(address);
|
const addrLower = normalizeAddress(address);
|
||||||
|
|
||||||
@@ -254,7 +261,9 @@ async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
|
|||||||
const ttJson = ttResp.ok ? await ttResp.json() : {};
|
const ttJson = ttResp.ok ? await ttResp.json() : {};
|
||||||
|
|
||||||
const txs = mergeTransactions(
|
const txs = mergeTransactions(
|
||||||
(txJson.items || []).map((tx) => parseTx(tx, addrLower)),
|
(txJson.items || []).map((tx) =>
|
||||||
|
parseTx(tx, addrLower, nativeCurrency),
|
||||||
|
),
|
||||||
(ttJson.items || []).map((tt) => parseTokenTransfer(tt, addrLower)),
|
(ttJson.items || []).map((tt) => parseTokenTransfer(tt, addrLower)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
// The native token's label on the screens that show a native amount.
|
||||||
|
//
|
||||||
|
// src/shared/networks.js gives each network a nativeCurrency, `ETH` on mainnet
|
||||||
|
// and `SepoliaETH` on Sepolia, and nothing read it: every screen wrote a
|
||||||
|
// hardcoded "ETH", so on Sepolia the balance, the value and the fee all read
|
||||||
|
// ETH (https://git.eeqj.de/sneak/AutistMask/issues/372). Each line is asserted
|
||||||
|
// on both networks, through the real Send, confirmation and approval screens,
|
||||||
|
// with only the node and the DOM stubbed.
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
jest.mock("ethers", () => {
|
||||||
|
const actual = jest.requireActual("ethers");
|
||||||
|
class StubProvider {
|
||||||
|
async lookupAddress() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 10 gwei expected, 20 gwei reserved per gas.
|
||||||
|
async getFeeData() {
|
||||||
|
return { maxFeePerGas: 20000000000n, gasPrice: 10000000000n };
|
||||||
|
}
|
||||||
|
async estimateGas() {
|
||||||
|
return 21000n;
|
||||||
|
}
|
||||||
|
async getCode() {
|
||||||
|
return "0x";
|
||||||
|
}
|
||||||
|
async getTransactionCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
JsonRpcProvider: StubProvider,
|
||||||
|
Network: { from: () => ({}) },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock("../src/shared/log", () => ({
|
||||||
|
log: {
|
||||||
|
debugf: () => {},
|
||||||
|
infof: () => {},
|
||||||
|
warnf: () => {},
|
||||||
|
errorf: () => {},
|
||||||
|
},
|
||||||
|
debugFetch: jest.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({ items: [] }),
|
||||||
|
})),
|
||||||
|
urlOrigin: () => "",
|
||||||
|
setRuntimeDebug: () => {},
|
||||||
|
isDebug: () => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
global.fetch = jest.fn(() => {
|
||||||
|
throw new Error("tests must not perform network requests");
|
||||||
|
});
|
||||||
|
|
||||||
|
// The approval the background hands the approval screen. Set per test.
|
||||||
|
let approvalDetails = null;
|
||||||
|
|
||||||
|
const { makeStorageStub } = require("./support/storageStub");
|
||||||
|
global.chrome = {
|
||||||
|
storage: makeStorageStub(),
|
||||||
|
runtime: {
|
||||||
|
connect: () => ({
|
||||||
|
postMessage() {},
|
||||||
|
disconnect() {},
|
||||||
|
onDisconnect: { addListener() {} },
|
||||||
|
}),
|
||||||
|
sendMessage(message, callback) {
|
||||||
|
callback(
|
||||||
|
message.type === "AUTISTMASK_GET_APPROVAL"
|
||||||
|
? approvalDetails
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// A stub DOM: every id resolves to a recording element.
|
||||||
|
const elements = new Map();
|
||||||
|
|
||||||
|
function makeEl(id) {
|
||||||
|
const handlers = new Map();
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
textContent: "",
|
||||||
|
innerHTML: "",
|
||||||
|
value: "",
|
||||||
|
disabled: false,
|
||||||
|
style: {},
|
||||||
|
dataset: {},
|
||||||
|
classList: {
|
||||||
|
add() {},
|
||||||
|
remove() {},
|
||||||
|
toggle() {},
|
||||||
|
contains: () => false,
|
||||||
|
},
|
||||||
|
handlers,
|
||||||
|
children: [],
|
||||||
|
addEventListener(name, fn) {
|
||||||
|
handlers.set(name, fn);
|
||||||
|
},
|
||||||
|
appendChild(child) {
|
||||||
|
this.children.push(child);
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
querySelectorAll: () => [],
|
||||||
|
querySelector: () => null,
|
||||||
|
remove() {},
|
||||||
|
focus() {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
global.document = {
|
||||||
|
getElementById(id) {
|
||||||
|
if (!elements.has(id)) elements.set(id, makeEl(id));
|
||||||
|
return elements.get(id);
|
||||||
|
},
|
||||||
|
createElement: (tag) => makeEl(tag),
|
||||||
|
body: { prepend() {}, appendChild() {} },
|
||||||
|
addEventListener() {},
|
||||||
|
};
|
||||||
|
global.navigator = { clipboard: { writeText() {} } };
|
||||||
|
|
||||||
|
const { state } = require("../src/shared/state");
|
||||||
|
const { NETWORKS } = require("../src/shared/networks");
|
||||||
|
const { clearPrices } = require("../src/shared/prices");
|
||||||
|
const send = require("../src/popup/views/send");
|
||||||
|
const confirmTx = require("../src/popup/views/confirmTx");
|
||||||
|
const approval = require("../src/popup/views/approval");
|
||||||
|
const { balanceLinesForAddress } = require("../src/popup/views/helpers");
|
||||||
|
|
||||||
|
const HOLDER = "0x" + "a".repeat(40);
|
||||||
|
const RECIPIENT = "0xC0FfEE0000000000000000000000000000c0fFEe";
|
||||||
|
|
||||||
|
function text(id) {
|
||||||
|
return global.document.getElementById(id).textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Press Review on the Send screen for a native send of `amount`, and show the
|
||||||
|
// confirmation screen it leads to with its fee estimate settled.
|
||||||
|
async function confirmSend(amount) {
|
||||||
|
let txInfo = null;
|
||||||
|
send.init({ showConfirmTx: (info) => (txInfo = info) });
|
||||||
|
state.selectedToken = "ETH";
|
||||||
|
global.document.getElementById("send-to").value = RECIPIENT;
|
||||||
|
global.document.getElementById("send-amount").value = amount;
|
||||||
|
await global.document
|
||||||
|
.getElementById("btn-send-review")
|
||||||
|
.handlers.get("click")();
|
||||||
|
confirmTx.show(txInfo);
|
||||||
|
for (let i = 0; i < 10; i++) await new Promise((r) => setTimeout(r, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The approval screen for a dApp transaction sending 0.01 of the native token
|
||||||
|
// with 21000 gas at up to 20 gwei, on the active network.
|
||||||
|
async function approveTx() {
|
||||||
|
approvalDetails = {
|
||||||
|
type: "tx",
|
||||||
|
origin: "https://dapp.example",
|
||||||
|
approvedFrom: HOLDER,
|
||||||
|
approvedTx: {
|
||||||
|
to: RECIPIENT,
|
||||||
|
value: "10000000000000000",
|
||||||
|
data: "0x",
|
||||||
|
chainId: NETWORKS[state.networkId].chainId,
|
||||||
|
gasLimit: "21000",
|
||||||
|
maxFeePerGas: "20000000000",
|
||||||
|
nonce: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await approval.show("1");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe.each([
|
||||||
|
["mainnet", "ETH"],
|
||||||
|
["sepolia", "SepoliaETH"],
|
||||||
|
])("on %s the native token reads %s", (networkId, symbol) => {
|
||||||
|
beforeEach(() => {
|
||||||
|
elements.clear();
|
||||||
|
clearPrices();
|
||||||
|
state.networkId = networkId;
|
||||||
|
state.wallets = [
|
||||||
|
{
|
||||||
|
name: "Wallet 1",
|
||||||
|
addresses: [{ address: HOLDER, balance: "1.5" }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
state.selectedWallet = 0;
|
||||||
|
state.selectedAddress = 0;
|
||||||
|
state.trackedTokens = [];
|
||||||
|
state.fraudContracts = [];
|
||||||
|
state.currentView = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the balance", async () => {
|
||||||
|
const addr = state.wallets[0].addresses[0];
|
||||||
|
expect(balanceLinesForAddress(addr, [], false)).toContain(
|
||||||
|
`<span>${symbol}</span><span>1.5000</span>`,
|
||||||
|
);
|
||||||
|
state.selectedToken = "ETH";
|
||||||
|
send.updateSendBalance();
|
||||||
|
expect(text("send-balance")).toBe("Current balance: 1.5000 " + symbol);
|
||||||
|
await confirmSend("0.1");
|
||||||
|
expect(text("confirm-balance")).toBe("1.5000 " + symbol);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the value", async () => {
|
||||||
|
await confirmSend("0.1");
|
||||||
|
expect(text("confirm-type")).toBe("Native " + symbol + " transfer");
|
||||||
|
expect(text("confirm-amount")).toBe("0.1 " + symbol);
|
||||||
|
await approveTx();
|
||||||
|
expect(text("approve-tx-value")).toBe("0.0100 " + symbol);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the fee", async () => {
|
||||||
|
await confirmSend("0.1");
|
||||||
|
// 21000 gas at 10 gwei expected, at 20 gwei reserved.
|
||||||
|
expect(text("confirm-fee-amount")).toBe("~0.0002 " + symbol);
|
||||||
|
expect(text("confirm-fee-reserve")).toBe(
|
||||||
|
"up to 0.0004 " + symbol + " reserved",
|
||||||
|
);
|
||||||
|
expect(text("confirm-gas-error")).toContain(
|
||||||
|
"You do not have enough " + symbol + " to pay the network fee",
|
||||||
|
);
|
||||||
|
await approveTx();
|
||||||
|
expect(text("approve-tx-fee")).toBe("0.0004 " + symbol);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the insufficient-balance error", async () => {
|
||||||
|
await confirmSend("2");
|
||||||
|
expect(
|
||||||
|
global.document.getElementById("confirm-errors").innerHTML,
|
||||||
|
).toContain(
|
||||||
|
"You have 1.5000 " +
|
||||||
|
symbol +
|
||||||
|
" but are trying to send 2 " +
|
||||||
|
symbol +
|
||||||
|
".",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
+62
-13
@@ -1219,7 +1219,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
|
|
||||||
test("queries only the two Blockscout endpoints for the address", async () => {
|
test("queries only the two Blockscout endpoints for the address", async () => {
|
||||||
respondWith([], []);
|
respondWith([], []);
|
||||||
await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH");
|
||||||
expect(debugFetch).toHaveBeenCalledTimes(2);
|
expect(debugFetch).toHaveBeenCalledTimes(2);
|
||||||
const urls = debugFetch.mock.calls.map((c) => c[0]);
|
const urls = debugFetch.mock.calls.map((c) => c[0]);
|
||||||
expect(urls).toContain(
|
expect(urls).toContain(
|
||||||
@@ -1283,7 +1283,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH");
|
||||||
expect(txs).toHaveLength(1);
|
expect(txs).toHaveLength(1);
|
||||||
const merged = txs[0];
|
const merged = txs[0];
|
||||||
// The received leg (the swap output) supplies the display amount.
|
// The received leg (the swap output) supplies the display amount.
|
||||||
@@ -1320,7 +1320,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH");
|
||||||
expect(txs).toHaveLength(1);
|
expect(txs).toHaveLength(1);
|
||||||
expect(txs[0].symbol).toBe("USDC");
|
expect(txs[0].symbol).toBe("USDC");
|
||||||
expect(txs[0].value).toBe("1500.5000");
|
expect(txs[0].value).toBe("1500.5000");
|
||||||
@@ -1346,7 +1346,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
});
|
});
|
||||||
respondWith([], [leg("1000000"), leg("2000000")]);
|
respondWith([], [leg("1000000"), leg("2000000")]);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH");
|
||||||
expect(txs).toHaveLength(1);
|
expect(txs).toHaveLength(1);
|
||||||
// Keyed by hash plus contract, so the later leg wins.
|
// Keyed by hash plus contract, so the later leg wins.
|
||||||
expect(txs[0].exactValue).toBe("2.0");
|
expect(txs[0].exactValue).toBe("2.0");
|
||||||
@@ -1386,7 +1386,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH");
|
||||||
expect(txs.map((t) => t.symbol).sort()).toEqual(["USDC", "WETH"]);
|
expect(txs.map((t) => t.symbol).sort()).toEqual(["USDC", "WETH"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1427,7 +1427,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH");
|
||||||
expect(txs).toHaveLength(1);
|
expect(txs).toHaveLength(1);
|
||||||
expect(txs[0].symbol).toBe("USDC");
|
expect(txs[0].symbol).toBe("USDC");
|
||||||
expect(txs[0].exactValue).toBe("1.0");
|
expect(txs[0].exactValue).toBe("1.0");
|
||||||
@@ -1452,10 +1452,43 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
});
|
});
|
||||||
respondWith([item(6), item(8), item(7)], []);
|
respondWith([item(6), item(8), item(7)], []);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, 2);
|
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH", 2);
|
||||||
expect(txs.map((t) => t.blockNumber)).toEqual([21000008, 21000007]);
|
expect(txs.map((t) => t.blockNumber)).toEqual([21000008, 21000007]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/372: the caller hands in
|
||||||
|
// the active network's nativeCurrency, and a native entry carries it.
|
||||||
|
test("a native entry is labelled with the native token symbol handed in", async () => {
|
||||||
|
respondWith(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
hash: "0x" + "a".repeat(64),
|
||||||
|
block_number: 21000090,
|
||||||
|
timestamp: TS,
|
||||||
|
from: { hash: ORDINARY_PEER },
|
||||||
|
to: { hash: VICTIM, is_contract: false },
|
||||||
|
value: "10000000000000000",
|
||||||
|
method: null,
|
||||||
|
status: "ok",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const sepolia = await fetchRecentTransactions(
|
||||||
|
VICTIM,
|
||||||
|
BLOCKSCOUT,
|
||||||
|
"SepoliaETH",
|
||||||
|
);
|
||||||
|
expect(sepolia[0].symbol).toBe("SepoliaETH");
|
||||||
|
expect(sepolia[0].value).toBe("0.0100");
|
||||||
|
const mainnet = await fetchRecentTransactions(
|
||||||
|
VICTIM,
|
||||||
|
BLOCKSCOUT,
|
||||||
|
"ETH",
|
||||||
|
);
|
||||||
|
expect(mainnet[0].symbol).toBe("ETH");
|
||||||
|
});
|
||||||
|
|
||||||
test("the fake token transfer survives fetching and is then filtered", async () => {
|
test("the fake token transfer survives fetching and is then filtered", async () => {
|
||||||
respondWith(
|
respondWith(
|
||||||
[],
|
[],
|
||||||
@@ -1476,7 +1509,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH");
|
||||||
expect(txs).toHaveLength(1);
|
expect(txs).toHaveLength(1);
|
||||||
expect(txs[0].contractAddress).toBe(FAKE_ETH_CONTRACT);
|
expect(txs[0].contractAddress).toBe(FAKE_ETH_CONTRACT);
|
||||||
expect(txs[0].holders).toBe(0);
|
expect(txs[0].holders).toBe(0);
|
||||||
@@ -1515,19 +1548,31 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
|
|
||||||
test("an omitted holders_count parses to null", async () => {
|
test("an omitted holders_count parses to null", async () => {
|
||||||
respondWith([], spamTransferWithToken(OMITTED));
|
respondWith([], spamTransferWithToken(OMITTED));
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(
|
||||||
|
VICTIM,
|
||||||
|
BLOCKSCOUT,
|
||||||
|
"ETH",
|
||||||
|
);
|
||||||
expect(txs[0].holders).toBeNull();
|
expect(txs[0].holders).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a null holders_count parses to null", async () => {
|
test("a null holders_count parses to null", async () => {
|
||||||
respondWith([], spamTransferWithToken(NULLED));
|
respondWith([], spamTransferWithToken(NULLED));
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(
|
||||||
|
VICTIM,
|
||||||
|
BLOCKSCOUT,
|
||||||
|
"ETH",
|
||||||
|
);
|
||||||
expect(txs[0].holders).toBeNull();
|
expect(txs[0].holders).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the transfer survives the low-holder filter", async () => {
|
test("the transfer survives the low-holder filter", async () => {
|
||||||
respondWith([], spamTransferWithToken(OMITTED));
|
respondWith([], spamTransferWithToken(OMITTED));
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(
|
||||||
|
VICTIM,
|
||||||
|
BLOCKSCOUT,
|
||||||
|
"ETH",
|
||||||
|
);
|
||||||
expect(filterTransactions(txs, filters()).transactions).toEqual(
|
expect(filterTransactions(txs, filters()).transactions).toEqual(
|
||||||
txs,
|
txs,
|
||||||
);
|
);
|
||||||
@@ -1539,7 +1584,11 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
// holder count is the only rule that can catch it.
|
// holder count is the only rule that can catch it.
|
||||||
test('a reported holders_count of "0" still parses to 0 and is filtered', async () => {
|
test('a reported holders_count of "0" still parses to 0 and is filtered', async () => {
|
||||||
respondWith([], spamTransferWithToken(ZERO));
|
respondWith([], spamTransferWithToken(ZERO));
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT);
|
const txs = await fetchRecentTransactions(
|
||||||
|
VICTIM,
|
||||||
|
BLOCKSCOUT,
|
||||||
|
"ETH",
|
||||||
|
);
|
||||||
expect(txs[0].holders).toBe(0);
|
expect(txs[0].holders).toBe(0);
|
||||||
expect(filterTransactions(txs, filters()).transactions).toEqual([]);
|
expect(filterTransactions(txs, filters()).transactions).toEqual([]);
|
||||||
});
|
});
|
||||||
@@ -1555,7 +1604,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
await expect(
|
await expect(
|
||||||
fetchRecentTransactions(VICTIM, BLOCKSCOUT),
|
fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH"),
|
||||||
).resolves.toEqual([]);
|
).resolves.toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user