From e517a8fae811fe56f9bec5f7e7a01d371b7181b2 Mon Sep 17 00:00:00 2001 From: "smx.pusha" <139338836+smxpusha@users.noreply.github.com> Date: Tue, 18 Aug 2026 10:22:44 +0200 Subject: [PATCH] FIX - recover expired crypto quotes Expire client-side trade quotes at their server deadline and clear rejected execution state so browser debug sessions can request and execute a fresh quote. --- frontend/src/stores/crypto.test.ts | 27 +++++++++++++++++++++++++++ frontend/src/stores/crypto.ts | 17 ++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/frontend/src/stores/crypto.test.ts b/frontend/src/stores/crypto.test.ts index cc5d02b..f2b57ea 100644 --- a/frontend/src/stores/crypto.test.ts +++ b/frontend/src/stores/crypto.test.ts @@ -81,6 +81,33 @@ describe('crypto store', () => { expect(crypto.pendingQuote).toBeNull() }) + it('removes an expired quote so the trade form can request a new one', async () => { + vi.useFakeTimers() + const expiringQuote = { ...quote, expiresAt: Date.now() + 1000 } + mockNuiCall.mockResolvedValueOnce({ data: expiringQuote, success: true }) + const crypto = useCryptoStore() + + await crypto.quote('aurora', 'buy', '1') + expect(crypto.pendingQuote).toEqual(expiringQuote) + + await vi.advanceTimersByTimeAsync(1000) + expect(crypto.pendingQuote).toBeNull() + vi.useRealTimers() + }) + + it('clears a rejected execution so a fresh quote can be requested', async () => { + mockNuiCall.mockResolvedValueOnce({ + error: 'quote_expired', + success: false, + }) + const crypto = useCryptoStore() + crypto.pendingQuote = quote + + expect(await crypto.executeQuote()).toBe(false) + expect(crypto.pendingQuote).toBeNull() + expect(crypto.error).toBe('quote_expired') + }) + it('keeps server errors and does not replace portfolio state', async () => { mockNuiCall.mockResolvedValueOnce({ error: 'quote_expired', diff --git a/frontend/src/stores/crypto.ts b/frontend/src/stores/crypto.ts index 3162195..ed834a1 100644 --- a/frontend/src/stores/crypto.ts +++ b/frontend/src/stores/crypto.ts @@ -82,6 +82,21 @@ export const useCryptoStore = defineStore('crypto', { side, }) this.pendingQuote = response.success ? (response.data ?? null) : null + if (this.pendingQuote) { + const quoteId = this.pendingQuote.id + const expiresAt = this.pendingQuote.expiresAt + globalThis.setTimeout( + () => { + if ( + this.pendingQuote?.id === quoteId && + this.pendingQuote.expiresAt <= Date.now() + ) { + this.pendingQuote = null + } + }, + Math.max(0, expiresAt - Date.now()), + ) + } return response }, async executeQuote(): Promise { @@ -90,9 +105,9 @@ export const useCryptoStore = defineStore('crypto', { idempotencyKey: requestKey('trade'), quoteId: this.pendingQuote.id, }) + this.pendingQuote = null if (!response.success || !response.data) return false this.data = response.data - this.pendingQuote = null return true }, async updateProfile(payload: {