diff --git a/CHANGELOG.md b/CHANGELOG.md index c3710b6269..d50d5748ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- Bitcoin: show account details for the persisted receive address type by default ## v4.51.0 - Bundle BitBox02 and BitBox02 Nova firmware version v9.26.1 diff --git a/frontends/web/src/routes/account/info/xpub-detail.tsx b/frontends/web/src/routes/account/info/xpub-detail.tsx index 01898685e7..99e805629e 100644 --- a/frontends/web/src/routes/account/info/xpub-detail.tsx +++ b/frontends/web/src/routes/account/info/xpub-detail.tsx @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useLoad } from '@/hooks/api'; -import { getInfo, TAccount, AccountCode } from '@/api/account'; +import { getInfo, TAccount, AccountCode, ScriptType, TSigningConfiguration } from '@/api/account'; import { findAccount } from '@/routes/account/utils'; import { GuidedContent, GuideWrapper, Header, Main } from '@/components/layout'; import { View, ViewContent } from '@/components/view/view'; @@ -20,6 +20,19 @@ type TProps = { code: AccountCode; }; +export const getDefaultSigningConfigurationIndex = ( + signingConfigurations: TSigningConfiguration[], + receiveScriptType: ScriptType | undefined, +): number => { + if (!receiveScriptType) { + return 0; + } + const index = signingConfigurations.findIndex( + cfg => cfg.bitcoinSimple?.scriptType === receiveScriptType + ); + return index === -1 ? 0 : index; +}; + export const XPubDetail = ({ accounts, code, @@ -27,7 +40,11 @@ export const XPubDetail = ({ const { t } = useTranslation(); const navigate = useNavigate(); const info = useLoad(getInfo(code)); - const [viewXPub, setViewXPub] = useState(0); + const [viewXPub, setViewXPub] = useState(); + + useEffect(() => { + setViewXPub(undefined); + }, [code]); const account = findAccount(accounts, code); if (!account || !info) { @@ -38,7 +55,14 @@ export const XPubDetail = ({ if (numberOfXPubs === 0) { return null; } - const safeViewXPub = Math.max(0, Math.min(viewXPub, numberOfXPubs - 1)); + const defaultViewXPub = getDefaultSigningConfigurationIndex( + info.signingConfigurations, + account.receiveScriptType + ); + const safeViewXPubIndex = (index: number | undefined) => ( + Math.max(0, Math.min(index ?? defaultViewXPub, numberOfXPubs - 1)) + ); + const safeViewXPub = safeViewXPubIndex(viewXPub); const config = info.signingConfigurations[safeViewXPub]; if (!config) { return null; @@ -46,7 +70,7 @@ export const XPubDetail = ({ const xpubTypes = info.signingConfigurations.map(cfg => cfg.bitcoinSimple?.scriptType); const showNextXPub = () => { - setViewXPub(prev => (prev + 1) % numberOfXPubs); + setViewXPub(prev => (safeViewXPubIndex(prev) + 1) % numberOfXPubs); }; const xpubType = xpubTypes[(safeViewXPub + 1) % numberOfXPubs]; diff --git a/frontends/web/tests/account-info.test.ts b/frontends/web/tests/account-info.test.ts new file mode 100644 index 0000000000..42b7abfe4f --- /dev/null +++ b/frontends/web/tests/account-info.test.ts @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { expect } from '@playwright/test'; +import { ChildProcess } from 'child_process'; +import { test } from './helpers/fixtures'; +import { deleteAccountsFile } from './helpers/fs'; +import { getAccountCodeFromUrl } from './helpers/account'; +import { cleanupRegtest, launchRegtest, setupRegtestWallet } from './helpers/regtest'; +import { ServeWallet } from './helpers/servewallet'; + +let servewallet: ServeWallet | undefined; +let regtest: ChildProcess | undefined; + +test('Account details default to persisted Taproot receive address type', async ({ + page, + host, + frontendPort, + servewalletPort, +}, testInfo) => { + await test.step('Start regtest and servewallet', async () => { + regtest = await launchRegtest(); + await setupRegtestWallet(); + + servewallet = new ServeWallet( + page, + servewalletPort, + frontendPort, + host, + testInfo.outputDir, + { regtest: true, testnet: false } + ); + await servewallet.start(); + }); + + let accountCode: string; + + await test.step('Unlock test wallet and select Taproot receive address type', async () => { + await page.getByRole('button', { name: 'Test wallet' }).click(); + await page.getByRole('button', { name: 'Unlock' }).click(); + await page.getByRole('link', { name: 'Bitcoin Regtest Bitcoin' }).click(); + accountCode = getAccountCodeFromUrl(page.url()); + + await page.getByRole('button', { name: 'Receive Bitcoin' }).click(); + await page.waitForURL('**/receive'); + await page.getByRole('button', { name: 'Change address type' }).click(); + await page.locator('label[for="p2tr"]').click(); + await expect(page.getByLabel('Taproot (newest format)')).toBeChecked(); + + const receiveScriptTypeResponse = page.waitForResponse((response) => + response.url().includes('/set-account-receive-script-type') && response.request().method() === 'POST' + ); + await page.getByRole('button', { name: 'Done' }).click(); + await receiveScriptTypeResponse; + }); + + await test.step('Open account details', async () => { + await page.getByRole('button', { name: 'Back' }).click(); + await page.waitForURL(/#\/account\/[^/]+$/); + await page.getByRole('link', { name: /Account info/i }).click(); + await page.getByRole('button', { name: 'View account details' }).click(); + await page.waitForURL(`**/account/${accountCode!}/info/xpub-detail`); + }); + + await test.step('Verify Taproot account details are shown by default', async () => { + await expect(page.getByText(/Currently displaying P2TR extended public key/)).toBeVisible(); + await expect(page.getByText('Taproot (bech32m, P2TR)')).toBeVisible(); + }); +}); + +test.beforeEach(async () => { + deleteAccountsFile(); +}); + +test.afterAll(async () => { + await servewallet?.stop(); + await cleanupRegtest(regtest); +});