From c7e5807601a697ff84646d8d282ddf271f4cd5f9 Mon Sep 17 00:00:00 2001 From: Nuno Lopes Date: Wed, 21 Jan 2026 16:00:47 +0000 Subject: [PATCH 1/2] add delay and retry to mAIner topup backend call Adds a 3-second delay between successful token transfer and backend topup call to ensure the ledger transaction is queryable. Also adds one automatic retry with a 2-second wait if the initial backend call fails. --- .../components/funnai/MainerTopUpModal.svelte | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte b/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte index 720071c8..f60a6eee 100644 --- a/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte +++ b/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte @@ -678,22 +678,52 @@ mainerAgent: cleanMainerAgent, }; - // Create the backend promise based on token type - let backendPromise: Promise; - if (selectedTokenSymbol === 'FUNNAI') { - // For FUNNAI, use the FUNNAI-specific endpoint - if (!$store.gameStateCanisterActor) { - throw new Error("Game state canister not available"); - }; - backendPromise = $store.gameStateCanisterActor.topUpCyclesForMainerAgentWithFunnai(topUpInput); - } else { - // For ICP, BOB, and ckBTC, use the standard ICP endpoint - if (!$store.gameStateCanisterActor) { - throw new Error("Game state canister not available"); + // Helper function to call the backend topup with retry + async function callBackendTopupWithRetry(topUpInput: any, tokenSymbol: string): Promise { + const DELAY_BEFORE_TOPUP_MS = 3000; // 3 second delay to ensure ledger transaction is queryable + + // Wait before calling the backend to ensure the ledger transaction is queryable + console.log(`Waiting ${DELAY_BEFORE_TOPUP_MS}ms before calling backend topup...`); + await new Promise(resolve => setTimeout(resolve, DELAY_BEFORE_TOPUP_MS)); + + const makeTopupCall = () => { + if (tokenSymbol === 'FUNNAI') { + if (!$store.gameStateCanisterActor) { + throw new Error("Game state canister not available"); + } + return $store.gameStateCanisterActor.topUpCyclesForMainerAgentWithFunnai(topUpInput); + } else { + if (!$store.gameStateCanisterActor) { + throw new Error("Game state canister not available"); + } + return $store.gameStateCanisterActor.topUpCyclesForMainerAgent(topUpInput); + } }; - backendPromise = $store.gameStateCanisterActor.topUpCyclesForMainerAgent(topUpInput); + + try { + const result = await makeTopupCall(); + return result; + } catch (firstError) { + console.warn("First backend topup call failed, retrying in 2 seconds...", firstError); + + // Wait 2 seconds before retry + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Retry once + try { + const retryResult = await makeTopupCall(); + console.log("Backend topup retry succeeded"); + return retryResult; + } catch (retryError) { + console.error("Backend topup retry also failed", retryError); + throw retryError; + } + } } + // Create the backend promise with delay and retry logic + let backendPromise: Promise = callBackendTopupWithRetry(topUpInput, selectedTokenSymbol); + // Handle celebration for max amounts (only for ICP) const shouldCelebrate = isMaxAmount && CELEBRATION_ENABLED && selectedTokenSymbol === 'ICP'; From bd97442eea699f8a70e92d9201f629c497d57af8 Mon Sep 17 00:00:00 2001 From: Nuno Lopes Date: Wed, 21 Jan 2026 16:24:11 +0000 Subject: [PATCH 2/2] also retry backend topup when it returns Err variant The retry logic now triggers not only on exceptions but also when the backend call returns successfully but with an Err result variant. --- .../components/funnai/MainerTopUpModal.svelte | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte b/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte index f60a6eee..b21c1f72 100644 --- a/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte +++ b/src/funnai_frontend/components/funnai/MainerTopUpModal.svelte @@ -700,11 +700,38 @@ } }; + // Helper to check if result is an error (Err variant) + const isErrorResult = (result: any): boolean => { + return result && typeof result === 'object' && 'Err' in result; + }; + + // Helper to get error from result + const getError = (result: any): any => { + return result?.Err; + }; + try { const result = await makeTopupCall(); + + // Check if result is an Err variant - if so, retry + if (isErrorResult(result)) { + console.warn("First backend topup call returned Err, retrying in 2 seconds...", getError(result)); + + // Wait 2 seconds before retry + await new Promise(resolve => setTimeout(resolve, 2000)); + + const retryResult = await makeTopupCall(); + if (isErrorResult(retryResult)) { + console.error("Backend topup retry also returned Err", getError(retryResult)); + } else { + console.log("Backend topup retry succeeded"); + } + return retryResult; + } + return result; } catch (firstError) { - console.warn("First backend topup call failed, retrying in 2 seconds...", firstError); + console.warn("First backend topup call threw exception, retrying in 2 seconds...", firstError); // Wait 2 seconds before retry await new Promise(resolve => setTimeout(resolve, 2000)); @@ -712,10 +739,14 @@ // Retry once try { const retryResult = await makeTopupCall(); - console.log("Backend topup retry succeeded"); + if (isErrorResult(retryResult)) { + console.error("Backend topup retry returned Err after exception", getError(retryResult)); + } else { + console.log("Backend topup retry succeeded"); + } return retryResult; } catch (retryError) { - console.error("Backend topup retry also failed", retryError); + console.error("Backend topup retry also threw exception", retryError); throw retryError; } }