Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e66aac60e2 |
No files matched your search
@@ -1165,25 +1165,37 @@ with the same floor (`.5` reads `.5000`).
|
|||||||
|
|
||||||
**Specific Exception — unknown token scale:** Calldata carries base units and no
|
**Specific Exception — unknown token scale:** Calldata carries base units and no
|
||||||
scale, so every amount on the dApp approval screen needs the token's `decimals`.
|
scale, so every amount on the dApp approval screen needs the token's `decimals`.
|
||||||
It is resolved from the bundled token list, then from the tokens the user tracks
|
It is resolved from these sources, in this order (`resolveTokenDecimals()` in
|
||||||
on the network the transaction is for, then, when that is the active network,
|
`src/shared/approvalAmount.js`):
|
||||||
from what the block explorer reported for the contract in the wallet's balances,
|
|
||||||
which are the active network's only (`resolveTokenDecimals()` in
|
- the bundled token list
|
||||||
`src/shared/approvalAmount.js`). Where none of them answers, the amount is not
|
- the tokens the user tracks on the network the transaction is for
|
||||||
formatted: the line reads `5000000000 base units (decimals unknown)`. A guessed
|
- what the block explorer reported for the contract in the wallet's balances.
|
||||||
scale is not an approximation but a different number — 1,000 units of a
|
These are the active network's only, and they are used only while "Use
|
||||||
6-decimal token formatted at 18 decimals reads `0.000000001` — on the screen
|
Blockscout" is on (`tokenSourcesByChainId()` in `src/popup/views/helpers.js`).
|
||||||
whose only job is to state what is being authorized. Both amount paths of that
|
|
||||||
screen take this rule: the ERC-20 `transfer`/`approve` line
|
The token's name comes from the same sources. While "Use Blockscout" is off, a
|
||||||
(`src/popup/views/approval.js`) and the swap's `Amount`, `Min. received`,
|
token's name or decimals may be missing from the bundled list and the tracked
|
||||||
`Moved from the sender` and `Permit2 allowance` lines and the `Paid to` line of
|
tokens. Then a line under the token says "This token was not looked up on
|
||||||
a move into the router (`src/shared/uniswap.js`). The token permission warning
|
Blockscout, because "Use Blockscout" is off in Settings." These screens show it:
|
||||||
on the signature screen takes the same rule for its amounts. An unbounded
|
|
||||||
allowance or permit needs no scale to describe and is still shown as
|
- TxApproval and SignApproval
|
||||||
`Unlimited`. A source's answer counts only if it is a whole number from 0 to 80:
|
- WaitTx, SuccessTx and ErrorTx, which follow TxApproval
|
||||||
`decimals()` returns a `uint8`, but `formatUnits()` cannot format more than 80
|
|
||||||
decimal places, so a token that reports 81 to 255 is shown as one whose scale
|
Where none of the sources answers, the amount is not formatted: the line reads
|
||||||
nothing knows.
|
`5000000000 base units (decimals unknown)`. A guessed scale is not an
|
||||||
|
approximation but a different number — 1,000 units of a 6-decimal token
|
||||||
|
formatted at 18 decimals reads `0.000000001` — on the screen whose only job is
|
||||||
|
to state what is being authorized. Both amount paths of that screen take this
|
||||||
|
rule: the ERC-20 `transfer`/`approve` line (`src/popup/views/approval.js`) and
|
||||||
|
the swap's `Amount`, `Min. received`, `Moved from the sender` and
|
||||||
|
`Permit2 allowance` lines and the `Paid to` line of a move into the router
|
||||||
|
(`src/shared/uniswap.js`). The token permission warning on the signature screen
|
||||||
|
takes the same rule for its amounts. An unbounded allowance or permit needs no
|
||||||
|
scale to describe and is still shown as `Unlimited`. A source's answer counts
|
||||||
|
only if it is a whole number from 0 to 80: `decimals()` returns a `uint8`, but
|
||||||
|
`formatUnits()` cannot format more than 80 decimal places, so a token that
|
||||||
|
reports 81 to 255 is shown as one whose scale nothing knows.
|
||||||
|
|
||||||
The rule holds only if nothing invents a scale UPSTREAM of it. Those three
|
The rule holds only if nothing invents a scale UPSTREAM of it. Those three
|
||||||
sources are read as authoritative, so a value written into one of them cannot be
|
sources are read as authoritative, so a value written into one of them cannot be
|
||||||
@@ -1206,12 +1218,13 @@ and compare against. Reading the stored field directly instead answers `null`
|
|||||||
for a bundled or tracked token the explorer merely omitted, which is not a
|
for a bundled or tracked token the explorer merely omitted, which is not a
|
||||||
refusal the wallet has any reason to make.
|
refusal the wallet has any reason to make.
|
||||||
|
|
||||||
The Send screen also consults every address's explorer reports, so a contract
|
While "Use Blockscout" is on, the Send screen also consults every address's
|
||||||
two addresses report different `decimals` for has no scale there, and the stored
|
explorer reports. So a contract two addresses report different `decimals` for
|
||||||
balance, formatted at one of those scales, is withdrawn with it. The Send
|
has no scale there, and the stored balance, formatted at one of those scales, is
|
||||||
screen's `Current balance` and the confirmation screen's balance line then both
|
withdrawn with it. The Send screen's `Current balance` and the confirmation
|
||||||
read `unknown (SYMBOL)`. The balance list formats each explorer row as it is
|
screen's balance line then both read `unknown (SYMBOL)`. The balance list
|
||||||
fetched, without that cross-address check, and shows the row's figure.
|
formats each explorer row as it is fetched, without that cross-address check,
|
||||||
|
and shows the row's figure.
|
||||||
|
|
||||||
**Decoded amount lines on the transaction approval screen:** the `Amount` line
|
**Decoded amount lines on the transaction approval screen:** the `Amount` line
|
||||||
of a decoded ERC-20 call, and the `Amount` and `Min. received` lines of a
|
of a decoded ERC-20 call, and the `Amount` and `Min. received` lines of a
|
||||||
@@ -2051,6 +2064,9 @@ view would leave a wallet one click from deletion.
|
|||||||
`Unknown (could not be decoded)` with no symbol, not the transaction's ETH
|
`Unknown (could not be decoded)` with no symbol, not the transaction's ETH
|
||||||
value. When that swap's `Token In` is `ETH (native)`, the transaction's
|
value. When that swap's `Token In` is `ETH (native)`, the transaction's
|
||||||
ETH value in the network's currency, as for any other transaction
|
ETH value in the network's currency, as for any other transaction
|
||||||
|
- While "Use Blockscout" is off, TxApproval's line under the amount, when
|
||||||
|
the token's name or decimals are missing from the bundled list and the
|
||||||
|
tracked tokens
|
||||||
- To: color dot + full address + etherscan link; for a contract creation,
|
- To: color dot + full address + etherscan link; for a contract creation,
|
||||||
which has no recipient, "This transaction creates a new contract. It has
|
which has no recipient, "This transaction creates a new contract. It has
|
||||||
no recipient." instead
|
no recipient." instead
|
||||||
@@ -2083,7 +2099,9 @@ view would leave a wallet one click from deletion.
|
|||||||
- **Elements**:
|
- **Elements**:
|
||||||
- "Transaction Confirmed" heading
|
- "Transaction Confirmed" heading
|
||||||
- Decoded action well (shown when the transaction carried recognized
|
- Decoded action well (shown when the transaction carried recognized
|
||||||
calldata; the top-level Amount and To are hidden in that case)
|
calldata; the top-level Amount and To are hidden in that case). While "Use
|
||||||
|
Blockscout" is off, it has TxApproval's line under each token whose name
|
||||||
|
or decimals are missing from the bundled list and the tracked tokens.
|
||||||
- Amount + symbol
|
- Amount + symbol
|
||||||
- To: color dot + full address + etherscan link, or for a contract creation
|
- To: color dot + full address + etherscan link, or for a contract creation
|
||||||
the same sentence as on WaitTx
|
the same sentence as on WaitTx
|
||||||
@@ -2106,6 +2124,7 @@ view would leave a wallet one click from deletion.
|
|||||||
- Amount + symbol, or for a swap the approval screen showed no `Amount` for,
|
- Amount + symbol, or for a swap the approval screen showed no `Amount` for,
|
||||||
the same as on WaitTx: `Unknown (could not be decoded)`, unless its
|
the same as on WaitTx: `Unknown (could not be decoded)`, unless its
|
||||||
`Token In` is `ETH (native)`
|
`Token In` is `ETH (native)`
|
||||||
|
- While "Use Blockscout" is off, the same line under the amount as on WaitTx
|
||||||
- To: color dot + full address + etherscan link, or for a contract creation
|
- To: color dot + full address + etherscan link, or for a contract creation
|
||||||
the same sentence as on WaitTx
|
the same sentence as on WaitTx
|
||||||
- Error message (dashed border box)
|
- Error message (dashed border box)
|
||||||
@@ -2579,6 +2598,12 @@ view would leave a wallet one click from deletion.
|
|||||||
a swap pays only the From address, a `Paid to` line for each other address
|
a swap pays only the From address, a `Paid to` line for each other address
|
||||||
it pays, or saying that a token stays in the router or who it pays is
|
it pays, or saying that a token stays in the router or who it pays is
|
||||||
unknown (see Transaction Decoding)
|
unknown (see Transaction Decoding)
|
||||||
|
- While "Use Blockscout" is off, a line under each token in the decoded
|
||||||
|
action whose name or decimals are missing from the bundled list and the
|
||||||
|
tracked tokens: "This token was not looked up on Blockscout, because "Use
|
||||||
|
Blockscout" is off in Settings." Blockscout's report of the user's
|
||||||
|
balances gives a token's name and decimals only while the setting is on
|
||||||
|
(see Unknown token scale).
|
||||||
- From: color dot + full address + etherscan link
|
- From: color dot + full address + etherscan link
|
||||||
- Contract: color dot + full address + etherscan link, token symbol label if
|
- Contract: color dot + full address + etherscan link, token symbol label if
|
||||||
known; for a contract creation, the same sentence as on WaitTx
|
known; for a contract creation, the same sentence as on WaitTx
|
||||||
@@ -2658,7 +2683,11 @@ view would leave a wallet one click from deletion.
|
|||||||
the site puts in the message, except a `Permit`'s token, which is the
|
the site puts in the message, except a `Permit`'s token, which is the
|
||||||
domain's `verifyingContract`; any those fields do not give is shown as
|
domain's `verifyingContract`; any those fields do not give is shown as
|
||||||
`Unknown`, and the domain, type and message lines still follow. Only typed
|
`Unknown`, and the domain, type and message lines still follow. Only typed
|
||||||
data that cannot be read at all is shown as raw text.
|
data that cannot be read at all is shown as raw text. While "Use
|
||||||
|
Blockscout" is off, a token whose name or decimals are missing from the
|
||||||
|
bundled list and the tracked tokens has the same line under it as on
|
||||||
|
TxApproval. As there, Blockscout's report of the user's balances gives a
|
||||||
|
token's name and decimals only while the setting is on.
|
||||||
- Raw data (`personal_sign` and `eth_sign`): the message's hex exactly as
|
- Raw data (`personal_sign` and `eth_sign`): the message's hex exactly as
|
||||||
the site sent it. The bytes it encodes are what is signed, as an EIP-191
|
the site sent it. The bytes it encodes are what is signed, as an EIP-191
|
||||||
personal message.
|
personal message.
|
||||||
@@ -2837,6 +2866,8 @@ Sent the user's data only while the user has turned the setting on:
|
|||||||
for Home, AddressDetail and AddressToken
|
for Home, AddressDetail and AddressToken
|
||||||
- the balances of the ERC-20 tokens the user has not tracked, for Home,
|
- the balances of the ERC-20 tokens the user has not tracked, for Home,
|
||||||
AddressDetail, Send and DeleteAddress
|
AddressDetail, Send and DeleteAddress
|
||||||
|
- the names and decimals of those tokens, for TxApproval, SignApproval,
|
||||||
|
WaitTx, SuccessTx and ErrorTx
|
||||||
- token holder counts, for spam filtering
|
- token holder counts, for spam filtering
|
||||||
- a transaction's block, network details and calldata, for
|
- a transaction's block, network details and calldata, for
|
||||||
TransactionDetail
|
TransactionDetail
|
||||||
|
|||||||
@@ -811,6 +811,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="text-xs text-muted mb-1">Amount</div>
|
<div class="text-xs text-muted mb-1">Amount</div>
|
||||||
<div id="wait-tx-summary" class="font-bold"></div>
|
<div id="wait-tx-summary" class="font-bold"></div>
|
||||||
|
<div id="wait-tx-token-note"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="text-xs text-muted mb-1">To</div>
|
<div class="text-xs text-muted mb-1">To</div>
|
||||||
@@ -857,6 +858,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="text-xs text-muted mb-1">Amount</div>
|
<div class="text-xs text-muted mb-1">Amount</div>
|
||||||
<div id="error-tx-summary" class="font-bold"></div>
|
<div id="error-tx-summary" class="font-bold"></div>
|
||||||
|
<div id="error-tx-token-note"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="text-xs text-muted mb-1">To</div>
|
<div class="text-xs text-muted mb-1">To</div>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const {
|
|||||||
onViewLeave,
|
onViewLeave,
|
||||||
formatFee,
|
formatFee,
|
||||||
tokenLabel,
|
tokenLabel,
|
||||||
|
tokenLookupOffHtml,
|
||||||
tokenSourcesByChainId,
|
tokenSourcesByChainId,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||||
@@ -401,6 +402,10 @@ function showTxApproval(details) {
|
|||||||
if (d.address) {
|
if (d.address) {
|
||||||
if (d.isToken) {
|
if (d.isToken) {
|
||||||
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`;
|
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`;
|
||||||
|
detailsHtml += tokenLookupOffHtml(
|
||||||
|
d.address,
|
||||||
|
approvedTx.chainId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
detailsHtml += approvalAddressHtml(
|
detailsHtml += approvalAddressHtml(
|
||||||
d.address,
|
d.address,
|
||||||
@@ -623,10 +628,7 @@ function permitWarningHtml(types, primaryType, domain, message) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const sources = {
|
const sources = tokenSourcesByChainId();
|
||||||
trackedTokens: state.trackedTokens,
|
|
||||||
wallets: state.wallets,
|
|
||||||
};
|
|
||||||
const spender = addressOrNull(
|
const spender = addressOrNull(
|
||||||
declaredField(types, primaryType, message, "spender")?.value,
|
declaredField(types, primaryType, message, "spender")?.value,
|
||||||
);
|
);
|
||||||
@@ -655,6 +657,7 @@ function permitWarningHtml(types, primaryType, domain, message) {
|
|||||||
html += `<div class="mb-2"><div>Token</div>`;
|
html += `<div class="mb-2"><div>Token</div>`;
|
||||||
if (token) {
|
if (token) {
|
||||||
html += `<div>${escapeHtml(tokenLabel(token) || "Unknown token")}</div>`;
|
html += `<div>${escapeHtml(tokenLabel(token) || "Unknown token")}</div>`;
|
||||||
|
html += tokenLookupOffHtml(token);
|
||||||
html += approvalAddressHtml(token);
|
html += approvalAddressHtml(token);
|
||||||
} else {
|
} else {
|
||||||
html += `<div>Unknown</div>`;
|
html += `<div>Unknown</div>`;
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ const {
|
|||||||
truncateAmountNeverZero,
|
truncateAmountNeverZero,
|
||||||
isBelowOneMillionth,
|
isBelowOneMillionth,
|
||||||
} = require("../../shared/amountDisplay");
|
} = require("../../shared/amountDisplay");
|
||||||
const { resolveTokenSymbol } = require("../../shared/approvalAmount");
|
const {
|
||||||
|
resolveTokenDecimals,
|
||||||
|
resolveTokenSymbol,
|
||||||
|
} = require("../../shared/approvalAmount");
|
||||||
const { getProvider } = require("../../shared/balances");
|
const { getProvider } = require("../../shared/balances");
|
||||||
const { DEBUG } = require("../../shared/constants");
|
const { DEBUG } = require("../../shared/constants");
|
||||||
const { escapeHtml } = require("../../shared/html");
|
const { escapeHtml } = require("../../shared/html");
|
||||||
@@ -306,10 +309,16 @@ function nativeCurrency() {
|
|||||||
// names a token on one network only, and every token balance held is the
|
// names a token on one network only, and every token balance held is the
|
||||||
// active network's, so another network gets the tokens tracked on it
|
// active network's, so another network gets the tokens tracked on it
|
||||||
// (state.networkTrackedTokens, see applyChainSwitchFields()) and no balances.
|
// (state.networkTrackedTokens, see applyChainSwitchFields()) and no balances.
|
||||||
|
//
|
||||||
|
// The balances give what Blockscout reported for each token. So they are given
|
||||||
|
// only while "Use Blockscout" is on.
|
||||||
function tokenSourcesByChainId(chainId) {
|
function tokenSourcesByChainId(chainId) {
|
||||||
const net = chainId ? networkByChainId(chainId) : currentNetwork();
|
const net = chainId ? networkByChainId(chainId) : currentNetwork();
|
||||||
if (net && net.id === state.networkId) {
|
if (net && net.id === state.networkId) {
|
||||||
return { trackedTokens: state.trackedTokens, wallets: state.wallets };
|
return {
|
||||||
|
trackedTokens: state.trackedTokens,
|
||||||
|
wallets: state.useBlockscout ? state.wallets : [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
trackedTokens: (net && state.networkTrackedTokens[net.id]) || [],
|
trackedTokens: (net && state.networkTrackedTokens[net.id]) || [],
|
||||||
@@ -317,11 +326,32 @@ function tokenSourcesByChainId(chainId) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The line shown under a token while "Use Blockscout" is off, when its name or
|
||||||
|
// decimals are missing from the bundled list and the tracked tokens. Otherwise
|
||||||
|
// "". The token is a contract address on the network with `chainId`, or on the
|
||||||
|
// active network when none is given. "ETH" and null are no contract, and get
|
||||||
|
// "".
|
||||||
|
function tokenLookupOffHtml(token, chainId) {
|
||||||
|
if (state.useBlockscout || !token || token === "ETH") return "";
|
||||||
|
const sources = tokenSourcesByChainId(chainId);
|
||||||
|
if (
|
||||||
|
resolveTokenSymbol(token, sources) !== null &&
|
||||||
|
resolveTokenDecimals(token, sources) !== null
|
||||||
|
) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
'<div class="text-xs text-muted">This token was not looked up on' +
|
||||||
|
' Blockscout, because "Use Blockscout" is off in Settings.</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// The symbol shown for a token line on the network with `chainId`, or on the
|
// The symbol shown for a token line on the network with `chainId`, or on the
|
||||||
// active network when none is given: from the bundled list, the tokens the
|
// active network when none is given: from the bundled list, the tokens the
|
||||||
// user tracks, and the explorer's report, the sources the amount line's scale
|
// user tracks, and, while "Use Blockscout" is on, the explorer's report. The
|
||||||
// comes from. Null when no source names one, so the token lines keep saying
|
// amount line's scale comes from the same sources. Null when no source names
|
||||||
// `Unknown token` for a token nothing knows.
|
// one, so the token lines keep saying `Unknown token` for a token nothing
|
||||||
|
// knows.
|
||||||
function tokenLabel(address, chainId) {
|
function tokenLabel(address, chainId) {
|
||||||
return resolveTokenSymbol(address, tokenSourcesByChainId(chainId));
|
return resolveTokenSymbol(address, tokenSourcesByChainId(chainId));
|
||||||
}
|
}
|
||||||
@@ -796,6 +826,7 @@ module.exports = {
|
|||||||
unknownableAmount,
|
unknownableAmount,
|
||||||
nativeCurrency,
|
nativeCurrency,
|
||||||
tokenSourcesByChainId,
|
tokenSourcesByChainId,
|
||||||
|
tokenLookupOffHtml,
|
||||||
tokenLabel,
|
tokenLabel,
|
||||||
formatFee,
|
formatFee,
|
||||||
addressDotHtml,
|
addressDotHtml,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const {
|
|||||||
renderAddressHtml,
|
renderAddressHtml,
|
||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
goBack,
|
goBack,
|
||||||
|
tokenSourcesByChainId,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, currentAddress } = require("../../shared/state");
|
const { state, currentAddress } = require("../../shared/state");
|
||||||
let ctx;
|
let ctx;
|
||||||
@@ -187,7 +188,9 @@ function renderSendTokenSelect(addr) {
|
|||||||
// so the transfer is encoded with the number the user read rather than with
|
// so the transfer is encoded with the number the user read rather than with
|
||||||
// whatever the contract answers at signing time (see
|
// whatever the contract answers at signing time (see
|
||||||
// src/shared/transferAmount.js). balances.js is formatting one explorer row at
|
// src/shared/transferAmount.js). balances.js is formatting one explorer row at
|
||||||
// fetch time and cannot consult a state it is in the middle of replacing.
|
// fetch time and cannot consult a state it is in the middle of replacing. As on
|
||||||
|
// the approval screens, tokenSourcesByChainId() gives the wallets only while
|
||||||
|
// "Use Blockscout" is on.
|
||||||
//
|
//
|
||||||
// The two resolutions can therefore differ, and where they do, the stored
|
// The two resolutions can therefore differ, and where they do, the stored
|
||||||
// `balance` is a quantity computed at a scale this screen has just declined to
|
// `balance` is a quantity computed at a scale this screen has just declined to
|
||||||
@@ -200,10 +203,7 @@ function tokenBalanceAndDecimals(addr, token) {
|
|||||||
const tb = (addr.tokenBalances || []).find(
|
const tb = (addr.tokenBalances || []).find(
|
||||||
(t) => t.address.toLowerCase() === token.toLowerCase(),
|
(t) => t.address.toLowerCase() === token.toLowerCase(),
|
||||||
);
|
);
|
||||||
const tokenDecimals = resolveTokenDecimals(token, {
|
const tokenDecimals = resolveTokenDecimals(token, tokenSourcesByChainId());
|
||||||
trackedTokens: state.trackedTokens,
|
|
||||||
wallets: state.wallets,
|
|
||||||
});
|
|
||||||
if (!tb) return { tokenBalance: "0", tokenDecimals };
|
if (!tb) return { tokenBalance: "0", tokenDecimals };
|
||||||
if (tokenDecimals === null) return { tokenBalance: null, tokenDecimals };
|
if (tokenDecimals === null) return { tokenBalance: null, tokenDecimals };
|
||||||
return { tokenBalance: tb.balance ?? null, tokenDecimals };
|
return { tokenBalance: tb.balance ?? null, tokenDecimals };
|
||||||
|
|||||||
@@ -252,10 +252,12 @@ async function renderSiteLists() {
|
|||||||
|
|
||||||
// A network switch, made by the user here or saved by the background once the
|
// A network switch, made by the user here or saved by the background once the
|
||||||
// user approved a site's request (src/popup/index.js), leaves every screen on
|
// user approved a site's request (src/popup/index.js), leaves every screen on
|
||||||
// the page showing the previous network. These two leave the page as README's
|
// the page showing the previous network. Two functions leave the page as
|
||||||
// Screen Map describes, the first before the switch is saved, so that save
|
// README's Screen Map describes:
|
||||||
// keeps the cut for a popup closed straight after, the second once `state`
|
//
|
||||||
// holds the new network.
|
// - cutViewStack(), before the switch is saved, so that save keeps the cut for
|
||||||
|
// a popup closed straight after
|
||||||
|
// - showNewSettings(), once `state` holds the new network
|
||||||
//
|
//
|
||||||
// The stack is cut at the first screen that cannot be rendered from saved
|
// The stack is cut at the first screen that cannot be rendered from saved
|
||||||
// state, such as Send or AddToken, and at AddressToken unless it shows the
|
// state, such as Send or AddToken, and at AddressToken unless it shows the
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const {
|
|||||||
displaySymbol,
|
displaySymbol,
|
||||||
clearViewStack,
|
clearViewStack,
|
||||||
tokenLabel,
|
tokenLabel,
|
||||||
|
tokenLookupOffHtml,
|
||||||
reviewedNetworkProvider,
|
reviewedNetworkProvider,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state } = require("../../shared/state");
|
const { state } = require("../../shared/state");
|
||||||
@@ -108,6 +109,10 @@ function startWait(txInfo, txHash, broadcastTime, pollNow) {
|
|||||||
}
|
}
|
||||||
$("wait-tx-summary").textContent =
|
$("wait-tx-summary").textContent =
|
||||||
txInfo.amount + (symbol ? " " + symbol : "");
|
txInfo.amount + (symbol ? " " + symbol : "");
|
||||||
|
$("wait-tx-token-note").innerHTML = tokenLookupOffHtml(
|
||||||
|
txInfo.token,
|
||||||
|
txInfo.chainId,
|
||||||
|
);
|
||||||
$("wait-tx-to").innerHTML = toAddressHtml(txInfo.to, txInfo.chainId);
|
$("wait-tx-to").innerHTML = toAddressHtml(txInfo.to, txInfo.chainId);
|
||||||
$("wait-tx-hash").innerHTML = txHashHtml(txHash, txInfo.chainId);
|
$("wait-tx-hash").innerHTML = txHashHtml(txHash, txInfo.chainId);
|
||||||
attachCopyHandlers("view-wait-tx");
|
attachCopyHandlers("view-wait-tx");
|
||||||
@@ -282,6 +287,7 @@ function decodedDetailsHtml(decoded, chainId) {
|
|||||||
if (d.isToken) {
|
if (d.isToken) {
|
||||||
const sym = tokenLabel(d.address, chainId) || "Unknown token";
|
const sym = tokenLabel(d.address, chainId) || "Unknown token";
|
||||||
html += `<div class="font-bold">${escapeHtml(sym)}</div>`;
|
html += `<div class="font-bold">${escapeHtml(sym)}</div>`;
|
||||||
|
html += tokenLookupOffHtml(d.address, chainId);
|
||||||
html += toAddressHtml(d.address, chainId);
|
html += toAddressHtml(d.address, chainId);
|
||||||
} else {
|
} else {
|
||||||
html += toAddressHtml(d.address, chainId);
|
html += toAddressHtml(d.address, chainId);
|
||||||
@@ -345,6 +351,7 @@ function showError(txInfo, txHash, message) {
|
|||||||
state.viewData = {
|
state.viewData = {
|
||||||
amount: txInfo.amount,
|
amount: txInfo.amount,
|
||||||
symbol: symbol,
|
symbol: symbol,
|
||||||
|
token: txInfo.token,
|
||||||
to: txInfo.to,
|
to: txInfo.to,
|
||||||
hash: txHash || null,
|
hash: txHash || null,
|
||||||
message: message,
|
message: message,
|
||||||
@@ -358,6 +365,7 @@ function renderError() {
|
|||||||
if (!d || !d.message) return;
|
if (!d || !d.message) return;
|
||||||
$("error-tx-summary").textContent =
|
$("error-tx-summary").textContent =
|
||||||
d.amount + (d.symbol ? " " + d.symbol : "");
|
d.amount + (d.symbol ? " " + d.symbol : "");
|
||||||
|
$("error-tx-token-note").innerHTML = tokenLookupOffHtml(d.token, d.chainId);
|
||||||
$("error-tx-to").innerHTML = toAddressHtml(d.to, d.chainId);
|
$("error-tx-to").innerHTML = toAddressHtml(d.to, d.chainId);
|
||||||
$("error-tx-message").textContent = d.message;
|
$("error-tx-message").textContent = d.message;
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ function walletsHolding(token, decimals) {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state.trackedTokens = [];
|
state.trackedTokens = [];
|
||||||
state.wallets = [];
|
state.wallets = [];
|
||||||
|
// The approval screen reads the explorer's report only while "Use
|
||||||
|
// Blockscout" is on.
|
||||||
|
state.useBlockscout = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveTokenDecimals", () => {
|
describe("resolveTokenDecimals", () => {
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
// While "Use Blockscout" is off, the approval screens and the screens after
|
||||||
|
// TxApproval name a token, and scale its amount, from the bundled list and the
|
||||||
|
// tracked tokens alone (https://git.eeqj.de/sneak/AutistMask/issues/668). A
|
||||||
|
// token they lack gets a line saying it was not looked up on Blockscout. While
|
||||||
|
// the setting is on, what Blockscout reported for a token the user holds names
|
||||||
|
// and scales it, as before.
|
||||||
|
//
|
||||||
|
// Booted through the real popup entry point, as the approval window or as a
|
||||||
|
// popup reopened onto SuccessTx. The vault is mocked so that no password has to
|
||||||
|
// be hashed: the stored secret decrypts to the test key below.
|
||||||
|
|
||||||
|
jest.mock("../src/shared/vault", () => ({
|
||||||
|
decryptWithPassword: jest.fn(),
|
||||||
|
encryptWithPassword: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { Interface, Wallet } = require("ethers");
|
||||||
|
const {
|
||||||
|
bootPopup,
|
||||||
|
cleanupPopup,
|
||||||
|
unversionedValidProfile,
|
||||||
|
} = require("./support/popupBoot");
|
||||||
|
|
||||||
|
// A published test key, never used for real funds.
|
||||||
|
const SIGNER_KEY =
|
||||||
|
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
|
||||||
|
const signer = new Wallet(SIGNER_KEY);
|
||||||
|
const ORIGIN = "https://dapp.example";
|
||||||
|
const RECIPIENT = "0x2222222222222222222222222222222222222222";
|
||||||
|
const TX_HASH = "0x" + "ab".repeat(32);
|
||||||
|
// Outside the bundled list, and not tracked.
|
||||||
|
const TOKEN = "0xE2E0000000000000000000000000000000000E2e";
|
||||||
|
// In the bundled list, at 6 decimals.
|
||||||
|
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
||||||
|
|
||||||
|
const LINE =
|
||||||
|
'This token was not looked up on Blockscout, because "Use Blockscout" is off in Settings.';
|
||||||
|
|
||||||
|
// TOKEN as Blockscout reports the user's holding of it.
|
||||||
|
const TOKEN_ROW = {
|
||||||
|
address: TOKEN,
|
||||||
|
name: "Novel",
|
||||||
|
symbol: "NOVEL",
|
||||||
|
decimals: "6",
|
||||||
|
balance: "5000.0",
|
||||||
|
holders: 5000,
|
||||||
|
};
|
||||||
|
|
||||||
|
// A transfer of 5,000 of a 6-decimal token to RECIPIENT.
|
||||||
|
const TRANSFER_DATA = new Interface([
|
||||||
|
"function transfer(address to, uint256 amount)",
|
||||||
|
]).encodeFunctionData("transfer", [RECIPIENT, 5000000000n]);
|
||||||
|
|
||||||
|
// An EIP-2612 permit letting RECIPIENT take 5,000 of TOKEN.
|
||||||
|
const PERMIT = {
|
||||||
|
types: {
|
||||||
|
EIP712Domain: [
|
||||||
|
{ name: "name", type: "string" },
|
||||||
|
{ name: "chainId", type: "uint256" },
|
||||||
|
{ name: "verifyingContract", type: "address" },
|
||||||
|
],
|
||||||
|
Permit: [
|
||||||
|
{ name: "owner", type: "address" },
|
||||||
|
{ name: "spender", type: "address" },
|
||||||
|
{ name: "value", type: "uint256" },
|
||||||
|
{ name: "nonce", type: "uint256" },
|
||||||
|
{ name: "deadline", type: "uint256" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
primaryType: "Permit",
|
||||||
|
domain: { name: "Novel", chainId: 1, verifyingContract: TOKEN },
|
||||||
|
message: {
|
||||||
|
owner: signer.address,
|
||||||
|
spender: RECIPIENT,
|
||||||
|
value: "5000000000",
|
||||||
|
nonce: "0",
|
||||||
|
deadline: "1790000000",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// The stored profile: the user's one address holds `tokenBalances`.
|
||||||
|
function profile(useBlockscout, tokenBalances, extra) {
|
||||||
|
return unversionedValidProfile({
|
||||||
|
useBlockscout,
|
||||||
|
wallets: [
|
||||||
|
{
|
||||||
|
type: "key",
|
||||||
|
name: "Wallet 1",
|
||||||
|
encryptedSecret: "encrypted-key",
|
||||||
|
addresses: [
|
||||||
|
{
|
||||||
|
address: signer.address,
|
||||||
|
balance: "1.0",
|
||||||
|
tokenBalances,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeAddress: signer.address,
|
||||||
|
allowedSites: { [signer.address]: [ORIGIN] },
|
||||||
|
...extra,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The approval window for the site's request: a transfer sent to `token`, or
|
||||||
|
// with `signParams`, a signature. The background answers the signed
|
||||||
|
// transaction with `txResponse`.
|
||||||
|
function bootApproval(stored, { token = TOKEN, signParams, txResponse } = {}) {
|
||||||
|
return bootPopup(stored, {
|
||||||
|
search: "?approval=approval-1",
|
||||||
|
runtime: {
|
||||||
|
connect: () => ({ postMessage: () => {} }),
|
||||||
|
sendMessage: (msg, reply) => {
|
||||||
|
if (msg.type === "AUTISTMASK_GET_APPROVAL") {
|
||||||
|
reply({
|
||||||
|
type: signParams ? "sign" : "tx",
|
||||||
|
origin: ORIGIN,
|
||||||
|
isPhishingDomain: false,
|
||||||
|
approvedFrom: signer.address,
|
||||||
|
signParams,
|
||||||
|
approvedTx: {
|
||||||
|
type: 2,
|
||||||
|
from: signer.address,
|
||||||
|
chainId: "0x1",
|
||||||
|
nonce: "0x7",
|
||||||
|
gasLimit: "0x30d40",
|
||||||
|
maxPriorityFeePerGas: "0x3b9aca00",
|
||||||
|
maxFeePerGas: "0x77359400",
|
||||||
|
to: token,
|
||||||
|
value: "0x0",
|
||||||
|
data: TRANSFER_DATA,
|
||||||
|
accessList: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (msg.type === "AUTISTMASK_TX_RESPONSE") {
|
||||||
|
reply(txResponse);
|
||||||
|
} else {
|
||||||
|
reply(undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// The page logs its failed RPC reads; the stub node in popupBoot.js
|
||||||
|
// answers none.
|
||||||
|
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupPopup();
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("TxApproval", () => {
|
||||||
|
test("says a token it cannot name was not looked up on Blockscout while Use Blockscout is off", async () => {
|
||||||
|
const page = await bootApproval(profile(false, []));
|
||||||
|
const details = page.node("approve-tx-decoded-details").innerHTML;
|
||||||
|
expect(details).toContain("Unknown token");
|
||||||
|
expect(details).toContain(LINE);
|
||||||
|
expect(details).toContain("5000000000 base units (decimals unknown)");
|
||||||
|
cleanupPopup();
|
||||||
|
|
||||||
|
// The bundled list names USDC, so there is nothing to say.
|
||||||
|
const bundled = await bootApproval(profile(false, []), {
|
||||||
|
token: USDC,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
bundled.node("approve-tx-decoded-details").innerHTML,
|
||||||
|
).not.toContain(LINE);
|
||||||
|
cleanupPopup();
|
||||||
|
|
||||||
|
const on = await bootApproval(profile(true, [TOKEN_ROW]));
|
||||||
|
const named = on.node("approve-tx-decoded-details").innerHTML;
|
||||||
|
expect(named).toContain("5000.0000 NOVEL");
|
||||||
|
expect(named).not.toContain(LINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("names nothing from a Blockscout entry still in the balances while Use Blockscout is off", async () => {
|
||||||
|
const page = await bootApproval(profile(false, [TOKEN_ROW]));
|
||||||
|
const details = page.node("approve-tx-decoded-details").innerHTML;
|
||||||
|
expect(details).not.toContain("NOVEL");
|
||||||
|
expect(details).toContain("Unknown token");
|
||||||
|
expect(details).toContain(LINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["WaitTx", { txHash: TX_HASH }, "wait-tx"],
|
||||||
|
[
|
||||||
|
"ErrorTx",
|
||||||
|
{ error: "The transaction could not be sent.", retryable: false },
|
||||||
|
"error-tx",
|
||||||
|
],
|
||||||
|
])(
|
||||||
|
"%s after it says so under the amount while Use Blockscout is off",
|
||||||
|
async (_name, txResponse, view) => {
|
||||||
|
const page = await bootApproval(profile(false, []), {
|
||||||
|
txResponse,
|
||||||
|
});
|
||||||
|
const { decryptWithPassword } = require("../src/shared/vault");
|
||||||
|
decryptWithPassword.mockResolvedValue(SIGNER_KEY);
|
||||||
|
page.node("approve-tx-password").value = "any password";
|
||||||
|
await page.click("btn-approve-tx");
|
||||||
|
expect(page.visibleViews()).toEqual([view]);
|
||||||
|
expect(page.node(view + "-token-note").innerHTML).toBe(
|
||||||
|
`<div class="text-xs text-muted">${LINE}</div>`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SuccessTx says so under the decoded token while Use Blockscout is off", async () => {
|
||||||
|
const page = await bootPopup(
|
||||||
|
profile(false, [], {
|
||||||
|
currentView: "success-tx",
|
||||||
|
viewStack: ["main"],
|
||||||
|
viewData: {
|
||||||
|
amount: "5000000000 base units (decimals unknown)",
|
||||||
|
symbol: "?",
|
||||||
|
to: RECIPIENT,
|
||||||
|
hash: TX_HASH,
|
||||||
|
blockNumber: 21000000,
|
||||||
|
chainId: "0x1",
|
||||||
|
decoded: {
|
||||||
|
name: "Token Transfer",
|
||||||
|
description: "Transfer ERC-20 token",
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
label: "Token",
|
||||||
|
value: TOKEN,
|
||||||
|
address: TOKEN,
|
||||||
|
isToken: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(page.visibleViews()).toEqual(["success-tx"]);
|
||||||
|
const decoded = page.node("success-tx-decoded").innerHTML;
|
||||||
|
expect(decoded).toContain("Unknown token");
|
||||||
|
expect(decoded).toContain(LINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SignApproval says so under a permit's token while Use Blockscout is off", async () => {
|
||||||
|
const signParams = {
|
||||||
|
method: "eth_signTypedData_v4",
|
||||||
|
typedData: JSON.stringify(PERMIT),
|
||||||
|
from: signer.address,
|
||||||
|
};
|
||||||
|
const page = await bootApproval(profile(false, []), { signParams });
|
||||||
|
const message = page.node("approve-sign-message").innerHTML;
|
||||||
|
expect(message).toContain("Unknown token");
|
||||||
|
expect(message).toContain(LINE);
|
||||||
|
cleanupPopup();
|
||||||
|
|
||||||
|
const on = await bootApproval(profile(true, [TOKEN_ROW]), { signParams });
|
||||||
|
const named = on.node("approve-sign-message").innerHTML;
|
||||||
|
expect(named).toContain("5000.0000 NOVEL");
|
||||||
|
expect(named).not.toContain(LINE);
|
||||||
|
});
|
||||||
@@ -168,6 +168,9 @@ beforeEach(() => {
|
|||||||
debugFetch.mockReset();
|
debugFetch.mockReset();
|
||||||
state.trackedTokens = [];
|
state.trackedTokens = [];
|
||||||
state.wallets = [];
|
state.wallets = [];
|
||||||
|
// The approval screens read the explorer's report only while the page's
|
||||||
|
// "Use Blockscout" is on.
|
||||||
|
state.useBlockscout = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("what fetchTokenBalances stores for an absent scale", () => {
|
describe("what fetchTokenBalances stores for an absent scale", () => {
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ function swapDetail(data, label) {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state.trackedTokens = [];
|
state.trackedTokens = [];
|
||||||
state.wallets = [];
|
state.wallets = [];
|
||||||
|
// The approval screen reads the explorer's report only while "Use
|
||||||
|
// Blockscout" is on.
|
||||||
|
state.useBlockscout = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("a swap of a token outside the bundled list", () => {
|
describe("a swap of a token outside the bundled list", () => {
|
||||||
|
|||||||
Reference in new issue
Block a user