From 48ee62a367a1bd94e159abf39279904d29218868 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:23:24 +0100 Subject: [PATCH 01/13] Add error types and interfaces for error handling --- src/types/errors.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/types/errors.ts diff --git a/src/types/errors.ts b/src/types/errors.ts new file mode 100644 index 0000000..72a977f --- /dev/null +++ b/src/types/errors.ts @@ -0,0 +1,19 @@ +// src/types/errors.ts +export type ErrorCategory = 'balance' | 'auth' | 'network' | 'contract' | 'wallet' | 'generic'; +export type ErrorSeverity = 'info' | 'warning' | 'error'; + +export interface ErrorDefinition { + category: ErrorCategory; + severity: ErrorSeverity; + humanTitle: string; + humanDescription: string; + troubleshootingSteps: string[]; + docsUrl?: string; + // For parameterized errors (e.g., extract amounts, addresses) + paramMap?: Record; +} + +export interface DecodedError extends ErrorDefinition { + rawError: string; + isUnknown?: boolean; +} From 4f0cfac255045ac6d617706e5212b65fbe093548 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:27:02 +0100 Subject: [PATCH 02/13] Add error decoder for transaction errors --- src/utils/errorDecoder.ts | 113 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/utils/errorDecoder.ts diff --git a/src/utils/errorDecoder.ts b/src/utils/errorDecoder.ts new file mode 100644 index 0000000..bdbaa3a --- /dev/null +++ b/src/utils/errorDecoder.ts @@ -0,0 +1,113 @@ +// src/utils/errorDecoder.ts +import type { DecodedError, ErrorDefinition } from '@/types/errors'; + +const ErrorCatalog: Record = { + 'tx_bad_seq': { + category: 'network', + severity: 'warning', + humanTitle: 'Sequence Number Mismatch', + humanDescription: 'Your account\'s transaction sequence has moved ahead of the submitted transaction.', + troubleshootingSteps: [ + 'Refresh the dashboard to sync latest sequence number', + 'Try submitting the transaction again' + ], + docsUrl: 'https://developers.stellar.org/docs/learn/encyclopedia/transactions/sequence-numbers' + }, + 'op_underfunded': { + category: 'balance', + severity: 'error', + humanTitle: 'Insufficient Funds', + humanDescription: 'The account does not have enough XLM to cover the minimum balance or transaction fees. Required minimum: {minBalance} XLM.', + troubleshootingSteps: [ + 'Add more XLM to your account', + 'Check current account balance in the wallet' + ] + }, + 'op_no_trust': { + category: 'auth', + severity: 'error', + humanTitle: 'No Trustline Established', + humanDescription: 'You need to establish a trustline for the asset before performing this operation.', + troubleshootingSteps: [ + 'Create a trustline for the required asset', + 'Confirm asset issuer in your wallet' + ] + }, + 'HostError: ValueUnknown': { + category: 'contract', + severity: 'error', + humanTitle: 'Contract Data Not Found', + humanDescription: 'The requested contract storage value was not found. This often occurs if data has expired or the contract state was reset.', + troubleshootingSteps: [ + 'The system will attempt to restore the data automatically', + 'Try the operation again in a few moments' + ] + }, + 'HostError: HostObjectError\\(ContractError\\(\\d+\\)\\)': { + category: 'contract', + severity: 'error', + humanTitle: 'Smart Contract Error', + humanDescription: 'The Soroban contract returned an error during execution. Check contract logic or input parameters.', + troubleshootingSteps: [ + 'Verify input parameters match contract expectations', + 'Review recent contract updates' + ] + }, + // Add 50-100+ more patterns here as needed (regex keys) + // Generic fallback + '.*': { + category: 'generic', + severity: 'error', + humanTitle: 'Unknown Error', + humanDescription: 'An unexpected error occurred. Raw details below.', + troubleshootingSteps: [ + 'Copy the raw error and report to support', + 'Try refreshing the page and retrying' + ] + } +}; + +function extractErrorMessage(rawError: unknown): string { + if (rawError instanceof Error) return rawError.message; + if (typeof rawError === 'string') return rawError; + if (rawError && typeof rawError === 'object' && 'message' in rawError) { + return (rawError as { message: string }).message; + } + if (rawError && typeof rawError === 'object' && 'error' in rawError) { + return String((rawError as { error: unknown }).error); + } + return String(rawError || 'Unknown error'); +} + +function interpolateParams(description: string, rawMessage: string): string { + // Simple param extraction example (extend as needed) + const minBalanceMatch = rawMessage.match(/minimum balance (\d+)/i); + if (minBalanceMatch) { + return description.replace('{minBalance}', minBalanceMatch[1]); + } + return description; +} + +export function decodeTransactionError(rawError: unknown): DecodedError { + const rawMessage = extractErrorMessage(rawError); + + for (const [pattern, definition] of Object.entries(ErrorCatalog)) { + const regex = new RegExp(pattern, 'i'); + if (regex.test(rawMessage)) { + const description = interpolateParams(definition.humanDescription, rawMessage); + return { + ...definition, + humanDescription: description, + rawError: rawMessage, + isUnknown: pattern === '.*' + }; + } + } + + // Should never reach here due to generic fallback + return { + ...ErrorCatalog['.*'], + rawError: rawMessage, + isUnknown: true + }; + } From f2806584a31332cb6a778ffa96b2d7e84895b84c Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:30:15 +0100 Subject: [PATCH 03/13] Add ErrorDisplay component for error handling --- src/components/shared/ErrorDisplay.tsx | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/components/shared/ErrorDisplay.tsx diff --git a/src/components/shared/ErrorDisplay.tsx b/src/components/shared/ErrorDisplay.tsx new file mode 100644 index 0000000..7f66ad2 --- /dev/null +++ b/src/components/shared/ErrorDisplay.tsx @@ -0,0 +1,78 @@ +// src/components/shared/ErrorDisplay.tsx +'use client'; + +import type { DecodedError } from '@/types/errors'; +import { useToast } from '@/components/Toast'; + +interface ErrorDisplayProps { + error: DecodedError | null; + onDismiss?: () => void; +} + +const severityIcons: Record = { + info: 'ℹ️', + warning: '⚠️', + error: '❌' +}; + +export function ErrorDisplay({ error, onDismiss }: ErrorDisplayProps) { + const { showToast } = useToast(); + + if (!error) return null; + + const copyRawError = () => { + navigator.clipboard.writeText(error.rawError); + showToast('Raw error copied to clipboard', 'info'); + }; + + return ( +
+
+ {severityIcons[error.severity]} +
+

