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,65 @@
1
+ import React from 'react';
2
+ import { useWallet } from '@solana/wallet-adapter-react';
3
+ import { useWalletModal } from '@solana/wallet-adapter-react-ui';
4
+ import styled from 'styled-components';
5
+
6
+ export const WalletButton: React.FC = () => {
7
+ const { publicKey, disconnect, connected } = useWallet();
8
+ const { setVisible } = useWalletModal();
9
+
10
+ const handleClick = () => {
11
+ if (connected) {
12
+ disconnect();
13
+ } else {
14
+ setVisible(true);
15
+ }
16
+ };
17
+
18
+ const formatAddress = (address: string) => {
19
+ return `${address.slice(0, 4)}...${address.slice(-4)}`;
20
+ };
21
+
22
+ return (
23
+ <Button onClick={handleClick} connected={connected}>
24
+ {connected && publicKey ? formatAddress(publicKey.toBase58()) : 'Connect Wallet'}
25
+ </Button>
26
+ );
27
+ };
28
+
29
+ const Button = styled.button<{ connected: boolean }>`
30
+ background: ${props =>
31
+ props.connected
32
+ ? 'var(--color-surface, #f9fafb)'
33
+ : 'linear-gradient(135deg, var(--color-primary, #6a8102), var(--color-secondary, #ffc107))'
34
+ };
35
+ color: ${props => props.connected ? 'var(--color-text, #1a1a1a)' : 'var(--color-background, #ffffff)'};
36
+ border: ${props => props.connected ? '1px solid var(--color-primary, #6a8102)' : 'none'};
37
+ padding: var(--spacing-sm, 0.5rem) var(--spacing-lg, 1.5rem);
38
+ border-radius: var(--border-radius-md, 0.375rem);
39
+ font-weight: 600;
40
+ transition: all 0.3s ease;
41
+ box-shadow: ${props =>
42
+ props.connected
43
+ ? '0 2px 8px rgba(106, 129, 2, 0.2)'
44
+ : '0 4px 12px rgba(106, 129, 2, 0.3)'
45
+ };
46
+ cursor: pointer;
47
+
48
+ &:hover {
49
+ transform: translateY(-2px);
50
+ box-shadow: ${props =>
51
+ props.connected
52
+ ? '0 4px 12px rgba(106, 129, 2, 0.3)'
53
+ : '0 6px 16px rgba(106, 129, 2, 0.4)'
54
+ };
55
+ }
56
+
57
+ &:active {
58
+ transform: translateY(0);
59
+ }
60
+
61
+ @media (max-width: 768px) {
62
+ padding: var(--spacing-xs, 0.25rem) var(--spacing-md, 1rem);
63
+ font-size: 0.875rem;
64
+ }
65
+ `;
@@ -0,0 +1,193 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { useWallet } from '@solana/wallet-adapter-react';
4
+ import type { WalletName as WalletNameType } from '@solana/wallet-adapter-base';
5
+
6
+ const ModalOverlay = styled.div<{ isOpen: boolean }>`
7
+ display: ${props => props.isOpen ? 'flex' : 'none'};
8
+ position: fixed;
9
+ top: 0;
10
+ left: 0;
11
+ right: 0;
12
+ bottom: 0;
13
+ background: rgba(0, 0, 0, 0.8);
14
+ backdrop-filter: blur(4px);
15
+ z-index: 1000;
16
+ align-items: center;
17
+ justify-content: center;
18
+ animation: fadeIn 0.2s ease-in-out;
19
+
20
+ @keyframes fadeIn {
21
+ from { opacity: 0; }
22
+ to { opacity: 1; }
23
+ }
24
+ `;
25
+
26
+ const ModalContent = styled.div`
27
+ background: var(--color-surface, #f9fafb);
28
+ border: 1px solid var(--color-primary, #6a8102);
29
+ border-radius: 1rem;
30
+ padding: 2rem;
31
+ max-width: 400px;
32
+ width: 90%;
33
+ animation: slideUp 0.3s ease-out;
34
+
35
+ @keyframes slideUp {
36
+ from {
37
+ transform: translateY(20px);
38
+ opacity: 0;
39
+ }
40
+ to {
41
+ transform: translateY(0);
42
+ opacity: 1;
43
+ }
44
+ }
45
+ `;
46
+
47
+ const ModalHeader = styled.div`
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: center;
51
+ margin-bottom: 1.5rem;
52
+ `;
53
+
54
+ const ModalTitle = styled.h2`
55
+ font-size: 1.5rem;
56
+ color: var(--color-text, #1a1a1a);
57
+ margin: 0;
58
+ `;
59
+
60
+ const CloseButton = styled.button`
61
+ background: none;
62
+ border: none;
63
+ color: var(--color-text-secondary, #6b7280);
64
+ font-size: 1.5rem;
65
+ cursor: pointer;
66
+ padding: 0;
67
+ width: 32px;
68
+ height: 32px;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ border-radius: 0.5rem;
73
+ transition: all 0.2s;
74
+
75
+ &:hover {
76
+ background: var(--color-background, #ffffff);
77
+ color: var(--color-text, #1a1a1a);
78
+ }
79
+ `;
80
+
81
+ const WalletList = styled.div`
82
+ display: flex;
83
+ flex-direction: column;
84
+ gap: 0.75rem;
85
+ `;
86
+
87
+ const WalletButton = styled.button`
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 1rem;
91
+ padding: 1rem;
92
+ background: var(--color-background, #ffffff);
93
+ border: 1px solid var(--color-primary, #6a8102);
94
+ border-radius: 0.5rem;
95
+ color: var(--color-text, #1a1a1a);
96
+ font-size: 1rem;
97
+ font-weight: 500;
98
+ cursor: pointer;
99
+ transition: all 0.2s;
100
+
101
+ &:hover {
102
+ background: var(--color-primary, #6a8102);
103
+ color: var(--color-background, #ffffff);
104
+ transform: translateX(4px);
105
+ }
106
+
107
+ &:disabled {
108
+ opacity: 0.5;
109
+ cursor: not-allowed;
110
+ transform: none;
111
+ }
112
+ `;
113
+
114
+ const WalletIcon = styled.div`
115
+ width: 32px;
116
+ height: 32px;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ font-size: 1.5rem;
121
+ `;
122
+
123
+ const WalletName = styled.span`
124
+ flex: 1;
125
+ text-align: left;
126
+ `;
127
+
128
+ const Description = styled.p`
129
+ color: var(--color-text-secondary, #6b7280);
130
+ font-size: 0.875rem;
131
+ margin-bottom: 1.5rem;
132
+ line-height: 1.5;
133
+ `;
134
+
135
+ interface WalletConnectionModalProps {
136
+ isOpen: boolean;
137
+ onClose: () => void;
138
+ }
139
+
140
+ const WalletConnectionModal: React.FC<WalletConnectionModalProps> = ({ isOpen, onClose }) => {
141
+ const { wallets, select, connecting } = useWallet();
142
+
143
+ const handleWalletSelect = async (walletName: WalletNameType) => {
144
+ try {
145
+ select(walletName);
146
+ onClose();
147
+ } catch (error) {
148
+ console.error('Failed to select wallet:', error);
149
+ }
150
+ };
151
+
152
+ const handleOverlayClick = (e: React.MouseEvent) => {
153
+ if (e.target === e.currentTarget) {
154
+ onClose();
155
+ }
156
+ };
157
+
158
+ return (
159
+ <ModalOverlay isOpen={isOpen} onClick={handleOverlayClick}>
160
+ <ModalContent>
161
+ <ModalHeader>
162
+ <ModalTitle>Connect Wallet</ModalTitle>
163
+ <CloseButton onClick={onClose}>×</CloseButton>
164
+ </ModalHeader>
165
+
166
+ <Description>
167
+ Select a wallet to connect to the Web3 Prediction Platform and start staking.
168
+ </Description>
169
+
170
+ <WalletList>
171
+ {wallets.map((wallet) => (
172
+ <WalletButton
173
+ key={wallet.adapter.name}
174
+ onClick={() => handleWalletSelect(wallet.adapter.name)}
175
+ disabled={connecting}
176
+ >
177
+ <WalletIcon>
178
+ {wallet.adapter.icon ? (
179
+ <img src={wallet.adapter.icon} alt={wallet.adapter.name} width={32} height={32} />
180
+ ) : (
181
+ '👛'
182
+ )}
183
+ </WalletIcon>
184
+ <WalletName>{wallet.adapter.name}</WalletName>
185
+ </WalletButton>
186
+ ))}
187
+ </WalletList>
188
+ </ModalContent>
189
+ </ModalOverlay>
190
+ );
191
+ };
192
+
193
+ export default WalletConnectionModal;
@@ -0,0 +1,104 @@
1
+ import React, { useMemo, ReactNode } from 'react';
2
+ import { ConnectionProvider, WalletProvider as SolanaWalletProvider } from '@solana/wallet-adapter-react';
3
+ import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
4
+ import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets';
5
+ import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
6
+ import { clusterApiUrl } from '@solana/web3.js';
7
+
8
+ require('@solana/wallet-adapter-react-ui/styles.css');
9
+
10
+ interface WalletProviderProps {
11
+ children: ReactNode;
12
+ network?: 'devnet' | 'mainnet-beta' | 'testnet';
13
+ rpcEndpoint?: string;
14
+ }
15
+
16
+ export const WalletProvider = ({
17
+ children,
18
+ network: networkProp,
19
+ rpcEndpoint: rpcEndpointProp
20
+ }: WalletProviderProps) => {
21
+ // Get network from prop, environment variable, or default to devnet
22
+ const networkEnv = networkProp ||
23
+ (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_SOLANA_NETWORK) ||
24
+ 'devnet';
25
+
26
+ const network = networkEnv === 'mainnet-beta'
27
+ ? WalletAdapterNetwork.Mainnet
28
+ : networkEnv === 'testnet'
29
+ ? WalletAdapterNetwork.Testnet
30
+ : WalletAdapterNetwork.Devnet;
31
+
32
+ // Use custom RPC endpoint if provided, otherwise use cluster API URL
33
+ const endpoint = useMemo(() => {
34
+ const customEndpoint = rpcEndpointProp ||
35
+ (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_RPC_ENDPOINT);
36
+ if (customEndpoint) {
37
+ return customEndpoint;
38
+ }
39
+ return clusterApiUrl(network);
40
+ }, [network, rpcEndpointProp]);
41
+
42
+ const wallets = useMemo(
43
+ () => {
44
+ const walletAdapters = [];
45
+
46
+ // Only add Phantom if it's not already registered as Standard Wallet
47
+ try {
48
+ const phantom = new PhantomWalletAdapter();
49
+ walletAdapters.push(phantom);
50
+ } catch (error) {
51
+ console.warn('Phantom wallet adapter already registered or error:', error);
52
+ }
53
+
54
+ // Add Solflare
55
+ try {
56
+ const solflare = new SolflareWalletAdapter();
57
+ walletAdapters.push(solflare);
58
+ } catch (error) {
59
+ console.warn('Solflare wallet adapter error:', error);
60
+ }
61
+
62
+ return walletAdapters;
63
+ },
64
+ []
65
+ );
66
+
67
+ // Disable WebSocket for custom RPC endpoints that may not support it
68
+ // Solana SDK will automatically fallback to HTTP polling
69
+ const connectionConfig = useMemo(() => {
70
+ const customEndpoint = rpcEndpointProp ||
71
+ (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_RPC_ENDPOINT);
72
+
73
+ // If using custom endpoint (not public Solana RPC), disable WebSocket
74
+ const isCustomEndpoint = customEndpoint &&
75
+ !customEndpoint.includes('api.devnet.solana.com') &&
76
+ !customEndpoint.includes('api.mainnet-beta.solana.com') &&
77
+ !customEndpoint.includes('api.testnet.solana.com');
78
+
79
+ return {
80
+ commitment: 'confirmed' as const,
81
+ // Disable WebSocket for custom endpoints to avoid connection errors
82
+ // SDK will automatically use HTTP polling instead
83
+ ...(isCustomEndpoint ? {
84
+ wsEndpoint: undefined,
85
+ disableRetryOnRateLimit: false,
86
+ } : {}),
87
+ };
88
+ }, [rpcEndpointProp]);
89
+
90
+ // Type assertion to fix React 18/19 compatibility
91
+ const ConnectionProviderComponent = ConnectionProvider as any;
92
+
93
+ // Type assertion to fix React 18/19 compatibility
94
+
95
+ return (
96
+ <ConnectionProviderComponent endpoint={endpoint} config={connectionConfig}>
97
+ <SolanaWalletProvider wallets={wallets} autoConnect>
98
+ <WalletModalProvider>
99
+ {children}
100
+ </WalletModalProvider>
101
+ </SolanaWalletProvider>
102
+ </ConnectionProviderComponent>
103
+ );
104
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Wallet Components Index
3
+ */
4
+ export * from './WalletProvider';
5
+ export * from './WalletButton';
6
+ export { default as WalletBalance } from './WalletBalance';
7
+ export { default as TransactionStatus } from './TransactionStatus';
8
+ export { default as WalletConnectionModal } from './WalletConnectionModal';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Components Index
3
+ * Export all shared components
4
+ */
5
+ export * from './Common';
6
+ export * from './Wallet';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Hooks Index
3
+ * Export all shared hooks
4
+ */
5
+
6
+ export * from './useProgram';
7
+ export * from './useVaults';
8
+ export * from './useTokenBalance';
9
+ export * from './useCache';
10
+ export * from './usePolling';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Caching Hook for Blockchain Data
3
+ * Requirements: 12.1 - Multi-level caching for blockchain data with cache invalidation
4
+ */
5
+ import React from 'react';
6
+ import { cacheManager } from '../utils/cacheManager';
7
+
8
+ interface CacheEntry<T> {
9
+ data: T;
10
+ timestamp: number;
11
+ ttl: number; // Time to live in milliseconds
12
+ }
13
+
14
+ export interface CacheOptions {
15
+ ttl?: number; // Default TTL in milliseconds (5 seconds for real-time data)
16
+ key?: string;
17
+ namespace?: string; // Cache namespace
18
+ }
19
+
20
+ /**
21
+ * Custom hook for caching data with automatic invalidation
22
+ */
23
+ export const useCache = <T,>(
24
+ fetchFn: () => Promise<T>,
25
+ options: CacheOptions = {}
26
+ ): {
27
+ data: T | null;
28
+ loading: boolean;
29
+ error: Error | null;
30
+ refetch: () => Promise<void>;
31
+ invalidate: () => void;
32
+ } => {
33
+ const { ttl, key, namespace = 'default' } = options;
34
+ const cacheKey = key || `cache_${fetchFn.toString()}`;
35
+
36
+ const [data, setData] = React.useState<T | null>(() => {
37
+ return cacheManager.get<T>(namespace, cacheKey);
38
+ });
39
+
40
+ const [loading, setLoading] = React.useState<boolean>(!data);
41
+ const [error, setError] = React.useState<Error | null>(null);
42
+
43
+ const fetchData = React.useCallback(async () => {
44
+ // Check cache first
45
+ const cached = cacheManager.get<T>(namespace, cacheKey);
46
+ if (cached) {
47
+ setData(cached);
48
+ setLoading(false);
49
+ return;
50
+ }
51
+
52
+ try {
53
+ setLoading(true);
54
+ setError(null);
55
+
56
+ const result = await fetchFn();
57
+
58
+ // Store in cache (using default TTL from namespace config if not provided)
59
+ const cacheTtl = ttl || 5000; // Default 5 seconds
60
+ cacheManager.set(namespace, cacheKey, result, cacheTtl);
61
+ setData(result);
62
+ } catch (err: any) {
63
+ setError(err);
64
+ setData(null);
65
+ } finally {
66
+ setLoading(false);
67
+ }
68
+ }, [fetchFn, namespace, cacheKey, ttl]);
69
+
70
+ React.useEffect(() => {
71
+ fetchData();
72
+ }, [fetchData]);
73
+
74
+ const invalidate = React.useCallback(() => {
75
+ cacheManager.invalidate(namespace, cacheKey);
76
+ setData(null);
77
+ fetchData();
78
+ }, [namespace, cacheKey, fetchData]);
79
+
80
+ return {
81
+ data,
82
+ loading,
83
+ error,
84
+ refetch: fetchData,
85
+ invalidate,
86
+ };
87
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Polling Hook for Real-Time Data Synchronization
3
+ * Requirements: 10.1 - 5-second blockchain data synchronization
4
+ */
5
+ import { useEffect, useRef, useCallback } from 'react';
6
+
7
+ export interface PollingOptions {
8
+ interval?: number; // Default 5000ms (5 seconds)
9
+ enabled?: boolean; // Default true
10
+ onError?: (error: Error) => void;
11
+ immediate?: boolean; // Run immediately on mount
12
+ }
13
+
14
+ /**
15
+ * Hook for polling data at regular intervals
16
+ * Implements Requirement 10.1: 5-second data synchronization
17
+ */
18
+ export const usePolling = (
19
+ callback: () => Promise<void> | void,
20
+ options: PollingOptions = {}
21
+ ): {
22
+ start: () => void;
23
+ stop: () => void;
24
+ isActive: boolean;
25
+ } => {
26
+ const {
27
+ interval = 5000, // 5 seconds default
28
+ enabled = true,
29
+ onError,
30
+ immediate = true,
31
+ } = options;
32
+
33
+ const intervalRef = useRef<NodeJS.Timeout | null>(null);
34
+ const isActiveRef = useRef<boolean>(false);
35
+ const callbackRef = useRef(callback);
36
+
37
+ // Update callback ref when it changes
38
+ useEffect(() => {
39
+ callbackRef.current = callback;
40
+ }, [callback]);
41
+
42
+ const execute = useCallback(async () => {
43
+ try {
44
+ await callbackRef.current();
45
+ } catch (error: any) {
46
+ console.error('Polling error:', error);
47
+ if (onError) {
48
+ onError(error);
49
+ }
50
+ }
51
+ }, [onError]);
52
+
53
+ const start = useCallback(() => {
54
+ if (isActiveRef.current) {
55
+ return; // Already running
56
+ }
57
+
58
+ isActiveRef.current = true;
59
+
60
+ // Execute immediately if requested
61
+ if (immediate) {
62
+ execute();
63
+ }
64
+
65
+ // Set up interval
66
+ intervalRef.current = setInterval(() => {
67
+ execute();
68
+ }, interval);
69
+ }, [interval, immediate, execute]);
70
+
71
+ const stop = useCallback(() => {
72
+ if (intervalRef.current) {
73
+ clearInterval(intervalRef.current);
74
+ intervalRef.current = null;
75
+ }
76
+ isActiveRef.current = false;
77
+ }, []);
78
+
79
+ // Auto-start/stop based on enabled flag
80
+ useEffect(() => {
81
+ if (enabled) {
82
+ start();
83
+ } else {
84
+ stop();
85
+ }
86
+
87
+ // Cleanup on unmount
88
+ return () => {
89
+ stop();
90
+ };
91
+ }, [enabled, start, stop]);
92
+
93
+ return {
94
+ start,
95
+ stop,
96
+ isActive: isActiveRef.current,
97
+ };
98
+ };
@@ -0,0 +1,93 @@
1
+ import { useMemo, useState, useEffect } from 'react';
2
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
3
+ import { Program, AnchorProvider, Idl } from '@coral-xyz/anchor';
4
+ import { PublicKey } from '@solana/web3.js';
5
+
6
+ // Program ID from environment or default
7
+ const getProgramId = (): PublicKey => {
8
+ const PROGRAM_ID_STRING = typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_PROGRAM_ID
9
+ ? process.env.NEXT_PUBLIC_PROGRAM_ID
10
+ : 'Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS';
11
+
12
+ try {
13
+ return new PublicKey(PROGRAM_ID_STRING);
14
+ } catch (error) {
15
+ console.error('Invalid Program ID:', PROGRAM_ID_STRING, error);
16
+ // Fallback to default
17
+ return new PublicKey('Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS');
18
+ }
19
+ };
20
+
21
+ const PROGRAM_ID = getProgramId();
22
+
23
+ export interface UseProgramOptions {
24
+ idlPath?: string; // Path to IDL file, default: '/web3_prediction_platform.json'
25
+ programId?: PublicKey; // Override program ID
26
+ }
27
+
28
+ export const useProgram = (options: UseProgramOptions = {}) => {
29
+ const { connection } = useConnection();
30
+ const wallet = useWallet();
31
+ const [idl, setIdl] = useState<Idl | null>(null);
32
+ const [idlError, setIdlError] = useState<string | null>(null);
33
+
34
+ const idlPath = options.idlPath || '/web3_prediction_platform.json';
35
+ const programId = options.programId || PROGRAM_ID;
36
+
37
+ // Load IDL from public folder (works with static export)
38
+ useEffect(() => {
39
+ const loadIdl = async () => {
40
+ try {
41
+ const response = await fetch(idlPath);
42
+ if (!response.ok) {
43
+ throw new Error(`Failed to load IDL: ${response.statusText}`);
44
+ }
45
+ const idlData = await response.json();
46
+ setIdl(idlData as Idl);
47
+ setIdlError(null);
48
+ } catch (error: any) {
49
+ console.error('Error loading IDL:', error);
50
+ setIdlError(error.message || 'Failed to load IDL');
51
+ setIdl(null);
52
+ }
53
+ };
54
+
55
+ loadIdl();
56
+ }, [idlPath]);
57
+
58
+ const provider = useMemo(() => {
59
+ if (!wallet.publicKey || !connection) return null;
60
+
61
+ try {
62
+ return new AnchorProvider(
63
+ connection,
64
+ wallet as any,
65
+ { commitment: 'confirmed' }
66
+ );
67
+ } catch (error) {
68
+ console.error('Error creating provider:', error);
69
+ return null;
70
+ }
71
+ }, [connection, wallet]);
72
+
73
+ const program = useMemo(() => {
74
+ if (!provider || !idl) return null;
75
+
76
+ try {
77
+ // Anchor Program constructor: new Program(idl, programId, provider)
78
+ // @ts-ignore - TypeScript inference issue with Program constructor argument types
79
+ return new Program(idl as any, programId, provider as AnchorProvider) as any;
80
+ } catch (error) {
81
+ console.error('Error creating program:', error);
82
+ return null;
83
+ }
84
+ }, [provider, idl, programId]);
85
+
86
+ return {
87
+ program,
88
+ provider,
89
+ idl,
90
+ idlError,
91
+ programId
92
+ };
93
+ };