keynesol-shared 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +118 -0
  2. package/dist/components/Common/ErrorBoundary.d.ts +23 -0
  3. package/dist/components/Common/ErrorBoundary.d.ts.map +1 -0
  4. package/dist/components/Common/ErrorBoundary.js +93 -0
  5. package/dist/components/Common/ErrorBoundary.jsx +103 -0
  6. package/dist/components/Common/ErrorMessage.d.ts +8 -0
  7. package/dist/components/Common/ErrorMessage.d.ts.map +1 -0
  8. package/dist/components/Common/ErrorMessage.js +36 -0
  9. package/dist/components/Common/ErrorMessage.jsx +40 -0
  10. package/dist/components/Common/Loading.d.ts +8 -0
  11. package/dist/components/Common/Loading.d.ts.map +1 -0
  12. package/dist/components/Common/Loading.js +41 -0
  13. package/dist/components/Common/Loading.jsx +44 -0
  14. package/dist/components/Common/LoadingIndicator.d.ts +17 -0
  15. package/dist/components/Common/LoadingIndicator.d.ts.map +1 -0
  16. package/dist/components/Common/LoadingIndicator.js +95 -0
  17. package/dist/components/Common/LoadingIndicator.jsx +108 -0
  18. package/dist/components/Common/ProgramStatus.d.ts +3 -0
  19. package/dist/components/Common/ProgramStatus.d.ts.map +1 -0
  20. package/dist/components/Common/ProgramStatus.js +26 -0
  21. package/dist/components/Common/ProgramStatus.jsx +27 -0
  22. package/dist/components/Common/Skeleton.d.ts +39 -0
  23. package/dist/components/Common/Skeleton.d.ts.map +1 -0
  24. package/dist/components/Common/Skeleton.js +53 -0
  25. package/dist/components/Common/Skeleton.jsx +67 -0
  26. package/dist/components/Common/SkeletonScreen.d.ts +18 -0
  27. package/dist/components/Common/SkeletonScreen.d.ts.map +1 -0
  28. package/dist/components/Common/SkeletonScreen.js +98 -0
  29. package/dist/components/Common/SkeletonScreen.jsx +108 -0
  30. package/dist/components/Common/index.d.ts +11 -0
  31. package/dist/components/Common/index.d.ts.map +1 -0
  32. package/dist/components/Common/index.js +10 -0
  33. package/dist/components/Wallet/TransactionStatus.d.ts +11 -0
  34. package/dist/components/Wallet/TransactionStatus.d.ts.map +1 -0
  35. package/dist/components/Wallet/TransactionStatus.js +97 -0
  36. package/dist/components/Wallet/TransactionStatus.jsx +106 -0
  37. package/dist/components/Wallet/WalletBalance.d.ts +4 -0
  38. package/dist/components/Wallet/WalletBalance.d.ts.map +1 -0
  39. package/dist/components/Wallet/WalletBalance.js +82 -0
  40. package/dist/components/Wallet/WalletBalance.jsx +86 -0
  41. package/dist/components/Wallet/WalletButton.d.ts +3 -0
  42. package/dist/components/Wallet/WalletButton.d.ts.map +1 -0
  43. package/dist/components/Wallet/WalletButton.js +51 -0
  44. package/dist/components/Wallet/WalletButton.jsx +53 -0
  45. package/dist/components/Wallet/WalletConnectionModal.d.ts +8 -0
  46. package/dist/components/Wallet/WalletConnectionModal.d.ts.map +1 -0
  47. package/dist/components/Wallet/WalletConnectionModal.js +150 -0
  48. package/dist/components/Wallet/WalletConnectionModal.jsx +170 -0
  49. package/dist/components/Wallet/WalletProvider.d.ts +9 -0
  50. package/dist/components/Wallet/WalletProvider.d.ts.map +1 -0
  51. package/dist/components/Wallet/WalletProvider.js +70 -0
  52. package/dist/components/Wallet/WalletProvider.jsx +75 -0
  53. package/dist/components/Wallet/index.d.ts +9 -0
  54. package/dist/components/Wallet/index.d.ts.map +1 -0
  55. package/dist/components/Wallet/index.js +8 -0
  56. package/dist/components/index.d.ts +7 -0
  57. package/dist/components/index.d.ts.map +1 -0
  58. package/dist/components/index.js +6 -0
  59. package/dist/hooks/index.d.ts +10 -0
  60. package/dist/hooks/index.d.ts.map +1 -0
  61. package/dist/hooks/index.js +9 -0
  62. package/dist/hooks/useCache.d.ts +16 -0
  63. package/dist/hooks/useCache.d.ts.map +1 -0
  64. package/dist/hooks/useCache.js +67 -0
  65. package/dist/hooks/usePolling.d.ts +16 -0
  66. package/dist/hooks/usePolling.d.ts.map +1 -0
  67. package/dist/hooks/usePolling.js +79 -0
  68. package/dist/hooks/useProgram.d.ts +14 -0
  69. package/dist/hooks/useProgram.d.ts.map +1 -0
  70. package/dist/hooks/useProgram.js +88 -0
  71. package/dist/hooks/useTokenBalance.d.ts +16 -0
  72. package/dist/hooks/useTokenBalance.d.ts.map +1 -0
  73. package/dist/hooks/useTokenBalance.js +100 -0
  74. package/dist/hooks/useVaults.d.ts +23 -0
  75. package/dist/hooks/useVaults.d.ts.map +1 -0
  76. package/dist/hooks/useVaults.js +98 -0
  77. package/dist/index.d.ts +12 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +17 -0
  80. package/dist/services/index.d.ts +7 -0
  81. package/dist/services/index.d.ts.map +1 -0
  82. package/dist/services/index.js +6 -0
  83. package/dist/services/reconciliationService.d.ts +76 -0
  84. package/dist/services/reconciliationService.d.ts.map +1 -0
  85. package/dist/services/reconciliationService.js +216 -0
  86. package/dist/services/syncService.d.ts +51 -0
  87. package/dist/services/syncService.d.ts.map +1 -0
  88. package/dist/services/syncService.js +218 -0
  89. package/dist/types/index.d.ts +201 -0
  90. package/dist/types/index.d.ts.map +1 -0
  91. package/dist/types/index.js +1 -0
  92. package/dist/utils/cacheManager.d.ts +73 -0
  93. package/dist/utils/cacheManager.d.ts.map +1 -0
  94. package/dist/utils/cacheManager.js +232 -0
  95. package/dist/utils/errorHandler.d.ts +76 -0
  96. package/dist/utils/errorHandler.d.ts.map +1 -0
  97. package/dist/utils/errorHandler.js +267 -0
  98. package/dist/utils/index.d.ts +12 -0
  99. package/dist/utils/index.d.ts.map +1 -0
  100. package/dist/utils/index.js +11 -0
  101. package/dist/utils/performanceMonitor.d.ts +75 -0
  102. package/dist/utils/performanceMonitor.d.ts.map +1 -0
  103. package/dist/utils/performanceMonitor.js +197 -0
  104. package/dist/utils/rpcRetry.d.ts +12 -0
  105. package/dist/utils/rpcRetry.d.ts.map +1 -0
  106. package/dist/utils/rpcRetry.js +47 -0
  107. package/dist/utils/supabase.d.ts +198 -0
  108. package/dist/utils/supabase.d.ts.map +1 -0
  109. package/dist/utils/supabase.js +50 -0
  110. package/dist/utils/toastService.d.ts +52 -0
  111. package/dist/utils/toastService.d.ts.map +1 -0
  112. package/dist/utils/toastService.js +139 -0
  113. package/dist/utils/tokenUtils.d.ts +33 -0
  114. package/dist/utils/tokenUtils.d.ts.map +1 -0
  115. package/dist/utils/tokenUtils.js +66 -0
  116. package/dist/utils/validation.d.ts +35 -0
  117. package/dist/utils/validation.d.ts.map +1 -0
  118. package/dist/utils/validation.js +83 -0
  119. package/package.json +45 -0
  120. package/src/components/Common/ErrorBoundary.tsx +135 -0
  121. package/src/components/Common/ErrorMessage.tsx +52 -0
  122. package/src/components/Common/Loading.tsx +56 -0
  123. package/src/components/Common/LoadingIndicator.tsx +143 -0
  124. package/src/components/Common/ProgramStatus.tsx +37 -0
  125. package/src/components/Common/Skeleton.tsx +83 -0
  126. package/src/components/Common/SkeletonScreen.tsx +166 -0
  127. package/src/components/Common/index.ts +10 -0
  128. package/src/components/Wallet/TransactionStatus.tsx +138 -0
  129. package/src/components/Wallet/WalletBalance.tsx +94 -0
  130. package/src/components/Wallet/WalletButton.tsx +65 -0
  131. package/src/components/Wallet/WalletConnectionModal.tsx +193 -0
  132. package/src/components/Wallet/WalletProvider.tsx +104 -0
  133. package/src/components/Wallet/index.ts +8 -0
  134. package/src/components/index.ts +6 -0
  135. package/src/hooks/index.ts +10 -0
  136. package/src/hooks/useCache.ts +87 -0
  137. package/src/hooks/usePolling.ts +98 -0
  138. package/src/hooks/useProgram.ts +93 -0
  139. package/src/hooks/useTokenBalance.ts +113 -0
  140. package/src/hooks/useVaults.ts +122 -0
  141. package/src/index.ts +23 -0
  142. package/src/services/index.ts +6 -0
  143. package/src/services/reconciliationService.ts +246 -0
  144. package/src/services/syncService.ts +238 -0
  145. package/src/types/index.ts +233 -0
  146. package/src/utils/cacheManager.ts +286 -0
  147. package/src/utils/errorHandler.ts +336 -0
  148. package/src/utils/index.ts +12 -0
  149. package/src/utils/performanceMonitor.ts +222 -0
  150. package/src/utils/rpcRetry.ts +55 -0
  151. package/src/utils/supabase.ts +253 -0
  152. package/src/utils/toastService.ts +166 -0
  153. package/src/utils/tokenUtils.ts +75 -0
  154. package/src/utils/validation.ts +107 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Cache Manager
