diff --git a/src/background/services/privacy-provider-client.ts b/src/background/services/privacy-provider-client.ts index ffe9328..6b27494 100644 --- a/src/background/services/privacy-provider-client.ts +++ b/src/background/services/privacy-provider-client.ts @@ -168,6 +168,7 @@ export class PrivacyProviderClient { "bundle.operations_count": String(params.operationsMLXDR.length), "bundle.channel_contract_id": params.channelContractId, }); + let operationsBundleId: string; try { const response = await axios.post<{ status: number; @@ -186,16 +187,16 @@ export class PrivacyProviderClient { }, }, ); - const operationsBundleId = response.data.data?.operationsBundleId; - if (!operationsBundleId || typeof operationsBundleId !== "string") { + const id = response.data.data?.operationsBundleId; + if (!id || typeof id !== "string") { throw new Error( `Invalid bundle response: operationsBundleId not found in ${ JSON.stringify(response.data) }`, ); } + operationsBundleId = id; if (span) endSpan(span, { code: 0 }); - return { id: operationsBundleId }; } catch (error: unknown) { if (span) endSpan(span, { code: 2, message: String(error) }); // Check if it's an authentication error @@ -207,5 +208,65 @@ export class PrivacyProviderClient { // Re-throw other errors as-is throw error; } + + // POST returns immediately with status:"PENDING" — poll until the + // executor settles the bundle on chain. Mirrors the e2e suite's + // waitForBundle. Without this, the popup goes home before settlement + // and the home view's balance polling races the executor. + await this.waitForBundle(token, operationsBundleId); + return { id: operationsBundleId }; + } + + private async waitForBundle( + token: string, + bundleId: string, + timeoutMs = 180_000, + pollIntervalMs = 5_000, + ): Promise { + const span = this.startSpan("GET /api/v1/bundle/:id (poll)", { + "bundle.id": bundleId, + }); + const start = Date.now(); + try { + while (Date.now() - start < timeoutMs) { + await new Promise((r) => setTimeout(r, pollIntervalMs)); + const res = await axios.get<{ + status: number; + message: string; + data: { status: string }; + }>( + `${this.baseUrl}/api/v1/bundle/${bundleId}`, + { + headers: { + Authorization: `Bearer ${token}`, + ...(span ? this.traceHeaders(span.spanId) : {}), + }, + }, + ); + const status = res.data.data?.status; + if (status === "COMPLETED") { + if (span) endSpan(span, { code: 0 }); + return; + } + if (status === "FAILED" || status === "EXPIRED") { + const err = new Error(`Bundle ${bundleId} ${status}`); + if (span) endSpan(span, { code: 2, message: err.message }); + throw err; + } + // PENDING / SUBMITTED / other intermediate → keep polling + } + const err = new Error( + `Bundle ${bundleId} timed out after ${timeoutMs}ms`, + ); + if (span) endSpan(span, { code: 2, message: err.message }); + throw err; + } catch (error: unknown) { + if (this.isAuthError(error)) { + throw new PrivacyProviderAuthError( + "Provider session expired or invalid. Please reconnect to the provider.", + ); + } + throw error; + } } } diff --git a/src/popup/api/deposit.ts b/src/popup/api/deposit.ts index f89699d..35f87c1 100644 --- a/src/popup/api/deposit.ts +++ b/src/popup/api/deposit.ts @@ -13,7 +13,7 @@ export const prepareDeposit = async ( return await callBackground({ type: MessageType.PrepareDeposit, ...params, - }); + }, { timeoutMs: 240_000 }); }; export const deposit = async ( @@ -22,5 +22,5 @@ export const deposit = async ( return await callBackground({ type: MessageType.Deposit, ...params, - }); + }, { timeoutMs: 240_000 }); }; diff --git a/src/popup/api/send.ts b/src/popup/api/send.ts index 0b43369..c3f90a8 100644 --- a/src/popup/api/send.ts +++ b/src/popup/api/send.ts @@ -13,7 +13,7 @@ export const send = async ( return await callBackground({ type: MessageType.Send, ...params, - }); + }, { timeoutMs: 240_000 }); }; export const prepareSend = async ( @@ -22,5 +22,5 @@ export const prepareSend = async ( return await callBackground({ type: MessageType.PrepareSend, ...params, - }); + }, { timeoutMs: 240_000 }); }; diff --git a/src/popup/api/withdraw.ts b/src/popup/api/withdraw.ts index 197f601..4139d0b 100644 --- a/src/popup/api/withdraw.ts +++ b/src/popup/api/withdraw.ts @@ -13,7 +13,7 @@ export const withdraw = async ( return await callBackground({ type: MessageType.Withdraw, ...params, - }); + }, { timeoutMs: 240_000 }); }; export const prepareWithdraw = async ( @@ -22,5 +22,5 @@ export const prepareWithdraw = async ( return await callBackground({ type: MessageType.PrepareWithdraw, ...params, - }); + }, { timeoutMs: 240_000 }); }; diff --git a/src/popup/pages/send-confirmation-page.tsx b/src/popup/pages/send-confirmation-page.tsx index 8c17d31..5da9e40 100644 --- a/src/popup/pages/send-confirmation-page.tsx +++ b/src/popup/pages/send-confirmation-page.tsx @@ -403,6 +403,7 @@ export function SendConfirmationPage() {