Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
34 changes: 29 additions & 5 deletions frontends/web/src/routes/account/info/xpub-detail.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -20,14 +20,31 @@ 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,
}: TProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const info = useLoad(getInfo(code));
const [viewXPub, setViewXPub] = useState<number>(0);
const [viewXPub, setViewXPub] = useState<number | undefined>();

useEffect(() => {
setViewXPub(undefined);
}, [code]);

const account = findAccount(accounts, code);
if (!account || !info) {
Expand All @@ -38,15 +55,22 @@ 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;
}
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];
Expand Down
77 changes: 77 additions & 0 deletions frontends/web/tests/account-info.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});