Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
504a25dead |
@@ -1059,18 +1059,17 @@ Which tokens an address shows is decided by `fetchTokenBalances()` in
|
|||||||
`src/shared/balances.js`, from the Blockscout `token-balances` response, so
|
`src/shared/balances.js`, from the Blockscout `token-balances` response, so
|
||||||
tokens do appear without the user adding them. An ERC-20 is shown when its
|
tokens do appear without the user adding them. An ERC-20 is shown when its
|
||||||
balance is nonzero and it is in the bundled known-token list, is tracked by the
|
balance is nonzero and it is in the bundled known-token list, is tracked by the
|
||||||
user, or has 1,000 or more holders; a token claiming a symbol from the bundled
|
user, or has 1,000 or more holders. A holding below 0.000001 of a token the user
|
||||||
list from any other contract address is always dropped, and so is any token
|
does not track is dust and is not shown; a tracked token's holding is shown
|
||||||
claiming a symbol that belongs to the native asset and therefore has no
|
whatever its size, also while "Show tracked tokens with zero balance" is off,
|
||||||
legitimate contract at all (`"ETH"`). That filter is unconditional — the "Hide
|
and one too small for the list's four decimal places reads `0.0000`. A token
|
||||||
tokens with fewer than 1,000 holders" setting governs the transaction history
|
claiming a symbol from the bundled list from any other contract address is
|
||||||
and the send-screen token selector, not this list. `fetchTokenBalances()` stores
|
always dropped, and so is any token claiming a symbol that belongs to the native
|
||||||
every nonzero holding of a token it admits exactly, however small, but a holding
|
asset and therefore has no legitimate contract at all (`"ETH"`). That filter is
|
||||||
below 0.000001 is left out of the balance lists, the send-screen token selector,
|
unconditional — the "Hide tokens with fewer than 1,000 holders" setting governs
|
||||||
the address total and the remove-address warning (`isBelowOneMillionth()` in
|
the transaction history and the send-screen token selector, not this list.
|
||||||
`src/shared/amountDisplay.js`). The Send and confirmation screens show it when
|
Tracked tokens with a zero balance are listed as well while "Show tracked tokens
|
||||||
its token is the one being sent. Tracked tokens with a zero balance are listed
|
with zero balance" is on.
|
||||||
as well while "Show tracked tokens with zero balance" is on.
|
|
||||||
|
|
||||||
#### Stored state and its version
|
#### Stored state and its version
|
||||||
|
|
||||||
|
|||||||
@@ -49,13 +49,12 @@ but the review is broader than any of them.
|
|||||||
token balance or a network fee below 0.000001 as zero
|
token balance or a network fee below 0.000001 as zero
|
||||||
([#343](https://git.eeqj.de/sneak/AutistMask/issues/343)). The stored balances
|
([#343](https://git.eeqj.de/sneak/AutistMask/issues/343)). The stored balances
|
||||||
(`src/shared/balances.js`) and the confirmation screen's fee were each cut to
|
(`src/shared/balances.js`) and the confirmation screen's fee were each cut to
|
||||||
six decimal places by a rule of their own, and a token holding cut to zero was
|
six decimal places by a rule of their own. Balances are now stored exactly,
|
||||||
dropped. Balances are now stored exactly, whatever decimals a token declares,
|
whatever decimals a token declares; the balance check reads a token balance to
|
||||||
and every nonzero token holding is kept; the balance check reads a token
|
its first 18 places, the most an amount can have. A token holding below
|
||||||
balance to its first 18 places, the most an amount can have. The balance
|
0.000001 is still not listed, but only for a token the user does not track, so
|
||||||
lists, the send-screen token selector, the address total and the
|
a tracked token's holding reaches the Send screen and the send is checked
|
||||||
remove-address warning leave out a holding below 0.000001 themselves, as
|
against it. The Send screen's `Current balance`, and the confirmation screen's
|
||||||
before. The Send screen's `Current balance`, and the confirmation screen's
|
|
||||||
balance, fee, reserve and insufficient-balance messages, go through
|
balance, fee, reserve and insufficient-balance messages, go through
|
||||||
`truncateAmountNeverZero()` in `src/shared/amountDisplay.js`, the helper the
|
`truncateAmountNeverZero()` in `src/shared/amountDisplay.js`, the helper the
|
||||||
approval screen already used. The confirmation and approval screens both
|
approval screen already used. The confirmation and approval screens both
|
||||||
|
|||||||
@@ -87,8 +87,7 @@ function recoveryPathText(wallet) {
|
|||||||
// AddressDetail, followed by the USD total when there is one to give — no
|
// AddressDetail, followed by the USD total when there is one to give — no
|
||||||
// total line at all on testnet or before the first price fetch, and no figure
|
// total line at all on testnet or before the first price fetch, and no figure
|
||||||
// when every holding here is one with no price, since "$0.00" directly under
|
// when every holding here is one with no price, since "$0.00" directly under
|
||||||
// "This address holds a balance." is a contradiction. A token holding below
|
// "This address holds a balance." is a contradiction.
|
||||||
// 0.000001 does not count, as the lines below leave it out.
|
|
||||||
function balanceWarningHtml(addr) {
|
function balanceWarningHtml(addr) {
|
||||||
if (!addressHoldsFunds(addr)) return " ";
|
if (!addressHoldsFunds(addr)) return " ";
|
||||||
const line = formatAddressTotal(getAddressValue(addr));
|
const line = formatAddressTotal(getAddressValue(addr));
|
||||||
|
|||||||
@@ -13,10 +13,7 @@
|
|||||||
// reasoning behind it are; it is re-exported below so views keep importing
|
// reasoning behind it are; it is re-exported below so views keep importing
|
||||||
// it from here.
|
// it from here.
|
||||||
const { formatEther } = require("ethers");
|
const { formatEther } = require("ethers");
|
||||||
const {
|
const { truncateAmountNeverZero } = require("../../shared/amountDisplay");
|
||||||
truncateAmountNeverZero,
|
|
||||||
isBelowOneMillionth,
|
|
||||||
} = require("../../shared/amountDisplay");
|
|
||||||
const { DEBUG } = require("../../shared/constants");
|
const { DEBUG } = require("../../shared/constants");
|
||||||
const { escapeHtml } = require("../../shared/html");
|
const { escapeHtml } = require("../../shared/html");
|
||||||
const { isDebug } = require("../../shared/log");
|
const { isDebug } = require("../../shared/log");
|
||||||
@@ -306,9 +303,6 @@ function balanceLinesForAddress(addr, trackedTokens, showZero) {
|
|||||||
);
|
);
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
for (const t of addr.tokenBalances || []) {
|
for (const t of addr.tokenBalances || []) {
|
||||||
// A holding below 0.000001 is not listed, tracked or not. A tracked
|
|
||||||
// token then gets the zero row below while showZero is on.
|
|
||||||
if (isBelowOneMillionth(t.balance)) continue;
|
|
||||||
// A null balance is a holding of an unstatable amount, not a holding
|
// A null balance is a holding of an unstatable amount, not a holding
|
||||||
// of zero, so the show-zero setting has no say over it: hiding it
|
// of zero, so the show-zero setting has no say over it: hiding it
|
||||||
// would be asserting the zero nobody established. Anything that does
|
// would be asserting the zero nobody established. Anything that does
|
||||||
@@ -339,16 +333,14 @@ function balanceLinesForAddress(addr, trackedTokens, showZero) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Whether an address holds anything at all: ETH or any ERC-20 the wallet
|
// Whether an address holds anything at all: ETH or any ERC-20 the wallet
|
||||||
// knows about, except a token holding below 0.000001, which the balance list
|
// knows about. Deliberately unrounded — the rendered lines round to four
|
||||||
// under the remove-address warning leaves out too. Deliberately unrounded —
|
// decimals, so a dust balance displays as 0.0000 while still being real
|
||||||
// the rendered lines round to four decimals, so a dust balance displays as
|
// money at a real address. Callers that warn about holdings must ask this,
|
||||||
// 0.0000 while still being real money at a real address. Callers that warn
|
// not the rendered figure.
|
||||||
// about holdings must ask this, not the rendered figure.
|
|
||||||
function addressHoldsFunds(addr) {
|
function addressHoldsFunds(addr) {
|
||||||
if (!addr) return false;
|
if (!addr) return false;
|
||||||
if (parseFloat(addr.balance || "0") > 0) return true;
|
if (parseFloat(addr.balance || "0") > 0) return true;
|
||||||
for (const t of addr.tokenBalances || []) {
|
for (const t of addr.tokenBalances || []) {
|
||||||
if (isBelowOneMillionth(t.balance)) continue;
|
|
||||||
// A null balance is a holding whose amount could not be stated —
|
// A null balance is a holding whose amount could not be stated —
|
||||||
// balances.js drops a row of zero base units before the scale is
|
// balances.js drops a row of zero base units before the scale is
|
||||||
// consulted, so a row that survived with no quantity is holding
|
// consulted, so a row that survived with no quantity is holding
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ const { resolveTokenDecimals } = require("../../shared/approvalAmount");
|
|||||||
const { resolveSymbol } = require("../../shared/tokenList");
|
const { resolveSymbol } = require("../../shared/tokenList");
|
||||||
const { isLowHolderCount } = require("../../shared/holders");
|
const { isLowHolderCount } = require("../../shared/holders");
|
||||||
const { isSpoofedSymbol } = require("../../shared/symbolSpoof");
|
const { isSpoofedSymbol } = require("../../shared/symbolSpoof");
|
||||||
const {
|
const { truncateAmountNeverZero } = require("../../shared/amountDisplay");
|
||||||
truncateAmountNeverZero,
|
|
||||||
isBelowOneMillionth,
|
|
||||||
} = require("../../shared/amountDisplay");
|
|
||||||
const { getAddress } = require("ethers");
|
const { getAddress } = require("ethers");
|
||||||
|
|
||||||
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
||||||
@@ -129,10 +126,6 @@ function renderSendTokenSelect(addr) {
|
|||||||
(state.fraudContracts || []).map((a) => a.toLowerCase()),
|
(state.fraudContracts || []).map((a) => a.toLowerCase()),
|
||||||
);
|
);
|
||||||
for (const t of addr.tokenBalances || []) {
|
for (const t of addr.tokenBalances || []) {
|
||||||
// A holding below 0.000001 is left out, as the balance lists leave it
|
|
||||||
// out. Its token's own screen can still send it: there
|
|
||||||
// state.selectedToken picks the token, not this list.
|
|
||||||
if (isBelowOneMillionth(t.balance)) continue;
|
|
||||||
if (isSpoofedSymbol(t.symbol, t.address)) continue;
|
if (isSpoofedSymbol(t.symbol, t.address)) continue;
|
||||||
if (fraudSet.has(t.address.toLowerCase())) continue;
|
if (fraudSet.has(t.address.toLowerCase())) continue;
|
||||||
// An unknown holder count does not withhold a token the user holds:
|
// An unknown holder count does not withhold a token the user holds:
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
// (`src/shared/uniswap.js`) — and a fix applied to one of them left the other
|
// (`src/shared/uniswap.js`) — and a fix applied to one of them left the other
|
||||||
// two showing a different number for the same value.
|
// two showing a different number for the same value.
|
||||||
//
|
//
|
||||||
// The two truncation functions below are the two policies, not two
|
// The two functions below are the two policies, not two implementations of
|
||||||
// implementations of one: summary lists truncate, and the screens that state
|
// one: summary lists truncate, and the screens that state what is being
|
||||||
// what is being authorized truncate with a floor. Keeping them adjacent is the
|
// authorized truncate with a floor. Keeping them adjacent is the point, so a
|
||||||
// point, so a change to the rule cannot reach one screen and miss another.
|
// change to the rule cannot reach one screen and miss another.
|
||||||
|
|
||||||
// Truncate to exactly four decimal places. Truncation, never rounding: an
|
// Truncate to exactly four decimal places. Truncation, never rounding: an
|
||||||
// amount must never be displayed as larger than it is, so 0.99999 stays
|
// amount must never be displayed as larger than it is, so 0.99999 stays
|
||||||
@@ -43,18 +43,4 @@ function truncateAmountNeverZero(val) {
|
|||||||
return parts[0] + "." + parts[1].slice(0, sig + 1);
|
return parts[0] + "." + parts[1].slice(0, sig + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether a stored token balance is a holding below 0.000001. The balance
|
module.exports = { truncateAmount, truncateAmountNeverZero };
|
||||||
// lists, the send-screen token selector, the address total and the
|
|
||||||
// remove-address warning leave such a holding out; the Send and confirmation
|
|
||||||
// screens show it when its token is the one being sent. Exact, because
|
|
||||||
// src/shared/balances.js stores plain decimal digits: below 0.000001 the
|
|
||||||
// balance reads "0.000000" and then more digits.
|
|
||||||
function isBelowOneMillionth(balance) {
|
|
||||||
return typeof balance === "string" && balance.startsWith("0.000000");
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
truncateAmount,
|
|
||||||
truncateAmountNeverZero,
|
|
||||||
isBelowOneMillionth,
|
|
||||||
};
|
|
||||||
|
|||||||
+14
-4
@@ -53,10 +53,7 @@ function requireNetworkId(networkId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A token balance as an exact decimal string, never cut: a cut stores a small
|
// A token balance as an exact decimal string, never cut: a cut stores a small
|
||||||
// nonzero holding as zero. fetchTokenBalances() stores every nonzero holding of
|
// nonzero holding as zero.
|
||||||
// a token it admits, however small; the screens that leave out one below
|
|
||||||
// 0.000001 decide that themselves, through isBelowOneMillionth() in
|
|
||||||
// src/shared/amountDisplay.js.
|
|
||||||
function formatTokenBalance(raw, decimals) {
|
function formatTokenBalance(raw, decimals) {
|
||||||
const val = formatUnits(raw, decimals);
|
const val = formatUnits(raw, decimals);
|
||||||
const parts = val.split(".");
|
const parts = val.split(".");
|
||||||
@@ -165,6 +162,19 @@ async function fetchTokenBalances(address, blockscoutUrl, trackedTokens) {
|
|||||||
// Skip spam tokens the user never asked to see
|
// Skip spam tokens the user never asked to see
|
||||||
if (!isKnown && !isTracked && !hasEnoughHolders) continue;
|
if (!isKnown && !isTracked && !hasEnoughHolders) continue;
|
||||||
|
|
||||||
|
// Skip dust: a holding below 0.000001 of a token the user does not
|
||||||
|
// track. A tracked token keeps it, so the Send and confirmation
|
||||||
|
// screens show that holding and check the send against it rather
|
||||||
|
// than against zero. Without a scale there is no such judgement to
|
||||||
|
// make, and the row is kept.
|
||||||
|
if (
|
||||||
|
!isTracked &&
|
||||||
|
scale !== null &&
|
||||||
|
raw * 1000000n < 10n ** BigInt(scale)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip tokens spoofing a known symbol from a different address.
|
// Skip tokens spoofing a known symbol from a different address.
|
||||||
// Every row here is an ERC-20 the explorer reported, so it has a
|
// Every row here is an ERC-20 the explorer reported, so it has a
|
||||||
// contract address; the native ETH balance is fetched over RPC in
|
// contract address; the native ETH balance is fetched over RPC in
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Price fetching with 5-minute cache, USD formatting, value aggregation.
|
// Price fetching with 5-minute cache, USD formatting, value aggregation.
|
||||||
|
|
||||||
const { getTopTokenPrices } = require("./tokenList");
|
const { getTopTokenPrices } = require("./tokenList");
|
||||||
const { isBelowOneMillionth } = require("./amountDisplay");
|
|
||||||
|
|
||||||
const PRICE_CACHE_TTL = 300000; // 5 minutes
|
const PRICE_CACHE_TTL = 300000; // 5 minutes
|
||||||
|
|
||||||
@@ -79,9 +78,6 @@ function getAddressValue(addr) {
|
|||||||
let usd = parseFloat(addr.balance || "0") * prices.ETH;
|
let usd = parseFloat(addr.balance || "0") * prices.ETH;
|
||||||
let partial = false;
|
let partial = false;
|
||||||
for (const token of addr.tokenBalances || []) {
|
for (const token of addr.tokenBalances || []) {
|
||||||
// A holding below 0.000001 is left out, as the balance lists leave it
|
|
||||||
// out, so the total never counts a holding the list does not show.
|
|
||||||
if (isBelowOneMillionth(token.balance)) continue;
|
|
||||||
// A null balance is a holding whose scale nothing knows, so it has no
|
// A null balance is a holding whose scale nothing knows, so it has no
|
||||||
// quantity to price — but it is still a holding, and a total that
|
// quantity to price — but it is still a holding, and a total that
|
||||||
// silently omits it would read as complete. That is exactly what
|
// silently omits it would read as complete. That is exactly what
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
// An ETH balance, a token balance or a fee below 0.000001 rendered as zero on
|
// An ETH balance, a token balance or a fee below 0.000001 rendered as zero on
|
||||||
// these screens (https://git.eeqj.de/sneak/AutistMask/issues/343): the stored
|
// these screens (https://git.eeqj.de/sneak/AutistMask/issues/343): the stored
|
||||||
// balances and the fee were each cut to six decimal places, a rule of their
|
// balances and the fee were each cut to six decimal places, a rule of their
|
||||||
// own, and a token holding cut to zero was dropped, while the approval screen
|
// own, while the approval screen showed the same fee through
|
||||||
// showed the same fee through src/shared/amountDisplay.js with the nonzero
|
// src/shared/amountDisplay.js with the nonzero floor. The balances are now
|
||||||
// floor. The balances are now stored exactly, every nonzero token holding
|
// stored exactly, and the screens show them and the fee through that helper.
|
||||||
// kept, and the screens show them and the fee through that helper.
|
|
||||||
//
|
//
|
||||||
// Driven through the real refreshBalances(), Send screen, confirmation screen
|
// Driven through the real refreshBalances(), Send screen, confirmation screen
|
||||||
// and approval screen, with only the node, the explorer and the DOM stubbed: a
|
// and approval screen, with only the node, the explorer and the DOM stubbed: a
|
||||||
@@ -118,12 +117,10 @@ function makeEl(id) {
|
|||||||
contains: () => false,
|
contains: () => false,
|
||||||
},
|
},
|
||||||
handlers,
|
handlers,
|
||||||
children: [],
|
|
||||||
addEventListener(name, fn) {
|
addEventListener(name, fn) {
|
||||||
handlers.set(name, fn);
|
handlers.set(name, fn);
|
||||||
},
|
},
|
||||||
appendChild(child) {
|
appendChild(child) {
|
||||||
this.children.push(child);
|
|
||||||
return child;
|
return child;
|
||||||
},
|
},
|
||||||
querySelectorAll: () => [],
|
querySelectorAll: () => [],
|
||||||
@@ -146,12 +143,7 @@ global.navigator = { clipboard: { writeText() {} } };
|
|||||||
|
|
||||||
const { refreshBalances } = require("../src/shared/balances");
|
const { refreshBalances } = require("../src/shared/balances");
|
||||||
const { state } = require("../src/shared/state");
|
const { state } = require("../src/shared/state");
|
||||||
const {
|
const { prices, clearPrices } = require("../src/shared/prices");
|
||||||
prices,
|
|
||||||
clearPrices,
|
|
||||||
formatAddressTotal,
|
|
||||||
getAddressValue,
|
|
||||||
} = require("../src/shared/prices");
|
|
||||||
const send = require("../src/popup/views/send");
|
const send = require("../src/popup/views/send");
|
||||||
const confirmTx = require("../src/popup/views/confirmTx");
|
const confirmTx = require("../src/popup/views/confirmTx");
|
||||||
const approval = require("../src/popup/views/approval");
|
const approval = require("../src/popup/views/approval");
|
||||||
@@ -319,8 +311,9 @@ describe("a tracked token holding below 0.000001 never renders as zero", () => {
|
|||||||
|
|
||||||
test("with more than 18 decimals and a holding below 10^-18", async () => {
|
test("with more than 18 decimals and a holding below 10^-18", async () => {
|
||||||
state.trackedTokens[0].decimals = 24;
|
state.trackedTokens[0].decimals = 24;
|
||||||
// One base unit, 0.000000000000000000000001 TOK.
|
// One base unit, 0.000000000000000000000001 TOK, and no ETH, so the
|
||||||
await refreshWith(10n ** 18n, [tokenRow(1n, { decimals: "24" })]);
|
// token is all the address holds.
|
||||||
|
await refreshWith(0n, [tokenRow(1n, { decimals: "24" })]);
|
||||||
state.selectedToken = TOKEN;
|
state.selectedToken = TOKEN;
|
||||||
send.updateSendBalance();
|
send.updateSendBalance();
|
||||||
expect(text("send-balance")).toBe(
|
expect(text("send-balance")).toBe(
|
||||||
@@ -332,74 +325,20 @@ describe("a tracked token holding below 0.000001 never renders as zero", () => {
|
|||||||
"You have 0.000000000000000000000001 TOK but are trying to send" +
|
"You have 0.000000000000000000000001 TOK but are trying to send" +
|
||||||
" 0.000000000000000001 TOK.",
|
" 0.000000000000000001 TOK.",
|
||||||
);
|
);
|
||||||
});
|
// Listed with zero balances hidden, and counted as funds.
|
||||||
});
|
|
||||||
|
|
||||||
// A token the user does not track, with enough holders to be admitted. The
|
|
||||||
// balance fetch dropped a holding of it below 0.000001, but the token stays
|
|
||||||
// selected while its own screen is open: after sending 2 of a 2.0000003
|
|
||||||
// holding, the user is back on that screen, and Send read the missing row as
|
|
||||||
// zero.
|
|
||||||
describe("an untracked token holding below 0.000001 never renders as zero", () => {
|
|
||||||
const row = () => tokenRow(HALF_MICRO_ETH, { holders_count: "50000" });
|
|
||||||
|
|
||||||
test("on the Send screen", async () => {
|
|
||||||
await refreshWith(10n ** 18n, [row()]);
|
|
||||||
state.selectedToken = TOKEN;
|
|
||||||
send.updateSendBalance();
|
|
||||||
expect(text("send-balance")).toBe("Current balance: 0.0000005 TOK");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("on the confirmation screen, which checks the send against it", async () => {
|
|
||||||
await refreshWith(10n ** 18n, [row()]);
|
|
||||||
await confirmSend("0.0000005", TOKEN);
|
|
||||||
expect(text("confirm-balance")).toBe("0.0000005 TOK");
|
|
||||||
expect(errors()).toBe("");
|
|
||||||
await confirmSend("0.0000006", TOKEN);
|
|
||||||
expect(errors()).toContain(
|
|
||||||
"You have 0.0000005 TOK but are trying to send 0.0000006 TOK.",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// The fetch keeps every holding, so the screens that showed only what it kept
|
|
||||||
// leave out a holding below 0.000001 themselves, and look as they did.
|
|
||||||
describe("a token holding below 0.000001 is still not listed", () => {
|
|
||||||
afterEach(() => {
|
|
||||||
clearPrices();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("for a token the user does not track", async () => {
|
|
||||||
prices.ETH = 3000;
|
|
||||||
await refreshWith(0n, [
|
|
||||||
tokenRow(HALF_MICRO_ETH, { holders_count: "50000" }),
|
|
||||||
]);
|
|
||||||
const addr = state.wallets[0].addresses[0];
|
|
||||||
expect(balanceLinesForAddress(addr, [], true)).not.toContain(TOKEN);
|
|
||||||
expect(balanceLinesForAddress(addr, [], false)).not.toContain(TOKEN);
|
|
||||||
send.renderSendTokenSelect(addr);
|
|
||||||
const options = global.document.getElementById("send-token").children;
|
|
||||||
expect(options.map((o) => o.value)).toEqual([]);
|
|
||||||
// Not an unpriced token in the total, and not funds on the
|
|
||||||
// remove-address warning.
|
|
||||||
expect(formatAddressTotal(getAddressValue(addr))).toBe("Total: $0.00");
|
|
||||||
expect(addressHoldsFunds(addr)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// As a tracked token holding nothing: listed only while zero balances are
|
|
||||||
// shown.
|
|
||||||
test("for a tracked token, unless zero balances are shown", async () => {
|
|
||||||
state.trackedTokens = [
|
|
||||||
{ address: TOKEN, symbol: "TOK", name: "Token", decimals: 18 },
|
|
||||||
];
|
|
||||||
await refreshWith(0n, [tokenRow(HALF_MICRO_ETH)]);
|
|
||||||
const addr = state.wallets[0].addresses[0];
|
const addr = state.wallets[0].addresses[0];
|
||||||
expect(
|
expect(
|
||||||
balanceLinesForAddress(addr, state.trackedTokens, false),
|
balanceLinesForAddress(addr, state.trackedTokens, false),
|
||||||
).not.toContain(TOKEN);
|
|
||||||
expect(
|
|
||||||
balanceLinesForAddress(addr, state.trackedTokens, true),
|
|
||||||
).toContain(`data-token="${TOKEN}"`);
|
).toContain(`data-token="${TOKEN}"`);
|
||||||
|
expect(addressHoldsFunds(addr)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("while an untracked holding below 0.000001 is still not listed", async () => {
|
||||||
|
state.trackedTokens = [];
|
||||||
|
await refreshWith(10n ** 18n, [
|
||||||
|
tokenRow(HALF_MICRO_ETH, { holders_count: "50000" }),
|
||||||
|
]);
|
||||||
|
expect(state.wallets[0].addresses[0].tokenBalances).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user