From f087ebe2f2e28bbd0e4c3cfe3407e77c0a78a7aa Mon Sep 17 00:00:00 2001 From: protom Date: Sat, 22 Aug 2026 16:56:38 +0200 Subject: [PATCH] fix: surface receive post-commit trust failures --- e2e/specs/receive.spec.ts | 51 ++++ .../receive/ReceiveLightningDialog.vue | 269 ++---------------- src/components/receive/ReceiveTokenDialog.vue | 243 ++-------------- src/composables/useMintTrustPrompt.ts | 46 +++ src/composables/useReceiveLightningDialog.ts | 245 ++++++++++++++++ src/composables/useReceiveTokenDialog.ts | 183 ++++++++++++ 6 files changed, 585 insertions(+), 452 deletions(-) create mode 100644 src/composables/useMintTrustPrompt.ts create mode 100644 src/composables/useReceiveLightningDialog.ts create mode 100644 src/composables/useReceiveTokenDialog.ts diff --git a/e2e/specs/receive.spec.ts b/e2e/specs/receive.spec.ts index ad55851..f5eeee7 100644 --- a/e2e/specs/receive.spec.ts +++ b/e2e/specs/receive.spec.ts @@ -5,6 +5,7 @@ import { MINT_ORIGIN, NOTE_PATH } from '../helpers/MintMocker'; import { createFreshWallet } from '../helpers/wallet'; const AMOUNT_MSAT = 21_000; // 21 sats +const MINT_PUBKEY = `02${'aa'.repeat(32)}`; // a syntactically valid bearer note against the mock mint - the k1 is a // fresh random secret, so every test redeems a distinct note @@ -58,4 +59,54 @@ test.describe('Receive bearer note', () => { await page.keyboard.press('Escape'); await expect(page.locator('.balance-card .text-h2')).toHaveText('0'); }); + + test('an already trusted current-owner mint bypasses the first-contact prompt', async ({ + page, + mint, + }) => { + await mint.mockNoteInfo({ amountMsat: AMOUNT_MSAT, mintPubkey: MINT_PUBKEY }); + await mint.mockRotateOk(); + await createFreshWallet(page); + const receiveDialog = page.locator('.q-dialog', { hasText: 'Receive bearer note' }); + await redeemNote(page, freshNoteUrl()); + const trustDialog = page.locator('.q-dialog', { hasText: 'New mint' }); + await expect(trustDialog).toBeVisible(); + await trustDialog.getByRole('button', { name: 'Just this once' }).click(); + await expect(trustDialog).toHaveCount(0); + await page.keyboard.press('Escape'); + await expect(receiveDialog).toHaveCount(0); + + await redeemNote(page, freshNoteUrl()); + + await expect(receiveDialog.getByText('Received 21 sats')).toBeVisible(); + await expect(trustDialog).toHaveCount(0); + await expect(page.locator('.balance-card .text-h2')).toHaveText('42'); + }); + + test('trust failure after commit keeps received funds and warns against retry', async ({ + page, + mint, + }) => { + await mint.mockNoteInfo({ amountMsat: AMOUNT_MSAT, mintPubkey: MINT_PUBKEY }); + await mint.mockRotateOk(); + await createFreshWallet(page); + await page.evaluate(() => { + localStorage.setItem('sattle_trusted_mints', '{"version":1,"ownerId":"malformed"}'); + }); + + const dialog = page.locator('.q-dialog', { hasText: 'Receive bearer note' }); + await redeemNote(page, freshNoteUrl()); + + await expect(dialog.getByText('Received 21 sats')).toBeVisible(); + await expect( + page.getByText(/Funds were saved.*receive succeeded.*do not retry/i), + ).toBeVisible(); + await expect(dialog.locator('.q-banner')).toHaveCount(0); + const trustDialog = page.locator('.q-dialog', { hasText: 'New mint' }); + await trustDialog.getByRole('button', { name: 'Just this once' }).click(); + await dialog.getByRole('button', { name: 'Done' }).click(); + await page.reload(); + + await expect(page.locator('.balance-card .text-h2')).toHaveText('21'); + }); }); diff --git a/src/components/receive/ReceiveLightningDialog.vue b/src/components/receive/ReceiveLightningDialog.vue index 73bb011..4851300 100644 --- a/src/components/receive/ReceiveLightningDialog.vue +++ b/src/components/receive/ReceiveLightningDialog.vue @@ -202,19 +202,8 @@