Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 73 additions & 12 deletions src/funnai_frontend/components/funnai/MainerTopUpModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -678,22 +678,83 @@
mainerAgent: cleanMainerAgent,
};

// Create the backend promise based on token type
let backendPromise: Promise<any>;
if (selectedTokenSymbol === 'FUNNAI') {
// For FUNNAI, use the FUNNAI-specific 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<any> {
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.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 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;
};
backendPromise = $store.gameStateCanisterActor.topUpCyclesForMainerAgent(topUpInput);

try {
const result = await makeTopupCall();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before we didn't await the backend response ("backendPromise") to avoid keeping the user on the screen too long. As we now await it, the user has to wait on the topup modal. Is there a good way to reconcile this with the retry?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it still closes the modal before the transaction completes. is that what you are considering here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, then this sounds good


// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the backend call doesn't return properly (OK but Err), we need to retry as well

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));

// Retry once
try {
const retryResult = await makeTopupCall();
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 threw exception", retryError);
throw retryError;
}
}
}

// Create the backend promise with delay and retry logic
let backendPromise: Promise<any> = callBackendTopupWithRetry(topUpInput, selectedTokenSymbol);

// Handle celebration for max amounts (only for ICP)
const shouldCelebrate = isMaxAmount && CELEBRATION_ENABLED && selectedTokenSymbol === 'ICP';

Expand Down