Author SHA1 Message Date
sneak bccfd61f60 docs: RULES.md states the branch-per-issue model (closes #193)
check / check (push) Failing after 18m14s
e2e / e2e-chrome (push) Successful in 19m31s
e2e / e2e-firefox (push) Successful in 4m29s
Replaces the "merge to main when done" checklist line with the wording
sneak approved on the issue.

Model: opus-5-5
2026-11-10 19:55:03 +00:00
19 changed files with 207 additions and 247 deletions

No files matched your search

+6 -7
View File
@@ -264,8 +264,6 @@ The shared modules, each in one line:
messaging, windows and the lock. messaging, windows and the lock.
- `buildInfo.js`: the version, license, author, commit and date compiled into - `buildInfo.js`: the version, license, author, commit and date compiled into
About. About.
- `calldataDecoder.js`: the ERC-20 `approve()` and `transfer()` decoder, which
hands other calldata to `uniswap.js`. Popup only.
- `chainSwitch.js`: the popup's network switch. - `chainSwitch.js`: the popup's network switch.
- `chainSwitchFields.js`: the fields a network switch changes. - `chainSwitchFields.js`: the fields a network switch changes.
- `constants.js`: `DEBUG`, the default endpoints, the ERC-20 ABI and the burn - `constants.js`: `DEBUG`, the default endpoints, the ERC-20 ABI and the burn
@@ -1381,7 +1379,7 @@ approval screens show a red warning.
### Transaction decoding ### Transaction decoding
TxApproval and TransactionDetail try to decode calldata into a summary TxApproval and TransactionDetail try to decode calldata into a summary
(`decodeCalldata()` in `src/shared/calldataDecoder.js`). SuccessTx repeats (`decodeCalldata()` in `src/popup/views/approval.js`). SuccessTx repeats
TxApproval's summary. Decoding is a display aid. When it fails, the raw calldata TxApproval's summary. Decoding is a display aid. When it fails, the raw calldata
is shown in full. No contract gets any treatment beyond a legible confirmation is shown in full. No contract gets any treatment beyond a legible confirmation
screen. screen.
@@ -1647,10 +1645,11 @@ The development dependencies ship nowhere:
### Crypto policy ### Crypto policy
In the extension, hashing, key derivation, signing and encryption go through Hashing, key derivation, signing and encryption go through `ethers` or
`ethers` or `libsodium-wrappers-sumo`. A new recovery phrase takes its random `libsodium-wrappers-sumo`. No primitive is implemented in this repository. A new
bytes from `ethers`. `build.js` and `script/lib/package.js` also hash files with recovery phrase draws its random bytes from the browser's
`ethers`. No primitive is implemented in this repository. `crypto.getRandomValues()`. The build tooling, `build.js` and `script/lib/`,
hashes files with Node's `crypto`; none of it ships in the extension.
### Content Security Policy ### Content Security Policy
+4 -1
View File
@@ -108,7 +108,10 @@ contradicts either, the originals govern.
- [ ] All tool invocations via `make` targets, never directly - [ ] All tool invocations via `make` targets, never directly
- [ ] `make check` = `make test` + `make lint` + `make fmt-check` - [ ] `make check` = `make test` + `make lint` + `make fmt-check`
- [ ] `main` always passes `make check` - [ ] `main` always passes `make check`
- [ ] Feature branches for all changes, merge to main when done - [ ] Branch per issue, cut from `next`. One PR per issue, based on `next`,
squash-merged into `next` on a passed independent review. `main` is
reached only by the per-milestone PR from `next`; releases are tagged from
`main`. `next` and `main` must stay green; feature branches may be red.
- [ ] No force-push to `main` - [ ] No force-push to `main`
- [ ] Pre-commit hook runs `make check` - [ ] Pre-commit hook runs `make check`
+5 -3
View File
@@ -1,8 +1,8 @@
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const crypto = require("crypto");
const { execSync } = require("child_process"); const { execSync } = require("child_process");
const esbuild = require("esbuild"); const esbuild = require("esbuild");
const { sha256 } = require("ethers");
const { resolveVersion } = require("./script/lib/version"); const { resolveVersion } = require("./script/lib/version");
const { const {
BACKGROUND_ENTRY_PREFIX, BACKGROUND_ENTRY_PREFIX,
@@ -314,8 +314,10 @@ function copyIcons(distDir) {
} }
function sha256File(absPath) { function sha256File(absPath) {
// ethers prefixes the hex with 0x; the receipt holds bare hex. return crypto
return sha256(fs.readFileSync(absPath)).slice(2); .createHash("sha256")
.update(fs.readFileSync(absPath))
.digest("hex");
} }
// Write the receipt for the files this build emitted. Deliberately records no // Write the receipt for the files this build emitted. Deliberately records no
+3 -9
View File
@@ -25,9 +25,9 @@
"use strict"; "use strict";
const crypto = require("crypto");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const ethers = require("ethers");
const { readZip, writeZip } = require("./zip"); const { readZip, writeZip } = require("./zip");
const { resolveVersion } = require("./version"); const { resolveVersion } = require("./version");
@@ -60,9 +60,8 @@ function fail(message) {
throw new Error(message); throw new Error(message);
} }
// Lowercase hex without the 0x prefix ethers adds, as sha256sum prints it.
function sha256(buf) { function sha256(buf) {
return ethers.sha256(buf).slice(2); return crypto.createHash("sha256").update(buf).digest("hex");
} }
// Every regular file under dir, as archive-root-relative forward-slashed // Every regular file under dir, as archive-root-relative forward-slashed
@@ -292,9 +291,4 @@ if (require.main === module) {
} }
} }
module.exports = { module.exports = { checkSelfContained, htmlReferences, manifestReferences };
checkSelfContained,
htmlReferences,
manifestReferences,
sha256,
};
+144 -12
View File
@@ -12,13 +12,9 @@ const {
formatFee, formatFee,
tokenLabel, tokenLabel,
blockscoutOffTokenNoteHtml, blockscoutOffTokenNoteHtml,
} = require("./helpers");
const {
state,
saveState,
currentNetwork,
tokenSourcesByChainId, tokenSourcesByChainId,
} = require("../../shared/state"); } = require("./helpers");
const { state, saveState, currentNetwork } = require("../../shared/state");
const { const {
networkById, networkById,
networkByChainId, networkByChainId,
@@ -30,24 +26,21 @@ const {
getAddress, getAddress,
getBigInt, getBigInt,
getBytes, getBytes,
Interface,
MaxUint256, MaxUint256,
toUtf8String, toUtf8String,
} = require("ethers"); } = require("ethers");
const { getPrice, formatUsd } = require("../../shared/prices"); const { getPrice, formatUsd } = require("../../shared/prices");
const { ERC20_ABI } = require("../../shared/constants");
const { INVISIBLE_CHARACTERS } = require("../../shared/symbolSpoof"); const { INVISIBLE_CHARACTERS } = require("../../shared/symbolSpoof");
const { const {
resolveTokenDecimals, resolveTokenDecimals,
resolveTokenSymbol, resolveTokenSymbol,
unknownDecimalsAmount, unknownDecimalsAmount,
} = require("../../shared/approvalAmount"); } = require("../../shared/approvalAmount");
const {
decodeCalldata,
tokenAmountText,
} = require("../../shared/calldataDecoder");
// Four decimals, with the nonzero floor these screens hold: every amount this // Four decimals, with the nonzero floor these screens hold: every amount this
// view renders — the ERC-20 line, the ETH value, the max fee — and every one // view renders — the ERC-20 line, the ETH value, the max fee — and every one
// it carries forward to the wait/success/error screens goes through it. The // it carries forward to the wait/success/error screens goes through it.
// ERC-20 line gets it from tokenAmountText().
const { const {
truncateAmountNeverZero: formatTxValue, truncateAmountNeverZero: formatTxValue,
} = require("../../shared/amountDisplay"); } = require("../../shared/amountDisplay");
@@ -61,8 +54,11 @@ const {
typedDataChainRefusal, typedDataChainRefusal,
} = require("../../shared/approvalVerify"); } = require("../../shared/approvalVerify");
const txStatus = require("./txStatus"); const txStatus = require("./txStatus");
const uniswap = require("../../shared/uniswap");
const { runtimeApi, sendMessage } = require("../../shared/browserApi"); const { runtimeApi, sendMessage } = require("../../shared/browserApi");
const erc20Iface = new Interface(ERC20_ABI);
// The link opens the explorer of the network with `chainId`, or of the active // The link opens the explorer of the network with `chainId`, or of the active
// network when none is given. A chain id that names no network has no // network when none is given. A chain id that names no network has no
// explorer, so the address is drawn without a link. // explorer, so the address is drawn without a link.
@@ -74,6 +70,141 @@ function approvalAddressHtml(address, chainId) {
return renderAddressHtml(address, { title, chainId }); return renderAddressHtml(address, { title, chainId });
} }
// The amount line for a decoded ERC-20 call. With a known scale it is the
// token quantity; with `decimals` null it is the base-unit integer with the
// unknown scale stated, because formatting it with an assumed scale is what
// showed a 5,000-token transfer as `0.0000`. `raw` is what the status screens
// carry, `display` is what the approval screen shows.
function tokenAmountText(rawAmount, decimals, symbol) {
if (decimals === null) {
const unknown = unknownDecimalsAmount(rawAmount);
return { raw: unknown, display: unknown };
}
const formatted = formatTxValue(formatUnits(rawAmount, decimals));
return {
raw: formatted,
display: formatted + (symbol ? " " + symbol : ""),
};
}
// Try to decode calldata sent from `fromAddress` to `toAddress` on the network
// with chain id `chainId`, using known ABIs.
// Returns { name, description, details } or null.
function decodeCalldata(data, toAddress, chainId, fromAddress) {
if (!data || data === "0x" || data.length < 10) return null;
// Where a token's name and scale are looked for, for every decoder below:
// what the wallet knows of the tokens on the network with `chainId`, which
// need not be the active one. The ERC-20 amount line and the swap's Amount
// and Min. received lines resolve the scale the same way, and refuse to
// format the same way when it is nowhere.
const decimalsSources = tokenSourcesByChainId(chainId);
// Try ERC-20 (approve / transfer)
try {
const parsed = erc20Iface.parseTransaction({ data });
if (parsed) {
const tokenSymbol = resolveTokenSymbol(toAddress, decimalsSources);
// null when no source knows this token's scale. It is not
// defaulted to 18: an amount formatted with a guessed scale is
// the wrong number, and for a token with fewer decimals than the
// guess it is the wrong number in the direction that reads as
// zero. See tokenAmountText().
const tokenDecimals = resolveTokenDecimals(
toAddress,
decimalsSources,
);
const contractLabel = tokenSymbol
? tokenSymbol + " (" + toAddress + ")"
: toAddress;
if (parsed.name === "approve") {
const spender = parsed.args[0];
const rawAmount = parsed.args[1];
const maxUint = BigInt(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
);
const isUnlimited = rawAmount === maxUint;
// An unbounded allowance needs no scale to describe, so it is
// still named rather than refused.
const amount = isUnlimited
? { raw: "Unlimited", display: "Unlimited" }
: tokenAmountText(rawAmount, tokenDecimals, tokenSymbol);
return {
name: "Token Approval",
description: tokenSymbol
? "Approve spending of your " + tokenSymbol
: "Approve spending of an ERC-20 token",
details: [
{
label: "Token",
value: contractLabel,
address: toAddress,
isToken: true,
},
{
label: "Spender",
value: spender,
address: spender,
},
{
label: "Amount",
value: amount.display,
rawValue: amount.raw,
},
],
};
}
if (parsed.name === "transfer") {
const to = parsed.args[0];
const rawAmount = parsed.args[1];
const amount = tokenAmountText(
rawAmount,
tokenDecimals,
tokenSymbol,
);
return {
name: "Token Transfer",
description: tokenSymbol
? "Transfer " + tokenSymbol
: "Transfer ERC-20 token",
details: [
{
label: "Token",
value: contractLabel,
address: toAddress,
isToken: true,
},
{ label: "Recipient", value: to, address: to },
{
label: "Amount",
value: amount.display,
rawValue: amount.raw,
},
],
};
}
}
} catch {
// Not ERC-20 — fall through
}
// Try Uniswap Universal Router
const routerResult = uniswap.decode(
data,
toAddress,
chainId,
decimalsSources,
fromAddress,
);
if (routerResult) return routerResult;
return null;
}
function showPhishingWarning(elementId, isPhishing) { function showPhishingWarning(elementId, isPhishing) {
const el = $(elementId); const el = $(elementId);
if (!el) return; if (!el) return;
@@ -1167,6 +1298,7 @@ function init(_ctx) {
module.exports = { module.exports = {
init, init,
show, show,
decodeCalldata,
formatTypedDataHtml, formatTypedDataHtml,
typedDataRefusal, typedDataRefusal,
}; };
+28 -6
View File
@@ -31,12 +31,7 @@ const { escapeHtml } = require("../../shared/html");
const { isDebug } = require("../../shared/log"); const { isDebug } = require("../../shared/log");
const { networkByChainId } = require("../../shared/networks"); const { networkByChainId } = require("../../shared/networks");
const { formatUsd, getPrice } = require("../../shared/prices"); const { formatUsd, getPrice } = require("../../shared/prices");
const { const { state, saveState, currentNetwork } = require("../../shared/state");
state,
saveState,
currentNetwork,
tokenSourcesByChainId,
} = require("../../shared/state");
const { displaySymbol } = require("../../shared/symbolDisplay"); const { displaySymbol } = require("../../shared/symbolDisplay");
const { isSpoofedSymbol } = require("../../shared/symbolSpoof"); const { isSpoofedSymbol } = require("../../shared/symbolSpoof");
const { walletHasRecoveryPhrase } = require("../../shared/wallet"); const { walletHasRecoveryPhrase } = require("../../shared/wallet");
@@ -310,6 +305,32 @@ function nativeCurrency() {
return currentNetwork().nativeCurrency; return currentNetwork().nativeCurrency;
} }
// What the wallet knows of the tokens on the network with this chain id, or
// on the active network when no chain id is given, as the `sources` that
// resolveTokenSymbol() and resolveTokenDecimals() take. A contract address
// 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
// (state.networkTrackedTokens, see applyChainSwitchFields()) and no balances.
//
// The wallets' balances hold what Blockscout reported for each token. So the
// wallets are returned only while "Use Blockscout" is on.
function tokenSourcesByChainId(chainId) {
const chainIdOrActive = chainId || currentNetwork().chainId;
const net = networkByChainId(chainIdOrActive);
if (net && net.id === state.networkId) {
return {
chainId: chainIdOrActive,
trackedTokens: state.trackedTokens,
wallets: state.useBlockscout ? state.wallets : [],
};
}
return {
chainId: chainIdOrActive,
trackedTokens: (net && state.networkTrackedTokens[net.id]) || [],
wallets: [],
};
}
// The line shown under a token while "Use Blockscout" is off. The token gets // The line shown under a token while "Use Blockscout" is off. The token gets
// the line only when the bundled list and the tracked tokens lack its name or // the line only when the bundled list and the tracked tokens lack its name or
// decimals. Otherwise this returns "". The token is a contract address on the // decimals. Otherwise this returns "". The token is a contract address on the
@@ -845,6 +866,7 @@ module.exports = {
keyMaterialName, keyMaterialName,
unknownableAmount, unknownableAmount,
nativeCurrency, nativeCurrency,
tokenSourcesByChainId,
blockscoutOffTokenNoteHtml, blockscoutOffTokenNoteHtml,
tokenLabel, tokenLabel,
formatFee, formatFee,
+2 -6
View File
@@ -10,13 +10,9 @@ const {
renderAddressHtml, renderAddressHtml,
attachCopyHandlers, attachCopyHandlers,
goBack, goBack,
} = require("./helpers");
const {
state,
currentAddress,
currentNetwork,
tokenSourcesByChainId, tokenSourcesByChainId,
} = require("../../shared/state"); } = require("./helpers");
const { state, currentAddress, currentNetwork } = require("../../shared/state");
let ctx; let ctx;
const { getProvider } = require("../../shared/balances"); const { getProvider } = require("../../shared/balances");
const { resolveTokenDecimals } = require("../../shared/approvalAmount"); const { resolveTokenDecimals } = require("../../shared/approvalAmount");
+1 -1
View File
@@ -27,7 +27,7 @@ const { nativeCurrencyByChainId } = require("../../shared/networks");
const { formatEther, formatUnits } = require("ethers"); const { formatEther, formatUnits } = require("ethers");
const { log, debugFetch } = require("../../shared/log"); const { log, debugFetch } = require("../../shared/log");
const { storedSettingIsOn } = require("../../shared/storedSettings"); const { storedSettingIsOn } = require("../../shared/storedSettings");
const { decodeCalldata } = require("../../shared/calldataDecoder"); const { decodeCalldata } = require("./approval");
/** /**
* Determine a human-readable transaction type string from tx fields. * Determine a human-readable transaction type string from tx fields.
+1 -1
View File
@@ -2,7 +2,7 @@
// the one exception to it, in one place. Three call sites had grown their own // the one exception to it, in one place. Three call sites had grown their own
// copy of the truncation — the history and balance lists // copy of the truncation — the history and balance lists
// (`src/shared/transactions.js`), the approval screen's ERC-20 amount line // (`src/shared/transactions.js`), the approval screen's ERC-20 amount line
// (`src/shared/calldataDecoder.js`) and its Uniswap swap detail lines // (`src/popup/views/approval.js`) and its Uniswap swap detail lines
// (`src/shared/uniswap.js`) — and a fix applied to one of them left the other // (`src/shared/uniswap.js`) — and a fix applied to one of them left the other
// two showing a different number for the same value. // two showing a different number for the same value.
// //
-155
View File
@@ -1,155 +0,0 @@
// Decode a transaction's calldata into the summary that TxApproval and
// TransactionDetail show: ERC-20 `approve` and `transfer` calls here, and
// Uniswap Universal Router calls through uniswap.js. The summary is data; the
// views turn its addresses into HTML.
const { formatUnits, Interface } = require("ethers");
const { ERC20_ABI } = require("./constants");
const { tokenSourcesByChainId } = require("./state");
const {
resolveTokenDecimals,
resolveTokenSymbol,
unknownDecimalsAmount,
} = require("./approvalAmount");
// Four decimals, with the nonzero floor the approval screens hold.
const { truncateAmountNeverZero } = require("./amountDisplay");
const uniswap = require("./uniswap");
const erc20Iface = new Interface(ERC20_ABI);
// The amount line for a decoded ERC-20 call. With a known scale it is the
// token quantity; with `decimals` null it is the base-unit integer with the
// unknown scale stated, because formatting it with an assumed scale is what
// showed a 5,000-token transfer as `0.0000`. `raw` is what the status screens
// carry, `display` is what the approval screen shows.
function tokenAmountText(rawAmount, decimals, symbol) {
if (decimals === null) {
const unknown = unknownDecimalsAmount(rawAmount);
return { raw: unknown, display: unknown };
}
const formatted = truncateAmountNeverZero(formatUnits(rawAmount, decimals));
return {
raw: formatted,
display: formatted + (symbol ? " " + symbol : ""),
};
}
// Try to decode calldata sent from `fromAddress` to `toAddress` on the network
// with chain id `chainId`, using known ABIs.
// Returns { name, description, details } or null.
function decodeCalldata(data, toAddress, chainId, fromAddress) {
if (!data || data === "0x" || data.length < 10) return null;
// Where a token's name and scale are looked for, for every decoder below:
// what the wallet knows of the tokens on the network with `chainId`, which
// need not be the active one. The ERC-20 amount line and the swap's Amount
// and Min. received lines resolve the scale the same way, and refuse to
// format the same way when it is nowhere.
const decimalsSources = tokenSourcesByChainId(chainId);
// Try ERC-20 (approve / transfer)
try {
const parsed = erc20Iface.parseTransaction({ data });
if (parsed) {
const tokenSymbol = resolveTokenSymbol(toAddress, decimalsSources);
// null when no source knows this token's scale. It is not
// defaulted to 18: an amount formatted with a guessed scale is
// the wrong number, and for a token with fewer decimals than the
// guess it is the wrong number in the direction that reads as
// zero. See tokenAmountText().
const tokenDecimals = resolveTokenDecimals(
toAddress,
decimalsSources,
);
const contractLabel = tokenSymbol
? tokenSymbol + " (" + toAddress + ")"
: toAddress;
if (parsed.name === "approve") {
const spender = parsed.args[0];
const rawAmount = parsed.args[1];
const maxUint = BigInt(
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
);
const isUnlimited = rawAmount === maxUint;
// An unbounded allowance needs no scale to describe, so it is
// still named rather than refused.
const amount = isUnlimited
? { raw: "Unlimited", display: "Unlimited" }
: tokenAmountText(rawAmount, tokenDecimals, tokenSymbol);
return {
name: "Token Approval",
description: tokenSymbol
? "Approve spending of your " + tokenSymbol
: "Approve spending of an ERC-20 token",
details: [
{
label: "Token",
value: contractLabel,
address: toAddress,
isToken: true,
},
{
label: "Spender",
value: spender,
address: spender,
},
{
label: "Amount",
value: amount.display,
rawValue: amount.raw,
},
],
};
}
if (parsed.name === "transfer") {
const to = parsed.args[0];
const rawAmount = parsed.args[1];
const amount = tokenAmountText(
rawAmount,
tokenDecimals,
tokenSymbol,
);
return {
name: "Token Transfer",
description: tokenSymbol
? "Transfer " + tokenSymbol
: "Transfer ERC-20 token",
details: [
{
label: "Token",
value: contractLabel,
address: toAddress,
isToken: true,
},
{ label: "Recipient", value: to, address: to },
{
label: "Amount",
value: amount.display,
rawValue: amount.raw,
},
],
};
}
}
} catch {
// Not ERC-20 — fall through
}
// Try Uniswap Universal Router
const routerResult = uniswap.decode(
data,
toAddress,
chainId,
decimalsSources,
fromAddress,
);
if (routerResult) return routerResult;
return null;
}
module.exports = { decodeCalldata, tokenAmountText };
+1 -28
View File
@@ -22,7 +22,7 @@
// same thing in the editor is fast feedback in front of the build, not the // same thing in the editor is fast feedback in front of the build, not the
// guarantee. // guarantee.
const { networkById, networkByChainId } = require("./networks"); const { networkById } = require("./networks");
const { copyChainSwitchFields } = require("./chainSwitchFields"); const { copyChainSwitchFields } = require("./chainSwitchFields");
const { const {
DEFAULT_STATE, DEFAULT_STATE,
@@ -125,32 +125,6 @@ function currentNetwork() {
return networkById(state.networkId); return networkById(state.networkId);
} }
// What the wallet knows of the tokens on the network with this chain id, or
// on the active network when no chain id is given, as the `sources` that
// resolveTokenSymbol() and resolveTokenDecimals() take. A contract address
// 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
// (state.networkTrackedTokens, see applyChainSwitchFields()) and no balances.
//
// The wallets' balances hold what Blockscout reported for each token. So the
// wallets are returned only while "Use Blockscout" is on.
function tokenSourcesByChainId(chainId) {
const chainIdOrActive = chainId || currentNetwork().chainId;
const net = networkByChainId(chainIdOrActive);
if (net && net.id === state.networkId) {
return {
chainId: chainIdOrActive,
trackedTokens: state.trackedTokens,
wallets: state.useBlockscout ? state.wallets : [],
};
}
return {
chainId: chainIdOrActive,
trackedTokens: (net && state.networkTrackedTokens[net.id]) || [],
wallets: [],
};
}
// `baseline` holds the persisted fields as this page held them when its last // `baseline` holds the persisted fields as this page held them when its last
// loadState() finished, or when its last successful saveState() began. // loadState() finished, or when its last successful saveState() began.
// takeStoredNetwork(), takeStoredTrackedTokens() and // takeStoredNetwork(), takeStoredTrackedTokens() and
@@ -846,6 +820,5 @@ module.exports = {
loadState, loadState,
currentAddress, currentAddress,
currentNetwork, currentNetwork,
tokenSourcesByChainId,
StateNotLoadedError, StateNotLoadedError,
}; };
+5 -2
View File
@@ -1,7 +1,7 @@
// Wallet operations: mnemonic generation, HD derivation, signing. // Wallet operations: mnemonic generation, HD derivation, signing.
// All crypto delegated to ethers.js. // All crypto delegated to ethers.js.
const { Mnemonic, HDNodeWallet, Wallet, randomBytes } = require("ethers"); const { Mnemonic, HDNodeWallet, Wallet } = require("ethers");
const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants"); const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants");
function generateMnemonic() { function generateMnemonic() {
@@ -10,7 +10,10 @@ function generateMnemonic() {
// settings toggle drives, which would let a user of a release build turn // settings toggle drives, which would let a user of a release build turn
// the hardcoded, publicly known test phrase back on for real wallets. // the hardcoded, publicly known test phrase back on for real wallets.
if (DEBUG) return DEBUG_MNEMONIC; if (DEBUG) return DEBUG_MNEMONIC;
return Mnemonic.fromEntropy(randomBytes(16)).phrase; const m = Mnemonic.fromEntropy(
globalThis.crypto.getRandomValues(new Uint8Array(16)),
);
return m.phrase;
} }
// Every extended key (xprv or xpub) entering the app goes through this. // Every extended key (xprv or xpub) entering the app goes through this.
+1 -1
View File
@@ -23,7 +23,7 @@ const {
resolveTokenDecimals, resolveTokenDecimals,
unknownDecimalsAmount, unknownDecimalsAmount,
} = require("../src/shared/approvalAmount"); } = require("../src/shared/approvalAmount");
const { decodeCalldata } = require("../src/shared/calldataDecoder"); const { decodeCalldata } = require("../src/popup/views/approval");
const iface = new Interface(ERC20_ABI); const iface = new Interface(ERC20_ABI);
+2 -2
View File
@@ -13,7 +13,7 @@
// asserted on `rawValue` alongside the displayed line. // asserted on `rawValue` alongside the displayed line.
// //
// Both amount paths of that screen are covered: the ERC-20 line decoded by // Both amount paths of that screen are covered: the ERC-20 line decoded by
// `src/shared/calldataDecoder.js`, and the swap's `Amount` and `Min. received` // `src/popup/views/approval.js`, and the swap's `Amount` and `Min. received`
// lines decoded by `src/shared/uniswap.js`. // lines decoded by `src/shared/uniswap.js`.
globalThis.chrome = { globalThis.chrome = {
@@ -23,7 +23,7 @@ globalThis.chrome = {
const { AbiCoder, Interface } = require("ethers"); const { AbiCoder, Interface } = require("ethers");
const { ERC20_ABI } = require("../src/shared/constants"); const { ERC20_ABI } = require("../src/shared/constants");
const { state } = require("../src/shared/state"); const { state } = require("../src/shared/state");
const { decodeCalldata } = require("../src/shared/calldataDecoder"); const { decodeCalldata } = require("../src/popup/views/approval");
const uniswap = require("../src/shared/uniswap"); const uniswap = require("../src/shared/uniswap");
const { const {
truncateAmount, truncateAmount,
+1 -1
View File
@@ -47,7 +47,7 @@ const { fetchTokenBalances } = require("../src/shared/balances");
const { debugFetch } = require("../src/shared/log"); const { debugFetch } = require("../src/shared/log");
const { state } = require("../src/shared/state"); const { state } = require("../src/shared/state");
const { unknownDecimalsAmount } = require("../src/shared/approvalAmount"); const { unknownDecimalsAmount } = require("../src/shared/approvalAmount");
const { decodeCalldata } = require("../src/shared/calldataDecoder"); const { decodeCalldata } = require("../src/popup/views/approval");
const { TOKEN_BY_ADDRESS } = require("../src/shared/tokenList"); const { TOKEN_BY_ADDRESS } = require("../src/shared/tokenList");
const HOLDER = "0x" + "a".repeat(40); const HOLDER = "0x" + "a".repeat(40);
-9
View File
@@ -17,7 +17,6 @@ const {
checkSelfContained, checkSelfContained,
htmlReferences, htmlReferences,
manifestReferences, manifestReferences,
sha256,
} = require("../script/lib/package"); } = require("../script/lib/package");
const { readZip, writeZip } = require("../script/lib/zip"); const { readZip, writeZip } = require("../script/lib/zip");
@@ -143,14 +142,6 @@ describe("archive self-containment", () => {
}); });
}); });
// release/SHA256SUMS is checked with `sha256sum -c`, which needs the digest
// as bare lowercase hex. The expected value is the FIPS 180-2 digest of "abc".
test("sha256 prints the digest as sha256sum does", () => {
expect(sha256(Buffer.from("abc"))).toBe(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
);
});
describe("the zip container", () => { describe("the zip container", () => {
const files = [ const files = [
{ name: "manifest.json", data: Buffer.from('{"a":1}') }, { name: "manifest.json", data: Buffer.from('{"a":1}') },
+1 -1
View File
@@ -22,7 +22,7 @@ const { Interface } = require("ethers");
const { ERC20_ABI } = require("../src/shared/constants"); const { ERC20_ABI } = require("../src/shared/constants");
const { state } = require("../src/shared/state"); const { state } = require("../src/shared/state");
const { resolveTokenSymbol } = require("../src/shared/approvalAmount"); const { resolveTokenSymbol } = require("../src/shared/approvalAmount");
const { decodeCalldata } = require("../src/shared/calldataDecoder"); const { decodeCalldata } = require("../src/popup/views/approval");
const iface = new Interface(ERC20_ABI); const iface = new Interface(ERC20_ABI);
+1 -1
View File
@@ -47,7 +47,7 @@ globalThis.chrome = {
const { state } = require("../src/shared/state"); const { state } = require("../src/shared/state");
const { fetchRecentTransactions } = require("../src/shared/transactions"); const { fetchRecentTransactions } = require("../src/shared/transactions");
const approval = require("../src/popup/views/approval"); const approval = require("../src/popup/views/approval");
const { decodeCalldata } = require("../src/shared/calldataDecoder"); const { decodeCalldata } = approval;
const transactionDetail = require("../src/popup/views/transactionDetail"); const transactionDetail = require("../src/popup/views/transactionDetail");
const MAINNET = "0x1"; const MAINNET = "0x1";
+1 -1
View File
@@ -18,7 +18,7 @@ globalThis.chrome = {
const { AbiCoder, Interface } = require("ethers"); const { AbiCoder, Interface } = require("ethers");
const { state } = require("../src/shared/state"); const { state } = require("../src/shared/state");
const { unknownDecimalsAmount } = require("../src/shared/approvalAmount"); const { unknownDecimalsAmount } = require("../src/shared/approvalAmount");
const { decodeCalldata } = require("../src/shared/calldataDecoder"); const { decodeCalldata } = require("../src/popup/views/approval");
const ROUTER = "0x66a9893cc07d91d95644aedd05d03f95e1dba8af"; const ROUTER = "0x66a9893cc07d91d95644aedd05d03f95e1dba8af";
const RECIPIENT = "0xC0FfEE0000000000000000000000000000c0fFEe"; const RECIPIENT = "0xC0FfEE0000000000000000000000000000c0fFEe";