1 Commits
Author SHA1 Message Date
clawbot 2c2f497dca fix: a swap split across steps the user pays for shows every amount it spends (closes #625)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
The Amount line gave only the first swap step's amount. It now gives the
total the steps take of Token In: the amount of the step that sets the
input side plus each later take of it by a step the user pays. A V4
step, the first included, takes what its SETTLEs paid by the user state.
A take of another token, one after an amount that is not a quantity, or
one of unknown amount gets lines of its own. A step that may take what
the decoder did not read gets one line saying so and leaves no figure.
A PERMIT2_PERMIT names Token In; its amount stands only in the plain
shape of Uniswap's own routes.

Also closes #628.

Model: opus-5-5
2026-10-20 05:01:09 +00:00
4 changed files with 17 additions and 84 deletions
-2
View File
@@ -2392,8 +2392,6 @@ view would leave a wallet one click from deletion.
- Raw data: full calldata displayed inline (shown if present) - Raw data: full calldata displayed inline (shown if present)
- Password input and an error line - Password input and an error line
- "Confirm" / "Reject" buttons - "Confirm" / "Reject" buttons
- **Behavior**: Every etherscan link on the screen opens the explorer of the
network the transaction was populated on, whichever network is active.
- **Transitions**: - **Transitions**:
- "Confirm" (correct password) → decrypts and signs the transaction it was - "Confirm" (correct password) → decrypts and signs the transaction it was
shown, exactly as shown, hands the signed transaction to the background to shown, exactly as shown, hands the signed transaction to the background to
+5 -20
View File
@@ -58,15 +58,9 @@ const { notify, runtimeApi, sendMessage } = require("../../shared/browserApi");
const erc20Iface = new Interface(ERC20_ABI); const erc20Iface = new Interface(ERC20_ABI);
// The link opens the explorer of the network with `chainId`, or of the active function approvalAddressHtml(address) {
// network when none is given. A chain id that names no network has no
// explorer, so the address is drawn without a link.
function approvalAddressHtml(address, chainId) {
const title = addressTitle(address, state.wallets); const title = addressTitle(address, state.wallets);
if (chainId && !networkByChainId(chainId)) { return renderAddressHtml(address, { title });
return renderAddressHtml(address, { title, noLink: true });
}
return renderAddressHtml(address, { title, chainId });
} }
// The amount line for a decoded ERC-20 call. With a known scale it is the // The amount line for a decoded ERC-20 call. With a known scale it is the
@@ -344,14 +338,8 @@ function showTxApproval(details) {
}; };
} }
// Every address link on this screen opens the explorer of the network the
// transaction is for, which the Network line names, whichever network is
// active.
$("approve-tx-origin").textContent = details.origin; $("approve-tx-origin").textContent = details.origin;
$("approve-tx-from").innerHTML = approvalAddressHtml( $("approve-tx-from").innerHTML = approvalAddressHtml(details.approvedFrom);
details.approvedFrom,
approvedTx.chainId,
);
// Show token symbol next to contract address if known on the network the // Show token symbol next to contract address if known on the network the
// transaction is for // transaction is for
@@ -361,7 +349,7 @@ function showTxApproval(details) {
if (symbol) { if (symbol) {
toHtml += `<div class="font-bold mb-1">${escapeHtml(symbol)}</div>`; toHtml += `<div class="font-bold mb-1">${escapeHtml(symbol)}</div>`;
} }
toHtml += approvalAddressHtml(toAddr, approvedTx.chainId); toHtml += approvalAddressHtml(toAddr);
$("approve-tx-to").innerHTML = toHtml; $("approve-tx-to").innerHTML = toHtml;
} else { } else {
$("approve-tx-to").innerHTML = escapeHtml(CONTRACT_CREATION_TEXT); $("approve-tx-to").innerHTML = escapeHtml(CONTRACT_CREATION_TEXT);
@@ -400,10 +388,7 @@ function showTxApproval(details) {
if (d.isToken) { if (d.isToken) {
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`; detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`;
} }
detailsHtml += approvalAddressHtml( detailsHtml += approvalAddressHtml(d.address);
d.address,
approvedTx.chainId,
);
} else { } else {
detailsHtml += `<div class="font-bold">${escapeHtml(d.value)}</div>`; detailsHtml += `<div class="font-bold">${escapeHtml(d.value)}</div>`;
} }
+11 -61
View File
@@ -1,16 +1,11 @@
// The approval screen for a site's Sepolia token transfer while Ethereum // The approval screen for a site's Sepolia token transfer while Ethereum
// Mainnet is active. // Mainnet is active (https://git.eeqj.de/sneak/AutistMask/issues/629).
// //
// The screen named the token, and scaled its amount, from the active network's // The screen named the token, and scaled its amount, from the active network's
// tracked tokens and balances, so a token the wallet tracks on Sepolia read as // tracked tokens and balances, so a token the wallet tracks on Sepolia read as
// an unknown token with its amount in base units, and the screens after it // an unknown token with its amount in base units, and the screens after it
// labelled the amount `?` (https://git.eeqj.de/sneak/AutistMask/issues/629). // labelled the amount `?`. It now uses what the wallet knows of the tokens on
// It now uses what the wallet knows of the tokens on the network the // the network the transaction is for.
// transaction is for.
//
// Its address links opened the active network's explorer
// (https://git.eeqj.de/sneak/AutistMask/issues/631). They now open the
// explorer of the network the transaction is for.
// //
// Driven through the real approval screen, password and Confirm included, // Driven through the real approval screen, password and Confirm included,
// against a minimal DOM stub in the shape tests/swapStatusAmount.test.js uses. // against a minimal DOM stub in the shape tests/swapStatusAmount.test.js uses.
@@ -112,13 +107,10 @@ function decodedLine(label) {
} }
// Open the approval screen, with Ethereum Mainnet active and FROM holding // Open the approval screen, with Ethereum Mainnet active and FROM holding
// `tokenBalances` there, for the site's transaction sending TRANSFER_DATA to // `tokenBalances` there, for the site's Sepolia transaction sending
// the token, on Sepolia unless `chainId` names another chain. Confirm answers // TRANSFER_DATA to the token. Confirm answers the signed transaction with a
// the signed transaction with a failure, which leads to the error screen. // failure, which leads to the error screen.
async function showTokenTransfer( async function showSepoliaTransfer(tokenBalances = []) {
tokenBalances = [],
chainId = NETWORKS.sepolia.chainId,
) {
globalThis.document = makeDocument(); globalThis.document = makeDocument();
globalThis.window = { location: { search: "" }, close: () => {} }; globalThis.window = { location: { search: "" }, close: () => {} };
globalThis.chrome.runtime = { globalThis.chrome.runtime = {
@@ -139,7 +131,7 @@ async function showTokenTransfer(
approvedTx: { approvedTx: {
type: 2, type: 2,
from: FROM, from: FROM,
chainId, chainId: NETWORKS.sepolia.chainId,
nonce: "0x7", nonce: "0x7",
gasLimit: "0x30d40", gasLimit: "0x30d40",
maxPriorityFeePerGas: "0x3b9aca00", maxPriorityFeePerGas: "0x3b9aca00",
@@ -191,7 +183,7 @@ describe("a token tracked on Sepolia only", () => {
}); });
test("is named on the Contract line and the decoded Token line, with its amount at its scale", async () => { test("is named on the Contract line and the decoded Token line, with its amount at its scale", async () => {
await showTokenTransfer(); await showSepoliaTransfer();
expect(node("approve-tx-to").innerHTML).toContain( expect(node("approve-tx-to").innerHTML).toContain(
'<div class="font-bold mb-1">TDOL</div>', '<div class="font-bold mb-1">TDOL</div>',
); );
@@ -204,7 +196,7 @@ describe("a token tracked on Sepolia only", () => {
}); });
test("labels the amount on the error screen that follows", async () => { test("labels the amount on the error screen that follows", async () => {
await showTokenTransfer(); await showSepoliaTransfer();
await confirm(); await confirm();
expect(state.currentView).toBe("error-tx"); expect(state.currentView).toBe("error-tx");
expect(node("error-tx-summary").textContent).toBe("1234.5000 TDOL"); expect(node("error-tx-summary").textContent).toBe("1234.5000 TDOL");
@@ -215,7 +207,7 @@ describe("a token tracked on Sepolia only", () => {
// tracks and holds there names nothing on Sepolia. // tracks and holds there names nothing on Sepolia.
test("a token tracked and held on mainnet only is not named, and its amount is in base units", async () => { test("a token tracked and held on mainnet only is not named, and its amount is in base units", async () => {
state.trackedTokens = [{ address: TOKEN, symbol: "MDOL", decimals: 18 }]; state.trackedTokens = [{ address: TOKEN, symbol: "MDOL", decimals: 18 }];
await showTokenTransfer([ await showSepoliaTransfer([
{ address: TOKEN, symbol: "MDOL", decimals: 18, balance: "1.0" }, { address: TOKEN, symbol: "MDOL", decimals: 18, balance: "1.0" },
]); ]);
expect(node("approve-tx-to").innerHTML).not.toContain("MDOL"); expect(node("approve-tx-to").innerHTML).not.toContain("MDOL");
@@ -226,45 +218,3 @@ test("a token tracked and held on mainnet only is not named, and its amount is i
'<div class="font-bold">1234500000 base units (decimals unknown)</div>', '<div class="font-bold">1234500000 base units (decimals unknown)</div>',
); );
}); });
// The From and Contract lines, and the decoded Token and Recipient lines.
test("every address links to the Sepolia explorer", async () => {
await showTokenTransfer();
const sepolia = NETWORKS.sepolia.explorerUrl;
expect(node("approve-tx-from").innerHTML).toContain(
`href="${sepolia}/address/${FROM}"`,
);
expect(node("approve-tx-to").innerHTML).toContain(
`href="${sepolia}/address/${TOKEN}"`,
);
expect(decodedLine("Token")).toContain(
`href="${sepolia}/address/${TOKEN}"`,
);
expect(decodedLine("Recipient")).toContain(
`href="${sepolia}/address/${RECIPIENT}"`,
);
for (const id of [
"approve-tx-from",
"approve-tx-to",
"approve-tx-decoded-details",
]) {
expect(node(id).innerHTML).not.toContain(NETWORKS.mainnet.explorerUrl);
}
});
// Chain id 1337 names no network here, so it has no explorer to link to.
test("a transaction on a chain id that names no network is shown with no address links", async () => {
await showTokenTransfer([], "0x539");
expect(state.currentView).toBe("approve-tx");
expect(node("approve-tx-network").textContent).toBe(
"Unknown network (chain id 1337)",
);
expect(node("approve-tx-from").innerHTML).toContain(FROM);
for (const id of [
"approve-tx-from",
"approve-tx-to",
"approve-tx-decoded-details",
]) {
expect(node(id).innerHTML).not.toContain("href=");
}
});
+1 -1
View File
@@ -475,7 +475,7 @@ describe("every way to a signature or the private key refuses a defective wallet
to: RECIPIENT, to: RECIPIENT,
value: "0x0", value: "0x0",
data: "0x", data: "0x",
chainId: "0x1", chainId: 1,
nonce: 0, nonce: 0,
gasLimit: "21000", gasLimit: "21000",
maxFeePerGas: "1000000000", maxFeePerGas: "1000000000",