From ce4a543d5efd631d20662a78d119457c5a73b8b6 Mon Sep 17 00:00:00 2001 From: Nikolas De Giorgis Date: Mon, 11 May 2026 11:01:17 +0100 Subject: [PATCH] frontend: sync account details with recv addr type Default the Bitcoin account details view to the persisted receive script type instead of always showing the first signing configuration. This makes accounts configured to receive on Taproot open Account details on Taproot information by default, while preserving the existing toggle between available signing configurations. --- CHANGELOG.md | 1 + .../src/routes/account/info/xpub-detail.tsx | 34 ++++++-- frontends/web/tests/account-info.test.ts | 77 +++++++++++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 frontends/web/tests/account-info.test.ts 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); +});