3
+ * Multi-level caching system for blockchain data
4
+ * Requirements: 6.2, 6.3
5
+ */
6
+
7
+ export type CacheStrategy = 'lru' | 'lfu' | 'fifo';
8
+
9
+ export interface CacheConfig {
10
+ ttl: number; // milliseconds
11
+ maxSize?: number; // maximum cache entries
12
+ strategy: CacheStrategy;
13
+ }
14
+
15
+ export interface CacheEntry<T> {
16
+ data: T;
17
+ timestamp: number;
18
+ hits: number;
19
+ ttl: number;
20
+ }
21
+
22
+ export interface CacheStats {
23
+ hits: number;
24
+ misses: number;
25
+ hitRate: number;
26
+ size: number;
27
+ oldestEntry: number;
28
+ newestEntry: number;
29
+ }
30
+
31
+ class CacheManager {
32
+ private caches: Map<string, Map<string, CacheEntry<any>>> = new Map();
33
+ private stats: Map<string, { hits: number; misses: number }> = new Map();
34
+ private configs: Map<string, CacheConfig> = new Map();
35
+
36
+ /**
37
+ * Get cached data
38
+ */
39
+ get<T>(namespace: string, key: string): T | null {
40
+ const cache = this.caches.get(namespace);
41
+ if (!cache) {
42
+ this.recordMiss(namespace);
43
+ return null;
44
+ }
45
+
46
+ const entry = cache.get(key);
47
+ if (!entry) {
48
+ this.recordMiss(namespace);
49
+ return null;
50
+ }
51
+
52
+ // Check if entry is expired
53
+ const now = Date.now();
54
+ if (now - entry.timestamp > entry.ttl) {
55
+ cache.delete(key);
56
+ this.recordMiss(namespace);
57
+ return null;
58
+ }
59
+
60
+ // Update hit count
61
+ entry.hits++;
62
+ this.recordHit(namespace);
63
+
64
+ return entry.data as T;
65
+ }
66
+
67
+ /**
68
+ * Set cached data
69
+ */
70
+ set<T>(namespace: string, key: string, data: T, ttl?: number): void {
71
+ const config = this.configs.get(namespace);
72
+ if (!config) {
73
+ console.warn(`No config found for namespace: ${namespace}`);
74
+ return;
75
+ }
76
+
77
+ let cache = this.caches.get(namespace);
78
+ if (!cache) {
79
+ cache = new Map();
80
+ this.caches.set(namespace, cache);
81
+ this.stats.set(namespace, { hits: 0, misses: 0 });
82
+ }
83
+
84
+ const entryTtl = ttl || config.ttl;
85
+ const entry: CacheEntry<T> = {
86
+ data,
87
+ timestamp: Date.now(),
88
+ hits: 0,
89
+ ttl: entryTtl,
90
+ };
91
+
92
+ // Check max size and evict if needed
93
+ if (config.maxSize && cache.size >= config.maxSize) {
94
+ this.evict(namespace, cache, config.strategy);
95
+ }
96
+
97
+ cache.set(key, entry);
98
+ }
99
+
100
+ /**
101
+ * Invalidate cache entry or entire namespace
102
+ */
103
+ invalidate(namespace: string, key?: string): void {
104
+ const cache = this.caches.get(namespace);
105
+ if (!cache) return;
106
+
107
+ if (key) {
108
+ cache.delete(key);
109
+ } else {
110
+ cache.clear();
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Clear all caches or specific namespace
116
+ */
117
+ clear(namespace?: string): void {
118
+ if (namespace) {
119
+ this.caches.delete(namespace);
120
+ this.stats.delete(namespace);
121
+ } else {
122
+ this.caches.clear();
123
+ this.stats.clear();
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Configure cache for namespace
129
+ */
130
+ configure(namespace: string, config: CacheConfig): void {
131
+ this.configs.set(namespace, config);
132
+ if (!this.stats.has(namespace)) {
133
+ this.stats.set(namespace, { hits: 0, misses: 0 });
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Get cache statistics
139
+ */
140
+ getStats(namespace: string): CacheStats | null {
141
+ const cache = this.caches.get(namespace);
142
+ const stats = this.stats.get(namespace);
143
+ if (!cache || !stats) return null;
144
+
145
+ const entries = Array.from(cache.values());
146
+ const timestamps = entries.map(e => e.timestamp);
147
+ const totalRequests = stats.hits + stats.misses;
148
+ const hitRate = totalRequests > 0 ? stats.hits / totalRequests : 0;
149
+
150
+ return {
151
+ hits: stats.hits,
152
+ misses: stats.misses,
153
+ hitRate,
154
+ size: cache.size,
155
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : 0,
156
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : 0,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Evict entry based on strategy
162
+ */
163
+ private evict(
164
+ namespace: string,
165
+ cache: Map<string, CacheEntry<any>>,
166
+ strategy: CacheStrategy
167
+ ): void {
168
+ let entryToEvict: string | null = null;
169
+
170
+ switch (strategy) {
171
+ case 'lru': {
172
+ // Evict least recently used (oldest timestamp)
173
+ let oldestTime = Infinity;
174
+ const entries = Array.from(cache.entries());
175
+ for (const [key, entry] of entries) {
176
+ if (entry.timestamp < oldestTime) {
177
+ oldestTime = entry.timestamp;
178
+ entryToEvict = key;
179
+ }
180
+ }
181
+ break;
182
+ }
183
+
184
+ case 'lfu': {
185
+ // Evict least frequently used (lowest hits)
186
+ let lowestHits = Infinity;
187
+ const entries = Array.from(cache.entries());
188
+ for (const [key, entry] of entries) {
189
+ if (entry.hits < lowestHits) {
190
+ lowestHits = entry.hits;
191
+ entryToEvict = key;
192
+ }
193
+ }
194
+ break;
195
+ }
196
+
197
+ case 'fifo': {
198
+ // Evict first in (oldest timestamp, same as LRU for simplicity)
199
+ let oldestTime = Infinity;
200
+ const entries = Array.from(cache.entries());
201
+ for (const [key, entry] of entries) {
202
+ if (entry.timestamp < oldestTime) {
203
+ oldestTime = entry.timestamp;
204
+ entryToEvict = key;
205
+ }
206
+ }
207
+ break;
208
+ }
209
+ }
210
+
211
+ if (entryToEvict) {
212
+ cache.delete(entryToEvict);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Record cache hit
218
+ */
219
+ private recordHit(namespace: string): void {
220
+ const stats = this.stats.get(namespace);
221
+ if (stats) {
222
+ stats.hits++;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Record cache miss
228
+ */
229
+ private recordMiss(namespace: string): void {
230
+ const stats = this.stats.get(namespace);
231
+ if (stats) {
232
+ stats.misses++;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Clean expired entries (should be called periodically)
238
+ */
239
+ cleanExpired(): void {
240
+ const now = Date.now();
241
+ const namespaces = Array.from(this.caches.entries());
242
+ for (const [namespace, cache] of namespaces) {
243
+ const entries = Array.from(cache.entries());
244
+ for (const [key, entry] of entries) {
245
+ if (now - entry.timestamp > entry.ttl) {
246
+ cache.delete(key);
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ // Export singleton instance
254
+ export const cacheManager = new CacheManager();
255
+
256
+ // Configure default caches
257
+ cacheManager.configure('vaults', {
258
+ ttl: 30000, // 30 seconds
259
+ maxSize: 100,
260
+ strategy: 'lru',
261
+ });
262
+
263
+ cacheManager.configure('userStakes', {
264
+ ttl: 10000, // 10 seconds
265
+ maxSize: 1000,
266
+ strategy: 'lru',
267
+ });
268
+
269
+ cacheManager.configure('platformMetrics', {
270
+ ttl: 60000, // 1 minute
271
+ maxSize: 50,
272
+ strategy: 'lru',
273
+ });
274
+
275
+ cacheManager.configure('transactions', {
276
+ ttl: 60000, // 1 minute
277
+ maxSize: 500,
278
+ strategy: 'lru',
279
+ });
280
+
281
+ // Clean expired entries every 5 minutes
282
+ if (typeof window !== 'undefined') {
283
+ setInterval(() => {
284
+ cacheManager.cleanExpired();
285
+ }, 5 * 60 * 1000);
286
+ }
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Error Handler Utility
3
+ * Comprehensive error handling system with classification, retry logic, and user-friendly messages
4
+ * Requirements: 4.1, 4.2, 4.3, 4.4, 4.5
5
+ */
6
+
7
+ export enum ErrorCategory {
8
+ NETWORK = 'network',
9
+ BLOCKCHAIN = 'blockchain',
10
+ VALIDATION = 'validation',
11
+ AUTHORIZATION = 'authorization',
12
+ DATA = 'data',
13
+ UNKNOWN = 'unknown',
14
+ }
15
+
16
+ export interface ErrorHandlerConfig {
17
+ retryAttempts?: number;
18
+ retryDelay?: number; // milliseconds
19
+ exponentialBackoff?: boolean;
20
+ onRetry?: (attempt: number, error: Error) => void;
21
+ }
22
+
23
+ export interface ErrorLog {
24
+ timestamp: number;
25
+ level: 'error' | 'warning' | 'info';
26
+ context: string;
27
+ message: string;
28
+ stack?: string;
29
+ metadata?: Record<string, any>;
30
+ userId?: string;
31
+ category: ErrorCategory;
32
+ }
33
+
34
+ class ErrorHandler {
35
+ private logs: ErrorLog[] = [];
36
+ private maxLogs: number = 1000;
37
+
38
+ /**
39
+ * Classify error into category
40
+ */
41
+ classifyError(error: Error): ErrorCategory {
42
+ const message = error.message.toLowerCase();
43
+ const stack = error.stack?.toLowerCase() || '';
44
+
45
+ // Network errors
46
+ if (
47
+ message.includes('network') ||
48
+ message.includes('fetch') ||
49
+ message.includes('timeout') ||
50
+ message.includes('connection') ||
51
+ message.includes('rpc') ||
52
+ message.includes('failed to fetch')
53
+ ) {
54
+ return ErrorCategory.NETWORK;
55
+ }
56
+
57
+ // Blockchain errors
58
+ if (
59
+ message.includes('transaction') ||
60
+ message.includes('insufficient') ||
61
+ message.includes('program') ||
62
+ message.includes('account') ||
63
+ message.includes('anchor') ||
64
+ message.includes('solana')
65
+ ) {
66
+ return ErrorCategory.BLOCKCHAIN;
67
+ }
68
+
69
+ // Validation errors
70
+ if (
71
+ message.includes('invalid') ||
72
+ message.includes('required') ||
73
+ message.includes('must be') ||
74
+ message.includes('out of range') ||
75
+ message.includes('validation')
76
+ ) {
77
+ return ErrorCategory.VALIDATION;
78
+ }
79
+
80
+ // Authorization errors
81
+ if (
82
+ message.includes('unauthorized') ||
83
+ message.includes('permission') ||
84
+ message.includes('access denied') ||
85
+ message.includes('wallet not connected') ||
86
+ message.includes('signature')
87
+ ) {
88
+ return ErrorCategory.AUTHORIZATION;
89
+ }
90
+
91
+ // Data errors
92
+ if (
93
+ message.includes('parse') ||
94
+ message.includes('json') ||
95
+ message.includes('data') ||
96
+ message.includes('supabase') ||
97
+ message.includes('database')
98
+ ) {
99
+ return ErrorCategory.DATA;
100
+ }
101
+
102
+ return ErrorCategory.UNKNOWN;
103
+ }
104
+
105
+ /**
106
+ * Get user-friendly error message
107
+ */
108
+ getErrorMessage(error: Error): string {
109
+ const category = this.classifyError(error);
110
+ const message = error.message;
111
+
112
+ switch (category) {
113
+ case ErrorCategory.NETWORK:
114
+ if (message.includes('timeout')) {
115
+ return 'Connection timeout. Please check your internet connection and try again.';
116
+ }
117
+ if (message.includes('rpc')) {
118
+ return 'Blockchain connection failed. Please try again in a moment.';
119
+ }
120
+ return 'Network error. Please check your connection and try again.';
121
+
122
+ case ErrorCategory.BLOCKCHAIN:
123
+ if (message.includes('insufficient')) {
124
+ return 'Insufficient balance. Please add more tokens to your wallet.';
125
+ }
126
+ if (message.includes('user rejected')) {
127
+ return 'Transaction cancelled.';
128
+ }
129
+ if (message.includes('slippage')) {
130
+ return 'Transaction failed due to price movement. Please try again.';
131
+ }
132
+ if (message.includes('simulation')) {
133
+ return 'Transaction simulation failed. Please check your inputs and try again.';
134
+ }
135
+ return 'Transaction failed. Please try again or contact support if the issue persists.';
136
+
137
+ case ErrorCategory.VALIDATION:
138
+ return message || 'Invalid input. Please check your values and try again.';
139
+
140
+ case ErrorCategory.AUTHORIZATION:
141
+ if (message.includes('wallet not connected')) {
142
+ return 'Please connect your wallet to continue.';
143
+ }
144
+ if (message.includes('unauthorized')) {
145
+ return 'You do not have permission to perform this action.';
146
+ }
147
+ return 'Authorization required. Please connect your wallet.';
148
+
149
+ case ErrorCategory.DATA:
150
+ return 'Data error. Please refresh the page and try again.';
151
+
152
+ default:
153
+ return message || 'An unexpected error occurred. Please try again.';
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Get recovery suggestion based on error category
159
+ */
160
+ getRecoverySuggestion(error: Error): string | null {
161
+ const category = this.classifyError(error);
162
+
163
+ switch (category) {
164
+ case ErrorCategory.NETWORK:
165
+ return 'Check your internet connection and try again. If the problem persists, try refreshing the page.';
166
+
167
+ case ErrorCategory.BLOCKCHAIN:
168
+ return 'Verify your wallet has sufficient balance and try again. If the issue continues, check the transaction on Solana Explorer.';
169
+
170
+ case ErrorCategory.VALIDATION:
171
+ return 'Please review your inputs and ensure all required fields are filled correctly.';
172
+
173
+ case ErrorCategory.AUTHORIZATION:
174
+ return 'Please connect your wallet and ensure you have the necessary permissions.';
175
+
176
+ case ErrorCategory.DATA:
177
+ return 'Try refreshing the page. If the problem persists, contact support.';
178
+
179
+ default:
180
+ return null;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Log error with context
186
+ */
187
+ logError(
188
+ error: Error,
189
+ context: string,
190
+ metadata?: Record<string, any>
191
+ ): void {
192
+ const category = this.classifyError(error);
193
+ const errorLog: ErrorLog = {
194
+ timestamp: Date.now(),
195
+ level: 'error',
196
+ context,
197
+ message: error.message,
198
+ stack: error.stack,
199
+ metadata,
200
+ userId: this.getCurrentUserId(),
201
+ category,
202
+ };
203
+
204
+ // Add to in-memory log
205
+ this.logs.push(errorLog);
206
+ if (this.logs.length > this.maxLogs) {
207
+ this.logs.shift();
208
+ }
209
+
210
+ // Log to console in development
211
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {
212
+ console.error(`[${context}]`, error, metadata);
213
+ }
214
+
215
+ // Send to monitoring service in production
216
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
217
+ this.sendToMonitoring(errorLog);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Wrap async function with error handling and retry logic
223
+ */
224
+ async wrapAsync<T>(
225
+ fn: () => Promise<T>,
226
+ config?: ErrorHandlerConfig
227
+ ): Promise<T> {
228
+ const {
229
+ retryAttempts = 3,
230
+ retryDelay = 1000,
231
+ exponentialBackoff = true,
232
+ onRetry,
233
+ } = config || {};
234
+
235
+ let lastError: Error;
236
+ let attempt = 0;
237
+
238
+ while (attempt < retryAttempts) {
239
+ try {
240
+ return await fn();
241
+ } catch (error) {
242
+ lastError = error as Error;
243
+ attempt++;
244
+
245
+ // Don't retry validation errors
246
+ if (this.classifyError(lastError) === ErrorCategory.VALIDATION) {
247
+ throw lastError;
248
+ }
249
+
250
+ // Don't retry on final attempt
251
+ if (attempt >= retryAttempts) {
252
+ break;
253
+ }
254
+
255
+ // Calculate delay with exponential backoff
256
+ const delay = exponentialBackoff
257
+ ? retryDelay * Math.pow(2, attempt - 1)
258
+ : retryDelay;
259
+
260
+ // Add jitter to prevent thundering herd
261
+ const jitter = Math.random() * 500;
262
+
263
+ // Call retry callback
264
+ if (onRetry) {
265
+ onRetry(attempt, lastError);
266
+ }
267
+
268
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
269
+ }
270
+ }
271
+
272
+ throw lastError!;
273
+ }
274
+
275
+ /**
276
+ * Handle error with appropriate recovery strategy
277
+ */
278
+ handleError(
279
+ error: Error,
280
+ context: string,
281
+ config?: ErrorHandlerConfig
282
+ ): void {
283
+ this.logError(error, context);
284
+
285
+ const category = this.classifyError(error);
286
+ const message = this.getErrorMessage(error);
287
+ const suggestion = this.getRecoverySuggestion(error);
288
+
289
+ // In a real implementation, this would trigger toast notifications
290
+ // For now, we'll just log it
291
+ console.error(`[${context}] ${message}`, suggestion ? `Suggestion: ${suggestion}` : '');
292
+ }
293
+
294
+ /**
295
+ * Get recent errors for debugging
296
+ */
297
+ getRecentErrors(count: number = 10): ErrorLog[] {
298
+ return this.logs.slice(-count);
299
+ }
300
+
301
+ /**
302
+ * Clear error logs
303
+ */
304
+ clearLogs(): void {
305
+ this.logs = [];
306
+ }
307
+
308
+ /**
309
+ * Get current user ID (if available)
310
+ */
311
+ private getCurrentUserId(): string | undefined {
312
+ // In a real implementation, this would get the current user's public key
313
+ if (typeof window !== 'undefined') {
314
+ // Could get from wallet connection state
315
+ return undefined;
316
+ }
317
+ return undefined;
318
+ }
319
+
320
+ /**
321
+ * Send error to monitoring service
322
+ */
323
+ private async sendToMonitoring(errorLog: ErrorLog): Promise<void> {
324
+ // In production, send to monitoring service (e.g., Sentry, LogRocket)
325
+ // For now, we'll just store it locally
326
+ try {
327
+ // Could send to Supabase or external service
328
+ // await supabase.from('error_logs').insert(errorLog);
329
+ } catch (e) {
330
+ console.error('Failed to send error to monitoring:', e);
331
+ }
332
+ }
333
+ }
334
+
335
+ // Export singleton instance
336
+ export const errorHandler = new ErrorHandler();
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Utils Index
3
+ * Export all utility functions
4
+ */
5
+
6
+ export * from './errorHandler';
7
+ export * from './cacheManager';
8
+ export * from './tokenUtils';
9
+ export * from './validation';
10
+ export * from './toastService';
11
+ export * from './rpcRetry';
12
+ export * from './performanceMonitor';