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,113 @@
1
+ /**
2
+ * Token Balance Hook
3
+ * Fetches balance for both SOL and SPL tokens (USDC)
4
+ */
5
+ import { useState, useEffect, useCallback } from 'react';
6
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
7
+ import { PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
8
+ import { getAccount, getAssociatedTokenAddress, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token';
9
+ import { isNativeSOL, getTokenDecimals, fromLamports } from '../utils/tokenUtils';
10
+ import { retryRpcCall } from '../utils/rpcRetry';
11
+
12
+ export interface TokenBalance {
13
+ sol: number;
14
+ usdc: number;
15
+ loading: boolean;
16
+ error: string | null;
17
+ }
18
+
19
+ export const useTokenBalance = (tokenMint?: PublicKey | string) => {
20
+ const { connection } = useConnection();
21
+ const { publicKey } = useWallet();
22
+ const [solBalance, setSolBalance] = useState<number>(0);
23
+ const [usdcBalance, setUsdcBalance] = useState<number>(0);
24
+ const [loading, setLoading] = useState(false);
25
+ const [error, setError] = useState<string | null>(null);
26
+
27
+ const fetchBalances = useCallback(async () => {
28
+ if (!connection || !publicKey) {
29
+ setSolBalance(0);
30
+ setUsdcBalance(0);
31
+ setLoading(false);
32
+ return;
33
+ }
34
+
35
+ try {
36
+ setLoading(true);
37
+ setError(null);
38
+
39
+ // Fetch SOL balance
40
+ const solBalanceLamports = await retryRpcCall(
41
+ () => connection.getBalance(publicKey)
42
+ );
43
+ setSolBalance(Number(solBalanceLamports) / LAMPORTS_PER_SOL);
44
+
45
+ // Fetch USDC balance if needed
46
+ try {
47
+ // USDC devnet mint
48
+ const usdcMint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
49
+ const usdcTokenAccount = await getAssociatedTokenAddress(
50
+ usdcMint,
51
+ publicKey,
52
+ false,
53
+ TOKEN_PROGRAM_ID,
54
+ ASSOCIATED_TOKEN_PROGRAM_ID
55
+ );
56
+
57
+ try {
58
+ const tokenAccount = await retryRpcCall(
59
+ () => getAccount(connection, usdcTokenAccount)
60
+ );
61
+ const amount = typeof tokenAccount.amount === 'bigint'
62
+ ? Number(tokenAccount.amount)
63
+ : tokenAccount.amount;
64
+ setUsdcBalance(fromLamports(amount, usdcMint));
65
+ } catch (err: any) {
66
+ // Token account doesn't exist, balance is 0
67
+ if (err.message?.includes('InvalidAccountData') || err.message?.includes('could not find account')) {
68
+ setUsdcBalance(0);
69
+ } else {
70
+ throw err;
71
+ }
72
+ }
73
+ } catch (err: any) {
74
+ console.warn('Error fetching USDC balance:', err);
75
+ setUsdcBalance(0);
76
+ }
77
+ } catch (err: any) {
78
+ console.error('Error fetching balances:', err);
79
+ setError(err.message);
80
+ } finally {
81
+ setLoading(false);
82
+ }
83
+ }, [connection, publicKey]);
84
+
85
+ useEffect(() => {
86
+ fetchBalances();
87
+
88
+ // Poll for balance updates every 5 seconds
89
+ const interval = setInterval(fetchBalances, 5000);
90
+ return () => clearInterval(interval);
91
+ }, [fetchBalances]);
92
+
93
+ // Get balance for specific token mint
94
+ const getBalance = useCallback((mint?: PublicKey | string): number => {
95
+ if (!mint) return solBalance; // Default to SOL
96
+
97
+ if (isNativeSOL(mint)) {
98
+ return solBalance;
99
+ }
100
+
101
+ // Assume USDC for now (can be extended for other tokens)
102
+ return usdcBalance;
103
+ }, [solBalance, usdcBalance]);
104
+
105
+ return {
106
+ sol: solBalance,
107
+ usdc: usdcBalance,
108
+ getBalance,
109
+ loading,
110
+ error,
111
+ refresh: fetchBalances,
112
+ };
113
+ };
@@ -0,0 +1,122 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
3
+ import { PublicKey } from '@solana/web3.js';
4
+ import { useProgram } from './useProgram';
5
+ import { usePolling } from './usePolling';
6
+ import { retryRpcCall } from '../utils/rpcRetry';
7
+ import * as anchor from '@coral-xyz/anchor';
8
+
9
+ // Frontend Vault type (different from blockchain Vault type in types/index.ts)
10
+ export interface Vault {
11
+ id: number;
12
+ aprRate: number;
13
+ totalStaked: number;
14
+ stakerCount: number;
15
+ isActive: boolean;
16
+ tokenMint: PublicKey | string; // Can be PublicKey or string for flexibility
17
+ // Admin-managed metadata
18
+ name?: string;
19
+ logo?: string;
20
+ lpOperation?: string;
21
+ lpOperationLogo?: string;
22
+ contentTitle?: string;
23
+ content?: string;
24
+ currentEpoch?: number;
25
+ }
26
+
27
+ export const useVaults = () => {
28
+ const { connection } = useConnection();
29
+ const { publicKey } = useWallet();
30
+ const { program } = useProgram();
31
+ const [vaults, setVaults] = useState<Vault[]>([]);
32
+ const [loading, setLoading] = useState(true);
33
+ const [error, setError] = useState<string | null>(null);
34
+
35
+ const loadVaults = useCallback(async () => {
36
+ if (!program) {
37
+ setLoading(false);
38
+ return;
39
+ }
40
+
41
+ try {
42
+ setLoading(true);
43
+ setError(null);
44
+
45
+ // Load all vault accounts with retry
46
+ const vaultAccounts = await retryRpcCall(
47
+ () => (program.account as any).vault.all()
48
+ ) as any[];
49
+
50
+ // Load vault metadata from localStorage
51
+ let vaultMetadata: Record<number, any> = {};
52
+ if (typeof window !== 'undefined') {
53
+ try {
54
+ vaultMetadata = JSON.parse(localStorage.getItem('vaultMetadata') || '{}');
55
+ } catch (e) {
56
+ console.warn('Failed to load vault metadata from localStorage:', e);
57
+ }
58
+ }
59
+
60
+ const loadedVaults: Vault[] = vaultAccounts.map((account: any) => {
61
+ // Handle both camelCase and snake_case field names (depending on IDL generation)
62
+ const vault = account.account;
63
+ const tokenMint = vault.tokenMint || vault.token_mint;
64
+ const vaultId = (vault.id || vault.vaultId)?.toNumber?.() || 0;
65
+ const metadata = vaultMetadata[vaultId] || {};
66
+
67
+ return {
68
+ id: vaultId,
69
+ aprRate: (vault.aprRate || vault.apr_rate)?.toNumber?.() || 0,
70
+ totalStaked: (vault.totalStaked || vault.total_staked)?.toNumber?.() || 0,
71
+ stakerCount: (vault.stakerCount || vault.staker_count)?.toNumber?.() || 0,
72
+ isActive: vault.isActive ?? vault.is_active ?? false,
73
+ tokenMint: tokenMint ? (typeof tokenMint === 'string' ? tokenMint : tokenMint.toString()) : '',
74
+ currentEpoch: (vault.currentEpoch || vault.current_epoch)?.toNumber?.() || 0,
75
+ // Load metadata from localStorage
76
+ name: metadata.name,
77
+ logo: metadata.logo,
78
+ lpOperation: metadata.lpOperation,
79
+ lpOperationLogo: metadata.lpOperationLogo,
80
+ contentTitle: metadata.contentTitle,
81
+ content: metadata.content,
82
+ };
83
+ });
84
+
85
+ setVaults(loadedVaults);
86
+ } catch (err: any) {
87
+ console.error('Error loading vaults:', err);
88
+ setError(err.message);
89
+ } finally {
90
+ setLoading(false);
91
+ }
92
+ }, [program]);
93
+
94
+ // Initial load
95
+ useEffect(() => {
96
+ loadVaults();
97
+ }, [loadVaults]);
98
+
99
+ // Real-time synchronization: Requirement 10.1 - 5-second blockchain sync
100
+ usePolling(
101
+ loadVaults,
102
+ {
103
+ interval: 5000, // 5 seconds as per Requirement 10.1
104
+ enabled: !!program, // Only poll when program is available
105
+ onError: (error) => {
106
+ console.error('Polling error in useVaults:', error);
107
+ setError(error.message);
108
+ },
109
+ }
110
+ );
111
+
112
+ const refreshVaults = useCallback(() => {
113
+ loadVaults();
114
+ }, [loadVaults]);
115
+
116
+ return {
117
+ vaults,
118
+ loading,
119
+ error,
120
+ refreshVaults,
121
+ };
122
+ };
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @keynesol/shared
3
+ * Shared code package for Keynesol Web3 Prediction Platform
4
+ */
5
+
6
+ // Export all components
7
+ export * from './components';
8
+
9
+ // Export all hooks
10
+ export * from './hooks';
11
+
12
+ // Export all utils
13
+ export * from './utils';
14
+ export * from './utils/supabase';
15
+
16
+ // Export all services
17
+ export * from './services';
18
+
19
+ // Export all types
20
+ export * from './types';
21
+
22
+ // Export IDL path (for reference)
23
+ export const IDL_PATH = '/web3_prediction_platform.json';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Services Index
3
+ * Export all shared services
4
+ */
5
+ export * from './syncService';
6
+ export * from './reconciliationService';
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Data Reconciliation Service
3
+ * Ensures consistency between Supabase and blockchain
4
+ * Requirements: 10.5
5
+ */
6
+ import { Connection } from '@solana/web3.js';
7
+ import { getSupabaseClient, isSupabaseConfigured, SupabaseConfig } from '../utils/supabase';
8
+ import { errorHandler } from '../utils/errorHandler';
9
+ import { cacheManager } from '../utils/cacheManager';
10
+
11
+ interface ReconciliationResult {
12
+ transactions: {
13
+ missing: number;
14
+ inconsistent: number;
15
+ fixed: number;
16
+ };
17
+ rewards: {
18
+ missing: number;
19
+ inconsistent: number;
20
+ fixed: number;
21
+ };
22
+ vaults: {
23
+ missing: number;
24
+ inconsistent: number;
25
+ fixed: number;
26
+ };
27
+ }
28
+
29
+ class ReconciliationService {
30
+ private connection: Connection | null = null;
31
+ private reconciliationInterval: NodeJS.Timeout | null = null;
32
+ private supabaseConfig?: SupabaseConfig;
33
+
34
+ /**
35
+ * Initialize reconciliation service
36
+ */
37
+ initialize(connection: Connection, supabaseConfig?: SupabaseConfig): void {
38
+ this.connection = connection;
39
+ this.supabaseConfig = supabaseConfig;
40
+ }
41
+
42
+ /**
43
+ * Start periodic reconciliation
44
+ */
45
+ startReconciliation(interval: number = 3600000): void {
46
+ if (this.reconciliationInterval) {
47
+ return; // Already running
48
+ }
49
+
50
+ // Initial reconciliation
51
+ this.reconcileAll();
52
+
53
+ // Periodic reconciliation (default: 1 hour)
54
+ this.reconciliationInterval = setInterval(() => {
55
+ this.reconcileAll();
56
+ }, interval);
57
+ }
58
+
59
+ /**
60
+ * Stop reconciliation
61
+ */
62
+ stopReconciliation(): void {
63
+ if (this.reconciliationInterval) {
64
+ clearInterval(this.reconciliationInterval);
65
+ this.reconciliationInterval = null;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Reconcile all data types
71
+ */
72
+ async reconcileAll(): Promise<ReconciliationResult> {
73
+ if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
74
+ return {
75
+ transactions: { missing: 0, inconsistent: 0, fixed: 0 },
76
+ rewards: { missing: 0, inconsistent: 0, fixed: 0 },
77
+ vaults: { missing: 0, inconsistent: 0, fixed: 0 },
78
+ };
79
+ }
80
+
81
+ try {
82
+ const [transactions, rewards, vaults] = await Promise.all([
83
+ this.reconcileTransactions(),
84
+ this.reconcileRewards(),
85
+ this.reconcileVaults(),
86
+ ]);
87
+
88
+ return {
89
+ transactions,
90
+ rewards,
91
+ vaults,
92
+ };
93
+ } catch (error) {
94
+ errorHandler.handleError(error as Error, 'ReconciliationService');
95
+ throw error;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Reconcile transactions
101
+ */
102
+ async reconcileTransactions(): Promise<{ missing: number; inconsistent: number; fixed: number }> {
103
+ if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
104
+ return { missing: 0, inconsistent: 0, fixed: 0 };
105
+ }
106
+
107
+ try {
108
+ const supabase = getSupabaseClient(this.supabaseConfig);
109
+
110
+ // Get recent transactions from Supabase
111
+ const { data: supabaseTxs } = await supabase
112
+ .from('transactions')
113
+ .select('signature, status, amount, vault_id')
114
+ .order('timestamp', { ascending: false })
115
+ .limit(100);
116
+
117
+ if (!supabaseTxs) {
118
+ return { missing: 0, inconsistent: 0, fixed: 0 };
119
+ }
120
+
121
+ let missing = 0;
122
+ let inconsistent = 0;
123
+ let fixed = 0;
124
+
125
+ // Verify each transaction on blockchain
126
+ const transactions = supabaseTxs as any[];
127
+ for (const tx of transactions) {
128
+ try {
129
+ const blockchainTx = await this.connection!.getTransaction(tx.signature, {
130
+ maxSupportedTransactionVersion: 0,
131
+ });
132
+
133
+ if (!blockchainTx) {
134
+ // Transaction not found on blockchain - mark as failed
135
+ if (tx.status !== 'failed') {
136
+ const updateData: Record<string, string> = { status: 'failed' };
137
+ await (supabase
138
+ .from('transactions') as any)
139
+ .update(updateData)
140
+ .eq('signature', tx.signature);
141
+ fixed++;
142
+ }
143
+ missing++;
144
+ } else {
145
+ // Transaction found - verify status
146
+ const isConfirmed = blockchainTx.meta?.err === null;
147
+ const expectedStatus = isConfirmed ? 'confirmed' : 'failed';
148
+
149
+ if (tx.status !== expectedStatus) {
150
+ const updateData = { status: expectedStatus };
151
+ await (supabase
152
+ .from('transactions') as any)
153
+ .update(updateData as any)
154
+ .eq('signature', tx.signature);
155
+ fixed++;
156
+ inconsistent++;
157
+ }
158
+ }
159
+ } catch (error) {
160
+ console.error(`Error reconciling transaction ${tx.signature}:`, error);
161
+ }
162
+ }
163
+
164
+ // Invalidate cache
165
+ cacheManager.invalidate('transactions');
166
+
167
+ return { missing, inconsistent, fixed };
168
+ } catch (error) {
169
+ errorHandler.handleError(error as Error, 'ReconciliationService.reconcileTransactions');
170
+ return { missing: 0, inconsistent: 0, fixed: 0 };
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Reconcile rewards
176
+ */
177
+ async reconcileRewards(): Promise<{ missing: number; inconsistent: number; fixed: number }> {
178
+ if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
179
+ return { missing: 0, inconsistent: 0, fixed: 0 };
180
+ }
181
+
182
+ try {
183
+ const supabase = getSupabaseClient(this.supabaseConfig);
184
+
185
+ // Get unclaimed rewards from Supabase
186
+ const { data: unclaimedRewards } = await supabase
187
+ .from('rewards')
188
+ .select('*')
189
+ .eq('claimed', false)
190
+ .limit(100);
191
+
192
+ if (!unclaimedRewards) {
193
+ return { missing: 0, inconsistent: 0, fixed: 0 };
194
+ }
195
+
196
+ let missing = 0;
197
+ let inconsistent = 0;
198
+ let fixed = 0;
199
+
200
+ // Verify rewards on blockchain
201
+ // This would require checking reward distribution transactions
202
+ // For now, this is a placeholder
203
+
204
+ cacheManager.invalidate('rewards');
205
+
206
+ return { missing, inconsistent, fixed };
207
+ } catch (error) {
208
+ errorHandler.handleError(error as Error, 'ReconciliationService.reconcileRewards');
209
+ return { missing: 0, inconsistent: 0, fixed: 0 };
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Reconcile vaults
215
+ */
216
+ async reconcileVaults(): Promise<{ missing: number; inconsistent: number; fixed: number }> {
217
+ if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
218
+ return { missing: 0, inconsistent: 0, fixed: 0 };
219
+ }
220
+
221
+ try {
222
+ // Reconcile vault history with current vault state
223
+ // This would involve:
224
+ // 1. Fetching current vault state from blockchain
225
+ // 2. Comparing with latest vault_history entry
226
+ // 3. Creating new history entry if state changed
227
+
228
+ cacheManager.invalidate('vaultHistory');
229
+
230
+ return { missing: 0, inconsistent: 0, fixed: 0 };
231
+ } catch (error) {
232
+ errorHandler.handleError(error as Error, 'ReconciliationService.reconcileVaults');
233
+ return { missing: 0, inconsistent: 0, fixed: 0 };
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Manual reconciliation trigger
239
+ */
240
+ async manualReconcile(): Promise<ReconciliationResult> {
241
+ return this.reconcileAll();
242
+ }
243
+ }
244
+
245
+ // Export singleton instance
246
+ export const reconciliationService = new ReconciliationService();