3 Commits
Author SHA1 Message Date
clawbot c150b83322 fix: Home's Send and Receive clear the selected token (closes #753)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
Three routes reach Home with a token still selected. Send used that token
whatever its dropdown showed, and Receive warned about it. Both now clear it
when they open, and Send's dropdown starts on ETH.

Model: opus-5-5
2026-11-03 16:09:42 +01:00
clawbot ee370b5ef7 fix: a WRAP_ETH says where its ETH goes (closes #747)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
A WRAP_ETH into the router now always reads `Moved to: The router`. That is
true whichever step then takes the WETH. Other takes keep
`The swap step that spends it`.

A later WRAP_ETH of CONTRACT_BALANCE to anyone but the router now gets a
`Moved from the sender` line reading `All the ETH the transaction carries`,
and `Moved to` names the recipient. It gets none after an UNWRAP_WETH into
the router, or while the router holds ETH from a V4 step.

README states both rules.

Model: opus-5-5
2026-11-03 15:44:15 +01:00
clawbot e438272125 fix: Back from AddressToken clears the selected token (closes #751)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
AddressDetail's show() clears the token, but "Back" onto a screen already drawn
only unhides it. Send and Receive then kept using the token from AddressToken,
whatever Send's dropdown showed. "Back" now clears the token and puts the
dropdown on ETH.

Model: opus-5-5
2026-11-03 15:27:40 +01:00
5 changed files with 365 additions and 2 deletions
+3 -2
View File
@@ -581,8 +581,9 @@ Transitions:
- "+": derives the next address in place.
- `[info]`: AddressDetail.
- `[x]`: DeleteAddress.
- "Send": Send. Refused with a flash message when the active address has a zero
ETH balance or belongs to a wallet carrying the `walletDefects.js` notice.
- "Send": Send, with its dropdown on ETH. Refused with a flash message when the
active address has a zero ETH balance or belongs to a wallet carrying the
`walletDefects.js` notice.
- "Receive": Receive.
- Tap a transaction row: TransactionDetail.
- "Add additional wallet...": AddWallet.
+5
View File
@@ -360,6 +360,11 @@ function init(_ctx) {
});
$("btn-address-token-back").addEventListener("click", () => {
// AddressDetail's show() clears the token, but Back onto a drawn
// screen only unhides it. "Send" below may have left the dropdown on
// the token as well.
state.selectedToken = null;
$("send-token").value = "ETH";
goBack();
});
+7
View File
@@ -510,11 +510,15 @@ function init(ctx) {
showFlash("Cannot send \u2014 zero balance.");
return;
}
// Send uses a selected token ahead of its dropdown, and AddressToken
// can leave one selected on the way here.
state.selectedToken = null;
$("send-to").value = "";
$("send-amount").value = "";
$("send-token-dropdown").classList.remove("hidden");
$("send-token-static").classList.add("hidden");
renderSendTokenSelect(addr);
$("send-token").value = "ETH";
updateSendBalance();
resetSendValidation();
pushCurrentView();
@@ -526,6 +530,9 @@ function init(ctx) {
showFlash("No active address selected.");
return;
}
// Receive warns about a selected token, and AddressToken can leave one
// selected on the way here.
state.selectedToken = null;
ctx.showReceive();
});
}
+165
View File
@@ -0,0 +1,165 @@
// "Back" from AddressToken leaves no token selected. AddressDetail's show()
// would clear it, but Back onto a drawn screen only unhides that screen, and
// Send and Receive both read the field
// (https://git.eeqj.de/sneak/AutistMask/issues/751). Driven through the real
// popup over a stored profile.
const {
bootPopup,
cleanupPopup,
unversionedValidProfile,
TOKEN_ADDRESS,
} = require("./support/popupBoot");
const RECIPIENT = "0x711e691962E5c054b78071C789A0C8CE657377aD";
const TOKEN = {
address: TOKEN_ADDRESS,
symbol: "TST",
decimals: 18,
name: "Test Token",
};
afterEach(() => {
cleanupPopup();
});
// The popup reopened onto AddressDetail, at an address holding 1.5 ETH and
// 2.5 TST.
function bootOnAddress() {
const profile = unversionedValidProfile({
currentView: "address",
viewStack: ["main"],
selectedWallet: 0,
selectedAddress: 0,
trackedTokens: [TOKEN],
});
profile.wallets[0].addresses[0].tokenBalances = [
{ ...TOKEN, balance: "2.5" },
];
return bootPopup(profile);
}
// AddressToken opened from AddressDetail, as tapping the token's row does.
async function openToken(page) {
const { state } = require("../src/shared/state");
state.selectedToken = TOKEN_ADDRESS;
require("../src/popup/views/helpers").pushCurrentView();
require("../src/popup/views/addressToken").show();
await page.settle();
expect(page.visibleViews()).toEqual(["address-token"]);
}
async function backFromToken(page) {
await page.click("btn-address-token-back");
expect(page.visibleViews()).toEqual(["address"]);
}
// ETH chosen in Send's dropdown, as the user does.
async function chooseEth(page) {
const dropdown = page.node("send-token");
dropdown.value = "ETH";
for (const fn of dropdown.listeners.change) await fn();
}
// Review for 1 of the token Send has, to RECIPIENT.
async function review(page) {
page.node("send-to").value = RECIPIENT;
page.node("send-amount").value = "1";
await page.click("btn-send-review");
expect(page.visibleViews()).toEqual(["confirm-tx"]);
}
describe("after Back from AddressToken", () => {
test("no token is selected, on the page or in the stored record", async () => {
const page = await bootOnAddress();
await openToken(page);
await backFromToken(page);
const { state } = require("../src/shared/state");
expect(state.selectedToken).toBeNull();
expect(page.storage.read("autistmask").selectedToken).toBeNull();
});
test("Send with ETH chosen shows the ETH balance", async () => {
const page = await bootOnAddress();
await openToken(page);
await backFromToken(page);
await page.click("btn-send");
await chooseEth(page);
expect(page.text("send-balance")).toBe("Current balance: 1.5000 ETH");
});
test("Send with ETH chosen reviews ETH", async () => {
const page = await bootOnAddress();
await openToken(page);
await backFromToken(page);
await page.click("btn-send");
await chooseEth(page);
await review(page);
expect(page.text("confirm-type")).toBe("Native ETH transfer");
});
test("Receive shows no token warning", async () => {
const page = await bootOnAddress();
await openToken(page);
await backFromToken(page);
await page.click("btn-receive");
expect(page.visibleViews()).toEqual(["receive"]);
expect(page.text("receive-erc20-warning")).toBe("");
});
// AddressToken's Send sets the dropdown to the token, and nothing else
// rebuilds it on the way back to AddressDetail.
test("Send opens on ETH after AddressToken's Send has been used", async () => {
const page = await bootOnAddress();
await openToken(page);
await page.click("btn-address-token-send");
await page.click("btn-send-back");
await backFromToken(page);
await page.click("btn-send");
expect(page.value("send-token")).toBe("ETH");
expect(page.text("send-balance")).toBe("Current balance: 1.5000 ETH");
});
});
describe("Send opened from AddressToken sends that token", () => {
async function expectSendsToken(page) {
await page.click("btn-address-token-send");
expect(page.text("send-balance")).toBe("Current balance: 2.5000 TST");
await review(page);
expect(page.text("confirm-type")).toBe("ERC-20 token transfer (TST)");
}
test("the first time", async () => {
const page = await bootOnAddress();
await openToken(page);
await expectSendsToken(page);
});
test("after Back from Send", async () => {
const page = await bootOnAddress();
await openToken(page);
await page.click("btn-address-token-send");
await page.click("btn-send-back");
await expectSendsToken(page);
});
test("when the token is opened again after Back from AddressToken", async () => {
const page = await bootOnAddress();
await openToken(page);
await backFromToken(page);
await openToken(page);
await expectSendsToken(page);
});
});
+185
View File
@@ -0,0 +1,185 @@
// Home's "Send" and "Receive" open with no token selected. Three routes reach
// Home with a token still selected, Send reads that token ahead of its dropdown,
// and Receive warns about it
// (https://git.eeqj.de/sneak/AutistMask/issues/753). Driven through the real
// popup over a stored profile.
const {
bootPopup,
cleanupPopup,
unversionedValidProfile,
TOKEN_ADDRESS,
} = require("./support/popupBoot");
const RECIPIENT = "0x711e691962E5c054b78071C789A0C8CE657377aD";
const OTHER_WALLET_ADDRESS = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
const PRIVATE_KEY = "0x" + "11".repeat(32);
const PASSWORD = "correct horse battery staple";
const TOKEN = {
address: TOKEN_ADDRESS,
symbol: "TST",
decimals: 18,
name: "Test Token",
};
afterEach(() => {
cleanupPopup();
});
// AddressToken opened from AddressDetail, as tapping the token's row does.
async function openToken(page) {
const { state } = require("../src/shared/state");
state.selectedToken = TOKEN_ADDRESS;
require("../src/popup/views/helpers").pushCurrentView();
require("../src/popup/views/addressToken").show();
await page.settle();
expect(page.visibleViews()).toEqual(["address-token"]);
}
// The popup open on AddressToken, at an address holding 1.5 ETH and 2.5 TST.
// A second wallet is there for the route that deletes one.
async function bootOnToken() {
const profile = unversionedValidProfile({
currentView: "address",
viewStack: ["main"],
selectedWallet: 0,
selectedAddress: 0,
trackedTokens: [TOKEN],
});
profile.wallets[0].addresses[0].tokenBalances = [
{ ...TOKEN, balance: "2.5" },
];
profile.wallets.push({
type: "hd",
name: "Wallet 2",
xpub: "xpub-wallet-2",
encryptedSecret: "encrypted-secret-2",
nextIndex: 1,
addresses: [
{
address: OTHER_WALLET_ADDRESS,
balance: "0",
tokenBalances: [],
},
],
});
const page = await bootPopup(profile);
await openToken(page);
return page;
}
async function reopenAfterTokenSend() {
const closed = await bootOnToken();
await closed.click("btn-address-token-send");
cleanupPopup();
return bootPopup(undefined, { storage: closed.storage });
}
async function addWalletFromTokenSettings() {
const page = await bootOnToken();
await page.click("btn-settings");
await page.click("btn-main-add-wallet");
await page.click("tab-privkey");
page.node("import-private-key").value = PRIVATE_KEY;
page.node("add-wallet-password").value = PASSWORD;
page.node("add-wallet-password-confirm").value = PASSWORD;
await page.click("btn-add-wallet-confirm");
return page;
}
// Deletes through the lost-password screen. It asks for the wallet's name, and
// the stored wallets here have no real password.
async function deleteWalletFromTokenSettings() {
const page = await bootOnToken();
await page.click("btn-settings");
// What the second wallet's [x] button in Settings does.
require("../src/popup/views/helpers").pushCurrentView();
require("../src/popup/views/deleteWallet").show(1);
await page.click("btn-delete-wallet-lost-password");
page.node("delete-wallet-lost-name-input").value = "Wallet 2";
await page.click("btn-delete-wallet-lost-confirm");
await page.click("btn-settings-back");
return page;
}
const ROUTES = [
["reopening the popup after AddressToken's Send", reopenAfterTokenSend],
[
"the Settings gear on AddressToken, then adding a wallet",
addWalletFromTokenSettings,
],
[
"the Settings gear on AddressToken, then deleting another wallet, then Back",
deleteWalletFromTokenSettings,
],
];
// A route that stopped reaching Home with the token selected would prove nothing
// in the tests below, so it fails here.
async function arriveOnHome(route) {
const page = await route();
const { state } = require("../src/shared/state");
expect(page.visibleViews()).toEqual(["main"]);
expect(state.selectedToken).toBe(TOKEN_ADDRESS);
return page;
}
// ETH chosen in Send's dropdown, as the user does.
async function chooseEth(page) {
const dropdown = page.node("send-token");
dropdown.value = "ETH";
for (const fn of dropdown.listeners.change) await fn();
}
// Review for 1 of the token Send has, to RECIPIENT.
async function review(page) {
page.node("send-to").value = RECIPIENT;
page.node("send-amount").value = "1";
await page.click("btn-send-review");
expect(page.visibleViews()).toEqual(["confirm-tx"]);
}
describe.each(ROUTES)("after %s", (_name, route) => {
test("choosing ETH on Home's Send reviews ETH", async () => {
const page = await arriveOnHome(route);
await page.click("btn-main-send");
await chooseEth(page);
await review(page);
expect(page.text("confirm-type")).toBe("Native ETH transfer");
expect(page.storage.read("autistmask").selectedToken).toBeNull();
});
test("Home's Send starts on ETH", async () => {
const page = await arriveOnHome(route);
await page.click("btn-main-send");
expect(page.text("send-balance")).toBe("Current balance: 1.5000 ETH");
expect(page.value("send-token")).toBe("ETH");
});
test("Home's Receive shows no token warning", async () => {
const page = await arriveOnHome(route);
await page.click("btn-main-receive");
expect(page.visibleViews()).toEqual(["receive"]);
expect(page.text("receive-erc20-warning")).toBe("");
expect(page.storage.read("autistmask").selectedToken).toBeNull();
});
});
test("AddressToken's Send still sends the token after Home's Send has opened", async () => {
const page = await arriveOnHome(deleteWalletFromTokenSettings);
await page.click("btn-main-send");
await page.click("btn-send-back");
await openToken(page);
await page.click("btn-address-token-send");
expect(page.text("send-balance")).toBe("Current balance: 2.5000 TST");
await review(page);
expect(page.text("confirm-type")).toBe("ERC-20 token transfer (TST)");
});