{error.humanTitle}

+

{error.humanDescription}

+ + {error.troubleshootingSteps.length > 0 && ( +
+

Troubleshooting Steps

+
    + {error.troubleshootingSteps.map((step, idx) => ( +
  1. {step}
  2. + ))} +
+
+ )} + + {error.docsUrl && ( + + Learn More → + + )} + +
+ + {onDismiss && ( + + )} +
+
+
+
+ ); +} From 87277c51b5d6cddcae4dbce1231e32086b124353 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:46:01 +0100 Subject: [PATCH 04/13] Update useSorobanStaking.ts --- src/hooks/useSorobanStaking.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hooks/useSorobanStaking.ts b/src/hooks/useSorobanStaking.ts index fa00c89..3e63011 100644 --- a/src/hooks/useSorobanStaking.ts +++ b/src/hooks/useSorobanStaking.ts @@ -4,6 +4,8 @@ import { useState, useCallback, useEffect, useRef } from 'react'; import { sendTransaction as rpcSendTransaction } from '@/src/lib/stellar/rpcClient'; import { sha256 } from '@/src/lib/crypto'; import { useTxRetryQueue, MAX_RETRY_ATTEMPTS, CONFIRMED_REMOVAL_DELAY_MS, computeBackoff } from '@/src/hooks/useTxRetryQueue'; +import { decodeTransactionError } from '@/utils/errorDecoder'; +import { ErrorDisplay } from '@/components/shared/ErrorDisplay'; // If using in a parent component export type SubmitState = 'idle' | 'submitting' | 'confirmed' | 'error'; @@ -122,10 +124,19 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su setState('error'); onToastRef.current?.(`Network error — will retry (attempt ${retryCount}/${MAX_RETRY_ATTEMPTS})`, 'error'); } + const decoded = decodeTransactionError(result.error); + setError(decoded.humanTitle); // Or store full DecodedError in state + // ... } catch (err: unknown) { + const decoded = decodeTransactionError(err); + // Update state to hold DecodedError instead of string if desired + setError(decoded.humanTitle); + // ... + } const retryCount = entry.retryCount + 1; const nextRetryAt = Date.now() + computeBackoff(retryCount); queue.updateEntry(computedHash, { retryCount, nextRetryAt, status: 'pending' }); + } else if (result.status === 'error') { const msg = err instanceof Error ? err.message : 'Unknown error'; setError(msg); setState('error'); From ffcf5b34d15b387e5c32f0a2b505d028284131a1 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:03:24 +0100 Subject: [PATCH 05/13] Implement Sentry error capturing for unknown errors Add error capturing for unknown errors using Sentry. --- src/utils/errorDecoder.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/utils/errorDecoder.ts b/src/utils/errorDecoder.ts index bdbaa3a..05bc48c 100644 --- a/src/utils/errorDecoder.ts +++ b/src/utils/errorDecoder.ts @@ -100,6 +100,15 @@ export function decodeTransactionError(rawError: unknown): DecodedError { humanDescription: description, rawError: rawMessage, isUnknown: pattern === '.*' + if (isUnknown) { + // Assuming @sentry/nextjs is installed + import('@sentry/nextjs').then(({ captureException }) => { + captureException(rawError, { + tags: { errorType: 'decoder-unknown' }, + fingerprint: ['error-decoder-unknown', rawMessage.substring(0, 100)] + }); + }); + } }; } } From 8259017ebe4cbfab080fcfc970171edfab339d48 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:07:05 +0100 Subject: [PATCH 06/13] Add tests for errorDecoder utility --- tests/errorDecoder.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/errorDecoder.test.ts diff --git a/tests/errorDecoder.test.ts b/tests/errorDecoder.test.ts new file mode 100644 index 0000000..ada8c6f --- /dev/null +++ b/tests/errorDecoder.test.ts @@ -0,0 +1,18 @@ +// tests/errorDecoder.test.ts +import { decodeTransactionError } from '@/utils/errorDecoder'; +import { describe, it, expect } from 'vitest'; + +describe('Error Decoder', () => { + it('decodes tx_bad_seq', () => { + const decoded = decodeTransactionError('tx_bad_seq'); + expect(decoded.humanTitle).toBe('Sequence Number Mismatch'); + expect(decoded.troubleshootingSteps.length).toBeGreaterThan(0); + }); + + it('handles unknown errors', () => { + const decoded = decodeTransactionError('some_cryptic_host_error_123'); + expect(decoded.isUnknown).toBe(true); + }); + + // Add more for HostError patterns, null/undefined, etc. +}); From 5975914c8d01de4503e5bb852d7e1316a89f4fc1 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Sat, 20 Jun 2026 16:38:37 +0100 Subject: [PATCH 07/13] Improve error handling and transaction state updates --- src/hooks/useSorobanStaking.ts | 42 ++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/hooks/useSorobanStaking.ts b/src/hooks/useSorobanStaking.ts index 3e63011..4be6cf5 100644 --- a/src/hooks/useSorobanStaking.ts +++ b/src/hooks/useSorobanStaking.ts @@ -99,11 +99,14 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su if (result.status === 'confirmed') { queue.updateEntry(computedHash, { status: 'confirmed' }); - setTxHash(result.txHash); + setTxHash(result.txHash ?? computedHash); setState('confirmed'); onToastRef.current?.('Transaction confirmed', 'success'); setTimeout(() => queue.removeEntry(computedHash), CONFIRMED_REMOVAL_DELAY_MS); - } else if (result.status === 'error') { + return; + } + + if (result.status === 'error') { if (result.code === 'tx_bad_seq') { queue.updateEntry(computedHash, { status: 'confirmed' }); setTxHash(computedHash); @@ -112,35 +115,40 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su setTimeout(() => queue.removeEntry(computedHash), CONFIRMED_REMOVAL_DELAY_MS); } else { queue.updateEntry(computedHash, { status: 'failed' }); - setError(result.error); + const decoded = decodeTransactionError(result.error); + setError(decoded.humanTitle); setState('error'); - onToastRef.current?.(result.error, 'error'); + onToastRef.current?.(decoded.humanTitle, 'error'); } - } else if (result.status === 'network_error') { + return; + } + + if (result.status === 'network_error') { const retryCount = entry.retryCount + 1; const nextRetryAt = Date.now() + computeBackoff(retryCount); queue.updateEntry(computedHash, { retryCount, nextRetryAt, status: 'pending' }); - setError(result.error); + const decoded = decodeTransactionError(result.error); + setError(decoded.humanTitle); setState('error'); onToastRef.current?.(`Network error — will retry (attempt ${retryCount}/${MAX_RETRY_ATTEMPTS})`, 'error'); + return; } - const decoded = decodeTransactionError(result.error); - setError(decoded.humanTitle); // Or store full DecodedError in state - // ... + + // Unknown result shape: decode and report + const decoded = decodeTransactionError((result as any).error); + setError(decoded.humanTitle); + setState('error'); + onToastRef.current?.(decoded.humanTitle, 'error'); } catch (err: unknown) { const decoded = decodeTransactionError(err); - // Update state to hold DecodedError instead of string if desired setError(decoded.humanTitle); - // ... - } + setState('error'); + onToastRef.current?.(decoded.humanTitle, 'error'); + + // schedule a retry entry const retryCount = entry.retryCount + 1; const nextRetryAt = Date.now() + computeBackoff(retryCount); queue.updateEntry(computedHash, { retryCount, nextRetryAt, status: 'pending' }); - } else if (result.status === 'error') { - const msg = err instanceof Error ? err.message : 'Unknown error'; - setError(msg); - setState('error'); - onToastRef.current?.(`Error — will retry (attempt ${retryCount}/${MAX_RETRY_ATTEMPTS})`, 'error'); } }, [queue]); From c9972ef9f48c07efed2340a0a86b1529fd587a04 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Sat, 20 Jun 2026 16:48:39 +0100 Subject: [PATCH 08/13] Fix missing newline at end of errorDecoder.ts From c83ae3745758a04ecad3ffcd737d3ec51a29614a Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:19:21 +0100 Subject: [PATCH 09/13] Refactor error handling in errorDecoder --- src/utils/errorDecoder.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/utils/errorDecoder.ts b/src/utils/errorDecoder.ts index 05bc48c..f91727f 100644 --- a/src/utils/errorDecoder.ts +++ b/src/utils/errorDecoder.ts @@ -95,21 +95,21 @@ export function decodeTransactionError(rawError: unknown): DecodedError { const regex = new RegExp(pattern, 'i'); if (regex.test(rawMessage)) { const description = interpolateParams(definition.humanDescription, rawMessage); - return { - ...definition, - humanDescription: description, - rawError: rawMessage, - isUnknown: pattern === '.*' - if (isUnknown) { - // Assuming @sentry/nextjs is installed - import('@sentry/nextjs').then(({ captureException }) => { - captureException(rawError, { - tags: { errorType: 'decoder-unknown' }, - fingerprint: ['error-decoder-unknown', rawMessage.substring(0, 100)] - }); - }); - } - }; + const isUnknown = pattern === '.*'; +if (isUnknown) { + import('@sentry/nextjs').then(({ captureException }) => { + captureException(rawError, { + tags: { errorType: 'decoder-unknown' }, + fingerprint: ['error-decoder-unknown', rawMessage.substring(0, 100)] + }); + }); +} +return { + ...definition, + humanDescription: description, + rawError: rawMessage, + isUnknown +}; } } From 6fb67ea9c876d09954349c4c52ea1867a2ae2c45 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:22:41 +0100 Subject: [PATCH 10/13] Fix error handling in transaction submission --- src/hooks/useSorobanStaking.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hooks/useSorobanStaking.ts b/src/hooks/useSorobanStaking.ts index 4be6cf5..b759208 100644 --- a/src/hooks/useSorobanStaking.ts +++ b/src/hooks/useSorobanStaking.ts @@ -5,7 +5,6 @@ import { sendTransaction as rpcSendTransaction } from '@/src/lib/stellar/rpcClie import { sha256 } from '@/src/lib/crypto'; import { useTxRetryQueue, MAX_RETRY_ATTEMPTS, CONFIRMED_REMOVAL_DELAY_MS, computeBackoff } from '@/src/hooks/useTxRetryQueue'; import { decodeTransactionError } from '@/utils/errorDecoder'; -import { ErrorDisplay } from '@/components/shared/ErrorDisplay'; // If using in a parent component export type SubmitState = 'idle' | 'submitting' | 'confirmed' | 'error'; @@ -140,7 +139,7 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su setState('error'); onToastRef.current?.(decoded.humanTitle, 'error'); } catch (err: unknown) { - const decoded = decodeTransactionError(err); + const decoded = decodeTransactionError(result.error); setError(decoded.humanTitle); setState('error'); onToastRef.current?.(decoded.humanTitle, 'error'); From a93c95df8feb637298aa3ef6af3f3fa3598265d3 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Sun, 21 Jun 2026 04:43:20 +0100 Subject: [PATCH 11/13] Improve error handling in useSorobanStaking hook --- src/hooks/useSorobanStaking.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hooks/useSorobanStaking.ts b/src/hooks/useSorobanStaking.ts index b759208..8a6bd7c 100644 --- a/src/hooks/useSorobanStaking.ts +++ b/src/hooks/useSorobanStaking.ts @@ -134,11 +134,15 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su } // Unknown result shape: decode and report - const decoded = decodeTransactionError((result as any).error); + const decoded = decodeTransactionError( + typeof result === 'object' && result !== null && 'error' in result + ? (result as { error: string }).error + : 'Unknown error' + ); setError(decoded.humanTitle); setState('error'); onToastRef.current?.(decoded.humanTitle, 'error'); - } catch (err: unknown) { + } catch { const decoded = decodeTransactionError(result.error); setError(decoded.humanTitle); setState('error'); From 5b2fc983d2cee9ee57fa228f4149e0b5d19aa092 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Sun, 21 Jun 2026 04:52:30 +0100 Subject: [PATCH 12/13] Refactor error handling in useSorobanStaking hook --- src/hooks/useSorobanStaking.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/hooks/useSorobanStaking.ts b/src/hooks/useSorobanStaking.ts index 8a6bd7c..adcbbd2 100644 --- a/src/hooks/useSorobanStaking.ts +++ b/src/hooks/useSorobanStaking.ts @@ -143,17 +143,18 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su setState('error'); onToastRef.current?.(decoded.humanTitle, 'error'); } catch { - const decoded = decodeTransactionError(result.error); - setError(decoded.humanTitle); - setState('error'); - onToastRef.current?.(decoded.humanTitle, 'error'); + const decoded = decodeTransactionError( + error instanceof Error ? error.message : 'Unknown error' + ); + setError(decoded.humanTitle); + setState('error'); + onToastRef.current?.(decoded.humanTitle, 'error'); - // schedule a retry entry - const retryCount = entry.retryCount + 1; - const nextRetryAt = Date.now() + computeBackoff(retryCount); - queue.updateEntry(computedHash, { retryCount, nextRetryAt, status: 'pending' }); - } - }, [queue]); + // schedule a retry entry + const retryCount = entry.retryCount + 1; + const nextRetryAt = Date.now() + computeBackoff(retryCount); + queue.updateEntry(computedHash, { retryCount, nextRetryAt, status: 'pending' }); + } return { submitStake, From b6135d6c7d3888341339e0bb6aea41dc8be75096 Mon Sep 17 00:00:00 2001 From: Benedict Balogun <50557035+wolfyres@users.noreply.github.com> Date: Sun, 21 Jun 2026 05:04:24 +0100 Subject: [PATCH 13/13] Refactor error handling and retry logic in useSorobanStaking Refactor error handling in transaction processing to use 'err' parameter and improve retry logic. --- src/hooks/useSorobanStaking.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/hooks/useSorobanStaking.ts b/src/hooks/useSorobanStaking.ts index adcbbd2..c415ef6 100644 --- a/src/hooks/useSorobanStaking.ts +++ b/src/hooks/useSorobanStaking.ts @@ -22,6 +22,7 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su const [txHash, setTxHash] = useState(null); const queue = useTxRetryQueue(); const onToastRef = useRef(onToast); + useEffect(() => { onToastRef.current = onToast; }, [onToast]); @@ -135,26 +136,28 @@ export function useSorobanStaking(onToast?: (message: string, type: 'info' | 'su // Unknown result shape: decode and report const decoded = decodeTransactionError( - typeof result === 'object' && result !== null && 'error' in result - ? (result as { error: string }).error - : 'Unknown error' + typeof result === 'object' && result !== null && 'error' in result + ? (result as { error: string }).error + : 'Unknown error' + ); + setError(decoded.humanTitle); + setState('error'); + onToastRef.current?.(decoded.humanTitle, 'error'); + + } catch (err: unknown) { // FIX: added err parameter + const decoded = decodeTransactionError( + err instanceof Error ? err.message : 'Unknown error' // FIX: using err instead of state error ); setError(decoded.humanTitle); setState('error'); onToastRef.current?.(decoded.humanTitle, 'error'); - } catch { - const decoded = decodeTransactionError( - error instanceof Error ? error.message : 'Unknown error' - ); - setError(decoded.humanTitle); - setState('error'); - onToastRef.current?.(decoded.humanTitle, 'error'); - // schedule a retry entry - const retryCount = entry.retryCount + 1; - const nextRetryAt = Date.now() + computeBackoff(retryCount); - queue.updateEntry(computedHash, { retryCount, nextRetryAt, status: 'pending' }); - } + // schedule a retry entry + const retryCount = entry.retryCount + 1; + const nextRetryAt = Date.now() + computeBackoff(retryCount); + queue.updateEntry(computedHash, { retryCount, nextRetryAt, status: 'pending' }); + } + }, [queue]); // FIX: properly closed the useCallback here return { submitStake,