chore: the native token's label follows the network (closes #372)
networks.js gives each network a nativeCurrency (ETH, SepoliaETH) and nothing read it: every screen wrote ETH. The wallet's balances and the Send and confirmation screens now use the active network's. A transaction's figures use the network its chain id names, through nativeCurrencyByChainId(): the approval value and fee, the wait, success and error screens, history entries, the detail screen and the fee-limit refusal, so a site switching networks cannot make one read as another network's coin. The "ETH" that selectedToken and txInfo.token hold is the native token's id and is unchanged. A token reporting any network's nativeCurrency is a spoof, and the detail screen calls an entry a token transfer when it has a token contract. Model: opus-5-5
This commit was merged in pull request #442.
This commit is contained in:
@@ -54,9 +54,11 @@ const {
|
||||
formatEther,
|
||||
getAddress,
|
||||
getBytes,
|
||||
toQuantity,
|
||||
verifyMessage,
|
||||
verifyTypedData,
|
||||
} = require("ethers");
|
||||
const { nativeCurrencyByChainId } = require("./networks");
|
||||
|
||||
// The only transaction types this wallet signs: legacy, EIP-2930 and
|
||||
// EIP-1559. populateTransaction() produces nothing else, so nothing else can
|
||||
@@ -407,12 +409,21 @@ function assertWithinCeilings(tx) {
|
||||
price = normalizeQuantity(tx.gasPrice, "gas price");
|
||||
}
|
||||
if (price !== null && gasLimit * price > MAX_TOTAL_FEE) {
|
||||
// The fee is paid in the native currency of the network the
|
||||
// transaction is for. Every caller's transaction names it.
|
||||
const nativeCurrency = nativeCurrencyByChainId(
|
||||
present(tx.chainId) ? toQuantity(tx.chainId) : null,
|
||||
);
|
||||
throw refuse(
|
||||
"This transaction would allow a network fee of up to " +
|
||||
formatEther(gasLimit * price) +
|
||||
" ETH, which is more than the " +
|
||||
" " +
|
||||
nativeCurrency +
|
||||
", which is more than the " +
|
||||
formatEther(MAX_TOTAL_FEE) +
|
||||
" ETH this wallet will sign for.",
|
||||
" " +
|
||||
nativeCurrency +
|
||||
" this wallet will sign for.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,15 @@ function networkByChainId(chainId) {
|
||||
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.
|
||||
// type: "address" | "tx" | "token" | "block"
|
||||
function explorerLink(network, type, value) {
|
||||
@@ -89,5 +98,6 @@ module.exports = {
|
||||
isKnownNetworkId,
|
||||
networkById,
|
||||
networkByChainId,
|
||||
nativeCurrencyByChainId,
|
||||
explorerLink,
|
||||
};
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
// KNOWN_SYMBOLS maps a symbol to the set of lowercased contract addresses
|
||||
// that may bear it, or to null. Null means the symbol belongs to the native
|
||||
// asset, which has no contract at all, so no contract may bear it and every
|
||||
// one that does is a spoof. "ETH" is the only such entry today; the rule is
|
||||
// written so that a second one needs no change here or at any call site.
|
||||
// one that does is a spoof. "ETH" is one such entry, and every network's
|
||||
// `nativeCurrency` in networks.js (`SepoliaETH`) is another, on every network.
|
||||
//
|
||||
// The value is a set because a ticker is not unique: seven symbols in the
|
||||
// bundled list belong to two real contracts each, and answering with one of
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// 511 tokens.
|
||||
|
||||
const { debugFetch } = require("./log");
|
||||
const { NETWORKS } = require("./networks");
|
||||
|
||||
const COINDESK_API = "https://data-api.coindesk.com/index/cc/v1/latest/tick";
|
||||
|
||||
@@ -3610,7 +3611,9 @@ for (const t of TOKENS) {
|
||||
// Build a map of symbol (uppercased) -> the set of contract addresses
|
||||
// (lowercased) that legitimately bear it. Used for spoofed-symbol detection.
|
||||
// "ETH" maps to null: the native asset has no contract, so no contract may
|
||||
// bear its symbol.
|
||||
// bear its symbol. So does every network's `nativeCurrency` in networks.js
|
||||
// (`SepoliaETH`), on every network, since that is the label the wallet shows
|
||||
// its native asset under on that network.
|
||||
//
|
||||
// The value is a set and not a single address because tickers are not unique
|
||||
// and the list above proves it: seven of these 512 tokens share a symbol with
|
||||
@@ -3624,6 +3627,9 @@ for (const t of TOKENS) {
|
||||
// loosen the rule, because a contract outside the set is still a spoof.
|
||||
const KNOWN_SYMBOLS = new Map();
|
||||
KNOWN_SYMBOLS.set("ETH", null);
|
||||
for (const network of Object.values(NETWORKS)) {
|
||||
KNOWN_SYMBOLS.set(network.nativeCurrency.toUpperCase(), null);
|
||||
}
|
||||
for (const t of TOKENS) {
|
||||
const upper = t.symbol.toUpperCase();
|
||||
if (!KNOWN_SYMBOLS.has(upper)) {
|
||||
|
||||
@@ -11,6 +11,7 @@ const { log, debugFetch } = require("./log");
|
||||
const { TOKEN_BY_ADDRESS } = require("./tokenList");
|
||||
const { parseHoldersCount, isLowHolderCount } = require("./holders");
|
||||
const { isSpoofedSymbol } = require("./symbolSpoof");
|
||||
const { nativeCurrencyByChainId } = require("./networks");
|
||||
// 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
|
||||
// same thing here as it does on the screens that refuse to format one.
|
||||
@@ -28,7 +29,7 @@ function normalizeAddress(addr) {
|
||||
return (addr || "").toLowerCase();
|
||||
}
|
||||
|
||||
function parseTx(tx, addrLower) {
|
||||
function parseTx(tx, addrLower, chainId) {
|
||||
const from = tx.from?.hash || "";
|
||||
const to = tx.to?.hash || "";
|
||||
const rawWei = tx.value || "0";
|
||||
@@ -36,7 +37,7 @@ function parseTx(tx, addrLower) {
|
||||
const method = tx.method || null;
|
||||
|
||||
// For contract calls, produce a meaningful label instead of "0.0000 ETH"
|
||||
let symbol = "ETH";
|
||||
let symbol = nativeCurrencyByChainId(chainId);
|
||||
let value = formatTxValue(formatEther(rawWei));
|
||||
let exactValue = formatEther(rawWei);
|
||||
let rawAmount = rawWei;
|
||||
@@ -90,10 +91,11 @@ function parseTx(tx, addrLower) {
|
||||
holders: null,
|
||||
isContractCall: toIsContract,
|
||||
method: method,
|
||||
chainId: chainId,
|
||||
};
|
||||
}
|
||||
|
||||
function parseTokenTransfer(tt, addrLower) {
|
||||
function parseTokenTransfer(tt, addrLower, chainId) {
|
||||
const from = tt.from?.hash || "";
|
||||
const to = tt.to?.hash || "";
|
||||
// The explorer's own answer, or null. Never a default: a transfer of
|
||||
@@ -139,6 +141,7 @@ function parseTokenTransfer(tt, addrLower) {
|
||||
// low-holder filter declines to judge a null, so a legitimate token
|
||||
// is not hidden because a field went missing upstream.
|
||||
holders: parseHoldersCount(tt.token?.holders_count),
|
||||
chainId: chainId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -221,7 +224,15 @@ function mergeTransactions(txs, tokenTransfers) {
|
||||
return merged;
|
||||
}
|
||||
|
||||
async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
|
||||
// `chainId` is the chain id of the network `blockscoutUrl` serves. Every entry
|
||||
// carries it, and a native entry is labelled with that network's
|
||||
// `nativeCurrency` from networks.js (`ETH`, `SepoliaETH`).
|
||||
async function fetchRecentTransactions(
|
||||
address,
|
||||
blockscoutUrl,
|
||||
chainId,
|
||||
count = 25,
|
||||
) {
|
||||
log.debugf("fetchRecentTransactions", address);
|
||||
const addrLower = normalizeAddress(address);
|
||||
|
||||
@@ -254,8 +265,10 @@ async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
|
||||
const ttJson = ttResp.ok ? await ttResp.json() : {};
|
||||
|
||||
const txs = mergeTransactions(
|
||||
(txJson.items || []).map((tx) => parseTx(tx, addrLower)),
|
||||
(ttJson.items || []).map((tt) => parseTokenTransfer(tt, addrLower)),
|
||||
(txJson.items || []).map((tx) => parseTx(tx, addrLower, chainId)),
|
||||
(ttJson.items || []).map((tt) =>
|
||||
parseTokenTransfer(tt, addrLower, chainId),
|
||||
),
|
||||
);
|
||||
|
||||
const result = txs.slice(0, count);
|
||||
|
||||
Reference in New Issue
Block a user