diff --git a/README.md b/README.md index d08f843..435cee2 100644 --- a/README.md +++ b/README.md @@ -1488,8 +1488,10 @@ view would leave a wallet one click from deletion. - **Transitions**: - "Import" with a valid entry and a matching password of at least 12 characters → creates the wallet, clears the navigation stack, and → - **Home**. The phrase and xprv modes then scan for further used addresses - and report the count as a flash message. + **Home**. When no address is active yet, as on the first wallet, the new + wallet's first address becomes the active one. The phrase and xprv modes + then scan for further used addresses and report the count as a flash + message. - "Import" with a missing, short or mismatched password → full-sentence error on the error line, no screen change - "Import" with an invalid entry or a duplicate wallet or address → flash diff --git a/src/popup/views/addWallet.js b/src/popup/views/addWallet.js index 660061a..e8a612d 100644 --- a/src/popup/views/addWallet.js +++ b/src/popup/views/addWallet.js @@ -194,6 +194,9 @@ async function importMnemonic(ctx) { }; state.wallets.push(wallet); state.hasWallet = true; + if (state.activeAddress === null) { + state.activeAddress = firstAddress; + } await saveState(); clearViewStack(); ctx.renderWalletList(); @@ -254,6 +257,9 @@ async function importPrivateKey(ctx) { addresses: [{ address: addr, balance: "0.0000", tokenBalances: [] }], }); state.hasWallet = true; + if (state.activeAddress === null) { + state.activeAddress = addr; + } await saveState(); clearViewStack(); ctx.renderWalletList(); @@ -315,6 +321,9 @@ async function importXprvKey(ctx) { }; state.wallets.push(wallet); state.hasWallet = true; + if (state.activeAddress === null) { + state.activeAddress = firstAddress; + } await saveState(); clearViewStack(); ctx.renderWalletList(); diff --git a/tests/firstWalletActiveAddress.test.js b/tests/firstWalletActiveAddress.test.js new file mode 100644 index 0000000..5a10e83 --- /dev/null +++ b/tests/firstWalletActiveAddress.test.js @@ -0,0 +1,69 @@ +// Adding the first wallet left no address active until the popup was reopened, +// so Send and Receive on Home only said "No active address selected." +// (https://git.eeqj.de/sneak/AutistMask/issues/512). Each test boots a fresh +// install, adds the first wallet through one of the three import tabs, and +// opens Receive from Home in the same popup. + +const { HDNodeWallet, Mnemonic, Wallet } = require("ethers"); + +const { bootPopup, cleanupPopup } = require("./support/popupBoot"); + +const PHRASE = + "abandon abandon abandon abandon abandon abandon " + + "abandon abandon abandon abandon abandon about"; +const PRIVATE_KEY = "0x" + "11".repeat(32); +// The master key of PHRASE, so its first address is the phrase's. +const XPRV = HDNodeWallet.fromSeed( + Mnemonic.fromPhrase(PHRASE).computeSeed(), +).extendedKey; +const PASSWORD = "correct horse battery staple"; + +const PHRASE_ADDRESS = HDNodeWallet.fromPhrase(PHRASE).address; + +const TABS = [ + { + tab: "tab-mnemonic", + field: "wallet-mnemonic", + secret: PHRASE, + address: PHRASE_ADDRESS, + }, + { + tab: "tab-privkey", + field: "import-private-key", + secret: PRIVATE_KEY, + address: new Wallet(PRIVATE_KEY).address, + }, + { + tab: "tab-xprv", + field: "import-xprv-key", + secret: XPRV, + address: PHRASE_ADDRESS, + }, +]; + +afterEach(() => { + cleanupPopup(); +}); + +describe("the first wallet's first address is active right away", () => { + test.each(TABS)( + "added from $tab, Receive opens on it", + async ({ tab, field, secret, address }) => { + const page = await bootPopup(undefined); + expect(page.pageErrors).toEqual([]); + await page.click("btn-welcome-add"); + await page.click(tab); + page.node(field).value = secret; + page.node("add-wallet-password").value = PASSWORD; + page.node("add-wallet-password-confirm").value = PASSWORD; + await page.click("btn-add-wallet-confirm"); + expect(page.visibleViews()).toEqual(["main"]); + + await page.click("btn-main-receive"); + expect(page.visibleViews()).toEqual(["receive"]); + expect(page.node("receive-address-block").dataset.full).toBe( + address, + ); + }, + ); +}); diff --git a/tests/support/popupBoot.js b/tests/support/popupBoot.js index 308d0d4..06938cf 100644 --- a/tests/support/popupBoot.js +++ b/tests/support/popupBoot.js @@ -258,12 +258,17 @@ async function bootPopup(stored, options) { formatAddressTotal: () => "", getAddressValue: () => ({ usd: null, partial: false }), })); + // scanForAddresses() answers in the real one's shape, a scan that found + // no used address: AddWallet reads `addresses` from it. jest.doMock("../../src/shared/balances", () => ({ fetchTokenBalances: jest.fn(async () => []), refreshBalances: jest.fn(async () => {}), lookupTokenInfo: jest.fn(async () => null), getProvider: () => ({}), - scanForAddresses: jest.fn(async () => []), + scanForAddresses: jest.fn(async () => ({ + addresses: [], + nextIndex: 1, + })), })); // filterTransactions() answers in the real one's shape: Home, // AddressDetail and AddressToken read both fields, and a bare list makes