Files
sattle/e2e/specs/wallet-lifecycle.spec.ts
T

122 lines
4.0 KiB
TypeScript

import { test, expect } from '../fixtures';
// Wallet lifecycle in the real browser: an unsupported LEGACY (alpha)
// install - the linking-key-only record, the pre-v2 bearer store and their
// ownerless residue - is never migrated (the cash root is unrecoverable
// from a linking key), so boot resets the device to the uninstalled state
// before any route can expose it, and a successor wallet starts without
// any of the old residue.
// every namespace the alpha wallet could have touched
const LEGACY_KEYS = [
'sattle_linking_key',
'sattle_bearers',
'sattle_wallet_material_v2',
'sattle_passkey_slots',
'sattle_nwc_connections',
'sattle_nwc_enabled',
'sattle_trusted_mints',
'sattle_biometric_wrap',
'sattle_activity',
'sattle_settings',
] as const;
test.describe('wallet lifecycle', () => {
test('a legacy alpha install is reset at boot and the successor starts clean', async ({
page,
}) => {
// Given a legacy encrypted linking-key record plus ownerless residue
await page.addInitScript(() => {
localStorage.setItem(
'sattle_linking_key',
JSON.stringify({
enc: true,
salt: '11'.repeat(16),
iv: '22'.repeat(12),
ciphertext: '33'.repeat(48),
}),
);
localStorage.setItem(
'sattle_bearers',
JSON.stringify([{ url: 'https://mint.example/note?k1=legacy', amount: 1000 }]),
);
localStorage.setItem(
'sattle_passkey_slots',
JSON.stringify([
{
credentialId: '11'.repeat(16),
hkdfSalt: '22'.repeat(16),
iv: '33'.repeat(12),
wrappedKey: '44'.repeat(48),
createdAt: 1,
},
]),
);
localStorage.setItem(
'sattle_nwc_connections',
JSON.stringify([
{
clientPubkey: '55'.repeat(32),
relays: ['wss://relay.example'],
budget: { maxMsat: 1000, periodMs: 60_000 },
spent: { periodStart: 0, msat: 0 },
createdAt: 1,
},
]),
);
localStorage.setItem('sattle_nwc_enabled', 'true');
localStorage.setItem(
'sattle_trusted_mints',
JSON.stringify([
{
server: 'legacy.example',
mintPubkey: `02${'aa'.repeat(32)}`,
addedAt: 1,
locked: false,
},
]),
);
});
// When the app boots
await page.goto('/#/');
// Then the alpha reset landed on the uninstalled state - no unlock
// surface for a wallet that can never be derived again
await expect(page.getByRole('button', { name: 'Get started' })).toBeVisible();
await expect(page.getByText('Wallet locked')).toHaveCount(0);
// ...and every namespace the alpha wallet could have touched is gone
const remaining = await page.evaluate(
(keys) => keys.filter((key) => localStorage.getItem(key) !== null),
[...LEGACY_KEYS],
);
expect(remaining).toEqual([]);
// When a successor wallet is created
await page.getByRole('button', { name: 'Get started' }).click();
await page.getByRole('button', { name: 'Create wallet' }).click();
await page.locator('.q-checkbox', { hasText: 'I wrote it down' }).click();
await page.getByRole('button', { name: 'Continue' }).click();
// Then it unlocks into a working wallet with zero adopted residue
// (settings/activity are the live app's own namespaces - the residue
// check covers only the stores a predecessor could have planted)
await expect(page.getByRole('button', { name: 'Receive' })).toBeVisible();
await expect(page.locator('.balance-card .text-h2')).toHaveText('0');
const residue = await page.evaluate(
(keys) => keys.filter((key) => localStorage.getItem(key) !== null),
[
'sattle_linking_key',
'sattle_bearers',
'sattle_passkey_slots',
'sattle_nwc_connections',
'sattle_nwc_enabled',
'sattle_trusted_mints',
'sattle_biometric_wrap',
],
);
expect(residue).toEqual([]);
});
});