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.
- package/README.md +118 -0
- package/dist/components/Common/ErrorBoundary.d.ts +23 -0
- package/dist/components/Common/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/Common/ErrorBoundary.js +93 -0
- package/dist/components/Common/ErrorBoundary.jsx +103 -0
- package/dist/components/Common/ErrorMessage.d.ts +8 -0
- package/dist/components/Common/ErrorMessage.d.ts.map +1 -0
- package/dist/components/Common/ErrorMessage.js +36 -0
- package/dist/components/Common/ErrorMessage.jsx +40 -0
- package/dist/components/Common/Loading.d.ts +8 -0
- package/dist/components/Common/Loading.d.ts.map +1 -0
- package/dist/components/Common/Loading.js +41 -0
- package/dist/components/Common/Loading.jsx +44 -0
- package/dist/components/Common/LoadingIndicator.d.ts +17 -0
- package/dist/components/Common/LoadingIndicator.d.ts.map +1 -0
- package/dist/components/Common/LoadingIndicator.js +95 -0
- package/dist/components/Common/LoadingIndicator.jsx +108 -0
- package/dist/components/Common/ProgramStatus.d.ts +3 -0
- package/dist/components/Common/ProgramStatus.d.ts.map +1 -0
- package/dist/components/Common/ProgramStatus.js +26 -0
- package/dist/components/Common/ProgramStatus.jsx +27 -0
- package/dist/components/Common/Skeleton.d.ts +39 -0
- package/dist/components/Common/Skeleton.d.ts.map +1 -0
- package/dist/components/Common/Skeleton.js +53 -0
- package/dist/components/Common/Skeleton.jsx +67 -0
- package/dist/components/Common/SkeletonScreen.d.ts +18 -0
- package/dist/components/Common/SkeletonScreen.d.ts.map +1 -0
- package/dist/components/Common/SkeletonScreen.js +98 -0
- package/dist/components/Common/SkeletonScreen.jsx +108 -0
- package/dist/components/Common/index.d.ts +11 -0
- package/dist/components/Common/index.d.ts.map +1 -0
- package/dist/components/Common/index.js +10 -0
- package/dist/components/Wallet/TransactionStatus.d.ts +11 -0
- package/dist/components/Wallet/TransactionStatus.d.ts.map +1 -0
- package/dist/components/Wallet/TransactionStatus.js +97 -0
- package/dist/components/Wallet/TransactionStatus.jsx +106 -0
- package/dist/components/Wallet/WalletBalance.d.ts +4 -0
- package/dist/components/Wallet/WalletBalance.d.ts.map +1 -0
- package/dist/components/Wallet/WalletBalance.js +82 -0
- package/dist/components/Wallet/WalletBalance.jsx +86 -0
- package/dist/components/Wallet/WalletButton.d.ts +3 -0
- package/dist/components/Wallet/WalletButton.d.ts.map +1 -0
- package/dist/components/Wallet/WalletButton.js +51 -0
- package/dist/components/Wallet/WalletButton.jsx +53 -0
- package/dist/components/Wallet/WalletConnectionModal.d.ts +8 -0
- package/dist/components/Wallet/WalletConnectionModal.d.ts.map +1 -0
- package/dist/components/Wallet/WalletConnectionModal.js +150 -0
- package/dist/components/Wallet/WalletConnectionModal.jsx +170 -0
- package/dist/components/Wallet/WalletProvider.d.ts +9 -0
- package/dist/components/Wallet/WalletProvider.d.ts.map +1 -0
- package/dist/components/Wallet/WalletProvider.js +70 -0
- package/dist/components/Wallet/WalletProvider.jsx +75 -0
- package/dist/components/Wallet/index.d.ts +9 -0
- package/dist/components/Wallet/index.d.ts.map +1 -0
- package/dist/components/Wallet/index.js +8 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +6 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/useCache.d.ts +16 -0
- package/dist/hooks/useCache.d.ts.map +1 -0
- package/dist/hooks/useCache.js +67 -0
- package/dist/hooks/usePolling.d.ts +16 -0
- package/dist/hooks/usePolling.d.ts.map +1 -0
- package/dist/hooks/usePolling.js +79 -0
- package/dist/hooks/useProgram.d.ts +14 -0
- package/dist/hooks/useProgram.d.ts.map +1 -0
- package/dist/hooks/useProgram.js +88 -0
- package/dist/hooks/useTokenBalance.d.ts +16 -0
- package/dist/hooks/useTokenBalance.d.ts.map +1 -0
- package/dist/hooks/useTokenBalance.js +100 -0
- package/dist/hooks/useVaults.d.ts +23 -0
- package/dist/hooks/useVaults.d.ts.map +1 -0
- package/dist/hooks/useVaults.js +98 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +6 -0
- package/dist/services/reconciliationService.d.ts +76 -0
- package/dist/services/reconciliationService.d.ts.map +1 -0
- package/dist/services/reconciliationService.js +216 -0
- package/dist/services/syncService.d.ts +51 -0
- package/dist/services/syncService.d.ts.map +1 -0
- package/dist/services/syncService.js +218 -0
- package/dist/types/index.d.ts +201 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/cacheManager.d.ts +73 -0
- package/dist/utils/cacheManager.d.ts.map +1 -0
- package/dist/utils/cacheManager.js +232 -0
- package/dist/utils/errorHandler.d.ts +76 -0
- package/dist/utils/errorHandler.d.ts.map +1 -0
- package/dist/utils/errorHandler.js +267 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +11 -0
- package/dist/utils/performanceMonitor.d.ts +75 -0
- package/dist/utils/performanceMonitor.d.ts.map +1 -0
- package/dist/utils/performanceMonitor.js +197 -0
- package/dist/utils/rpcRetry.d.ts +12 -0
- package/dist/utils/rpcRetry.d.ts.map +1 -0
- package/dist/utils/rpcRetry.js +47 -0
- package/dist/utils/supabase.d.ts +198 -0
- package/dist/utils/supabase.d.ts.map +1 -0
- package/dist/utils/supabase.js +50 -0
- package/dist/utils/toastService.d.ts +52 -0
- package/dist/utils/toastService.d.ts.map +1 -0
- package/dist/utils/toastService.js +139 -0
- package/dist/utils/tokenUtils.d.ts +33 -0
- package/dist/utils/tokenUtils.d.ts.map +1 -0
- package/dist/utils/tokenUtils.js +66 -0
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +83 -0
- package/package.json +45 -0
- package/src/components/Common/ErrorBoundary.tsx +135 -0
- package/src/components/Common/ErrorMessage.tsx +52 -0
- package/src/components/Common/Loading.tsx +56 -0
- package/src/components/Common/LoadingIndicator.tsx +143 -0
- package/src/components/Common/ProgramStatus.tsx +37 -0
- package/src/components/Common/Skeleton.tsx +83 -0
- package/src/components/Common/SkeletonScreen.tsx +166 -0
- package/src/components/Common/index.ts +10 -0
- package/src/components/Wallet/TransactionStatus.tsx +138 -0
- package/src/components/Wallet/WalletBalance.tsx +94 -0
- package/src/components/Wallet/WalletButton.tsx +65 -0
- package/src/components/Wallet/WalletConnectionModal.tsx +193 -0
- package/src/components/Wallet/WalletProvider.tsx +104 -0
- package/src/components/Wallet/index.ts +8 -0
- package/src/components/index.ts +6 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/useCache.ts +87 -0
- package/src/hooks/usePolling.ts +98 -0
- package/src/hooks/useProgram.ts +93 -0
- package/src/hooks/useTokenBalance.ts +113 -0
- package/src/hooks/useVaults.ts +122 -0
- package/src/index.ts +23 -0
- package/src/services/index.ts +6 -0
- package/src/services/reconciliationService.ts +246 -0
- package/src/services/syncService.ts +238 -0
- package/src/types/index.ts +233 -0
- package/src/utils/cacheManager.ts +286 -0
- package/src/utils/errorHandler.ts +336 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/performanceMonitor.ts +222 -0
- package/src/utils/rpcRetry.ts +55 -0
- package/src/utils/supabase.ts +253 -0
- package/src/utils/toastService.ts +166 -0
- package/src/utils/tokenUtils.ts +75 -0
- 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,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
|
+
};
|