Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a39df08b0 |
@@ -846,19 +846,13 @@ 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 a network's `nativeCurrency` in
|
The native token's label is the active network's `nativeCurrency` in
|
||||||
`src/shared/networks.js`: `ETH` on mainnet, `SepoliaETH` on Sepolia. The
|
`src/shared/networks.js`: `ETH` on mainnet, `SepoliaETH` on Sepolia. Wherever
|
||||||
wallet's balances and the Send and confirmation screens, which send on the
|
this document shows ETH as the label of a native balance, value or fee, in a
|
||||||
active network, use the active network's. A transaction's figures use the one of
|
"Native ETH transfer" type line, in the contract-recipient warning or in the
|
||||||
the network its chain id names, whichever network is active: the value and fee
|
refusal of a fee above 1 ETH, Sepolia shows `SepoliaETH`. The swap lines keep
|
||||||
on the approval screen, the amount on the wait, success and error screens, the
|
`ETH`, the router's own name for the native currency, and the ETH/USD price
|
||||||
transaction history and the transaction detail screen, and the refusal of a fee
|
line, shown on mainnet only, keeps its fixed wording.
|
||||||
above 1 ETH. A chain id that names no network reads `ETH`. Wherever this
|
|
||||||
document shows ETH as the label of a native balance, value or fee, in a "Native
|
|
||||||
ETH transfer" type line, in the contract-recipient warning or in that refusal,
|
|
||||||
Sepolia shows `SepoliaETH`. The swap lines keep `ETH`, the router's own name for
|
|
||||||
the native currency, and the ETH/USD price line, shown on mainnet only, keeps
|
|
||||||
its fixed wording.
|
|
||||||
|
|
||||||
**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
|
||||||
|
|||||||
@@ -45,18 +45,17 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-10-04: The native token's label follows the network
|
- 2026-10-04: The native token's label follows the active network
|
||||||
([#372](https://git.eeqj.de/sneak/AutistMask/issues/372)). `networks.js` gives
|
([#372](https://git.eeqj.de/sneak/AutistMask/issues/372)). `networks.js` gives
|
||||||
each network a `nativeCurrency` and nothing read it: every screen wrote `ETH`,
|
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`. Every native
|
so on Sepolia the balance, the value and the fee all read `ETH`. The balance
|
||||||
figure now reads `nativeCurrency`, which is `ETH` on mainnet and `SepoliaETH`
|
lists, Send, confirmation, approval, wait, success and error screens, the
|
||||||
on Sepolia: the balance lists, Send and confirmation screens and the
|
transaction history, the contract-recipient warning and the refusal of a fee
|
||||||
contract-recipient warning the active network's; the approval, wait, success,
|
above the limit now read `nativeCurrency`, which is `ETH` on mainnet and
|
||||||
error and transaction detail screens, the transaction history and the refusal
|
`SepoliaETH` on Sepolia. A token claiming any network's `nativeCurrency` is
|
||||||
of a fee above the limit that of the network the transaction's chain id names.
|
dropped as a fake, as one claiming `ETH` already was, and the transaction
|
||||||
A token claiming any network's `nativeCurrency` is dropped as a fake, as one
|
detail screen calls an entry a token transfer when it has a token contract,
|
||||||
claiming `ETH` already was, and the transaction detail screen calls an entry a
|
not by its symbol.
|
||||||
token transfer when it has a token contract, not by its symbol.
|
|
||||||
- 2026-10-04: A popup that is already open when the stored profile becomes
|
- 2026-10-04: A popup that is already open when the stored profile becomes
|
||||||
unreadable moves to the recovery screen
|
unreadable moves to the recovery screen
|
||||||
([#373](https://git.eeqj.de/sneak/AutistMask/issues/373)). It used to stay on
|
([#373](https://git.eeqj.de/sneak/AutistMask/issues/373)). It used to stay on
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ const {
|
|||||||
addressTitle,
|
addressTitle,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
renderAddressHtml,
|
renderAddressHtml,
|
||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
goBack,
|
goBack,
|
||||||
pushCurrentView,
|
pushCurrentView,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
const { state, saveState } = require("../../shared/state");
|
||||||
const { formatAddressTotal, getAddressValue } = require("../../shared/prices");
|
const { formatAddressTotal, getAddressValue } = require("../../shared/prices");
|
||||||
const {
|
const {
|
||||||
fetchRecentTransactions,
|
fetchRecentTransactions,
|
||||||
@@ -153,7 +154,7 @@ async function loadTransactions(address) {
|
|||||||
const rawTxs = await fetchRecentTransactions(
|
const rawTxs = await fetchRecentTransactions(
|
||||||
address,
|
address,
|
||||||
state.blockscoutUrl,
|
state.blockscoutUrl,
|
||||||
currentNetwork().chainId,
|
nativeCurrency(),
|
||||||
);
|
);
|
||||||
const result = filterTransactions(rawTxs, {
|
const result = filterTransactions(rawTxs, {
|
||||||
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const {
|
|||||||
goBack,
|
goBack,
|
||||||
pushCurrentView,
|
pushCurrentView,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
const { state, saveState } = require("../../shared/state");
|
||||||
const { TOKEN_BY_ADDRESS, resolveSymbol } = require("../../shared/tokenList");
|
const { TOKEN_BY_ADDRESS, resolveSymbol } = require("../../shared/tokenList");
|
||||||
const { formatUsd, getPrice } = require("../../shared/prices");
|
const { formatUsd, getPrice } = require("../../shared/prices");
|
||||||
const {
|
const {
|
||||||
@@ -227,7 +227,7 @@ async function loadTransactions(address, tokenId) {
|
|||||||
const rawTxs = await fetchRecentTransactions(
|
const rawTxs = await fetchRecentTransactions(
|
||||||
address,
|
address,
|
||||||
state.blockscoutUrl,
|
state.blockscoutUrl,
|
||||||
currentNetwork().chainId,
|
nativeCurrency(),
|
||||||
);
|
);
|
||||||
const result = filterTransactions(rawTxs, {
|
const result = filterTransactions(rawTxs, {
|
||||||
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
hideSpoofedSymbols: state.hideSpoofedSymbols,
|
||||||
|
|||||||
@@ -10,12 +10,10 @@ 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 {
|
const { networkByChainId } = require("../../shared/networks");
|
||||||
networkByChainId,
|
|
||||||
nativeCurrencyByChainId,
|
|
||||||
} = require("../../shared/networks");
|
|
||||||
const {
|
const {
|
||||||
formatEther,
|
formatEther,
|
||||||
formatUnits,
|
formatUnits,
|
||||||
@@ -222,12 +220,8 @@ function showTxFee(approvedTx) {
|
|||||||
const gasLimit = BigInt(approvedTx.gasLimit);
|
const gasLimit = BigInt(approvedTx.gasLimit);
|
||||||
const feePerGas = BigInt(approvedTx.maxFeePerGas || approvedTx.gasPrice);
|
const feePerGas = BigInt(approvedTx.maxFeePerGas || approvedTx.gasPrice);
|
||||||
// Through formatFee(), as the confirmation screen's fee is, so the same
|
// Through formatFee(), as the confirmation screen's fee is, so the same
|
||||||
// fee reads the same on both. In the native currency of the network shown
|
// fee reads the same on both.
|
||||||
// above, as the value is.
|
$("approve-tx-fee").textContent = formatFee(gasLimit * feePerGas);
|
||||||
$("approve-tx-fee").textContent = formatFee(
|
|
||||||
gasLimit * feePerGas,
|
|
||||||
nativeCurrencyByChainId(approvedTx.chainId),
|
|
||||||
);
|
|
||||||
|
|
||||||
let detail =
|
let detail =
|
||||||
gasLimit.toString() +
|
gasLimit.toString() +
|
||||||
@@ -271,7 +265,6 @@ function showTxApproval(details) {
|
|||||||
amount: formatTxValue(ethValue),
|
amount: formatTxValue(ethValue),
|
||||||
token: "ETH",
|
token: "ETH",
|
||||||
tokenSymbol: null,
|
tokenSymbol: null,
|
||||||
chainId: approvedTx.chainId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// If this is an ERC-20 call, try to extract the real recipient and amount
|
// If this is an ERC-20 call, try to extract the real recipient and amount
|
||||||
@@ -337,14 +330,10 @@ function showTxApproval(details) {
|
|||||||
const ethPrice = getPrice("ETH");
|
const ethPrice = getPrice("ETH");
|
||||||
const ethUsd = ethPrice ? parseFloat(ethValueFormatted) * ethPrice : null;
|
const ethUsd = ethPrice ? parseFloat(ethValueFormatted) * ethPrice : null;
|
||||||
const usdStr = formatUsd(ethUsd);
|
const usdStr = formatUsd(ethUsd);
|
||||||
// In the native currency of the network the transaction is for, which the
|
|
||||||
// Network line names, not the active network's: a site can switch the
|
|
||||||
// active network after this transaction is prepared and back before it is
|
|
||||||
// signed.
|
|
||||||
$("approve-tx-value").textContent =
|
$("approve-tx-value").textContent =
|
||||||
ethValueFormatted +
|
ethValueFormatted +
|
||||||
" " +
|
" " +
|
||||||
nativeCurrencyByChainId(approvedTx.chainId) +
|
nativeCurrency() +
|
||||||
(usdStr ? " (" + usdStr + ")" : "");
|
(usdStr ? " (" + usdStr + ")" : "");
|
||||||
|
|
||||||
showTxFee(approvedTx);
|
showTxFee(approvedTx);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const {
|
|||||||
onViewLeave,
|
onViewLeave,
|
||||||
formatFee,
|
formatFee,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, currentNetwork } = require("../../shared/state");
|
const { state } = require("../../shared/state");
|
||||||
const { getSignerForAddress } = require("../../shared/wallet");
|
const { getSignerForAddress } = require("../../shared/wallet");
|
||||||
const { decryptWithPassword } = require("../../shared/vault");
|
const { decryptWithPassword } = require("../../shared/vault");
|
||||||
const { formatUsd, getPrice } = require("../../shared/prices");
|
const { formatUsd, getPrice } = require("../../shared/prices");
|
||||||
@@ -398,8 +398,7 @@ async function estimateGas(txInfo) {
|
|||||||
// The fee line goes through formatFee(), as the approval screen's
|
// The fee line goes through formatFee(), as the approval screen's
|
||||||
// does, so the same fee reads the same on both.
|
// does, so the same fee reads the same on both.
|
||||||
if (estimateWei !== null && estimateWei < gasCostWei) {
|
if (estimateWei !== null && estimateWei < gasCostWei) {
|
||||||
$("confirm-fee-amount").textContent =
|
$("confirm-fee-amount").textContent = "~" + formatFee(estimateWei);
|
||||||
"~" + formatFee(estimateWei, nativeCurrency());
|
|
||||||
$("confirm-fee-reserve").textContent =
|
$("confirm-fee-reserve").textContent =
|
||||||
"up to " +
|
"up to " +
|
||||||
truncateAmountNeverZero(formatEther(gasCostWei)) +
|
truncateAmountNeverZero(formatEther(gasCostWei)) +
|
||||||
@@ -411,10 +410,7 @@ async function estimateGas(txInfo) {
|
|||||||
// No spread to report: either there is no estimate, or the node
|
// No spread to report: either there is no estimate, or the node
|
||||||
// quotes a gas price at or above maxFeePerGas, so the expected
|
// quotes a gas price at or above maxFeePerGas, so the expected
|
||||||
// cost is not below the reserve. Show the reserve alone.
|
// cost is not below the reserve. Show the reserve alone.
|
||||||
$("confirm-fee-amount").textContent = formatFee(
|
$("confirm-fee-amount").textContent = formatFee(gasCostWei);
|
||||||
gasCostWei,
|
|
||||||
nativeCurrency(),
|
|
||||||
);
|
|
||||||
setVisible("confirm-fee-reserve", false);
|
setVisible("confirm-fee-reserve", false);
|
||||||
}
|
}
|
||||||
feeStatus = FEE_KNOWN;
|
feeStatus = FEE_KNOWN;
|
||||||
@@ -534,10 +530,6 @@ function init(_ctx) {
|
|||||||
$("btn-confirm-send").disabled = true;
|
$("btn-confirm-send").disabled = true;
|
||||||
$("btn-confirm-send").classList.add("text-muted");
|
$("btn-confirm-send").classList.add("text-muted");
|
||||||
|
|
||||||
// The network it is sent on. The wait, success and error screens
|
|
||||||
// label its amount by this, not by the network active when they draw.
|
|
||||||
pendingTx.chainId = currentNetwork().chainId;
|
|
||||||
|
|
||||||
let tx;
|
let tx;
|
||||||
try {
|
try {
|
||||||
const signer = getSignerForAddress(
|
const signer = getSignerForAddress(
|
||||||
|
|||||||
@@ -268,29 +268,25 @@ function unknownableAmount(balance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The active network's native token symbol, `ETH` on mainnet and `SepoliaETH`
|
// The active network's native token symbol, `ETH` on mainnet and `SepoliaETH`
|
||||||
// on Sepolia, as src/shared/networks.js names it. The wallet's balances and
|
// on Sepolia, as src/shared/networks.js names it. Every screen labels a native
|
||||||
// the Send and confirmation screens, which send on the active network, label a
|
// amount with this. The "ETH" that state.selectedToken and txInfo.token hold
|
||||||
// native amount with this; a transaction already made or requested is labelled
|
// is the native token's id, not its label, and stays "ETH" on every network.
|
||||||
// by its own chain id, through nativeCurrencyByChainId() in networks.js. 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() {
|
function nativeCurrency() {
|
||||||
return currentNetwork().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() and labelled `symbol`, the
|
// the ETH figure through truncateAmountNeverZero(), then its USD value when the
|
||||||
// native currency of the network the fee is paid on, then its USD value when
|
// ETH price is known. The USD value is of the exact fee, not of the truncated
|
||||||
// the ETH price is known. The USD value is of the exact fee, not of the
|
// figure.
|
||||||
// truncated figure.
|
function formatFee(wei) {
|
||||||
function formatFee(wei, symbol) {
|
|
||||||
const eth = formatEther(wei);
|
const eth = formatEther(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) +
|
truncateAmountNeverZero(eth) +
|
||||||
" " +
|
" " +
|
||||||
symbol +
|
nativeCurrency() +
|
||||||
(usd ? " (" + usd + ")" : "")
|
(usd ? " (" + usd + ")" : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,7 @@ const {
|
|||||||
attachCopyHandlers,
|
attachCopyHandlers,
|
||||||
pushCurrentView,
|
pushCurrentView,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const {
|
const { state, saveState, currentAddress } = require("../../shared/state");
|
||||||
state,
|
|
||||||
saveState,
|
|
||||||
currentAddress,
|
|
||||||
currentNetwork,
|
|
||||||
} = require("../../shared/state");
|
|
||||||
const { notify } = require("../../shared/browserApi");
|
const { notify } = require("../../shared/browserApi");
|
||||||
const {
|
const {
|
||||||
updateSendBalance,
|
updateSendBalance,
|
||||||
@@ -191,7 +186,7 @@ async function loadHomeTxs(ctx) {
|
|||||||
fetchRecentTransactions(
|
fetchRecentTransactions(
|
||||||
addr,
|
addr,
|
||||||
state.blockscoutUrl,
|
state.blockscoutUrl,
|
||||||
currentNetwork().chainId,
|
nativeCurrency(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const results = await Promise.all(fetches);
|
const results = await Promise.all(fetches);
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ const {
|
|||||||
etherscanLinkHtml,
|
etherscanLinkHtml,
|
||||||
explorerUrl,
|
explorerUrl,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
goBack,
|
goBack,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state } = require("../../shared/state");
|
const { state } = require("../../shared/state");
|
||||||
const { nativeCurrencyByChainId } = require("../../shared/networks");
|
|
||||||
const { formatEther, formatUnits } = require("ethers");
|
const { formatEther, formatUnits } = require("ethers");
|
||||||
const makeBlockie = require("ethereum-blockies-base64");
|
const makeBlockie = require("ethereum-blockies-base64");
|
||||||
const { log, debugFetch } = require("../../shared/log");
|
const { log, debugFetch } = require("../../shared/log");
|
||||||
@@ -45,7 +45,7 @@ function getTransactionType(tx) {
|
|||||||
// By the token contract, not the symbol: a token chooses its own symbol
|
// By the token contract, not the symbol: a token chooses its own symbol
|
||||||
// and can report the native token's, but only a token transfer has one.
|
// and can report the native token's, but only a token transfer has one.
|
||||||
if (tx.contractAddress) return "ERC-20 Token Transfer";
|
if (tx.contractAddress) return "ERC-20 Token Transfer";
|
||||||
return "Native " + nativeCurrencyByChainId(tx.chainId) + " Transfer";
|
return "Native " + nativeCurrency() + " Transfer";
|
||||||
}
|
}
|
||||||
|
|
||||||
function blockieHtml(address) {
|
function blockieHtml(address) {
|
||||||
@@ -87,11 +87,6 @@ function show(tx) {
|
|||||||
isContractCall: tx.isContractCall || false,
|
isContractCall: tx.isContractCall || false,
|
||||||
method: tx.method || null,
|
method: tx.method || null,
|
||||||
contractAddress: tx.contractAddress || null,
|
contractAddress: tx.contractAddress || null,
|
||||||
// The network the history entry was read from. The type line and
|
|
||||||
// the fee are in its native currency, not the active network's:
|
|
||||||
// a site can switch the active network before a later popup
|
|
||||||
// shows this screen again.
|
|
||||||
chainId: tx.chainId,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
render();
|
render();
|
||||||
@@ -187,7 +182,7 @@ function render() {
|
|||||||
if (el) el.classList.add("hidden");
|
if (el) el.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFullTxDetails(tx.hash, tx.to, tx.chainId);
|
loadFullTxDetails(tx.hash, tx.to);
|
||||||
|
|
||||||
const isoStr = isoDate(tx.timestamp);
|
const isoStr = isoDate(tx.timestamp);
|
||||||
$("tx-detail-time").innerHTML =
|
$("tx-detail-time").innerHTML =
|
||||||
@@ -205,7 +200,7 @@ function showDetailField(sectionId, contentId, value) {
|
|||||||
section.classList.remove("hidden");
|
section.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateOnChainDetails(txData, chainId) {
|
function populateOnChainDetails(txData) {
|
||||||
// Block number
|
// Block number
|
||||||
if (txData.block_number != null) {
|
if (txData.block_number != null) {
|
||||||
const blockLink = explorerUrl("block", String(txData.block_number));
|
const blockLink = explorerUrl("block", String(txData.block_number));
|
||||||
@@ -235,7 +230,7 @@ function populateOnChainDetails(txData, chainId) {
|
|||||||
showDetailField(
|
showDetailField(
|
||||||
"tx-detail-fee-section",
|
"tx-detail-fee-section",
|
||||||
"tx-detail-fee",
|
"tx-detail-fee",
|
||||||
feeEth + " " + nativeCurrencyByChainId(chainId),
|
feeEth + " " + nativeCurrency(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +290,7 @@ function populateOnChainDetails(txData, chainId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadFullTxDetails(txHash, toAddress, chainId) {
|
async function loadFullTxDetails(txHash, toAddress) {
|
||||||
const section = $("tx-detail-calldata-section");
|
const section = $("tx-detail-calldata-section");
|
||||||
const actionEl = $("tx-detail-calldata-action");
|
const actionEl = $("tx-detail-calldata-action");
|
||||||
const detailsEl = $("tx-detail-calldata-details");
|
const detailsEl = $("tx-detail-calldata-details");
|
||||||
@@ -312,7 +307,7 @@ async function loadFullTxDetails(txHash, toAddress, chainId) {
|
|||||||
const txData = await resp.json();
|
const txData = await resp.json();
|
||||||
|
|
||||||
// Populate on-chain detail fields (block, nonce, gas, fee)
|
// Populate on-chain detail fields (block, nonce, gas, fee)
|
||||||
populateOnChainDetails(txData, chainId);
|
populateOnChainDetails(txData);
|
||||||
|
|
||||||
const inputData = txData.raw_input || txData.input || null;
|
const inputData = txData.raw_input || txData.input || null;
|
||||||
if (!inputData || inputData === "0x") return;
|
if (!inputData || inputData === "0x") return;
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ const {
|
|||||||
etherscanLinkHtml,
|
etherscanLinkHtml,
|
||||||
explorerUrl,
|
explorerUrl,
|
||||||
displaySymbol,
|
displaySymbol,
|
||||||
|
nativeCurrency,
|
||||||
clearViewStack,
|
clearViewStack,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { resolveTokenSymbol } = require("../../shared/approvalAmount");
|
const { resolveTokenSymbol } = require("../../shared/approvalAmount");
|
||||||
const { state } = require("../../shared/state");
|
const { state } = require("../../shared/state");
|
||||||
const { nativeCurrencyByChainId } = require("../../shared/networks");
|
|
||||||
const { getProvider } = require("../../shared/balances");
|
const { getProvider } = require("../../shared/balances");
|
||||||
const { log } = require("../../shared/log");
|
const { log } = require("../../shared/log");
|
||||||
|
|
||||||
@@ -87,13 +87,9 @@ function startWait(txInfo, txHash, broadcastTime, pollNow) {
|
|||||||
endWait();
|
endWait();
|
||||||
const id = waitId;
|
const id = waitId;
|
||||||
|
|
||||||
// A native amount, here and on the success and error screens, is in the
|
|
||||||
// native currency of txInfo.chainId, the network the transaction was sent
|
|
||||||
// on, not the active network's: a site can switch the active network
|
|
||||||
// while this screen is open or before a later popup resumes it.
|
|
||||||
const symbol =
|
const symbol =
|
||||||
txInfo.token === "ETH"
|
txInfo.token === "ETH"
|
||||||
? nativeCurrencyByChainId(txInfo.chainId)
|
? 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);
|
||||||
@@ -198,10 +194,9 @@ function showWait(txInfo, txHash) {
|
|||||||
// an object merely missing one of them throws a TypeError out of
|
// an object merely missing one of them throws a TypeError out of
|
||||||
// restoreView() — which init() does not guard, skipping the rest of popup
|
// restoreView() — which init() does not guard, skipping the rest of popup
|
||||||
// init and leaving wait-tx on screen with no back control. A non-numeric
|
// init and leaving wait-tx on screen with no back control. A non-numeric
|
||||||
// broadcastTime leaves an unexitable wait counting "NaNs". txInfo.token,
|
// broadcastTime leaves an unexitable wait counting "NaNs". txInfo.token and
|
||||||
// txInfo.tokenSymbol and txInfo.chainId are deliberately unchecked: they are
|
// txInfo.tokenSymbol are deliberately unchecked: they are compared and
|
||||||
// compared and coalesced rather than dereferenced, and tokenSymbol is null for
|
// coalesced rather than dereferenced, and tokenSymbol is null for ETH.
|
||||||
// ETH.
|
|
||||||
function restoreWait() {
|
function restoreWait() {
|
||||||
const d = state.viewData;
|
const d = state.viewData;
|
||||||
if (!d || !d.pendingWait) return false;
|
if (!d || !d.pendingWait) return false;
|
||||||
@@ -229,7 +224,7 @@ function showSuccess(txInfo, txHash, blockNumber) {
|
|||||||
|
|
||||||
const symbol =
|
const symbol =
|
||||||
txInfo.token === "ETH"
|
txInfo.token === "ETH"
|
||||||
? nativeCurrencyByChainId(txInfo.chainId)
|
? nativeCurrency()
|
||||||
: displaySymbol(txInfo.tokenSymbol || "?");
|
: displaySymbol(txInfo.tokenSymbol || "?");
|
||||||
state.viewData = {
|
state.viewData = {
|
||||||
amount: txInfo.amount,
|
amount: txInfo.amount,
|
||||||
@@ -326,7 +321,7 @@ function showError(txInfo, txHash, message) {
|
|||||||
|
|
||||||
const symbol =
|
const symbol =
|
||||||
txInfo.token === "ETH"
|
txInfo.token === "ETH"
|
||||||
? nativeCurrencyByChainId(txInfo.chainId)
|
? nativeCurrency()
|
||||||
: displaySymbol(txInfo.tokenSymbol || "?");
|
: displaySymbol(txInfo.tokenSymbol || "?");
|
||||||
state.viewData = {
|
state.viewData = {
|
||||||
amount: txInfo.amount,
|
amount: txInfo.amount,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const {
|
|||||||
verifyMessage,
|
verifyMessage,
|
||||||
verifyTypedData,
|
verifyTypedData,
|
||||||
} = require("ethers");
|
} = require("ethers");
|
||||||
const { nativeCurrencyByChainId } = require("./networks");
|
const { networkByChainId } = require("./networks");
|
||||||
|
|
||||||
// The only transaction types this wallet signs: legacy, EIP-2930 and
|
// The only transaction types this wallet signs: legacy, EIP-2930 and
|
||||||
// EIP-1559. populateTransaction() produces nothing else, so nothing else can
|
// EIP-1559. populateTransaction() produces nothing else, so nothing else can
|
||||||
@@ -410,10 +410,12 @@ function assertWithinCeilings(tx) {
|
|||||||
}
|
}
|
||||||
if (price !== null && gasLimit * price > MAX_TOTAL_FEE) {
|
if (price !== null && gasLimit * price > MAX_TOTAL_FEE) {
|
||||||
// The fee is paid in the native currency of the network the
|
// The fee is paid in the native currency of the network the
|
||||||
// transaction is for. Every caller's transaction names it.
|
// transaction is for. Every caller's transaction names it; one
|
||||||
const nativeCurrency = nativeCurrencyByChainId(
|
// that does not, or names a network not in networks.js, says ETH.
|
||||||
present(tx.chainId) ? toQuantity(tx.chainId) : null,
|
const network = present(tx.chainId)
|
||||||
);
|
? networkByChainId(toQuantity(tx.chainId))
|
||||||
|
: null;
|
||||||
|
const nativeCurrency = network ? network.nativeCurrency : "ETH";
|
||||||
throw refuse(
|
throw refuse(
|
||||||
"This transaction would allow a network fee of up to " +
|
"This transaction would allow a network fee of up to " +
|
||||||
formatEther(gasLimit * price) +
|
formatEther(gasLimit * price) +
|
||||||
|
|||||||
@@ -76,15 +76,6 @@ function networkByChainId(chainId) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The native currency of the network with this chain id. A transaction's
|
|
||||||
// value and fee are labelled with the one of the chain the transaction is on,
|
|
||||||
// which need not be the active network. `ETH` when the chain id is missing or
|
|
||||||
// no network here has it.
|
|
||||||
function nativeCurrencyByChainId(chainId) {
|
|
||||||
const network = networkByChainId(chainId);
|
|
||||||
return network ? network.nativeCurrency : "ETH";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a block explorer link for the given path type and value.
|
// Build a block explorer link for the given path type and value.
|
||||||
// type: "address" | "tx" | "token" | "block"
|
// type: "address" | "tx" | "token" | "block"
|
||||||
function explorerLink(network, type, value) {
|
function explorerLink(network, type, value) {
|
||||||
@@ -98,6 +89,5 @@ module.exports = {
|
|||||||
isKnownNetworkId,
|
isKnownNetworkId,
|
||||||
networkById,
|
networkById,
|
||||||
networkByChainId,
|
networkByChainId,
|
||||||
nativeCurrencyByChainId,
|
|
||||||
explorerLink,
|
explorerLink,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const { log, debugFetch } = require("./log");
|
|||||||
const { TOKEN_BY_ADDRESS } = require("./tokenList");
|
const { TOKEN_BY_ADDRESS } = require("./tokenList");
|
||||||
const { parseHoldersCount, isLowHolderCount } = require("./holders");
|
const { parseHoldersCount, isLowHolderCount } = require("./holders");
|
||||||
const { isSpoofedSymbol } = require("./symbolSpoof");
|
const { isSpoofedSymbol } = require("./symbolSpoof");
|
||||||
const { nativeCurrencyByChainId } = require("./networks");
|
|
||||||
// The uint8 test every scale in this wallet goes through. Shared, not copied:
|
// The uint8 test every scale in this wallet goes through. Shared, not copied:
|
||||||
// a scale is either reported or it is unknown, and "unknown" must mean the
|
// a scale is either reported or it is unknown, and "unknown" must mean the
|
||||||
// same thing here as it does on the screens that refuse to format one.
|
// same thing here as it does on the screens that refuse to format one.
|
||||||
@@ -29,7 +28,7 @@ function normalizeAddress(addr) {
|
|||||||
return (addr || "").toLowerCase();
|
return (addr || "").toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTx(tx, addrLower, chainId) {
|
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";
|
||||||
@@ -37,7 +36,7 @@ function parseTx(tx, addrLower, chainId) {
|
|||||||
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 = nativeCurrencyByChainId(chainId);
|
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;
|
||||||
@@ -91,11 +90,10 @@ function parseTx(tx, addrLower, chainId) {
|
|||||||
holders: null,
|
holders: null,
|
||||||
isContractCall: toIsContract,
|
isContractCall: toIsContract,
|
||||||
method: method,
|
method: method,
|
||||||
chainId: chainId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTokenTransfer(tt, addrLower, chainId) {
|
function parseTokenTransfer(tt, addrLower) {
|
||||||
const from = tt.from?.hash || "";
|
const from = tt.from?.hash || "";
|
||||||
const to = tt.to?.hash || "";
|
const to = tt.to?.hash || "";
|
||||||
// The explorer's own answer, or null. Never a default: a transfer of
|
// The explorer's own answer, or null. Never a default: a transfer of
|
||||||
@@ -141,7 +139,6 @@ function parseTokenTransfer(tt, addrLower, chainId) {
|
|||||||
// low-holder filter declines to judge a null, so a legitimate token
|
// low-holder filter declines to judge a null, so a legitimate token
|
||||||
// is not hidden because a field went missing upstream.
|
// is not hidden because a field went missing upstream.
|
||||||
holders: parseHoldersCount(tt.token?.holders_count),
|
holders: parseHoldersCount(tt.token?.holders_count),
|
||||||
chainId: chainId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +221,12 @@ function mergeTransactions(txs, tokenTransfers) {
|
|||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `chainId` is the chain id of the network `blockscoutUrl` serves. Every entry
|
// `nativeCurrency` is the active network's native token symbol from
|
||||||
// carries it, and a native entry is labelled with that network's
|
// networks.js (`ETH`, `SepoliaETH`), which a native entry is labelled with.
|
||||||
// `nativeCurrency` from networks.js (`ETH`, `SepoliaETH`).
|
|
||||||
async function fetchRecentTransactions(
|
async function fetchRecentTransactions(
|
||||||
address,
|
address,
|
||||||
blockscoutUrl,
|
blockscoutUrl,
|
||||||
chainId,
|
nativeCurrency,
|
||||||
count = 25,
|
count = 25,
|
||||||
) {
|
) {
|
||||||
log.debugf("fetchRecentTransactions", address);
|
log.debugf("fetchRecentTransactions", address);
|
||||||
@@ -265,10 +261,10 @@ async function fetchRecentTransactions(
|
|||||||
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, chainId)),
|
(txJson.items || []).map((tx) =>
|
||||||
(ttJson.items || []).map((tt) =>
|
parseTx(tx, addrLower, nativeCurrency),
|
||||||
parseTokenTransfer(tt, addrLower, chainId),
|
|
||||||
),
|
),
|
||||||
|
(ttJson.items || []).map((tt) => parseTokenTransfer(tt, addrLower)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = txs.slice(0, count);
|
const result = txs.slice(0, count);
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
// ETH (https://git.eeqj.de/sneak/AutistMask/issues/372). Each line is asserted
|
// ETH (https://git.eeqj.de/sneak/AutistMask/issues/372). Each line is asserted
|
||||||
// on both networks, through the real Send, confirmation and approval screens,
|
// on both networks, through the real Send, confirmation and approval screens,
|
||||||
// with only the node and the DOM stubbed. So is that a token cannot pass for
|
// with only the node and the DOM stubbed. So is that a token cannot pass for
|
||||||
// the native token by reporting its label, and that a transaction's figures
|
// the native token by reporting its label.
|
||||||
// carry its own network's label when another network is active.
|
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@@ -30,9 +29,6 @@ jest.mock("ethers", () => {
|
|||||||
async getTransactionCount() {
|
async getTransactionCount() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
async getTransactionReceipt() {
|
|
||||||
return { blockNumber: 21000000 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
@@ -58,21 +54,6 @@ jest.mock("../src/shared/log", () => ({
|
|||||||
isDebug: () => false,
|
isDebug: () => false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Signing a send succeeds without a key, and sending answers with a hash.
|
|
||||||
jest.mock("../src/shared/vault", () => ({
|
|
||||||
...jest.requireActual("../src/shared/vault"),
|
|
||||||
decryptWithPassword: async () => "secret",
|
|
||||||
}));
|
|
||||||
jest.mock("../src/shared/wallet", () => ({
|
|
||||||
...jest.requireActual("../src/shared/wallet"),
|
|
||||||
getSignerForAddress: () => ({
|
|
||||||
connect: () => ({
|
|
||||||
populateTransaction: async (request) => request,
|
|
||||||
sendTransaction: async () => ({ hash: "0x" + "3".repeat(64) }),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
global.fetch = jest.fn(() => {
|
global.fetch = jest.fn(() => {
|
||||||
throw new Error("tests must not perform network requests");
|
throw new Error("tests must not perform network requests");
|
||||||
});
|
});
|
||||||
@@ -156,10 +137,8 @@ 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");
|
||||||
const transactionDetail = require("../src/popup/views/transactionDetail");
|
const transactionDetail = require("../src/popup/views/transactionDetail");
|
||||||
const txStatus = require("../src/popup/views/txStatus");
|
|
||||||
const { balanceLinesForAddress } = require("../src/popup/views/helpers");
|
const { balanceLinesForAddress } = require("../src/popup/views/helpers");
|
||||||
const { filterTransactions } = require("../src/shared/transactions");
|
const { filterTransactions } = require("../src/shared/transactions");
|
||||||
const { debugFetch } = require("../src/shared/log");
|
|
||||||
|
|
||||||
const HOLDER = "0x" + "a".repeat(40);
|
const HOLDER = "0x" + "a".repeat(40);
|
||||||
const RECIPIENT = "0xC0FfEE0000000000000000000000000000c0fFEe";
|
const RECIPIENT = "0xC0FfEE0000000000000000000000000000c0fFEe";
|
||||||
@@ -186,8 +165,8 @@ async function confirmSend(amount) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The approval screen for a dApp transaction sending 0.01 of the native token
|
// The approval screen for a dApp transaction sending 0.01 of the native token
|
||||||
// with 21000 gas at up to 20 gwei, on the network with `chainId`.
|
// with 21000 gas at up to 20 gwei, on the active network.
|
||||||
async function approveTx(chainId) {
|
async function approveTx() {
|
||||||
approvalDetails = {
|
approvalDetails = {
|
||||||
type: "tx",
|
type: "tx",
|
||||||
origin: "https://dapp.example",
|
origin: "https://dapp.example",
|
||||||
@@ -196,7 +175,7 @@ async function approveTx(chainId) {
|
|||||||
to: RECIPIENT,
|
to: RECIPIENT,
|
||||||
value: "10000000000000000",
|
value: "10000000000000000",
|
||||||
data: "0x",
|
data: "0x",
|
||||||
chainId,
|
chainId: NETWORKS[state.networkId].chainId,
|
||||||
gasLimit: "21000",
|
gasLimit: "21000",
|
||||||
maxFeePerGas: "20000000000",
|
maxFeePerGas: "20000000000",
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
@@ -242,7 +221,7 @@ describe.each([
|
|||||||
await confirmSend("0.1");
|
await confirmSend("0.1");
|
||||||
expect(text("confirm-type")).toBe("Native " + symbol + " transfer");
|
expect(text("confirm-type")).toBe("Native " + symbol + " transfer");
|
||||||
expect(text("confirm-amount")).toBe("0.1 " + symbol);
|
expect(text("confirm-amount")).toBe("0.1 " + symbol);
|
||||||
await approveTx(NETWORKS[networkId].chainId);
|
await approveTx();
|
||||||
expect(text("approve-tx-value")).toBe("0.0100 " + symbol);
|
expect(text("approve-tx-value")).toBe("0.0100 " + symbol);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -256,7 +235,7 @@ describe.each([
|
|||||||
expect(text("confirm-gas-error")).toContain(
|
expect(text("confirm-gas-error")).toContain(
|
||||||
"You do not have enough " + symbol + " to pay the network fee",
|
"You do not have enough " + symbol + " to pay the network fee",
|
||||||
);
|
);
|
||||||
await approveTx(NETWORKS[networkId].chainId);
|
await approveTx();
|
||||||
expect(text("approve-tx-fee")).toBe("0.0004 " + symbol);
|
expect(text("approve-tx-fee")).toBe("0.0004 " + symbol);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -322,7 +301,6 @@ describe.each([
|
|||||||
isError: false,
|
isError: false,
|
||||||
direction: "received",
|
direction: "received",
|
||||||
directionLabel: "Received",
|
directionLabel: "Received",
|
||||||
chainId: NETWORKS[networkId].chainId,
|
|
||||||
};
|
};
|
||||||
transactionDetail.show({ ...entry, contractAddress: null });
|
transactionDetail.show({ ...entry, contractAddress: null });
|
||||||
expect(text("tx-detail-type")).toBe("Native " + symbol + " Transfer");
|
expect(text("tx-detail-type")).toBe("Native " + symbol + " Transfer");
|
||||||
@@ -343,119 +321,3 @@ describe.each([
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// A transaction's value and fee are in the native currency of the network the
|
|
||||||
// transaction is on, which need not be the active one. A site can switch the
|
|
||||||
// active network after its transaction is prepared and back before it is
|
|
||||||
// signed, and a popup opened after a switch shows a sent or listed transaction
|
|
||||||
// again. The wallet's balances follow the active network; these do not.
|
|
||||||
describe.each([
|
|
||||||
["mainnet", "sepolia", "ETH"],
|
|
||||||
["sepolia", "mainnet", "SepoliaETH"],
|
|
||||||
])(
|
|
||||||
"a %s transaction shown with %s active reads %s",
|
|
||||||
(txNetworkId, activeNetworkId, symbol) => {
|
|
||||||
const chainId = NETWORKS[txNetworkId].chainId;
|
|
||||||
const hash = "0x" + "3".repeat(64);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
elements.clear();
|
|
||||||
clearPrices();
|
|
||||||
state.networkId = activeNetworkId;
|
|
||||||
state.wallets = [
|
|
||||||
{
|
|
||||||
name: "Wallet 1",
|
|
||||||
addresses: [{ address: HOLDER, balance: "1.5" }],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
state.selectedWallet = 0;
|
|
||||||
state.selectedAddress = 0;
|
|
||||||
state.trackedTokens = [];
|
|
||||||
state.fraudContracts = [];
|
|
||||||
state.currentView = null;
|
|
||||||
txStatus.init({ doRefreshAndRender() {} });
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
txStatus.endWait();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("the approval screen's value and fee", async () => {
|
|
||||||
await approveTx(chainId);
|
|
||||||
expect(text("approve-tx-network")).toBe(NETWORKS[txNetworkId].name);
|
|
||||||
expect(text("approve-tx-value")).toBe("0.0100 " + symbol);
|
|
||||||
expect(text("approve-tx-fee")).toBe("0.0004 " + symbol);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("the wait, success and error screens", async () => {
|
|
||||||
const txInfo = {
|
|
||||||
from: HOLDER,
|
|
||||||
to: RECIPIENT,
|
|
||||||
amount: "0.0100",
|
|
||||||
token: "ETH",
|
|
||||||
tokenSymbol: null,
|
|
||||||
chainId,
|
|
||||||
};
|
|
||||||
txStatus.showWait(txInfo, hash);
|
|
||||||
expect(text("wait-tx-summary")).toBe("0.0100 " + symbol);
|
|
||||||
txStatus.showError(txInfo, hash, "Failed.");
|
|
||||||
expect(text("error-tx-summary")).toBe("0.0100 " + symbol);
|
|
||||||
// A later popup resumes the wait, and the receipt is there.
|
|
||||||
state.viewData = {
|
|
||||||
pendingWait: { txInfo, hash, broadcastTime: Date.now() },
|
|
||||||
};
|
|
||||||
txStatus.restoreWait();
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
await new Promise((r) => setTimeout(r, 0));
|
|
||||||
}
|
|
||||||
expect(text("success-tx-summary")).toBe("0.0100 " + symbol);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sent from the Send screen on the transaction's network, then
|
|
||||||
// resumed by a popup that opens after the active network changed.
|
|
||||||
test("the wait screen after a send", async () => {
|
|
||||||
state.networkId = txNetworkId;
|
|
||||||
await confirmSend("0.1");
|
|
||||||
confirmTx.init({});
|
|
||||||
global.document.getElementById("confirm-tx-password").value = "pw";
|
|
||||||
await global.document
|
|
||||||
.getElementById("btn-confirm-send")
|
|
||||||
.handlers.get("click")();
|
|
||||||
expect(text("wait-tx-summary")).toBe("0.1 " + symbol);
|
|
||||||
state.networkId = activeNetworkId;
|
|
||||||
txStatus.restoreWait();
|
|
||||||
expect(text("wait-tx-summary")).toBe("0.1 " + symbol);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("the transaction detail screen's type line and fee", async () => {
|
|
||||||
debugFetch.mockImplementationOnce(async () => ({
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
json: async () => ({ fee: { value: "21000000000000" } }),
|
|
||||||
}));
|
|
||||||
transactionDetail.show({
|
|
||||||
hash,
|
|
||||||
from: RECIPIENT,
|
|
||||||
to: HOLDER,
|
|
||||||
value: "1.0000",
|
|
||||||
exactValue: "1.0",
|
|
||||||
symbol,
|
|
||||||
timestamp: 1790000000,
|
|
||||||
isError: false,
|
|
||||||
direction: "received",
|
|
||||||
directionLabel: "Received",
|
|
||||||
contractAddress: null,
|
|
||||||
chainId,
|
|
||||||
});
|
|
||||||
expect(text("tx-detail-type")).toBe(
|
|
||||||
"Native " + symbol + " Transfer",
|
|
||||||
);
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
await new Promise((r) => setTimeout(r, 0));
|
|
||||||
}
|
|
||||||
expect(
|
|
||||||
global.document.getElementById("tx-detail-fee").innerHTML,
|
|
||||||
).toContain("0.000021 " + symbol);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
+17
-21
@@ -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, "0x1");
|
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, "0x1");
|
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, "0x1");
|
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, "0x1");
|
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, "0x1");
|
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,13 +1427,12 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
|
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");
|
||||||
expect(txs[0].direction).toBe("sent");
|
expect(txs[0].direction).toBe("sent");
|
||||||
expect(txs[0].contractAddress).toBe(USDC_CONTRACT);
|
expect(txs[0].contractAddress).toBe(USDC_CONTRACT);
|
||||||
expect(txs[0].chainId).toBe("0x1");
|
|
||||||
// The surviving row is the token row, and the filters keep it.
|
// The surviving row is the token row, and the filters keep it.
|
||||||
const kept = filterTransactions(txs, filters()).transactions;
|
const kept = filterTransactions(txs, filters()).transactions;
|
||||||
expect(kept).toHaveLength(1);
|
expect(kept).toHaveLength(1);
|
||||||
@@ -1453,14 +1452,13 @@ 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, "0x1", 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
|
// https://git.eeqj.de/sneak/AutistMask/issues/372: the caller hands in
|
||||||
// the chain id of the network the explorer serves. Every entry carries
|
// the active network's nativeCurrency, and a native entry carries it.
|
||||||
// it, and a native entry is labelled with that network's nativeCurrency.
|
test("a native entry is labelled with the native token symbol handed in", async () => {
|
||||||
test("a native entry is labelled by the chain id handed in", async () => {
|
|
||||||
respondWith(
|
respondWith(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -1479,18 +1477,16 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
const sepolia = await fetchRecentTransactions(
|
const sepolia = await fetchRecentTransactions(
|
||||||
VICTIM,
|
VICTIM,
|
||||||
BLOCKSCOUT,
|
BLOCKSCOUT,
|
||||||
"0xaa36a7",
|
"SepoliaETH",
|
||||||
);
|
);
|
||||||
expect(sepolia[0].symbol).toBe("SepoliaETH");
|
expect(sepolia[0].symbol).toBe("SepoliaETH");
|
||||||
expect(sepolia[0].value).toBe("0.0100");
|
expect(sepolia[0].value).toBe("0.0100");
|
||||||
expect(sepolia[0].chainId).toBe("0xaa36a7");
|
|
||||||
const mainnet = await fetchRecentTransactions(
|
const mainnet = await fetchRecentTransactions(
|
||||||
VICTIM,
|
VICTIM,
|
||||||
BLOCKSCOUT,
|
BLOCKSCOUT,
|
||||||
"0x1",
|
"ETH",
|
||||||
);
|
);
|
||||||
expect(mainnet[0].symbol).toBe("ETH");
|
expect(mainnet[0].symbol).toBe("ETH");
|
||||||
expect(mainnet[0].chainId).toBe("0x1");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the fake token transfer survives fetching and is then filtered", async () => {
|
test("the fake token transfer survives fetching and is then filtered", async () => {
|
||||||
@@ -1513,7 +1509,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const txs = await fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1");
|
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);
|
||||||
@@ -1555,7 +1551,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
const txs = await fetchRecentTransactions(
|
const txs = await fetchRecentTransactions(
|
||||||
VICTIM,
|
VICTIM,
|
||||||
BLOCKSCOUT,
|
BLOCKSCOUT,
|
||||||
"0x1",
|
"ETH",
|
||||||
);
|
);
|
||||||
expect(txs[0].holders).toBeNull();
|
expect(txs[0].holders).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -1565,7 +1561,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
const txs = await fetchRecentTransactions(
|
const txs = await fetchRecentTransactions(
|
||||||
VICTIM,
|
VICTIM,
|
||||||
BLOCKSCOUT,
|
BLOCKSCOUT,
|
||||||
"0x1",
|
"ETH",
|
||||||
);
|
);
|
||||||
expect(txs[0].holders).toBeNull();
|
expect(txs[0].holders).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -1575,7 +1571,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
const txs = await fetchRecentTransactions(
|
const txs = await fetchRecentTransactions(
|
||||||
VICTIM,
|
VICTIM,
|
||||||
BLOCKSCOUT,
|
BLOCKSCOUT,
|
||||||
"0x1",
|
"ETH",
|
||||||
);
|
);
|
||||||
expect(filterTransactions(txs, filters()).transactions).toEqual(
|
expect(filterTransactions(txs, filters()).transactions).toEqual(
|
||||||
txs,
|
txs,
|
||||||
@@ -1591,7 +1587,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
const txs = await fetchRecentTransactions(
|
const txs = await fetchRecentTransactions(
|
||||||
VICTIM,
|
VICTIM,
|
||||||
BLOCKSCOUT,
|
BLOCKSCOUT,
|
||||||
"0x1",
|
"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([]);
|
||||||
@@ -1608,7 +1604,7 @@ describe("fetchRecentTransactions merge and dedup", () => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
await expect(
|
await expect(
|
||||||
fetchRecentTransactions(VICTIM, BLOCKSCOUT, "0x1"),
|
fetchRecentTransactions(VICTIM, BLOCKSCOUT, "ETH"),
|
||||||
).resolves.toEqual([]);
|
).resolves.toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user